// 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 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/downgrade.dart'; import 'package:flutter_tools/src/persistent_tool_state.dart'; import 'package:mockito/mockito.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fakes.dart'; import '../../src/test_flutter_command_runner.dart'; void main() { FileSystem fileSystem; BufferLogger bufferLogger; AnsiTerminal terminal; ProcessManager processManager; MockStdio mockStdio; setUpAll(() { Cache.disableLocking(); }); tearDownAll(() { Cache.enableLocking(); }); setUp(() { mockStdio = MockStdio(); processManager = FakeProcessManager.any(); terminal = MockTerminal(); fileSystem = MemoryFileSystem.test(); bufferLogger = BufferLogger.test(terminal: terminal); }); testUsingContext('Downgrade exits on unknown channel', () async { final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'unknown'); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"invalid"}'); final DowngradeCommand command = DowngradeCommand( persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger), processManager: processManager, terminal: terminal, stdio: mockStdio, flutterVersion: fakeFlutterVersion, logger: bufferLogger, ); expect(createTestCommandRunner(command).run(const ['downgrade']), throwsToolExit(message: 'Flutter is not currently on a known channel.')); }); testUsingContext('Downgrade exits on no recorded version', () async { final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'dev'); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"abcd"}'); final DowngradeCommand command = DowngradeCommand( persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger), processManager: FakeProcessManager.list([ const FakeCommand( command: [ 'git', 'describe', '--tags', 'abcd' ], exitCode: 0, stdout: 'v1.2.3' ) ]), terminal: terminal, stdio: mockStdio, flutterVersion: fakeFlutterVersion, logger: bufferLogger, ); expect(createTestCommandRunner(command).run(const ['downgrade']), throwsToolExit(message: 'There is no previously recorded version for channel "dev".\n' 'Channel "master" was previously on: v1.2.3.' ), ); }); testUsingContext('Downgrade exits on unknown recorded version', () async { final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'master'); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"invalid"}'); final DowngradeCommand command = DowngradeCommand( persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger), processManager: FakeProcessManager.list([ const FakeCommand( command: [ 'git', 'describe', '--tags', 'invalid' ], exitCode: 1, ) ]), terminal: terminal, stdio: mockStdio, flutterVersion: fakeFlutterVersion, logger: bufferLogger, ); expect(createTestCommandRunner(command).run(const ['downgrade']), throwsToolExit(message: 'Failed to parse version for downgrade')); }); testUsingContext('Downgrade prompts for user input when terminal is attached - y', () async { final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'master'); when(mockStdio.hasTerminal).thenReturn(true); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}'); final DowngradeCommand command = DowngradeCommand( persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger), processManager: processManager, terminal: terminal, stdio: mockStdio, flutterVersion: fakeFlutterVersion, logger: bufferLogger, ); when(terminal.promptForCharInput( const ['y', 'n'], prompt: anyNamed('prompt'), logger: anyNamed('logger'), )).thenAnswer((Invocation invocation) async => 'y'); await createTestCommandRunner(command).run(const ['downgrade']); verify(terminal.promptForCharInput( const ['y', 'n'], prompt: anyNamed('prompt'), logger: anyNamed('logger'), )).called(1); expect(bufferLogger.statusText, contains('Success')); }); testUsingContext('Downgrade prompts for user input when terminal is attached - n', () async { final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'master'); when(mockStdio.hasTerminal).thenReturn(true); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}'); final DowngradeCommand command = DowngradeCommand( persistentToolState: PersistentToolState.test(directory: fileSystem.currentDirectory, logger: bufferLogger), processManager: processManager, terminal: terminal, stdio: mockStdio, flutterVersion: fakeFlutterVersion, logger: bufferLogger, ); when(terminal.promptForCharInput( const ['y', 'n'], prompt: anyNamed('prompt'), logger: anyNamed('logger'), )).thenAnswer((Invocation invocation) async => 'n'); await createTestCommandRunner(command).run(const ['downgrade']); verify(terminal.promptForCharInput( const ['y', 'n'], prompt: anyNamed('prompt'), logger: anyNamed('logger'), )).called(1); expect(bufferLogger.statusText, isNot(contains('Success'))); }); testUsingContext('Downgrade does not prompt when there is no terminal', () async { final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'master'); when(mockStdio.hasTerminal).thenReturn(false); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}'); final DowngradeCommand command = DowngradeCommand( persistentToolState: PersistentToolState.test( directory: fileSystem.currentDirectory, logger: bufferLogger, ), processManager: processManager, terminal: terminal, stdio: mockStdio, flutterVersion: fakeFlutterVersion, logger: bufferLogger, ); await createTestCommandRunner(command).run(const ['downgrade']); verifyNever(terminal.promptForCharInput( const ['y', 'n'], prompt: anyNamed('prompt'), logger: anyNamed('logger'), )); expect(bufferLogger.statusText, contains('Success')); }); testUsingContext('Downgrade performs correct git commands', () async { final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'master'); when(mockStdio.hasTerminal).thenReturn(false); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"g6b00b5e88"}'); final DowngradeCommand command = DowngradeCommand( persistentToolState: PersistentToolState.test( directory: fileSystem.currentDirectory, logger: bufferLogger, ), processManager: FakeProcessManager.list([ const FakeCommand( command: [ 'git', 'describe', '--tags', 'g6b00b5e88' ], stdout: 'v1.2.3', ), const FakeCommand( command: [ 'git', 'reset', '--hard', 'g6b00b5e88' ], ), const FakeCommand( command: [ 'git', 'checkout', 'master', '--' ] ), ]), terminal: terminal, stdio: mockStdio, flutterVersion: fakeFlutterVersion, logger: bufferLogger, ); await createTestCommandRunner(command).run(const ['downgrade']); expect(bufferLogger.statusText, contains('Success')); }); } class MockTerminal extends Mock implements AnsiTerminal {} class MockStdio extends Mock implements Stdio {}