| // 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:args/command_runner.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/commands/channel.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| import 'package:flutter_tools/src/version.dart'; |
| |
| import '../src/common.dart'; |
| import '../src/context.dart'; |
| import '../src/fake_process_manager.dart'; |
| import '../src/fakes.dart' show FakeFlutterVersion; |
| import '../src/test_flutter_command_runner.dart'; |
| |
| void main() { |
| group('channel', () { |
| late FakeProcessManager fakeProcessManager; |
| |
| setUp(() { |
| fakeProcessManager = FakeProcessManager.empty(); |
| }); |
| |
| setUpAll(() { |
| Cache.disableLocking(); |
| }); |
| |
| Future<void> simpleChannelTest(List<String> args) async { |
| fakeProcessManager.addCommands(const <FakeCommand>[ |
| FakeCommand( |
| command: <String>['git', 'branch', '-r'], |
| stdout: |
| ' origin/branch-1\n' |
| ' origin/branch-2\n' |
| ' origin/master\n' |
| ' origin/main\n' |
| ' origin/stable\n' |
| ' origin/beta', |
| ), |
| ]); |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(args); |
| expect(testLogger.errorText, hasLength(0)); |
| // The bots may return an empty list of channels (network hiccup?) |
| // and when run locally the list of branches might be different |
| // so we check for the header text rather than any specific channel name. |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace('Flutter channels:'), |
| ); |
| } |
| |
| testUsingContext('list', () async { |
| await simpleChannelTest(<String>['channel']); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| }); |
| |
| testUsingContext('verbose list', () async { |
| await simpleChannelTest(<String>['channel', '-v']); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| }); |
| |
| testUsingContext('sorted by stability', () async { |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| |
| fakeProcessManager.addCommand( |
| const FakeCommand( |
| command: <String>['git', 'branch', '-r'], |
| stdout: 'origin/beta\n' |
| 'origin/master\n' |
| 'origin/main\n' |
| 'origin/stable\n', |
| ), |
| ); |
| |
| await runner.run(<String>['channel']); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(testLogger.errorText, hasLength(0)); |
| expect(testLogger.statusText, |
| 'Flutter channels:\n' |
| '* master (latest development branch, for contributors)\n' |
| ' main (latest development branch, follows master channel)\n' |
| ' beta (updated monthly, recommended for experienced users)\n' |
| ' stable (updated quarterly, for new users and for production app releases)\n', |
| ); |
| |
| // clear buffer for next process |
| testLogger.clear(); |
| |
| // Extra branches. |
| fakeProcessManager.addCommand( |
| const FakeCommand( |
| command: <String>['git', 'branch', '-r'], |
| stdout: 'origin/beta\n' |
| 'origin/master\n' |
| 'origin/dependabot/bundler\n' |
| 'origin/main\n' |
| 'origin/v1.4.5-hotfixes\n' |
| 'origin/stable\n', |
| ), |
| ); |
| |
| await runner.run(<String>['channel']); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(testLogger.errorText, hasLength(0)); |
| expect(testLogger.statusText, |
| 'Flutter channels:\n' |
| '* master (latest development branch, for contributors)\n' |
| ' main (latest development branch, follows master channel)\n' |
| ' beta (updated monthly, recommended for experienced users)\n' |
| ' stable (updated quarterly, for new users and for production app releases)\n', |
| ); |
| |
| // clear buffer for next process |
| testLogger.clear(); |
| |
| // Missing branches. |
| fakeProcessManager.addCommand( |
| const FakeCommand( |
| command: <String>['git', 'branch', '-r'], |
| stdout: 'origin/master\n' |
| 'origin/dependabot/bundler\n' |
| 'origin/v1.4.5-hotfixes\n' |
| 'origin/stable\n' |
| 'origin/beta\n', |
| ), |
| ); |
| |
| await runner.run(<String>['channel']); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(testLogger.errorText, hasLength(0)); |
| // check if available official channels are in order of stability |
| int prev = -1; |
| int next = -1; |
| for (final String branch in kOfficialChannels) { |
| next = testLogger.statusText.indexOf(branch); |
| if (next != -1) { |
| expect(prev < next, isTrue); |
| prev = next; |
| } |
| } |
| |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| }); |
| |
| testUsingContext('ignores lines with unexpected output', () async { |
| fakeProcessManager.addCommand( |
| const FakeCommand( |
| command: <String>['git', 'branch', '-r'], |
| stdout: 'origin/beta\n' |
| 'origin/stable\n' |
| 'upstream/beta\n' |
| 'upstream/stable\n' |
| 'foo', |
| ), |
| ); |
| |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['channel']); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(testLogger.errorText, hasLength(0)); |
| expect(testLogger.statusText, |
| 'Flutter channels:\n' |
| '* beta (updated monthly, recommended for experienced users)\n' |
| ' stable (updated quarterly, for new users and for production app releases)\n' |
| ); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| FlutterVersion: () => FakeFlutterVersion(branch: 'beta'), |
| }); |
| |
| testUsingContext('handles custom branches', () async { |
| fakeProcessManager.addCommand( |
| const FakeCommand( |
| command: <String>['git', 'branch', '-r'], |
| stdout: 'origin/beta\n' |
| 'origin/stable\n' |
| 'origin/foo', |
| ), |
| ); |
| |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['channel']); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(testLogger.errorText, hasLength(0)); |
| expect(testLogger.statusText, |
| 'Flutter channels:\n' |
| ' beta (updated monthly, recommended for experienced users)\n' |
| ' stable (updated quarterly, for new users and for production app releases)\n' |
| '* foo\n' |
| '\n' |
| 'Currently not on an official channel.\n', |
| ); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| FlutterVersion: () => FakeFlutterVersion(branch: 'foo'), |
| }); |
| |
| testUsingContext('removes duplicates', () async { |
| fakeProcessManager.addCommand( |
| const FakeCommand( |
| command: <String>['git', 'branch', '-r'], |
| stdout: 'origin/beta\n' |
| 'origin/stable\n' |
| 'upstream/beta\n' |
| 'upstream/stable\n', |
| ), |
| ); |
| |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['channel']); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(testLogger.errorText, hasLength(0)); |
| expect(testLogger.statusText, |
| 'Flutter channels:\n' |
| '* beta (updated monthly, recommended for experienced users)\n' |
| ' stable (updated quarterly, for new users and for production app releases)\n' |
| ); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| FlutterVersion: () => FakeFlutterVersion(branch: 'beta'), |
| }); |
| |
| testUsingContext('can switch channels', () async { |
| fakeProcessManager.addCommands(const <FakeCommand>[ |
| FakeCommand( |
| command: <String>['git', 'fetch'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'checkout', 'beta', '--'] |
| ), |
| FakeCommand( |
| command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], |
| ), |
| ]); |
| |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['channel', 'beta']); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace("Switching to flutter channel 'beta'..."), |
| ); |
| expect(testLogger.errorText, hasLength(0)); |
| |
| fakeProcessManager.addCommands(const <FakeCommand>[ |
| FakeCommand( |
| command: <String>['git', 'fetch'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/stable'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'checkout', 'stable', '--'], |
| ), |
| FakeCommand( |
| command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], |
| ), |
| ]); |
| |
| await runner.run(<String>['channel', 'stable']); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => fakeProcessManager, |
| }); |
| |
| testUsingContext('switching channels prompts to run flutter upgrade', () async { |
| fakeProcessManager.addCommands(const <FakeCommand>[ |
| FakeCommand( |
| command: <String>['git', 'fetch'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'checkout', 'beta', '--'] |
| ), |
| FakeCommand( |
| command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], |
| ), |
| ]); |
| |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['channel', 'beta']); |
| |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace("Successfully switched to flutter channel 'beta'."), |
| ); |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace( |
| "To ensure that you're on the latest build " |
| "from this channel, run 'flutter upgrade'"), |
| ); |
| expect(testLogger.errorText, hasLength(0)); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => fakeProcessManager, |
| }); |
| |
| // This verifies that bug https://github.com/flutter/flutter/issues/21134 |
| // doesn't return. |
| testUsingContext('removes version stamp file when switching channels', () async { |
| fakeProcessManager.addCommands(const <FakeCommand>[ |
| FakeCommand( |
| command: <String>['git', 'fetch'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], |
| ), |
| FakeCommand( |
| command: <String>['git', 'checkout', 'beta', '--'] |
| ), |
| FakeCommand( |
| command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], |
| ), |
| ]); |
| |
| final File versionCheckFile = globals.cache.getStampFileFor( |
| VersionCheckStamp.flutterVersionCheckStampFile, |
| ); |
| |
| /// Create a bogus "leftover" version check file to make sure it gets |
| /// removed when the channel changes. The content doesn't matter. |
| versionCheckFile.createSync(recursive: true); |
| versionCheckFile.writeAsStringSync(''' |
| { |
| "lastTimeVersionWasChecked": "2151-08-29 10:17:30.763802", |
| "lastKnownRemoteVersion": "2151-09-26 15:56:19.000Z" |
| } |
| '''); |
| |
| final ChannelCommand command = ChannelCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['channel', 'beta']); |
| |
| expect(testLogger.statusText, isNot(contains('A new version of Flutter'))); |
| expect(testLogger.errorText, hasLength(0)); |
| expect(versionCheckFile.existsSync(), isFalse); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => fakeProcessManager, |
| }); |
| }); |
| } |