| // 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 'dart:async'; |
| import 'dart:typed_data'; |
| |
| import 'package:args/args.dart'; |
| import 'package:args/command_runner.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:file_testing/file_testing.dart'; |
| import 'package:flutter_tools/src/base/context.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/logger.dart'; |
| import 'package:flutter_tools/src/base/os.dart'; |
| import 'package:flutter_tools/src/base/platform.dart'; |
| import 'package:flutter_tools/src/base/terminal.dart'; |
| import 'package:flutter_tools/src/base/user_messages.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/commands/custom_devices.dart'; |
| import 'package:flutter_tools/src/custom_devices/custom_device_config.dart'; |
| import 'package:flutter_tools/src/custom_devices/custom_devices_config.dart'; |
| import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/fakes.dart'; |
| |
| const String linuxFlutterRoot = '/flutter'; |
| const String windowsFlutterRoot = r'C:\flutter'; |
| |
| const String defaultConfigLinux1 = r''' |
| { |
| "$schema": "file:///flutter/packages/flutter_tools/static/custom-devices.schema.json", |
| "custom-devices": [ |
| { |
| "id": "pi", |
| "label": "Raspberry Pi", |
| "sdkNameAndVersion": "Raspberry Pi 4 Model B+", |
| "platform": "linux-arm64", |
| "enabled": false, |
| "ping": [ |
| "ping", |
| "-w", |
| "1", |
| "-c", |
| "1", |
| "raspberrypi" |
| ], |
| "pingSuccessRegex": null, |
| "postBuild": null, |
| "install": [ |
| "scp", |
| "-r", |
| "-o", |
| "BatchMode=yes", |
| "${localPath}", |
| "pi@raspberrypi:/tmp/${appName}" |
| ], |
| "uninstall": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "pi@raspberrypi", |
| "rm -rf \"/tmp/${appName}\"" |
| ], |
| "runDebug": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "pi@raspberrypi", |
| "flutter-pi \"/tmp/${appName}\"" |
| ], |
| "forwardPort": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "-o", |
| "ExitOnForwardFailure=yes", |
| "-L", |
| "127.0.0.1:${hostPort}:127.0.0.1:${devicePort}", |
| "pi@raspberrypi", |
| "echo 'Port forwarding success'; read" |
| ], |
| "forwardPortSuccessRegex": "Port forwarding success", |
| "screenshot": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "pi@raspberrypi", |
| "fbgrab /tmp/screenshot.png && cat /tmp/screenshot.png | base64 | tr -d ' \\n\\t'" |
| ] |
| } |
| ] |
| } |
| '''; |
| const String defaultConfigLinux2 = r''' |
| { |
| "custom-devices": [ |
| { |
| "id": "pi", |
| "label": "Raspberry Pi", |
| "sdkNameAndVersion": "Raspberry Pi 4 Model B+", |
| "platform": "linux-arm64", |
| "enabled": false, |
| "ping": [ |
| "ping", |
| "-w", |
| "1", |
| "-c", |
| "1", |
| "raspberrypi" |
| ], |
| "pingSuccessRegex": null, |
| "postBuild": null, |
| "install": [ |
| "scp", |
| "-r", |
| "-o", |
| "BatchMode=yes", |
| "${localPath}", |
| "pi@raspberrypi:/tmp/${appName}" |
| ], |
| "uninstall": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "pi@raspberrypi", |
| "rm -rf \"/tmp/${appName}\"" |
| ], |
| "runDebug": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "pi@raspberrypi", |
| "flutter-pi \"/tmp/${appName}\"" |
| ], |
| "forwardPort": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "-o", |
| "ExitOnForwardFailure=yes", |
| "-L", |
| "127.0.0.1:${hostPort}:127.0.0.1:${devicePort}", |
| "pi@raspberrypi", |
| "echo 'Port forwarding success'; read" |
| ], |
| "forwardPortSuccessRegex": "Port forwarding success", |
| "screenshot": [ |
| "ssh", |
| "-o", |
| "BatchMode=yes", |
| "pi@raspberrypi", |
| "fbgrab /tmp/screenshot.png && cat /tmp/screenshot.png | base64 | tr -d ' \\n\\t'" |
| ] |
| } |
| ], |
| "$schema": "file:///flutter/packages/flutter_tools/static/custom-devices.schema.json" |
| } |
| '''; |
| |
| final Platform windowsPlatform = FakePlatform( |
| operatingSystem: 'windows', |
| environment: <String, String>{ |
| 'FLUTTER_ROOT': windowsFlutterRoot, |
| } |
| ); |
| |
| class FakeTerminal implements Terminal { |
| factory FakeTerminal({required Platform platform}) { |
| return FakeTerminal._private( |
| stdio: FakeStdio(), |
| platform: platform |
| ); |
| } |
| |
| FakeTerminal._private({ |
| required this.stdio, |
| required Platform platform |
| }) : |
| terminal = AnsiTerminal( |
| stdio: stdio, |
| platform: platform |
| ); |
| |
| final FakeStdio stdio; |
| final AnsiTerminal terminal; |
| |
| void simulateStdin(String line) { |
| stdio.simulateStdin(line); |
| } |
| |
| @override |
| set usesTerminalUi(bool value) => terminal.usesTerminalUi = value; |
| |
| @override |
| bool get usesTerminalUi => terminal.usesTerminalUi; |
| |
| @override |
| String bolden(String message) => terminal.bolden(message); |
| |
| @override |
| String clearScreen() => terminal.clearScreen(); |
| |
| @override |
| String color(String message, TerminalColor color) => terminal.color(message, color); |
| |
| @override |
| Stream<String> get keystrokes => terminal.keystrokes; |
| |
| @override |
| Future<String> promptForCharInput( |
| List<String> acceptedCharacters, { |
| required Logger logger, |
| String? prompt, |
| int? defaultChoiceIndex, |
| bool displayAcceptedCharacters = true |
| }) => terminal.promptForCharInput( |
| acceptedCharacters, |
| logger: logger, |
| prompt: prompt, |
| defaultChoiceIndex: defaultChoiceIndex, |
| displayAcceptedCharacters: displayAcceptedCharacters |
| ); |
| |
| @override |
| bool get singleCharMode => terminal.singleCharMode; |
| @override |
| set singleCharMode(bool value) => terminal.singleCharMode = value; |
| |
| @override |
| bool get stdinHasTerminal => terminal.stdinHasTerminal; |
| |
| @override |
| String get successMark => terminal.successMark; |
| |
| @override |
| bool get supportsColor => terminal.supportsColor; |
| |
| @override |
| bool get supportsEmoji => terminal.supportsEmoji; |
| |
| @override |
| String get warningMark => terminal.warningMark; |
| |
| @override |
| int get preferredStyle => terminal.preferredStyle; |
| } |
| |
| class FakeCommandRunner extends FlutterCommandRunner { |
| FakeCommandRunner({ |
| required Platform platform, |
| required FileSystem fileSystem, |
| required Logger logger, |
| UserMessages? userMessages |
| }) : _platform = platform, |
| _fileSystem = fileSystem, |
| _logger = logger, |
| _userMessages = userMessages ?? UserMessages(), |
| assert(platform != null), |
| assert(fileSystem != null), |
| assert(logger != null); |
| |
| final Platform _platform; |
| final FileSystem _fileSystem; |
| final Logger _logger; |
| final UserMessages _userMessages; |
| |
| @override |
| Future<void> runCommand(ArgResults topLevelResults) async { |
| final Logger logger = (topLevelResults['verbose'] as bool) ? VerboseLogger(_logger) : _logger; |
| |
| return context.run<void>( |
| overrides: <Type, Generator>{ |
| Logger: () => logger, |
| }, |
| body: () { |
| Cache.flutterRoot ??= Cache.defaultFlutterRoot( |
| platform: _platform, |
| fileSystem: _fileSystem, |
| userMessages: _userMessages, |
| ); |
| // For compatibility with tests that set this to a relative path. |
| Cache.flutterRoot = _fileSystem.path.normalize(_fileSystem.path.absolute(Cache.flutterRoot!)); |
| return super.runCommand(topLevelResults); |
| } |
| ); |
| } |
| } |
| |
| /// May take platform, logger, processManager and fileSystem from context if |
| /// not explicitly specified. |
| CustomDevicesCommand createCustomDevicesCommand({ |
| CustomDevicesConfig Function(FileSystem, Logger)? config, |
| Terminal Function(Platform)? terminal, |
| Platform? platform, |
| FileSystem? fileSystem, |
| ProcessManager? processManager, |
| Logger? logger, |
| PrintFn? usagePrintFn, |
| bool featureEnabled = false |
| }) { |
| platform ??= FakePlatform(); |
| processManager ??= FakeProcessManager.any(); |
| fileSystem ??= MemoryFileSystem.test(); |
| usagePrintFn ??= print; |
| logger ??= BufferLogger.test(); |
| |
| return CustomDevicesCommand.test( |
| customDevicesConfig: config != null |
| ? config(fileSystem, logger) |
| : CustomDevicesConfig.test( |
| platform: platform, |
| fileSystem: fileSystem, |
| directory: fileSystem.directory('/'), |
| logger: logger |
| ), |
| operatingSystemUtils: FakeOperatingSystemUtils( |
| hostPlatform: platform.isLinux ? HostPlatform.linux_x64 |
| : platform.isWindows ? HostPlatform.windows_x64 |
| : platform.isMacOS ? HostPlatform.darwin_x64 |
| : throw UnsupportedError('Unsupported operating system') |
| ), |
| terminal: terminal != null |
| ? terminal(platform) |
| : FakeTerminal(platform: platform), |
| platform: platform, |
| featureFlags: TestFeatureFlags(areCustomDevicesEnabled: featureEnabled), |
| processManager: processManager, |
| fileSystem: fileSystem, |
| logger: logger, |
| usagePrintFn: usagePrintFn, |
| ); |
| } |
| |
| /// May take platform, logger, processManager and fileSystem from context if |
| /// not explicitly specified. |
| CommandRunner<void> createCustomDevicesCommandRunner({ |
| CustomDevicesConfig Function(FileSystem, Logger)? config, |
| Terminal Function(Platform)? terminal, |
| Platform? platform, |
| FileSystem? fileSystem, |
| ProcessManager? processManager, |
| Logger? logger, |
| PrintFn? usagePrintFn, |
| bool featureEnabled = false, |
| }) { |
| platform ??= FakePlatform(); |
| fileSystem ??= MemoryFileSystem.test(); |
| logger ??= BufferLogger.test(); |
| |
| return FakeCommandRunner( |
| platform: platform, |
| fileSystem: fileSystem, |
| logger: logger |
| )..addCommand( |
| createCustomDevicesCommand( |
| config: config, |
| terminal: terminal, |
| platform: platform, |
| fileSystem: fileSystem, |
| processManager: processManager, |
| logger: logger, |
| usagePrintFn: usagePrintFn, |
| featureEnabled: featureEnabled |
| ) |
| ); |
| } |
| |
| FakeTerminal createFakeTerminalForAddingSshDevice({ |
| required Platform platform, |
| required String id, |
| required String label, |
| required String sdkNameAndVersion, |
| required String enabled, |
| required String hostname, |
| required String username, |
| required String runDebug, |
| required String usePortForwarding, |
| required String screenshot, |
| required String apply |
| }) { |
| return FakeTerminal(platform: platform) |
| ..simulateStdin(id) |
| ..simulateStdin(label) |
| ..simulateStdin(sdkNameAndVersion) |
| ..simulateStdin(enabled) |
| ..simulateStdin(hostname) |
| ..simulateStdin(username) |
| ..simulateStdin(runDebug) |
| ..simulateStdin(usePortForwarding) |
| ..simulateStdin(screenshot) |
| ..simulateStdin(apply); |
| } |
| |
| void main() { |
| const String featureNotEnabledMessage = 'Custom devices feature must be enabled. Enable using `flutter config --enable-custom-devices`.'; |
| |
| setUpAll(() { |
| Cache.disableLocking(); |
| }); |
| |
| group('linux', () { |
| setUp(() { |
| Cache.flutterRoot = linuxFlutterRoot; |
| }); |
| |
| testUsingContext( |
| 'custom-devices command shows config file in help when feature is enabled', |
| () async { |
| final BufferLogger logger = BufferLogger.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| logger: logger, |
| usagePrintFn: (Object o) => logger.printStatus(o.toString()), |
| featureEnabled: true |
| ); |
| await expectLater( |
| runner.run(const <String>['custom-devices', '--help']), |
| completes |
| ); |
| expect( |
| logger.statusText, |
| contains('Makes changes to the config file at "/.flutter_custom_devices.json".') |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'running custom-devices command without arguments prints usage', |
| () async { |
| final BufferLogger logger = BufferLogger.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| logger: logger, |
| usagePrintFn: (Object o) => logger.printStatus(o.toString()), |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices']), |
| completes |
| ); |
| expect( |
| logger.statusText, |
| contains('Makes changes to the config file at "/.flutter_custom_devices.json".') |
| ); |
| } |
| ); |
| |
| // test behaviour with disabled feature |
| testUsingContext( |
| 'custom-devices add command fails when feature is not enabled', |
| () async { |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner(); |
| expect( |
| runner.run(const <String>['custom-devices', 'add']), |
| throwsToolExit(message: featureNotEnabledMessage), |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices delete command fails when feature is not enabled', |
| () async { |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner(); |
| expect( |
| runner.run(const <String>['custom-devices', 'delete', '-d', 'testid']), |
| throwsToolExit(message: featureNotEnabledMessage), |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices list command fails when feature is not enabled', |
| () async { |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner(); |
| expect( |
| runner.run(const <String>['custom-devices', 'list']), |
| throwsToolExit(message: featureNotEnabledMessage), |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices reset command fails when feature is not enabled', |
| () async { |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner(); |
| expect( |
| runner.run(const <String>['custom-devices', 'reset']), |
| throwsToolExit(message: featureNotEnabledMessage), |
| ); |
| } |
| ); |
| |
| // test add command |
| testUsingContext( |
| 'custom-devices add command correctly adds ssh device config on linux', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| terminal: (Platform platform) => createFakeTerminalForAddingSshDevice( |
| platform: platform, |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: 'y', |
| hostname: 'testhostname', |
| username: 'testuser', |
| runDebug: 'testrundebug', |
| usePortForwarding: 'y', |
| screenshot: 'testscreenshot', |
| apply: 'y' |
| ), |
| fileSystem: fs, |
| processManager: FakeProcessManager.any(), |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'add', '--no-check']), |
| completes |
| ); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test() |
| ); |
| |
| expect( |
| config.devices, |
| contains( |
| CustomDeviceConfig( |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: true, |
| pingCommand: const <String>[ |
| 'ping', |
| '-c', '1', |
| '-w', '1', |
| 'testhostname', |
| ], |
| postBuildCommand: null, // ignore: avoid_redundant_argument_values |
| installCommand: const <String>[ |
| 'scp', |
| '-r', |
| '-o', 'BatchMode=yes', |
| r'${localPath}', |
| r'testuser@testhostname:/tmp/${appName}', |
| ], |
| uninstallCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| r'rm -rf "/tmp/${appName}"', |
| ], |
| runDebugCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| 'testrundebug', |
| ], |
| forwardPortCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-o', 'ExitOnForwardFailure=yes', |
| '-L', r'127.0.0.1:${hostPort}:127.0.0.1:${devicePort}', |
| 'testuser@testhostname', |
| "echo 'Port forwarding success'; read", |
| ], |
| forwardPortSuccessRegex: RegExp('Port forwarding success'), |
| screenshotCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| 'testscreenshot', |
| ], |
| ) |
| ) |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices add command correctly adds ipv4 ssh device config', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| terminal: (Platform platform) => createFakeTerminalForAddingSshDevice( |
| platform: platform, |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: 'y', |
| hostname: '192.168.178.1', |
| username: 'testuser', |
| runDebug: 'testrundebug', |
| usePortForwarding: 'y', |
| screenshot: 'testscreenshot', |
| apply: 'y', |
| ), |
| processManager: FakeProcessManager.any(), |
| fileSystem: fs, |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'add', '--no-check']), |
| completes |
| ); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test() |
| ); |
| |
| expect( |
| config.devices, |
| contains( |
| CustomDeviceConfig( |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: true, |
| pingCommand: const <String>[ |
| 'ping', |
| '-c', '1', |
| '-w', '1', |
| '192.168.178.1', |
| ], |
| postBuildCommand: null, // ignore: avoid_redundant_argument_values |
| installCommand: const <String>[ |
| 'scp', |
| '-r', |
| '-o', 'BatchMode=yes', |
| r'${localPath}', |
| r'testuser@192.168.178.1:/tmp/${appName}', |
| ], |
| uninstallCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@192.168.178.1', |
| r'rm -rf "/tmp/${appName}"', |
| ], |
| runDebugCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@192.168.178.1', |
| 'testrundebug', |
| ], |
| forwardPortCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-o', 'ExitOnForwardFailure=yes', |
| '-L', r'127.0.0.1:${hostPort}:127.0.0.1:${devicePort}', |
| 'testuser@192.168.178.1', |
| "echo 'Port forwarding success'; read", |
| ], |
| forwardPortSuccessRegex: RegExp('Port forwarding success'), |
| screenshotCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@192.168.178.1', |
| 'testscreenshot', |
| ], |
| ), |
| ), |
| ); |
| }, |
| ); |
| |
| testUsingContext( |
| 'custom-devices add command correctly adds ipv6 ssh device config', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| terminal: (Platform platform) => createFakeTerminalForAddingSshDevice( |
| platform: platform, |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: 'y', |
| hostname: '::1', |
| username: 'testuser', |
| runDebug: 'testrundebug', |
| usePortForwarding: 'y', |
| screenshot: 'testscreenshot', |
| apply: 'y', |
| ), |
| fileSystem: fs, |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'add', '--no-check']), |
| completes |
| ); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test() |
| ); |
| |
| expect( |
| config.devices, |
| contains( |
| CustomDeviceConfig( |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: true, |
| pingCommand: const <String>[ |
| 'ping', |
| '-6', |
| '-c', '1', |
| '-w', '1', |
| '::1', |
| ], |
| postBuildCommand: null, // ignore: avoid_redundant_argument_values |
| installCommand: const <String>[ |
| 'scp', |
| '-r', |
| '-o', 'BatchMode=yes', |
| '-6', |
| r'${localPath}', |
| r'testuser@[::1]:/tmp/${appName}', |
| ], |
| uninstallCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-6', |
| 'testuser@[::1]', |
| r'rm -rf "/tmp/${appName}"', |
| ], |
| runDebugCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-6', |
| 'testuser@[::1]', |
| 'testrundebug', |
| ], |
| forwardPortCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-o', 'ExitOnForwardFailure=yes', |
| '-6', |
| '-L', r'[::1]:${hostPort}:[::1]:${devicePort}', |
| 'testuser@[::1]', |
| "echo 'Port forwarding success'; read", |
| ], |
| forwardPortSuccessRegex: RegExp('Port forwarding success'), |
| screenshotCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-6', |
| 'testuser@[::1]', |
| 'testscreenshot', |
| ], |
| ), |
| ), |
| ); |
| }, |
| ); |
| |
| testUsingContext( |
| 'custom-devices add command correctly adds non-forwarding ssh device config', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| terminal: (Platform platform) => createFakeTerminalForAddingSshDevice( |
| platform: platform, |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: 'y', |
| hostname: 'testhostname', |
| username: 'testuser', |
| runDebug: 'testrundebug', |
| usePortForwarding: 'n', |
| screenshot: 'testscreenshot', |
| apply: 'y', |
| ), |
| fileSystem: fs, |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'add', '--no-check']), |
| completes |
| ); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test() |
| ); |
| |
| expect( |
| config.devices, |
| contains( |
| const CustomDeviceConfig( |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: true, |
| pingCommand: <String>[ |
| 'ping', |
| '-c', '1', |
| '-w', '1', |
| 'testhostname', |
| ], |
| postBuildCommand: null, // ignore: avoid_redundant_argument_values |
| installCommand: <String>[ |
| 'scp', |
| '-r', |
| '-o', 'BatchMode=yes', |
| r'${localPath}', |
| r'testuser@testhostname:/tmp/${appName}', |
| ], |
| uninstallCommand: <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| r'rm -rf "/tmp/${appName}"', |
| ], |
| runDebugCommand: <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| 'testrundebug', |
| ], |
| screenshotCommand: <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| 'testscreenshot', |
| ], |
| ), |
| ), |
| ); |
| }, |
| ); |
| |
| testUsingContext( |
| 'custom-devices add command correctly adds non-screenshotting ssh device config', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| terminal: (Platform platform) => createFakeTerminalForAddingSshDevice( |
| platform: platform, |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: 'y', |
| hostname: 'testhostname', |
| username: 'testuser', |
| runDebug: 'testrundebug', |
| usePortForwarding: 'y', |
| screenshot: '', |
| apply: 'y', |
| ), |
| fileSystem: fs, |
| featureEnabled: true, |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'add', '--no-check']), |
| completes, |
| ); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test() |
| ); |
| |
| expect( |
| config.devices, |
| contains( |
| CustomDeviceConfig( |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: true, |
| pingCommand: const <String>[ |
| 'ping', |
| '-c', '1', |
| '-w', '1', |
| 'testhostname', |
| ], |
| postBuildCommand: null, // ignore: avoid_redundant_argument_values |
| installCommand: const <String>[ |
| 'scp', |
| '-r', |
| '-o', 'BatchMode=yes', |
| r'${localPath}', |
| r'testuser@testhostname:/tmp/${appName}', |
| ], |
| uninstallCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| r'rm -rf "/tmp/${appName}"', |
| ], |
| runDebugCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| 'testrundebug', |
| ], |
| forwardPortCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-o', 'ExitOnForwardFailure=yes', |
| '-L', r'127.0.0.1:${hostPort}:127.0.0.1:${devicePort}', |
| 'testuser@testhostname', |
| "echo 'Port forwarding success'; read", |
| ], |
| forwardPortSuccessRegex: RegExp('Port forwarding success'), |
| ) |
| ) |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices delete command deletes device and creates backup', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test(), |
| ); |
| |
| config.add(CustomDeviceConfig.exampleUnix.copyWith(id: 'testid')); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| config: (_, __) => config, |
| fileSystem: fs, |
| featureEnabled: true |
| ); |
| |
| final Uint8List contentsBefore = fs.file('.flutter_custom_devices.json').readAsBytesSync(); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'delete', '-d', 'testid']), |
| completes |
| ); |
| expect(fs.file('/.flutter_custom_devices.json.bak'), exists); |
| expect(config.devices, hasLength(0)); |
| |
| final Uint8List backupContents = fs.file('.flutter_custom_devices.json.bak').readAsBytesSync(); |
| expect(contentsBefore, equals(backupContents)); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices delete command without device argument throws tool exit', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test(), |
| ); |
| config.add(CustomDeviceConfig.exampleUnix.copyWith(id: 'testid2')); |
| final Uint8List contentsBefore = fs.file('.flutter_custom_devices.json').readAsBytesSync(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| featureEnabled: true |
| ); |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'delete']), |
| throwsToolExit() |
| ); |
| |
| final Uint8List contentsAfter = fs.file('.flutter_custom_devices.json').readAsBytesSync(); |
| expect(contentsBefore, equals(contentsAfter)); |
| expect(fs.file('.flutter_custom_devices.json.bak').existsSync(), isFalse); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices delete command throws tool exit with invalid device id', |
| () async { |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| featureEnabled: true |
| ); |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'delete', '-d', 'testid']), |
| throwsToolExit(message: 'Couldn\'t find device with id "testid" in config at "/.flutter_custom_devices.json"') |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices list command throws tool exit when config contains errors', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| |
| fs.file('.flutter_custom_devices.json').writeAsStringSync('{"custom-devices": {}}'); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| fileSystem: fs, |
| logger: logger, |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'list']), |
| throwsToolExit(message: 'Could not list custom devices.') |
| ); |
| expect( |
| logger.errorText, |
| contains("Could not load custom devices config. config['custom-devices'] is not a JSON array.") |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices list command prints message when no devices found', |
| () async { |
| final BufferLogger logger = BufferLogger.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| logger: logger, |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'list']), |
| completes |
| ); |
| expect( |
| logger.statusText, |
| contains('No custom devices found in "/.flutter_custom_devices.json"') |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices list command lists all devices', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| |
| CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: logger, |
| )..add( |
| CustomDeviceConfig.exampleUnix.copyWith(id: 'testid', label: 'testlabel', enabled: true) |
| )..add( |
| CustomDeviceConfig.exampleUnix.copyWith(id: 'testid2', label: 'testlabel2', enabled: false) |
| ); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| logger: logger, |
| fileSystem: fs, |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'list']), |
| completes |
| ); |
| expect( |
| logger.statusText, |
| contains('List of custom devices in "/.flutter_custom_devices.json":') |
| ); |
| expect( |
| logger.statusText, |
| contains('id: testid, label: testlabel, enabled: true') |
| ); |
| expect( |
| logger.statusText, |
| contains('id: testid2, label: testlabel2, enabled: false') |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| 'custom-devices reset correctly backs up the config file', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| |
| CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: logger, |
| )..add( |
| CustomDeviceConfig.exampleUnix.copyWith(id: 'testid', label: 'testlabel', enabled: true) |
| )..add( |
| CustomDeviceConfig.exampleUnix.copyWith(id: 'testid2', label: 'testlabel2', enabled: false) |
| ); |
| |
| final Uint8List contentsBefore = fs.file('.flutter_custom_devices.json').readAsBytesSync(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| logger: logger, |
| fileSystem: fs, |
| featureEnabled: true |
| ); |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'reset']), |
| completes |
| ); |
| expect( |
| logger.statusText, |
| contains( |
| 'Successfully resetted the custom devices config file and created a ' |
| 'backup at "/.flutter_custom_devices.json.bak".' |
| ) |
| ); |
| |
| final Uint8List backupContents = fs.file('.flutter_custom_devices.json.bak').readAsBytesSync(); |
| expect(contentsBefore, equals(backupContents)); |
| expect( |
| fs.file('.flutter_custom_devices.json').readAsStringSync(), |
| anyOf(equals(defaultConfigLinux1), equals(defaultConfigLinux2)) |
| ); |
| } |
| ); |
| |
| testUsingContext( |
| "custom-devices reset outputs correct msg when config file didn't exist", |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| logger: logger, |
| fileSystem: fs, |
| featureEnabled: true |
| ); |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'reset']), |
| completes |
| ); |
| expect( |
| logger.statusText, |
| contains( |
| 'Successfully resetted the custom devices config file.' |
| ) |
| ); |
| |
| expect(fs.file('.flutter_custom_devices.json.bak'), isNot(exists)); |
| expect( |
| fs.file('.flutter_custom_devices.json').readAsStringSync(), |
| anyOf(equals(defaultConfigLinux1), equals(defaultConfigLinux2)) |
| ); |
| } |
| ); |
| }); |
| |
| group('windows', () { |
| setUp(() { |
| Cache.flutterRoot = windowsFlutterRoot; |
| }); |
| |
| testUsingContext( |
| 'custom-devices add command correctly adds ssh device config on windows', |
| () async { |
| final MemoryFileSystem fs = MemoryFileSystem.test(style: FileSystemStyle.windows); |
| |
| final CommandRunner<void> runner = createCustomDevicesCommandRunner( |
| terminal: (Platform platform) => createFakeTerminalForAddingSshDevice( |
| platform: platform, |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: 'y', |
| hostname: 'testhostname', |
| username: 'testuser', |
| runDebug: 'testrundebug', |
| usePortForwarding: 'y', |
| screenshot: 'testscreenshot', |
| apply: 'y', |
| ), |
| fileSystem: fs, |
| platform: windowsPlatform, |
| featureEnabled: true |
| ); |
| |
| await expectLater( |
| runner.run(const <String>['custom-devices', 'add', '--no-check']), |
| completes |
| ); |
| |
| final CustomDevicesConfig config = CustomDevicesConfig.test( |
| fileSystem: fs, |
| directory: fs.directory('/'), |
| logger: BufferLogger.test() |
| ); |
| |
| expect( |
| config.devices, |
| contains( |
| CustomDeviceConfig( |
| id: 'testid', |
| label: 'testlabel', |
| sdkNameAndVersion: 'testsdknameandversion', |
| enabled: true, |
| pingCommand: const <String>[ |
| 'ping', |
| '-n', '1', |
| '-w', '500', |
| 'testhostname', |
| ], |
| pingSuccessRegex: RegExp(r'[<=]\d+ms'), |
| postBuildCommand: null, // ignore: avoid_redundant_argument_values |
| installCommand: const <String>[ |
| 'scp', |
| '-r', |
| '-o', 'BatchMode=yes', |
| r'${localPath}', |
| r'testuser@testhostname:/tmp/${appName}', |
| ], |
| uninstallCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| r'rm -rf "/tmp/${appName}"', |
| ], |
| runDebugCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| 'testrundebug', |
| ], |
| forwardPortCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| '-o', 'ExitOnForwardFailure=yes', |
| '-L', r'127.0.0.1:${hostPort}:127.0.0.1:${devicePort}', |
| 'testuser@testhostname', |
| "echo 'Port forwarding success'; read", |
| ], |
| forwardPortSuccessRegex: RegExp('Port forwarding success'), |
| screenshotCommand: const <String>[ |
| 'ssh', |
| '-o', 'BatchMode=yes', |
| 'testuser@testhostname', |
| 'testscreenshot', |
| ], |
| ), |
| ), |
| ); |
| }, |
| ); |
| }); |
| } |