// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @dart = 2.8 import 'dart:convert' show jsonDecode; import 'package:args/command_runner.dart'; import 'package:dev_tools/proto/conductor_state.pb.dart' as pb; import 'package:dev_tools/proto/conductor_state.pbenum.dart' show ReleasePhase; import 'package:dev_tools/repository.dart'; import 'package:dev_tools/start.dart'; import 'package:dev_tools/state.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:platform/platform.dart'; import '../../../packages/flutter_tools/test/src/fake_process_manager.dart'; import './common.dart'; void main() { group('start command', () { const String flutterRoot = '/flutter'; const String checkoutsParentDirectory = '$flutterRoot/dev/tools/'; const String frameworkMirror = 'https://github.com/user/flutter.git'; const String engineMirror = 'https://github.com/user/engine.git'; const String candidateBranch = 'flutter-1.2-candidate.3'; const String releaseChannel = 'stable'; const String revision = 'abcd1234'; Checkouts checkouts; MemoryFileSystem fileSystem; FakePlatform platform; TestStdio stdio; FakeProcessManager processManager; setUp(() { stdio = TestStdio(); fileSystem = MemoryFileSystem.test(); }); CommandRunner createRunner({ Map environment, String operatingSystem, List commands, }) { operatingSystem ??= const LocalPlatform().operatingSystem; final String pathSeparator = operatingSystem == 'windows' ? r'\' : '/'; environment ??= { 'HOME': '/path/to/user/home', }; final Directory homeDir = fileSystem.directory( environment['HOME'], ); // Tool assumes this exists homeDir.createSync(recursive: true); platform = FakePlatform( environment: environment, operatingSystem: operatingSystem, pathSeparator: pathSeparator, ); processManager = FakeProcessManager.list(commands ?? []); checkouts = Checkouts( fileSystem: fileSystem, parentDirectory: fileSystem.directory(checkoutsParentDirectory), platform: platform, processManager: processManager, stdio: stdio, ); final StartCommand command = StartCommand( checkouts: checkouts, flutterRoot: fileSystem.directory(flutterRoot), ); return CommandRunner('codesign-test', '')..addCommand(command); } tearDown(() { // Ensure we don't re-use these between tests. processManager = null; checkouts = null; platform = null; }); test('throws exception if run from Windows', () async { final CommandRunner runner = createRunner( commands: [ const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], stdout: revision, ), ], operatingSystem: 'windows', ); await expectLater( () async => runner.run(['start']), throwsExceptionWith( 'Error! This tool is only supported on macOS and Linux', ), ); }); test('throws if --$kFrameworkMirrorOption not provided', () async { final CommandRunner runner = createRunner( commands: [ const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], stdout: revision, ), ], ); await expectLater( () async => runner.run(['start']), throwsExceptionWith( 'Expected either the CLI arg --$kFrameworkMirrorOption or the environment variable FRAMEWORK_MIRROR to be provided', ), ); }); test('creates state file if provided correct inputs', () async { const String revision2 = 'def789'; const String revision3 = '123abc'; final List engineCommands = [ FakeCommand( command: [ 'git', 'clone', '--origin', 'upstream', '--', EngineRepository.defaultUpstream, fileSystem.path.join( checkoutsParentDirectory, 'flutter_conductor_checkouts', 'engine', ), ], ), const FakeCommand( command: ['git', 'remote', 'add', 'mirror', engineMirror], ), const FakeCommand( command: ['git', 'fetch', 'mirror'], ), const FakeCommand( command: ['git', 'checkout', 'upstream/$candidateBranch'], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], stdout: revision2, ), const FakeCommand( command: [ 'git', 'checkout', '-b', 'cherrypicks-$candidateBranch', ], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], stdout: revision2, ), ]; final List frameworkCommands = [ FakeCommand( command: [ 'git', 'clone', '--origin', 'upstream', '--', FrameworkRepository.defaultUpstream, fileSystem.path.join( checkoutsParentDirectory, 'flutter_conductor_checkouts', 'framework', ), ], ), const FakeCommand( command: ['git', 'remote', 'add', 'mirror', frameworkMirror], ), const FakeCommand( command: ['git', 'fetch', 'mirror'], ), const FakeCommand( command: ['git', 'checkout', 'upstream/$candidateBranch'], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], stdout: revision3, ), const FakeCommand( command: [ 'git', 'checkout', '-b', 'cherrypicks-$candidateBranch', ], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], stdout: revision3, ), ]; final CommandRunner runner = createRunner( commands: [ const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], stdout: revision, ), ...engineCommands, ...frameworkCommands, ], ); final String stateFilePath = fileSystem.path.join( platform.environment['HOME'], kStateFileName, ); await runner.run([ 'start', '--$kFrameworkMirrorOption', frameworkMirror, '--$kEngineMirrorOption', engineMirror, '--$kCandidateOption', candidateBranch, '--$kReleaseOption', releaseChannel, '--$kStateOption', stateFilePath, ]); final File stateFile = fileSystem.file(stateFilePath); final pb.ConductorState state = pb.ConductorState(); state.mergeFromProto3Json( jsonDecode(stateFile.readAsStringSync()), ); expect(state.isInitialized(), true); expect(state.releaseChannel, releaseChannel); expect(state.engine.candidateBranch, candidateBranch); expect(state.engine.startingGitHead, revision2); expect(state.framework.candidateBranch, candidateBranch); expect(state.framework.startingGitHead, revision3); expect(state.lastPhase, ReleasePhase.INITIALIZE); expect(state.conductorVersion, revision); }); }, onPlatform: { 'windows': const Skip('Flutter Conductor only supported on macos/linux'), }); }