// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:convert'; import 'dart:io' hide Platform; import 'dart:typed_data'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'package:path/path.dart' as path; import 'package:platform/platform.dart' show FakePlatform; import '../prepare_package.dart'; import 'fake_process_manager.dart'; void main() { final String testRef = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'; for (String platformName in ['macos', 'linux', 'windows']) { final FakePlatform platform = new FakePlatform( operatingSystem: platformName, environment: {}, ); group('ProcessRunner for $platform', () { test('Returns stdout', () async { final FakeProcessManager fakeProcessManager = new FakeProcessManager(); fakeProcessManager.fakeResults = >{ 'echo test': [new ProcessResult(0, 0, 'output', 'error')], }; final ProcessRunner processRunner = new ProcessRunner( subprocessOutput: false, platform: platform, processManager: fakeProcessManager); final String output = await processRunner.runProcess(['echo', 'test']); expect(output, equals('output')); }); test('Throws on process failure', () async { final FakeProcessManager fakeProcessManager = new FakeProcessManager(); fakeProcessManager.fakeResults = >{ 'echo test': [new ProcessResult(0, -1, 'output', 'error')], }; final ProcessRunner processRunner = new ProcessRunner( subprocessOutput: false, platform: platform, processManager: fakeProcessManager); expect( expectAsync1((List commandLine) async { return processRunner.runProcess(commandLine); })(['echo', 'test']), throwsA(const isInstanceOf())); }); }); group('ArchiveCreator for $platformName', () { ArchiveCreator creator; Directory tmpDir; Directory flutterDir; FakeProcessManager processManager; final List> args = >[]; final List> namedArgs = >[]; String flutter; Future fakeHttpReader(Uri url, {Map headers}) { return new Future.value(new Uint8List(0)); } setUp(() async { processManager = new FakeProcessManager(); args.clear(); namedArgs.clear(); tmpDir = await Directory.systemTemp.createTemp('flutter_'); flutterDir = new Directory(path.join(tmpDir.path, 'flutter')); flutterDir.createSync(recursive: true); creator = new ArchiveCreator( tmpDir, tmpDir, testRef, Branch.dev, processManager: processManager, subprocessOutput: false, platform: platform, httpReader: fakeHttpReader, ); flutter = path.join(creator.flutterRoot.absolute.path, 'bin', 'flutter'); }); tearDown(() async { // On Windows, the directory is locked and not able to be deleted yet. So // we just leave some (very small, because we're not actually building // archives here) trash around to be deleted at the next reboot. if (!platform.isWindows) { await tmpDir.delete(recursive: true); } }); test('sets PUB_CACHE properly', () async { final String createBase = path.join(tmpDir.absolute.path, 'create_'); final Map> calls = >{ 'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null, 'git reset --hard $testRef': null, 'git remote remove origin': null, 'git remote add origin https://github.com/flutter/flutter.git': null, 'git describe --tags --abbrev=0': [new ProcessResult(0, 0, 'v1.2.3', '')], }; if (platform.isWindows) { calls['7za x ${path.join(tmpDir.path, 'mingit.zip')}'] = null; } calls.addAll(>{ '$flutter doctor': null, '$flutter update-packages': null, '$flutter precache': null, '$flutter ide-config': null, '$flutter create --template=app ${createBase}app': null, '$flutter create --template=package ${createBase}package': null, '$flutter create --template=plugin ${createBase}plugin': null, 'git clean -f -X **/.packages': null, }); final String archiveName = path.join(tmpDir.absolute.path, 'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}'); if (platform.isWindows) { calls['7za a -tzip -mx=9 $archiveName flutter'] = null; } else if (platform.isMacOS) { calls['zip -r -9 $archiveName flutter'] = null; } else if (platform.isLinux) { calls['tar cJf $archiveName flutter'] = null; } processManager.fakeResults = calls; await creator.initializeRepo(); await creator.createArchive(); expect( verify(processManager.start( captureAny, workingDirectory: captureAny, environment: captureAny, )).captured[1]['PUB_CACHE'], endsWith(path.join('flutter', '.pub-cache')), ); }); test('calls the right commands for archive output', () async { final String createBase = path.join(tmpDir.absolute.path, 'create_'); final Map> calls = >{ 'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null, 'git reset --hard $testRef': null, 'git remote remove origin': null, 'git remote add origin https://github.com/flutter/flutter.git': null, 'git describe --tags --abbrev=0': [new ProcessResult(0, 0, 'v1.2.3', '')], }; if (platform.isWindows) { calls['7za x ${path.join(tmpDir.path, 'mingit.zip')}'] = null; } calls.addAll(>{ '$flutter doctor': null, '$flutter update-packages': null, '$flutter precache': null, '$flutter ide-config': null, '$flutter create --template=app ${createBase}app': null, '$flutter create --template=package ${createBase}package': null, '$flutter create --template=plugin ${createBase}plugin': null, 'git clean -f -X **/.packages': null, }); final String archiveName = path.join(tmpDir.absolute.path, 'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}'); if (platform.isWindows) { calls['7za a -tzip -mx=9 $archiveName flutter'] = null; } else if (platform.isMacOS) { calls['zip -r -9 $archiveName flutter'] = null; } else if (platform.isLinux) { calls['tar cJf $archiveName flutter'] = null; } processManager.fakeResults = calls; creator = new ArchiveCreator( tmpDir, tmpDir, testRef, Branch.dev, processManager: processManager, subprocessOutput: false, platform: platform, httpReader: fakeHttpReader, ); await creator.initializeRepo(); await creator.createArchive(); processManager.verifyCalls(calls.keys.toList()); }); test('throws when a command errors out', () async { final Map> calls = >{ 'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': [new ProcessResult(0, 0, 'output1', '')], 'git reset --hard $testRef': [new ProcessResult(0, -1, 'output2', '')], }; processManager.fakeResults = calls; expect(expectAsync0(creator.initializeRepo), throwsA(const isInstanceOf())); }); }); group('ArchivePublisher for $platformName', () { FakeProcessManager processManager; Directory tempDir; setUp(() async { processManager = new FakeProcessManager(); tempDir = await Directory.systemTemp.createTemp('flutter_'); tempDir.createSync(); }); tearDown(() async { // On Windows, the directory is locked and not able to be deleted yet. So // we just leave some (very small, because we're not actually building // archives here) trash around to be deleted at the next reboot. if (!platform.isWindows) { await tempDir.delete(recursive: true); } }); test('calls the right processes', () async { final String releasesName = 'releases_$platformName.json'; final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip'; final String archiveMime = platform.isLinux ? 'application/x-gtar' : 'application/zip'; final String archivePath = path.join(tempDir.absolute.path, archiveName); final String gsArchivePath = 'gs://flutter_infra/releases/dev/$platformName/$archiveName'; final String jsonPath = path.join(tempDir.absolute.path, releasesName); final String gsJsonPath = 'gs://flutter_infra/releases/$releasesName'; final String releasesJson = '''{ "base_url": "https://storage.googleapis.com/flutter_infra/releases", "current_release": { "beta": "6da8ec6bd0c4801b80d666869e4069698561c043", "dev": "f88c60b38c3a5ef92115d24e3da4175b4890daba" }, "releases": { "6da8ec6bd0c4801b80d666869e4069698561c043": { "${platformName}_archive": "dev/linux/flutter_${platformName}_0.21.0-beta.zip", "release_date": "2017-12-19T10:30:00,847287019-08:00", "version": "0.21.0-beta" }, "f88c60b38c3a5ef92115d24e3da4175b4890daba": { "${platformName}_archive": "dev/linux/flutter_${platformName}_0.22.0-dev.zip", "release_date": "2018-01-19T13:30:09,728487019-08:00", "version": "0.22.0-dev" } } } '''; final Map> calls = >{ 'gsutil rm $gsArchivePath': null, 'gsutil -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null, 'gsutil cat $gsJsonPath': [new ProcessResult(0, 0, releasesJson, '')], 'gsutil rm $gsJsonPath': null, 'gsutil -h Content-Type:application/json cp $jsonPath $gsJsonPath': null, }; processManager.fakeResults = calls; final File outputFile = new File(path.join(tempDir.absolute.path, archiveName)); assert(tempDir.existsSync()); final ArchivePublisher publisher = new ArchivePublisher( tempDir, testRef, Branch.dev, '1.2.3', outputFile, processManager: processManager, subprocessOutput: false, platform: platform, ); assert(tempDir.existsSync()); await publisher.publishArchive(); processManager.verifyCalls(calls.keys.toList()); final File releaseFile = new File(jsonPath); expect(releaseFile.existsSync(), isTrue); final String contents = releaseFile.readAsStringSync(); // Make sure new data is added. expect(contents, contains('"dev": "$testRef"')); expect(contents, contains('"$testRef": {')); expect(contents, contains('"${platformName}_archive": "dev/$platformName/$archiveName"')); // Make sure existing entries are preserved. expect(contents, contains('"6da8ec6bd0c4801b80d666869e4069698561c043": {')); expect(contents, contains('"f88c60b38c3a5ef92115d24e3da4175b4890daba": {')); expect(contents, contains('"beta": "6da8ec6bd0c4801b80d666869e4069698561c043"')); // Make sure it's valid JSON, and in the right format. final Map jsonData = json.decode(contents); final JsonEncoder encoder = const JsonEncoder.withIndent(' '); expect(contents, equals(encoder.convert(jsonData))); }); }); } }