// 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. import 'package:file/memory.dart'; import 'package:platform/platform.dart'; import 'package:dev_tools/roll_dev.dart'; import 'package:dev_tools/globals.dart'; import 'package:dev_tools/repository.dart'; import '../../../packages/flutter_tools/test/src/fake_process_manager.dart'; import './common.dart'; void main() { group('rollDev()', () { const String usage = 'usage info...'; const String level = 'm'; const String commit = 'abcde012345'; const String remote = 'origin'; const String lastVersion = '1.2.0-0.0.pre'; const String nextVersion = '1.2.0-1.0.pre'; const String checkoutsParentDirectory = '/path/to/directory/'; FakeArgResults fakeArgResults; MemoryFileSystem fileSystem; TestStdio stdio; Repository repo; Checkouts checkouts; FakePlatform platform; FakeProcessManager processManager; setUp(() { stdio = TestStdio(); fileSystem = MemoryFileSystem.test(); platform = FakePlatform(); processManager = FakeProcessManager.list([]); checkouts = Checkouts( fileSystem: fileSystem, parentDirectory: fileSystem.directory(checkoutsParentDirectory), platform: platform, processManager: processManager, ); repo = checkouts.addRepo( platform: platform, repoType: RepositoryType.framework, stdio: stdio, ); }); test('returns false if level not provided', () { fakeArgResults = FakeArgResults( level: null, commit: commit, remote: remote, ); expect( rollDev( argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, usage: usage, ), false, ); }); test('returns false if commit not provided', () { fakeArgResults = FakeArgResults( level: level, commit: null, remote: remote, ); expect( rollDev( argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, usage: usage, ), false, ); }); test('throws exception if git checkout not clean', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ], stdout: ' M dev/conductor/bin/conductor.dart'), ]); fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, ); Exception exception; try { rollDev( argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, usage: usage, ); } on Exception catch (e) { exception = e; } const String pattern = r'Your git repository is not clean. Try running ' '"git clean -fd". Warning, this will delete files! Run with -n to find ' 'out which ones.'; expect(exception?.toString(), contains(pattern)); }); test('does not reset or tag if --just-print is specified', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ]), const FakeCommand(command: [ 'git', 'fetch', remote, '--tags', ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'describe', '--match', '*.*.*-*.*.pre', '--exact-match', '--tags', 'refs/remotes/$remote/dev', ], stdout: lastVersion), const FakeCommand(command: [ 'git', 'rev-parse', lastVersion, ], stdout: 'zxy321'), ]); fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, justPrint: true, ); expect( rollDev( usage: usage, argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, ), false, ); expect(stdio.stdout.contains(nextVersion), true); }); test( 'exits with exception if --skip-tagging is provided but commit isn\'t ' 'already tagged', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ]), const FakeCommand(command: [ 'git', 'fetch', remote, '--tags', ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'describe', '--match', '*.*.*-*.*.pre', '--exact-match', '--tags', 'refs/remotes/$remote/dev', ], stdout: lastVersion), const FakeCommand(command: [ 'git', 'rev-parse', lastVersion, ], stdout: 'zxy321'), const FakeCommand(command: [ 'git', 'describe', '--exact-match', '--tags', commit, ], exitCode: 1), ]); const String exceptionMessage = 'The $kSkipTagging flag is only supported ' 'for tagged commits.'; fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, skipTagging: true, ); expect( () => rollDev( usage: usage, argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, ), throwsExceptionWith(exceptionMessage), ); }); test('throws exception if desired commit is already tip of dev branch', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ]), const FakeCommand(command: [ 'git', 'fetch', remote, '--tags', ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'describe', '--match', '*.*.*-*.*.pre', '--exact-match', '--tags', 'refs/remotes/$remote/dev', ], stdout: lastVersion), // [commit] is already [lastVersion] const FakeCommand(command: [ 'git', 'rev-parse', lastVersion, ], stdout: commit), ]); fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, justPrint: true, ); expect( () => rollDev( usage: usage, argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, ), throwsExceptionWith( 'Commit $commit is already on the dev branch as $lastVersion', ), ); }); test( 'does not tag if last release is not direct ancestor of desired ' 'commit and --force not supplied', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ]), const FakeCommand(command: [ 'git', 'fetch', remote, '--tags', ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'describe', '--match', '*.*.*-*.*.pre', '--exact-match', '--tags', 'refs/remotes/$remote/dev', ], stdout: lastVersion), const FakeCommand(command: [ 'git', 'rev-parse', lastVersion, ], stdout: 'zxy321'), const FakeCommand(command: [ 'git', 'merge-base', '--is-ancestor', lastVersion, commit, ], exitCode: 1), ]); fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, ); const String errorMessage = 'The previous dev tag $lastVersion is not a ' 'direct ancestor of $commit.'; expect( () => rollDev( argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, usage: usage, ), throwsExceptionWith(errorMessage), ); }); test('does not tag but updates branch if --skip-tagging provided', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ]), const FakeCommand(command: [ 'git', 'fetch', remote, '--tags', ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'describe', '--match', '*.*.*-*.*.pre', '--exact-match', '--tags', 'refs/remotes/$remote/dev', ], stdout: lastVersion), const FakeCommand(command: [ 'git', 'rev-parse', lastVersion, ], stdout: 'zxy321'), const FakeCommand(command: [ 'git', 'describe', '--exact-match', '--tags', commit, ]), const FakeCommand(command: [ 'git', 'merge-base', '--is-ancestor', lastVersion, commit, ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'push', remote, '$commit:dev', ]), ]); fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, skipTagging: true, ); expect( rollDev( usage: usage, argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, ), true, ); }); test('successfully tags and publishes release', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ]), const FakeCommand(command: [ 'git', 'fetch', remote, '--tags', ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'describe', '--match', '*.*.*-*.*.pre', '--exact-match', '--tags', 'refs/remotes/$remote/dev', ], stdout: lastVersion), const FakeCommand(command: [ 'git', 'rev-parse', lastVersion, ], stdout: 'zxy321'), const FakeCommand(command: [ 'git', 'merge-base', '--is-ancestor', lastVersion, commit, ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'tag', nextVersion, commit, ]), const FakeCommand(command: [ 'git', 'push', remote, nextVersion, ]), const FakeCommand(command: [ 'git', 'push', remote, '$commit:dev', ]), ]); fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, ); expect( rollDev( usage: usage, argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, ), true, ); }); test('successfully publishes release with --force', () { processManager.addCommands([ const FakeCommand(command: [ 'git', 'clone', '--', kUpstreamRemote, '${checkoutsParentDirectory}checkouts/framework', ]), const FakeCommand(command: [ 'git', 'remote', 'get-url', remote, ], stdout: kUpstreamRemote), const FakeCommand(command: [ 'git', 'status', '--porcelain', ]), const FakeCommand(command: [ 'git', 'fetch', remote, '--tags', ]), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'describe', '--match', '*.*.*-*.*.pre', '--exact-match', '--tags', 'refs/remotes/$remote/dev', ], stdout: lastVersion), const FakeCommand(command: [ 'git', 'rev-parse', lastVersion, ], stdout: 'zxy321'), const FakeCommand(command: [ 'git', 'rev-parse', commit, ], stdout: commit), const FakeCommand(command: [ 'git', 'tag', nextVersion, commit, ]), const FakeCommand(command: [ 'git', 'push', remote, nextVersion, ]), const FakeCommand(command: [ 'git', 'push', '--force', remote, '$commit:dev', ]), ]); fakeArgResults = FakeArgResults( level: level, commit: commit, remote: remote, force: true, ); expect( rollDev( argResults: fakeArgResults, fileSystem: fileSystem, platform: platform, repository: repo, stdio: stdio, usage: usage, ), true, ); expect(processManager.hasRemainingExpectations, false); }); }, onPlatform: { 'windows': const Skip('Flutter Conductor only supported on macos/linux'), }); }