flutter/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
Dan Field c417c4623c
Refactor ShaderTarget to not explicitly mention impeller or Skia (#141460)
Refactors `ShaderTarget` to make it opaque as to whether it's using Impeller or SkSL and instead has it focus on the target platform it's generating for.

ImpellerC includes SkSL right now whether you ask for it or not. 

The tester target also might need SkSL or Vulkan depending on whether `--enable-impeller` is passed.
2024-01-31 21:30:02 +00:00

1485 lines
51 KiB
Dart

// 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 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/signals.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_devtools_handler.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:test/fake.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import '../src/common.dart';
import '../src/fake_vm_services.dart';
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
id: '1',
pauseEvent: vm_service.Event(
kind: vm_service.EventKind.kResume,
timestamp: 0
),
breakpoints: <vm_service.Breakpoint>[],
extensionRPCs: <String>[],
libraries: <vm_service.LibraryRef>[
vm_service.LibraryRef(
id: '1',
uri: 'file:///hello_world/main.dart',
name: '',
),
],
livePorts: 0,
name: 'test',
number: '1',
pauseOnExit: false,
runnable: true,
startTime: 0,
isSystemIsolate: false,
isolateFlags: <vm_service.IsolateFlag>[],
);
final FlutterView fakeFlutterView = FlutterView(
id: 'a',
uiIsolate: fakeUnpausedIsolate,
);
final FakeVmServiceRequest listViews = FakeVmServiceRequest(
method: kListViewsMethod,
jsonResponse: <String, Object>{
'views': <Object>[
fakeFlutterView.toJson(),
],
},
);
void main() {
testWithoutContext('keyboard input handling single help character', () async {
final TestRunner testRunner = TestRunner();
final Logger logger = BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
final TerminalHandler terminalHandler = TerminalHandler(
testRunner,
logger: logger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('h');
expect(testRunner.hasHelpBeenPrinted, true);
});
testWithoutContext('keyboard input handling help character surrounded with newlines', () async {
final TestRunner testRunner = TestRunner();
final Logger logger = BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
final TerminalHandler terminalHandler = TerminalHandler(
testRunner,
logger: logger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('\nh\n');
expect(testRunner.hasHelpBeenPrinted, true);
});
group('keycode verification, brought to you by the letter', () {
testWithoutContext('a, can handle trailing newlines', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('a\n');
expect(terminalHandler.lastReceivedCommand, 'a');
});
testWithoutContext('n, can handle trailing only newlines', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
await terminalHandler.processTerminalInput('\n\n');
expect(terminalHandler.lastReceivedCommand, '');
});
testWithoutContext('a - debugToggleProfileWidgetBuilds', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'enabled': 'false',
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
'enabled': 'true',
},
jsonResponse: <String, Object>{
'enabled': 'true',
},
),
]);
await terminalHandler.processTerminalInput('a');
});
testWithoutContext('a - debugToggleProfileWidgetBuilds with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'enabled': 'false',
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
'enabled': 'true',
},
jsonResponse: <String, Object>{
'enabled': 'true',
},
),
], web: true);
await terminalHandler.processTerminalInput('a');
});
testWithoutContext('j unsupported jank metrics for web', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], web: true);
await terminalHandler.processTerminalInput('j');
expect(terminalHandler.logger.warningText.contains('Unable to get jank metrics for web'), true);
});
testWithoutContext('a - debugToggleProfileWidgetBuilds without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('a');
});
testWithoutContext('b - debugToggleBrightness', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'Brightness.light',
}
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'Brightness.dark',
},
jsonResponse: <String, Object>{
'value': 'Brightness.dark',
}
),
]);
await terminalHandler.processTerminalInput('b');
expect(terminalHandler.logger.statusText, contains('Changed brightness to Brightness.dark'));
});
testWithoutContext('b - debugToggleBrightness with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'Brightness.light',
}
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'Brightness.dark',
},
jsonResponse: <String, Object>{
'value': 'Brightness.dark',
}
),
], web: true);
await terminalHandler.processTerminalInput('b');
expect(terminalHandler.logger.statusText, contains('Changed brightness to Brightness.dark'));
});
testWithoutContext('b - debugToggleBrightness without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('b');
});
testWithoutContext('d,D - detach', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('d');
expect(runner.calledDetach, true);
runner.calledDetach = false;
await terminalHandler.processTerminalInput('D');
expect(runner.calledDetach, true);
});
testWithoutContext('h,H,? - printHelp', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('h');
expect(runner.calledPrintWithDetails, true);
runner.calledPrintWithDetails = false;
await terminalHandler.processTerminalInput('H');
expect(runner.calledPrintWithDetails, true);
runner.calledPrintWithDetails = false;
await terminalHandler.processTerminalInput('?');
expect(runner.calledPrintWithDetails, true);
});
testWithoutContext('i - debugToggleWidgetInspector', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.inspector.show',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('i');
});
testWithoutContext('i - debugToggleWidgetInspector with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.inspector.show',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('i');
});
testWithoutContext('i - debugToggleWidgetInspector without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('i');
});
testWithoutContext('I - debugToggleInvertOversizedImages', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('I - debugToggleInvertOversizedImages with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('I - debugToggleInvertOversizedImages without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('I - debugToggleInvertOversizedImages in profile mode is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], buildMode: BuildMode.profile);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('L - debugDumpLayerTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'LAYER TREE',
}
),
]);
await terminalHandler.processTerminalInput('L');
expect(terminalHandler.logger.statusText, contains('LAYER TREE'));
});
testWithoutContext('L - debugDumpLayerTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'LAYER TREE',
}
),
], web: true);
await terminalHandler.processTerminalInput('L');
expect(terminalHandler.logger.statusText, contains('LAYER TREE'));
});
testWithoutContext('L - debugDumpLayerTree with service protocol and profile mode is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], buildMode: BuildMode.profile);
await terminalHandler.processTerminalInput('L');
});
testWithoutContext('L - debugDumpLayerTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('L');
});
testWithoutContext('f - debugDumpFocusTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpFocusTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'FOCUS TREE',
}
),
]);
await terminalHandler.processTerminalInput('f');
expect(terminalHandler.logger.statusText, contains('FOCUS TREE'));
});
testWithoutContext('f - debugDumpLayerTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpFocusTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'FOCUS TREE',
}
),
], web: true);
await terminalHandler.processTerminalInput('f');
expect(terminalHandler.logger.statusText, contains('FOCUS TREE'));
});
testWithoutContext('f - debugDumpFocusTree with service protocol and profile mode is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], buildMode: BuildMode.profile);
await terminalHandler.processTerminalInput('f');
});
testWithoutContext('f - debugDumpFocusTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('f');
});
testWithoutContext('o,O - debugTogglePlatform', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
// Request 1.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'iOS',
},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'windows',
},
jsonResponse: <String, Object>{
'value': 'windows',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'android',
},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'iOS',
},
jsonResponse: <String, Object>{
'value': 'iOS',
},
),
]);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
expect(terminalHandler.logger.statusText, contains('Switched operating system to windows'));
expect(terminalHandler.logger.statusText, contains('Switched operating system to iOS'));
});
testWithoutContext('o,O - debugTogglePlatform with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
// Request 1.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'iOS',
},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'windows',
},
jsonResponse: <String, Object>{
'value': 'windows',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'android',
},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'iOS',
},
jsonResponse: <String, Object>{
'value': 'iOS',
},
),
], web: true);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
expect(terminalHandler.logger.statusText, contains('Switched operating system to windows'));
expect(terminalHandler.logger.statusText, contains('Switched operating system to iOS'));
});
testWithoutContext('o,O - debugTogglePlatform without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
});
testWithoutContext('p - debugToggleDebugPaintSizeEnabled', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugPaint',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('p');
});
testWithoutContext('p - debugToggleDebugPaintSizeEnabled with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugPaint',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('p');
});
testWithoutContext('p - debugToggleDebugPaintSizeEnabled without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('p');
});
testWithoutContext('P - debugTogglePerformanceOverlayOverride', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.showPerformanceOverlay',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('P');
});
testWithoutContext('P - debugTogglePerformanceOverlayOverride with web target is skipped ', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], web: true);
await terminalHandler.processTerminalInput('P');
});
testWithoutContext('P - debugTogglePerformanceOverlayOverride without service protocol is skipped ', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('P');
});
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
]);
await terminalHandler.processTerminalInput('S');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
], web: true);
await terminalHandler.processTerminalInput('S');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('S');
});
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
]);
await terminalHandler.processTerminalInput('U');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
], web: true);
await terminalHandler.processTerminalInput('U');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('U');
});
testWithoutContext('t,T - debugDumpRenderTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 2',
},
),
]);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
expect(terminalHandler.logger.statusText, contains('RENDER DATA 1'));
expect(terminalHandler.logger.statusText, contains('RENDER DATA 2'));
});
testWithoutContext('t,T - debugDumpRenderTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 2',
},
),
], web: true);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
expect(terminalHandler.logger.statusText, contains('RENDER DATA 1'));
expect(terminalHandler.logger.statusText, contains('RENDER DATA 2'));
});
testWithoutContext('t,T - debugDumpRenderTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
});
testWithoutContext('w,W - debugDumpApp', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 2',
},
),
]);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 1'));
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 2'));
});
testWithoutContext('w,W - debugDumpApp with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 2',
},
),
], web: true);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 1'));
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 2'));
});
testWithoutContext('v - launchDevToolsInBrowser', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
final FakeResidentDevtoolsHandler devtoolsHandler = runner.residentDevtoolsHandler as FakeResidentDevtoolsHandler;
expect(devtoolsHandler.calledLaunchDevToolsInBrowser, isFalse);
await terminalHandler.processTerminalInput('v');
expect(devtoolsHandler.calledLaunchDevToolsInBrowser, isTrue);
});
testWithoutContext('w,W - debugDumpApp without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
testWithoutContext('q,Q - exit', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('q');
expect(runner.calledExit, true);
runner.calledExit = false;
await terminalHandler.processTerminalInput('Q');
expect(runner.calledExit, true);
});
testWithoutContext('r - hotReload unsupported', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsHotReload: false);
await terminalHandler.processTerminalInput('r');
});
testWithoutContext('R - hotRestart unsupported', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsRestart: false);
await terminalHandler.processTerminalInput('R');
});
testWithoutContext('r - hotReload', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('r');
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
});
testWithoutContext('R - hotRestart', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('R');
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
});
testWithoutContext('r - hotReload with non-fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('r');
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
expect(terminalHandler.logger.statusText, contains('Try again after fixing the above error(s).'));
});
testWithoutContext('R - hotRestart with non-fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('R');
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
expect(terminalHandler.logger.statusText, contains('Try again after fixing the above error(s).'));
});
testWithoutContext('r - hotReload with fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1, fatalReloadError: true);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await expectLater(() => terminalHandler.processTerminalInput('r'), throwsToolExit());
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
});
testWithoutContext('R - hotRestart with fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1, fatalReloadError: true);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await expectLater(() => terminalHandler.processTerminalInput('R'), throwsToolExit());
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
});
});
testWithoutContext('ResidentRunner clears the screen when it should', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1, fatalReloadError: true);
const String message = 'This should be cleared';
expect(terminalHandler.logger.statusText, equals(''));
terminalHandler.logger.printStatus(message);
expect(terminalHandler.logger.statusText, equals('$message\n')); // printStatus makes a newline
await terminalHandler.processTerminalInput('c');
expect(terminalHandler.logger.statusText, equals(''));
});
testWithoutContext('s, can take screenshot on debug device that supports screenshot', () async {
final BufferLogger logger = BufferLogger.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'false',
},
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'true',
},
),
], logger: logger, supportsScreenshot: true);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, contains('Screenshot written to flutter_01.png (0kB)'));
});
testWithoutContext('s, will not take screenshot on non-web device without screenshot tooling support', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], logger: logger, fileSystem: fileSystem);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, isNot(contains('Screenshot written to')));
});
testWithoutContext('s, can take screenshot on debug web device that does not support screenshot', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'false',
},
),
FakeVmServiceRequest(
method: 'ext.dwds.screenshot',
args: <String, Object>{},
jsonResponse: <String, Object>{
'data': base64.encode(<int>[1, 2, 3, 4]),
},
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'true',
},
),
], logger: logger, web: true, fileSystem: fileSystem);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, contains('Screenshot written to flutter_01.png (0kB)'));
expect(fileSystem.currentDirectory.childFile('flutter_01.png').readAsBytesSync(), <int>[1, 2, 3, 4]);
});
testWithoutContext('s, can take screenshot on device that does not support service protocol', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
logger: logger,
supportsScreenshot: true,
supportsServiceProtocol: false,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, contains('Screenshot written to flutter_01.png (0kB)'));
expect(fileSystem.currentDirectory.childFile('flutter_01.png').readAsBytesSync(), <int>[1, 2, 3, 4]);
});
testWithoutContext('s, does not take a screenshot on a device that does not support screenshot or the service protocol', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
logger: logger,
supportsServiceProtocol: false,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, '\n');
expect(fileSystem.currentDirectory.childFile('flutter_01.png'), isNot(exists));
});
testWithoutContext('s, does not take a screenshot on a web device that does not support screenshot or the service protocol', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
logger: logger,
supportsServiceProtocol: false,
web: true,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, '\n');
expect(fileSystem.currentDirectory.childFile('flutter_01.png'), isNot(exists));
});
testWithoutContext('s, bails taking screenshot on debug device if dwds.screenshot throws RpcError, restoring banner', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'false',
},
),
const FakeVmServiceRequest(
method: 'ext.dwds.screenshot',
// Failed response,
errorCode: RPCErrorCodes.kInternalError,
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'true',
},
),
],
logger: logger,
web: true,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.errorText, contains('Error'));
});
testWithoutContext('s, bails taking screenshot on debug device if debugAllowBanner during second request', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'false',
},
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'enabled': 'true',
},
// Failed response,
errorCode: RPCErrorCodes.kInternalError,
),
],
logger: logger,
supportsScreenshot: true,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.errorText, contains('Error'));
});
testWithoutContext('pidfile creation', () {
final BufferLogger testLogger = BufferLogger.test();
final Signals signals = _TestSignals(Signals.defaultExitSignals);
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
final FakeResidentRunner residentRunner = FakeResidentRunner(
FlutterDevice(
FakeDevice(),
buildInfo: BuildInfo.debug,
generator: FakeResidentCompiler(),
developmentShaderCompiler: const FakeShaderCompiler(),
),
testLogger,
fs,
);
residentRunner
..supportsRestart = true
..supportsServiceProtocol = true
..stayResident = true;
const String filename = 'test.pid';
final TerminalHandler terminalHandler = TerminalHandler(
residentRunner,
logger: testLogger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
pidFile: filename,
);
expect(fs.file(filename), isNot(exists));
terminalHandler.setupTerminal();
terminalHandler.registerSignalHandlers();
expect(fs.file(filename), exists);
terminalHandler.stop();
expect(fs.file(filename), isNot(exists));
});
}
class FakeResidentRunner extends ResidentHandlers {
FakeResidentRunner(FlutterDevice device, this.logger, this.fileSystem) : flutterDevices = <FlutterDevice>[device];
bool calledDetach = false;
bool calledPrint = false;
bool calledExit = false;
bool calledPrintWithDetails = false;
bool calledReload = false;
bool calledRestart = false;
int reloadExitCode = 0;
bool fatalReloadError = false;
@override
final Logger logger;
@override
final FileSystem fileSystem;
@override
final List<FlutterDevice> flutterDevices;
@override
bool canHotReload = true;
@override
bool hotMode = true;
@override
bool isRunningDebug = true;
@override
bool isRunningProfile = false;
@override
bool isRunningRelease = false;
@override
bool stayResident = true;
@override
bool supportsRestart = true;
@override
bool supportsServiceProtocol = true;
@override
bool supportsWriteSkSL = true;
@override
Future<void> cleanupAfterSignal() async { }
@override
Future<void> detach() async {
calledDetach = true;
}
@override
Future<void> exit() async {
calledExit = true;
}
@override
void printHelp({required bool details}) {
if (details) {
calledPrintWithDetails = true;
} else {
calledPrint = true;
}
}
@override
Future<void> runSourceGenerators() async { }
@override
Future<OperationResult> restart({bool fullRestart = false, bool pause = false, String? reason}) async {
if (fullRestart && !supportsRestart) {
throw StateError('illegal restart');
}
if (!fullRestart && !canHotReload) {
throw StateError('illegal reload');
}
if (fullRestart) {
calledRestart = true;
} else {
calledReload = true;
}
return OperationResult(reloadExitCode, '', fatal: fatalReloadError);
}
@override
ResidentDevtoolsHandler get residentDevtoolsHandler => _residentDevtoolsHandler;
final ResidentDevtoolsHandler _residentDevtoolsHandler = FakeResidentDevtoolsHandler();
}
class FakeResidentDevtoolsHandler extends Fake implements ResidentDevtoolsHandler {
bool calledLaunchDevToolsInBrowser = false;
@override
bool launchDevToolsInBrowser({List<FlutterDevice?>? flutterDevices}) {
return calledLaunchDevToolsInBrowser = true;
}
}
class FakeDevice extends Fake implements Device {
@override
bool isSupported() => true;
@override
bool supportsScreenshot = false;
@override
String get name => 'Fake Device';
@override
Future<void> takeScreenshot(File file) async {
if (!supportsScreenshot) {
throw StateError('illegal screenshot attempt');
}
file.writeAsBytesSync(<int>[1, 2, 3, 4]);
}
}
TerminalHandler setUpTerminalHandler(List<FakeVmServiceRequest> requests, {
bool supportsRestart = true,
bool supportsServiceProtocol = true,
bool supportsHotReload = true,
bool web = false,
bool fatalReloadError = false,
bool supportsScreenshot = false,
int reloadExitCode = 0,
BuildMode buildMode = BuildMode.debug,
Logger? logger,
FileSystem? fileSystem,
}) {
final Logger testLogger = logger ?? BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final FileSystem localFileSystem = fileSystem ?? MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(MemoryFileSystem.test());
final FlutterDevice device = FlutterDevice(
FakeDevice()..supportsScreenshot = supportsScreenshot,
buildInfo: BuildInfo(buildMode, '', treeShakeIcons: false),
generator: FakeResidentCompiler(),
developmentShaderCompiler: const FakeShaderCompiler(),
targetPlatform: web
? TargetPlatform.web_javascript
: TargetPlatform.android_arm,
);
device.vmService = FakeVmServiceHost(requests: requests).vmService;
final FakeResidentRunner residentRunner = FakeResidentRunner(device, testLogger, localFileSystem)
..supportsServiceProtocol = supportsServiceProtocol
..supportsRestart = supportsRestart
..canHotReload = supportsHotReload
..fatalReloadError = fatalReloadError
..reloadExitCode = reloadExitCode;
switch (buildMode) {
case BuildMode.debug:
residentRunner
..isRunningDebug = true
..isRunningProfile = false
..isRunningRelease = false;
case BuildMode.profile:
residentRunner
..isRunningDebug = false
..isRunningProfile = true
..isRunningRelease = false;
case BuildMode.release:
residentRunner
..isRunningDebug = false
..isRunningProfile = false
..isRunningRelease = true;
case _:
// NOOP
}
return TerminalHandler(
residentRunner,
logger: testLogger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
}
class FakeResidentCompiler extends Fake implements ResidentCompiler { }
class TestRunner extends Fake implements ResidentRunner {
bool hasHelpBeenPrinted = false;
@override
Future<void> cleanupAfterSignal() async { }
@override
Future<void> cleanupAtFinish() async { }
@override
void printHelp({ bool? details }) {
hasHelpBeenPrinted = true;
}
@override
Future<int?> run({
Completer<DebugConnectionInfo>? connectionInfoCompleter,
Completer<void>? appStartedCompleter,
bool enableDevTools = false,
String? route,
}) async => null;
@override
Future<int?> attach({
Completer<DebugConnectionInfo>? connectionInfoCompleter,
Completer<void>? appStartedCompleter,
bool allowExistingDdsInstance = false,
bool enableDevTools = false,
bool needsFullRestart = true,
}) async => null;
}
class _TestSignals implements Signals {
_TestSignals(this.exitSignals);
final List<ProcessSignal> exitSignals;
final Map<ProcessSignal, Map<Object, SignalHandler>> _handlersTable =
<ProcessSignal, Map<Object, SignalHandler>>{};
@override
Object addHandler(ProcessSignal signal, SignalHandler handler) {
final Object token = Object();
_handlersTable.putIfAbsent(signal, () => <Object, SignalHandler>{})[token] = handler;
return token;
}
@override
Future<bool> removeHandler(ProcessSignal signal, Object token) async {
if (!_handlersTable.containsKey(signal)) {
return false;
}
if (!_handlersTable[signal]!.containsKey(token)) {
return false;
}
_handlersTable[signal]!.remove(token);
return true;
}
@override
Stream<Object> get errors => _errors.stream;
final StreamController<Object> _errors = StreamController<Object>();
}
class FakeShaderCompiler implements DevelopmentShaderCompiler {
const FakeShaderCompiler();
@override
void configureCompiler(TargetPlatform? platform) { }
@override
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
throw UnimplementedError();
}
}