blob: d5cf915032aaeb859698983d7b7c1e9d869be99a [file] [log] [blame]
// 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/tools/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,
error: FakeRPCError(code: 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,
error: const FakeRPCError(code: 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();
}
}