Fix stack trace parsing on non-debug builds; add e2e tests (#50652)
* Fix stack trace parsing on non-debug builds; add e2e tests
This commit is contained in:
parent
c725f107a1
commit
b34046903b
@ -7,6 +7,7 @@ web_shard_template: &WEB_SHARD_TEMPLATE
|
|||||||
# As of October 2019, the Web shards needed more than 6G of RAM.
|
# As of October 2019, the Web shards needed more than 6G of RAM.
|
||||||
CPU: 2
|
CPU: 2
|
||||||
MEMORY: 8G
|
MEMORY: 8G
|
||||||
|
CHROME_NO_SANDBOX: true
|
||||||
GOLD_SERVICE_ACCOUNT: ENCRYPTED[3afeea5ac7201151c3d0dc9648862f0462b5e4f55dc600ca8b692319622f7c3eda3d577b1b16cc2ef0311b7314c1c095]
|
GOLD_SERVICE_ACCOUNT: ENCRYPTED[3afeea5ac7201151c3d0dc9648862f0462b5e4f55dc600ca8b692319622f7c3eda3d577b1b16cc2ef0311b7314c1c095]
|
||||||
script:
|
script:
|
||||||
- dart --enable-asserts ./dev/bots/test.dart
|
- dart --enable-asserts ./dev/bots/test.dart
|
||||||
@ -173,6 +174,9 @@ task:
|
|||||||
- dart --enable-asserts ./dev/bots/test.dart
|
- dart --enable-asserts ./dev/bots/test.dart
|
||||||
- bash <(curl -s https://codecov.io/bash) -c -f packages/flutter_tools/coverage/lcov.info -F flutter_tool
|
- bash <(curl -s https://codecov.io/bash) -c -f packages/flutter_tools/coverage/lcov.info -F flutter_tool
|
||||||
|
|
||||||
|
- name: web_integration_tests
|
||||||
|
<< : *WEB_SHARD_TEMPLATE
|
||||||
|
|
||||||
- name: web_tests-0-linux
|
- name: web_tests-0-linux
|
||||||
<< : *WEB_SHARD_TEMPLATE
|
<< : *WEB_SHARD_TEMPLATE
|
||||||
|
|
||||||
|
59
dev/bots/browser.dart
Normal file
59
dev/bots/browser.dart
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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:io' as io;
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||||
|
import 'package:shelf_static/shelf_static.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_devicelab/framework/browser.dart';
|
||||||
|
|
||||||
|
/// Runs Chrome, opens the given `appUrl`, and returns the result reported by the
|
||||||
|
/// app.
|
||||||
|
///
|
||||||
|
/// The app is served from the `appDirectory`. Typically, the app is built
|
||||||
|
/// using `flutter build web` and served from `build/web`.
|
||||||
|
///
|
||||||
|
/// The launched app is expected to report the result by sending an HTTP POST
|
||||||
|
/// request to "/test-result" containing result data as plain text body of the
|
||||||
|
/// request. This function has no opinion about what that string contains.
|
||||||
|
Future<String> evalTestAppInChrome({
|
||||||
|
@required String appUrl,
|
||||||
|
@required String appDirectory,
|
||||||
|
int serverPort = 8080,
|
||||||
|
int browserDebugPort = 8081,
|
||||||
|
}) async {
|
||||||
|
io.HttpServer server;
|
||||||
|
Chrome chrome;
|
||||||
|
try {
|
||||||
|
final Completer<String> resultCompleter = Completer<String>();
|
||||||
|
server = await io.HttpServer.bind('localhost', serverPort);
|
||||||
|
final Cascade cascade = Cascade()
|
||||||
|
.add((Request request) async {
|
||||||
|
if (request.requestedUri.path.endsWith('/test-result')) {
|
||||||
|
resultCompleter.complete(await request.readAsString());
|
||||||
|
return Response.ok('Test results received');
|
||||||
|
}
|
||||||
|
return Response.notFound('');
|
||||||
|
})
|
||||||
|
.add(createStaticHandler(appDirectory));
|
||||||
|
shelf_io.serveRequests(server, cascade.handler);
|
||||||
|
final io.Directory userDataDirectory = io.Directory.systemTemp.createTempSync('chrome_user_data_');
|
||||||
|
chrome = await Chrome.launch(ChromeOptions(
|
||||||
|
headless: true,
|
||||||
|
debugPort: browserDebugPort,
|
||||||
|
url: appUrl,
|
||||||
|
userDataDirectory: userDataDirectory.path,
|
||||||
|
windowHeight: 500,
|
||||||
|
windowWidth: 500,
|
||||||
|
), onError: resultCompleter.completeError);
|
||||||
|
return await resultCompleter.future;
|
||||||
|
} finally {
|
||||||
|
chrome?.stop();
|
||||||
|
await server?.close();
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,8 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
args: 1.5.2
|
args: 1.5.2
|
||||||
crypto: 2.1.3
|
crypto: 2.1.3
|
||||||
|
flutter_devicelab:
|
||||||
|
path: ../devicelab
|
||||||
googleapis: 0.54.0
|
googleapis: 0.54.0
|
||||||
googleapis_auth: 0.2.11+1
|
googleapis_auth: 0.2.11+1
|
||||||
http: 0.12.0+4
|
http: 0.12.0+4
|
||||||
|
@ -53,6 +53,15 @@ Stream<String> runAndGetStdout(String executable, List<String> arguments, {
|
|||||||
print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
|
print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs the `executable` and waits until the process exits.
|
||||||
|
///
|
||||||
|
/// If the process exits with a non-zero exit code, exits this process with
|
||||||
|
/// exit code 1, unless `expectNonZeroExit` is set to true.
|
||||||
|
///
|
||||||
|
/// `outputListener` is called for every line of standard output from the
|
||||||
|
/// process, and is given the [Process] object. This can be used to interrupt
|
||||||
|
/// an indefinitely running process, for example, by waiting until the process
|
||||||
|
/// emits certain output.
|
||||||
Future<void> runCommand(String executable, List<String> arguments, {
|
Future<void> runCommand(String executable, List<String> arguments, {
|
||||||
String workingDirectory,
|
String workingDirectory,
|
||||||
Map<String, String> environment,
|
Map<String, String> environment,
|
||||||
@ -63,6 +72,7 @@ Future<void> runCommand(String executable, List<String> arguments, {
|
|||||||
CapturedOutput output,
|
CapturedOutput output,
|
||||||
bool skip = false,
|
bool skip = false,
|
||||||
bool Function(String) removeLine,
|
bool Function(String) removeLine,
|
||||||
|
void Function(String, Process) outputListener,
|
||||||
}) async {
|
}) async {
|
||||||
assert(
|
assert(
|
||||||
(outputMode == OutputMode.capture) == (output != null),
|
(outputMode == OutputMode.capture) == (output != null),
|
||||||
@ -88,7 +98,13 @@ Future<void> runCommand(String executable, List<String> arguments, {
|
|||||||
.transform<String>(const Utf8Decoder())
|
.transform<String>(const Utf8Decoder())
|
||||||
.transform(const LineSplitter())
|
.transform(const LineSplitter())
|
||||||
.where((String line) => removeLine == null || !removeLine(line))
|
.where((String line) => removeLine == null || !removeLine(line))
|
||||||
.map((String line) => '$line\n')
|
.map((String line) {
|
||||||
|
final String formattedLine = '$line\n';
|
||||||
|
if (outputListener != null) {
|
||||||
|
outputListener(formattedLine, process);
|
||||||
|
}
|
||||||
|
return formattedLine;
|
||||||
|
})
|
||||||
.transform(const Utf8Encoder());
|
.transform(const Utf8Encoder());
|
||||||
switch (outputMode) {
|
switch (outputMode) {
|
||||||
case OutputMode.print:
|
case OutputMode.print:
|
||||||
|
@ -11,6 +11,7 @@ import 'package:googleapis_auth/auth_io.dart' as auth;
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
import 'browser.dart';
|
||||||
import 'flutter_compact_formatter.dart';
|
import 'flutter_compact_formatter.dart';
|
||||||
import 'run_command.dart';
|
import 'run_command.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
@ -116,7 +117,8 @@ Future<void> main(List<String> args) async {
|
|||||||
'hostonly_devicelab_tests': _runHostOnlyDeviceLabTests,
|
'hostonly_devicelab_tests': _runHostOnlyDeviceLabTests,
|
||||||
'tool_coverage': _runToolCoverage,
|
'tool_coverage': _runToolCoverage,
|
||||||
'tool_tests': _runToolTests,
|
'tool_tests': _runToolTests,
|
||||||
'web_tests': _runWebTests,
|
'web_tests': _runWebUnitTests,
|
||||||
|
'web_integration_tests': _runWebIntegrationTests,
|
||||||
});
|
});
|
||||||
} on ExitException catch (error) {
|
} on ExitException catch (error) {
|
||||||
error.apply();
|
error.apply();
|
||||||
@ -522,7 +524,7 @@ Future<void> _runFrameworkCoverage() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runWebTests() async {
|
Future<void> _runWebUnitTests() async {
|
||||||
final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
|
final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
|
||||||
|
|
||||||
final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
|
final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
|
||||||
@ -585,6 +587,94 @@ Future<void> _runWebTests() async {
|
|||||||
await selectSubshard(subshards);
|
await selectSubshard(subshards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _runWebIntegrationTests() async {
|
||||||
|
await _runWebStackTraceTest('profile');
|
||||||
|
await _runWebStackTraceTest('release');
|
||||||
|
await _runWebDebugStackTraceTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _runWebStackTraceTest(String buildMode) async {
|
||||||
|
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
|
||||||
|
final String appBuildDirectory = path.join('$testAppDirectory', 'build', 'web');
|
||||||
|
|
||||||
|
// Build the app.
|
||||||
|
await runCommand(
|
||||||
|
flutter,
|
||||||
|
<String>[ 'clean' ],
|
||||||
|
workingDirectory: testAppDirectory,
|
||||||
|
);
|
||||||
|
await runCommand(
|
||||||
|
flutter,
|
||||||
|
<String>[
|
||||||
|
'build',
|
||||||
|
'web',
|
||||||
|
'--$buildMode',
|
||||||
|
'-t',
|
||||||
|
'lib/stack_trace.dart',
|
||||||
|
],
|
||||||
|
workingDirectory: testAppDirectory,
|
||||||
|
environment: <String, String>{
|
||||||
|
'FLUTTER_WEB': 'true',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Run the app.
|
||||||
|
final String result = await evalTestAppInChrome(
|
||||||
|
appUrl: 'http://localhost:8080/index.html',
|
||||||
|
appDirectory: appBuildDirectory,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.contains('--- TEST SUCCEEDED ---')) {
|
||||||
|
print('${green}Web stack trace integration test passed.$reset');
|
||||||
|
} else {
|
||||||
|
print(result);
|
||||||
|
print('${red}Web stack trace integration test failed.$reset');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug mode is special because `flutter build web` doesn't build in debug mode.
|
||||||
|
///
|
||||||
|
/// Instead, we use `flutter run --debug` and sniff out the standard output.
|
||||||
|
Future<void> _runWebDebugStackTraceTest() async {
|
||||||
|
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
|
||||||
|
final CapturedOutput output = CapturedOutput();
|
||||||
|
bool success = false;
|
||||||
|
await runCommand(
|
||||||
|
flutter,
|
||||||
|
<String>[
|
||||||
|
'run',
|
||||||
|
'--debug',
|
||||||
|
'-d',
|
||||||
|
'chrome',
|
||||||
|
'--web-run-headless',
|
||||||
|
'lib/stack_trace.dart',
|
||||||
|
],
|
||||||
|
output: output,
|
||||||
|
outputMode: OutputMode.capture,
|
||||||
|
outputListener: (String line, Process process) {
|
||||||
|
if (line.contains('--- TEST SUCCEEDED ---')) {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
if (success || line.contains('--- TEST FAILED ---')) {
|
||||||
|
process.stdin.add('q'.codeUnits);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
workingDirectory: testAppDirectory,
|
||||||
|
environment: <String, String>{
|
||||||
|
'FLUTTER_WEB': 'true',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
print('${green}Web stack trace integration test passed.$reset');
|
||||||
|
} else {
|
||||||
|
print(output.stdout);
|
||||||
|
print('${red}Web stack trace integration test failed.$reset');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) async {
|
Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) async {
|
||||||
final List<String> batch = <String>[];
|
final List<String> batch = <String>[];
|
||||||
for (int i = 0; i < tests.length; i += 1) {
|
for (int i = 0; i < tests.length; i += 1) {
|
||||||
|
138
dev/devicelab/lib/framework/browser.dart
Normal file
138
dev/devicelab/lib/framework/browser.dart
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// 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:io' as io;
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
import 'utils.dart' show forwardStandardStreams;
|
||||||
|
|
||||||
|
/// Options passed to Chrome when launching it.
|
||||||
|
class ChromeOptions {
|
||||||
|
ChromeOptions({
|
||||||
|
this.userDataDirectory,
|
||||||
|
this.url,
|
||||||
|
this.windowWidth = 1024,
|
||||||
|
this.windowHeight = 1024,
|
||||||
|
this.headless,
|
||||||
|
this.debugPort,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// If not null passed as `--user-data-dir`.
|
||||||
|
final String userDataDirectory;
|
||||||
|
|
||||||
|
/// If not null launches a Chrome tab at this URL.
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
/// The width of the Chrome window.
|
||||||
|
///
|
||||||
|
/// This is important for screenshots and benchmarks.
|
||||||
|
final int windowWidth;
|
||||||
|
|
||||||
|
/// The height of the Chrome window.
|
||||||
|
///
|
||||||
|
/// This is important for screenshots and benchmarks.
|
||||||
|
final int windowHeight;
|
||||||
|
|
||||||
|
/// Launches code in "headless" mode, which allows running Chrome in
|
||||||
|
/// environments without a display, such as LUCI and Cirrus.
|
||||||
|
final bool headless;
|
||||||
|
|
||||||
|
/// The port Chrome will use for its debugging protocol.
|
||||||
|
///
|
||||||
|
/// If null, Chrome is launched without debugging. When running in headless
|
||||||
|
/// mode without a debug port, Chrome quits immediately. For most tests it is
|
||||||
|
/// typical to set [headless] to true and set a non-null debug port.
|
||||||
|
final int debugPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function called when the Chrome process encounters an error.
|
||||||
|
typedef ChromeErrorCallback = void Function(String);
|
||||||
|
|
||||||
|
/// Manages a single Chrome process.
|
||||||
|
class Chrome {
|
||||||
|
Chrome._(this._chromeProcess, this._onError) {
|
||||||
|
// If the Chrome process quits before it was asked to quit, notify the
|
||||||
|
// error listener.
|
||||||
|
_chromeProcess.exitCode.then((int exitCode) {
|
||||||
|
if (!_isStopped) {
|
||||||
|
_onError('Chrome process exited prematurely with exit code $exitCode');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Launches Chrome with the give [options].
|
||||||
|
///
|
||||||
|
/// The [onError] callback is called with an error message when the Chrome
|
||||||
|
/// process encounters an error. In particular, [onError] is called when the
|
||||||
|
/// Chrome process exits prematurely, i.e. before [stop] is called.
|
||||||
|
static Future<Chrome> launch(ChromeOptions options, { String workingDirectory, @required ChromeErrorCallback onError }) async {
|
||||||
|
final io.ProcessResult versionResult = io.Process.runSync(_findSystemChromeExecutable(), const <String>['--version']);
|
||||||
|
print('Launching ${versionResult.stdout}');
|
||||||
|
|
||||||
|
final List<String> args = <String>[
|
||||||
|
if (options.userDataDirectory != null)
|
||||||
|
'--user-data-dir=${options.userDataDirectory}',
|
||||||
|
if (options.url != null)
|
||||||
|
options.url,
|
||||||
|
if (io.Platform.environment['CHROME_NO_SANDBOX'] == 'true')
|
||||||
|
'--no-sandbox',
|
||||||
|
if (options.headless)
|
||||||
|
'--headless',
|
||||||
|
if (options.debugPort != null)
|
||||||
|
'--remote-debugging-port=${options.debugPort}',
|
||||||
|
'--window-size=${options.windowWidth},${options.windowHeight}',
|
||||||
|
'--disable-extensions',
|
||||||
|
'--disable-popup-blocking',
|
||||||
|
// Indicates that the browser is in "browse without sign-in" (Guest session) mode.
|
||||||
|
'--bwsi',
|
||||||
|
'--no-first-run',
|
||||||
|
'--no-default-browser-check',
|
||||||
|
'--disable-default-apps',
|
||||||
|
'--disable-translate',
|
||||||
|
];
|
||||||
|
final io.Process chromeProcess = await io.Process.start(
|
||||||
|
_findSystemChromeExecutable(),
|
||||||
|
args,
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
);
|
||||||
|
forwardStandardStreams(chromeProcess);
|
||||||
|
return Chrome._(chromeProcess, onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
final io.Process _chromeProcess;
|
||||||
|
final ChromeErrorCallback _onError;
|
||||||
|
bool _isStopped = false;
|
||||||
|
|
||||||
|
/// Stops the Chrome process.
|
||||||
|
void stop() {
|
||||||
|
_isStopped = true;
|
||||||
|
_chromeProcess.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _findSystemChromeExecutable() {
|
||||||
|
// On some environments, such as the Dart HHH tester, Chrome resides in a
|
||||||
|
// non-standard location and is provided via the following environment
|
||||||
|
// variable.
|
||||||
|
final String envExecutable = io.Platform.environment['CHROME_EXECUTABLE'];
|
||||||
|
if (envExecutable != null) {
|
||||||
|
return envExecutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (io.Platform.isLinux) {
|
||||||
|
final io.ProcessResult which =
|
||||||
|
io.Process.runSync('which', <String>['google-chrome']);
|
||||||
|
|
||||||
|
if (which.exitCode != 0) {
|
||||||
|
throw Exception('Failed to locate system Chrome installation.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (which.stdout as String).trim();
|
||||||
|
} else if (io.Platform.isMacOS) {
|
||||||
|
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
||||||
|
} else {
|
||||||
|
throw Exception('Web benchmarks cannot run on ${io.Platform.operatingSystem} yet.');
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import 'package:shelf/shelf.dart';
|
|||||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||||
import 'package:shelf_static/shelf_static.dart';
|
import 'package:shelf_static/shelf_static.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_devicelab/framework/browser.dart';
|
||||||
import 'package:flutter_devicelab/framework/framework.dart';
|
import 'package:flutter_devicelab/framework/framework.dart';
|
||||||
import 'package:flutter_devicelab/framework/utils.dart';
|
import 'package:flutter_devicelab/framework/utils.dart';
|
||||||
|
|
||||||
@ -74,30 +75,12 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
));
|
));
|
||||||
|
|
||||||
server = await io.HttpServer.bind('localhost', benchmarkServerPort);
|
server = await io.HttpServer.bind('localhost', benchmarkServerPort);
|
||||||
io.Process chromeProcess;
|
Chrome chrome;
|
||||||
try {
|
try {
|
||||||
shelf_io.serveRequests(server, cascade.handler);
|
shelf_io.serveRequests(server, cascade.handler);
|
||||||
|
|
||||||
final bool isChromeNoSandbox =
|
|
||||||
io.Platform.environment['CHROME_NO_SANDBOX'] == 'true';
|
|
||||||
|
|
||||||
final String dartToolDirectory = path.join('$macrobenchmarksDirectory/.dart_tool');
|
final String dartToolDirectory = path.join('$macrobenchmarksDirectory/.dart_tool');
|
||||||
final String userDataDir = io.Directory(dartToolDirectory).createTempSync('chrome_user_data_').path;
|
final String userDataDir = io.Directory(dartToolDirectory).createTempSync('chrome_user_data_').path;
|
||||||
final List<String> args = <String>[
|
|
||||||
'--user-data-dir=$userDataDir',
|
|
||||||
'http://localhost:$benchmarkServerPort/index.html',
|
|
||||||
if (isChromeNoSandbox)
|
|
||||||
'--no-sandbox',
|
|
||||||
'--window-size=1024,1024',
|
|
||||||
'--disable-extensions',
|
|
||||||
'--disable-popup-blocking',
|
|
||||||
// Indicates that the browser is in "browse without sign-in" (Guest session) mode.
|
|
||||||
'--bwsi',
|
|
||||||
'--no-first-run',
|
|
||||||
'--no-default-browser-check',
|
|
||||||
'--disable-default-apps',
|
|
||||||
'--disable-translate',
|
|
||||||
];
|
|
||||||
|
|
||||||
// TODO(yjbanov): temporarily disables headful Chrome until we get
|
// TODO(yjbanov): temporarily disables headful Chrome until we get
|
||||||
// devicelab hardware that is able to run it. Our current
|
// devicelab hardware that is able to run it. Our current
|
||||||
@ -106,38 +89,33 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
final bool isUncalibratedSmokeTest = io.Platform.environment['CALIBRATED'] != 'true';
|
final bool isUncalibratedSmokeTest = io.Platform.environment['CALIBRATED'] != 'true';
|
||||||
// final bool isUncalibratedSmokeTest =
|
// final bool isUncalibratedSmokeTest =
|
||||||
// io.Platform.environment['UNCALIBRATED_SMOKE_TEST'] == 'true';
|
// io.Platform.environment['UNCALIBRATED_SMOKE_TEST'] == 'true';
|
||||||
if (isUncalibratedSmokeTest) {
|
final ChromeOptions options = ChromeOptions(
|
||||||
print('Running in headless mode because running on uncalibrated hardware.');
|
url: 'http://localhost:$benchmarkServerPort/index.html',
|
||||||
args.add('--headless');
|
userDataDirectory: userDataDir,
|
||||||
|
windowHeight: 1024,
|
||||||
|
windowWidth: 1024,
|
||||||
|
headless: isUncalibratedSmokeTest,
|
||||||
// When running in headless mode Chrome exits immediately unless
|
// When running in headless mode Chrome exits immediately unless
|
||||||
// a debug port is specified.
|
// a debug port is specified.
|
||||||
args.add('--remote-debugging-port=${benchmarkServerPort + 1}');
|
debugPort: isUncalibratedSmokeTest ? benchmarkServerPort + 1 : null,
|
||||||
}
|
);
|
||||||
|
|
||||||
chromeProcess = await startProcess(
|
print('Launching Chrome.');
|
||||||
_findSystemChromeExecutable(),
|
chrome = await Chrome.launch(
|
||||||
args,
|
options,
|
||||||
|
onError: (String error) {
|
||||||
|
profileData.completeError(Exception(error));
|
||||||
|
},
|
||||||
workingDirectory: cwd,
|
workingDirectory: cwd,
|
||||||
);
|
);
|
||||||
|
|
||||||
bool receivedProfileData = false;
|
|
||||||
chromeProcess.exitCode.then((int exitCode) {
|
|
||||||
if (!receivedProfileData) {
|
|
||||||
profileData.completeError(Exception(
|
|
||||||
'Chrome process existed prematurely with exit code $exitCode',
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
forwardStandardStreams(chromeProcess);
|
|
||||||
|
|
||||||
print('Waiting for the benchmark to report benchmark profile.');
|
print('Waiting for the benchmark to report benchmark profile.');
|
||||||
|
|
||||||
final String backend = useCanvasKit ? 'canvaskit' : 'html';
|
final String backend = useCanvasKit ? 'canvaskit' : 'html';
|
||||||
final Map<String, dynamic> taskResult = <String, dynamic>{};
|
final Map<String, dynamic> taskResult = <String, dynamic>{};
|
||||||
final List<String> benchmarkScoreKeys = <String>[];
|
final List<String> benchmarkScoreKeys = <String>[];
|
||||||
final List<Map<String, dynamic>> profiles = await profileData.future;
|
final List<Map<String, dynamic>> profiles = await profileData.future;
|
||||||
|
|
||||||
print('Received profile data');
|
print('Received profile data');
|
||||||
receivedProfileData = true;
|
|
||||||
for (final Map<String, dynamic> profile in profiles) {
|
for (final Map<String, dynamic> profile in profiles) {
|
||||||
final String benchmarkName = profile['name'] as String;
|
final String benchmarkName = profile['name'] as String;
|
||||||
final String benchmarkScoreKey = '$benchmarkName.$backend.averageDrawFrameDuration';
|
final String benchmarkScoreKey = '$benchmarkName.$backend.averageDrawFrameDuration';
|
||||||
@ -148,32 +126,7 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
return TaskResult.success(taskResult, benchmarkScoreKeys: benchmarkScoreKeys);
|
return TaskResult.success(taskResult, benchmarkScoreKeys: benchmarkScoreKeys);
|
||||||
} finally {
|
} finally {
|
||||||
server.close();
|
server.close();
|
||||||
chromeProcess?.kill();
|
chrome.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String _findSystemChromeExecutable() {
|
|
||||||
// On some environments, such as the Dart HHH tester, Chrome resides in a
|
|
||||||
// non-standard location and is provided via the following environment
|
|
||||||
// variable.
|
|
||||||
final String envExecutable = io.Platform.environment['CHROME_EXECUTABLE'];
|
|
||||||
if (envExecutable != null) {
|
|
||||||
return envExecutable;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (io.Platform.isLinux) {
|
|
||||||
final io.ProcessResult which =
|
|
||||||
io.Process.runSync('which', <String>['google-chrome']);
|
|
||||||
|
|
||||||
if (which.exitCode != 0) {
|
|
||||||
throw Exception('Failed to locate system Chrome installation.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (which.stdout as String).trim();
|
|
||||||
} else if (io.Platform.isMacOS) {
|
|
||||||
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
||||||
} else {
|
|
||||||
throw Exception('Web benchmarks cannot run on ${io.Platform.operatingSystem} yet.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
162
dev/integration_tests/web/lib/stack_trace.dart
Normal file
162
dev/integration_tests/web/lib/stack_trace.dart
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// 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:html' as html;
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:meta/dart2js.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Expected sequence of method calls.
|
||||||
|
const List<String> callChain = <String>['baz', 'bar', 'foo'];
|
||||||
|
|
||||||
|
final List<StackFrame> expectedProfileStackFrames = callChain.map<StackFrame>((String method) {
|
||||||
|
return StackFrame(
|
||||||
|
number: -1,
|
||||||
|
packageScheme: '<unknown>',
|
||||||
|
package: '<unknown>',
|
||||||
|
packagePath: '<unknown>',
|
||||||
|
line: -1,
|
||||||
|
column: -1,
|
||||||
|
className: 'Object',
|
||||||
|
method: method,
|
||||||
|
source: '',
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
// TODO(yjbanov): fix these stack traces when https://github.com/flutter/flutter/issues/50753 is fixed.
|
||||||
|
const List<StackFrame> expectedDebugStackFrames = <StackFrame>[
|
||||||
|
StackFrame(
|
||||||
|
number: -1,
|
||||||
|
packageScheme: 'package',
|
||||||
|
package: 'web_integration',
|
||||||
|
packagePath: 'stack_trace.dart.lib.js',
|
||||||
|
line: 138,
|
||||||
|
column: 15,
|
||||||
|
className: '<unknown>',
|
||||||
|
method: 'baz',
|
||||||
|
source: '',
|
||||||
|
),
|
||||||
|
StackFrame(
|
||||||
|
number: -1,
|
||||||
|
packageScheme: 'package',
|
||||||
|
package: 'web_integration',
|
||||||
|
packagePath: 'stack_trace.dart.lib.js',
|
||||||
|
line: 135,
|
||||||
|
column: 17,
|
||||||
|
className: '<unknown>',
|
||||||
|
method: 'bar',
|
||||||
|
source: '',
|
||||||
|
),
|
||||||
|
StackFrame(
|
||||||
|
number: -1,
|
||||||
|
packageScheme: 'package',
|
||||||
|
package: 'web_integration',
|
||||||
|
packagePath: 'stack_trace.dart.lib.js',
|
||||||
|
line: 132,
|
||||||
|
column: 17,
|
||||||
|
className: '<unknown>',
|
||||||
|
method: 'foo',
|
||||||
|
source: '',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Tests that we do not crash while parsing Web stack traces.
|
||||||
|
///
|
||||||
|
/// This test is run in debug, profile, and release modes.
|
||||||
|
void main() {
|
||||||
|
final StringBuffer output = StringBuffer();
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
foo();
|
||||||
|
} catch (expectedError, expectedStackTrace) {
|
||||||
|
final List<StackFrame> parsedFrames = StackFrame.fromStackTrace(expectedStackTrace);
|
||||||
|
if (parsedFrames.isEmpty) {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to parse stack trace. Got empty list of stack frames.\n'
|
||||||
|
'Stack trace:\n$expectedStackTrace'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbols in release mode are randomly obfuscated, so there's no good way to
|
||||||
|
// validate the contents. However, profile mode can be checked.
|
||||||
|
if (kProfileMode) {
|
||||||
|
_checkStackFrameContents(parsedFrames, expectedProfileStackFrames, expectedStackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
_checkStackFrameContents(parsedFrames, expectedDebugStackFrames, expectedStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.writeln('--- TEST SUCCEEDED ---');
|
||||||
|
} catch (unexpectedError, unexpectedStackTrace) {
|
||||||
|
output.writeln('--- UNEXPECTED EXCEPTION ---');
|
||||||
|
output.writeln(unexpectedError);
|
||||||
|
output.writeln(unexpectedStackTrace);
|
||||||
|
output.writeln('--- TEST FAILED ---');
|
||||||
|
}
|
||||||
|
print(output);
|
||||||
|
html.HttpRequest.request(
|
||||||
|
'/test-result',
|
||||||
|
method: 'POST',
|
||||||
|
sendData: '$output',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@noInline
|
||||||
|
void foo() {
|
||||||
|
bar();
|
||||||
|
}
|
||||||
|
|
||||||
|
@noInline
|
||||||
|
void bar() {
|
||||||
|
baz();
|
||||||
|
}
|
||||||
|
|
||||||
|
@noInline
|
||||||
|
void baz() {
|
||||||
|
throw Exception('Test error message');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkStackFrameContents(List<StackFrame> parsedFrames, List<StackFrame> expectedFrames, dynamic stackTrace) {
|
||||||
|
// Filter out stack frames outside this library so this test is less brittle.
|
||||||
|
final List<StackFrame> actual = parsedFrames
|
||||||
|
.where((StackFrame frame) => callChain.contains(frame.method))
|
||||||
|
.toList();
|
||||||
|
final bool stackFramesAsExpected = ListEquality<StackFrame>(StackFrameEquality()).equals(actual, expectedFrames);
|
||||||
|
if (!stackFramesAsExpected) {
|
||||||
|
throw Exception(
|
||||||
|
'Stack frames parsed incorrectly:\n'
|
||||||
|
'Expected:\n${expectedFrames.join('\n')}\n'
|
||||||
|
'Actual:\n${actual.join('\n')}\n'
|
||||||
|
'Stack trace:\n$stackTrace'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use custom equality to ignore [StackFrame.source], which is not important
|
||||||
|
/// for the purposes of this test.
|
||||||
|
class StackFrameEquality implements Equality<StackFrame> {
|
||||||
|
@override
|
||||||
|
bool equals(StackFrame e1, StackFrame e2) {
|
||||||
|
return e1.number == e2.number &&
|
||||||
|
e1.packageScheme == e2.packageScheme &&
|
||||||
|
e1.package == e2.package &&
|
||||||
|
e1.packagePath == e2.packagePath &&
|
||||||
|
e1.line == e2.line &&
|
||||||
|
e1.column == e2.column &&
|
||||||
|
e1.className == e2.className &&
|
||||||
|
e1.method == e2.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int hash(StackFrame e) {
|
||||||
|
return hashValues(e.number, e.packageScheme, e.package, e.packagePath, e.line, e.column, e.className, e.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isValidKey(Object o) => o is StackFrame;
|
||||||
|
}
|
@ -6,6 +6,7 @@ import 'dart:ui' show hashValues;
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
import 'constants.dart';
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
|
||||||
/// A object representation of a frame from a stack trace.
|
/// A object representation of a frame from a stack trace.
|
||||||
@ -86,10 +87,22 @@ class StackFrame {
|
|||||||
.trim()
|
.trim()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(fromStackTraceLine)
|
.map(fromStackTraceLine)
|
||||||
|
// On the Web in non-debug builds the stack trace includes the exception
|
||||||
|
// message that precedes the stack trace itself. fromStackTraceLine will
|
||||||
|
// return null in that case. We will skip it here.
|
||||||
|
.skipWhile((StackFrame frame) => frame == null)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
static StackFrame _parseWebFrame(String line) {
|
static StackFrame _parseWebFrame(String line) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
return _parseWebDebugFrame(line);
|
||||||
|
} else {
|
||||||
|
return _parseWebNonDebugFrame(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static StackFrame _parseWebDebugFrame(String line) {
|
||||||
final bool hasPackage = line.startsWith('package');
|
final bool hasPackage = line.startsWith('package');
|
||||||
final RegExp parser = hasPackage
|
final RegExp parser = hasPackage
|
||||||
? RegExp(r'^(package:.+) (\d+):(\d+)\s+(.+)$')
|
? RegExp(r'^(package:.+) (\d+):(\d+)\s+(.+)$')
|
||||||
@ -120,6 +133,50 @@ class StackFrame {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-debug builds do not point to dart code but compiled JavaScript, so
|
||||||
|
// line numbers are meaningless. We only attempt to parse the class and
|
||||||
|
// method name, which is more or less readable in profile builds, and
|
||||||
|
// minified in release builds.
|
||||||
|
static final RegExp _webNonDebugFramePattern = RegExp(r'^\s*at ([^\s]+).*$');
|
||||||
|
|
||||||
|
// Parses `line` as a stack frame in profile and release Web builds. If not
|
||||||
|
// recognized as a stack frame, returns null.
|
||||||
|
static StackFrame _parseWebNonDebugFrame(String line) {
|
||||||
|
final Match match = _webNonDebugFramePattern.firstMatch(line);
|
||||||
|
if (match == null) {
|
||||||
|
// On the Web in non-debug builds the stack trace includes the exception
|
||||||
|
// message that precedes the stack trace itself. Example:
|
||||||
|
//
|
||||||
|
// TypeError: Cannot read property 'hello$0' of null
|
||||||
|
// at _GalleryAppState.build$1 (http://localhost:8080/main.dart.js:149790:13)
|
||||||
|
// at StatefulElement.build$0 (http://localhost:8080/main.dart.js:129138:37)
|
||||||
|
// at StatefulElement.performRebuild$0 (http://localhost:8080/main.dart.js:129032:23)
|
||||||
|
//
|
||||||
|
// Instead of crashing when a line is not recognized as a stack frame, we
|
||||||
|
// return null. The caller, such as fromStackString, can then just skip
|
||||||
|
// this frame.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> classAndMethod = match.group(1).split('.');
|
||||||
|
final String className = classAndMethod.length > 1 ? classAndMethod.first : '<unknown>';
|
||||||
|
final String method = classAndMethod.length > 1
|
||||||
|
? classAndMethod.skip(1).join('.')
|
||||||
|
: classAndMethod.single;
|
||||||
|
|
||||||
|
return StackFrame(
|
||||||
|
number: -1,
|
||||||
|
packageScheme: '<unknown>',
|
||||||
|
package: '<unknown>',
|
||||||
|
packagePath: '<unknown>',
|
||||||
|
line: -1,
|
||||||
|
column: -1,
|
||||||
|
className: className,
|
||||||
|
method: method,
|
||||||
|
source: line,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a single [StackFrame] from a single line of a [StackTrace].
|
/// Parses a single [StackFrame] from a single line of a [StackTrace].
|
||||||
static StackFrame fromStackTraceLine(String line) {
|
static StackFrame fromStackTraceLine(String line) {
|
||||||
assert(line != null);
|
assert(line != null);
|
||||||
|
@ -338,6 +338,9 @@ class RunCommand extends RunCommandBase {
|
|||||||
|
|
||||||
DebuggingOptions _createDebuggingOptions() {
|
DebuggingOptions _createDebuggingOptions() {
|
||||||
final BuildInfo buildInfo = getBuildInfo();
|
final BuildInfo buildInfo = getBuildInfo();
|
||||||
|
final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port')
|
||||||
|
? int.parse(stringArg('web-browser-debug-port'))
|
||||||
|
: null;
|
||||||
if (buildInfo.mode.isRelease) {
|
if (buildInfo.mode.isRelease) {
|
||||||
return DebuggingOptions.disabled(
|
return DebuggingOptions.disabled(
|
||||||
buildInfo,
|
buildInfo,
|
||||||
@ -345,6 +348,8 @@ class RunCommand extends RunCommandBase {
|
|||||||
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
|
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
|
||||||
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
|
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
|
||||||
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
|
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
|
||||||
|
webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
|
||||||
|
webBrowserDebugPort: browserDebugPort,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return DebuggingOptions.enabled(
|
return DebuggingOptions.enabled(
|
||||||
@ -367,6 +372,8 @@ class RunCommand extends RunCommandBase {
|
|||||||
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
|
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
|
||||||
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
|
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
|
||||||
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
|
webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
|
||||||
|
webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
|
||||||
|
webBrowserDebugPort: browserDebugPort,
|
||||||
vmserviceOutFile: stringArg('vmservice-out-file'),
|
vmserviceOutFile: stringArg('vmservice-out-file'),
|
||||||
// Allow forcing fast-start to off to prevent doing more work on devices that
|
// Allow forcing fast-start to off to prevent doing more work on devices that
|
||||||
// don't support it.
|
// don't support it.
|
||||||
|
@ -536,6 +536,8 @@ class DebuggingOptions {
|
|||||||
this.hostname,
|
this.hostname,
|
||||||
this.port,
|
this.port,
|
||||||
this.webEnableExposeUrl,
|
this.webEnableExposeUrl,
|
||||||
|
this.webRunHeadless = false,
|
||||||
|
this.webBrowserDebugPort,
|
||||||
this.vmserviceOutFile,
|
this.vmserviceOutFile,
|
||||||
this.fastStart = false,
|
this.fastStart = false,
|
||||||
}) : debuggingEnabled = true;
|
}) : debuggingEnabled = true;
|
||||||
@ -545,6 +547,8 @@ class DebuggingOptions {
|
|||||||
this.port,
|
this.port,
|
||||||
this.hostname,
|
this.hostname,
|
||||||
this.webEnableExposeUrl,
|
this.webEnableExposeUrl,
|
||||||
|
this.webRunHeadless = false,
|
||||||
|
this.webBrowserDebugPort,
|
||||||
this.cacheSkSL = false,
|
this.cacheSkSL = false,
|
||||||
}) : debuggingEnabled = false,
|
}) : debuggingEnabled = false,
|
||||||
useTestFonts = false,
|
useTestFonts = false,
|
||||||
@ -585,6 +589,17 @@ class DebuggingOptions {
|
|||||||
final String port;
|
final String port;
|
||||||
final String hostname;
|
final String hostname;
|
||||||
final bool webEnableExposeUrl;
|
final bool webEnableExposeUrl;
|
||||||
|
|
||||||
|
/// Whether to run the browser in headless mode.
|
||||||
|
///
|
||||||
|
/// Some CI environments do not provide a display and fail to launch the
|
||||||
|
/// browser with full graphics stack. Some browsers provide a special
|
||||||
|
/// "headless" mode that runs the browser with no graphics.
|
||||||
|
final bool webRunHeadless;
|
||||||
|
|
||||||
|
/// The port the browser should use for its debugging protocol.
|
||||||
|
final int webBrowserDebugPort;
|
||||||
|
|
||||||
/// A file where the vmservice URL should be written after the application is started.
|
/// A file where the vmservice URL should be written after the application is started.
|
||||||
final String vmserviceOutFile;
|
final String vmserviceOutFile;
|
||||||
final bool fastStart;
|
final bool fastStart;
|
||||||
|
@ -173,6 +173,19 @@ abstract class FlutterCommand extends Command<void> {
|
|||||||
'when running on remote machines.',
|
'when running on remote machines.',
|
||||||
hide: hide,
|
hide: hide,
|
||||||
);
|
);
|
||||||
|
argParser.addFlag('web-run-headless',
|
||||||
|
defaultsTo: false,
|
||||||
|
help: 'Launches the browser in headless mode. Currently only Chrome '
|
||||||
|
'supports this option.',
|
||||||
|
hide: true,
|
||||||
|
);
|
||||||
|
argParser.addOption('web-browser-debug-port',
|
||||||
|
help: 'The debug port the browser should use. If not specified, a '
|
||||||
|
'random port is selected. Currently only Chrome supports this option. '
|
||||||
|
'It serves the Chrome DevTools Protocol '
|
||||||
|
'(https://chromedevtools.github.io/devtools-protocol/).',
|
||||||
|
hide: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void usesTargetOption() {
|
void usesTargetOption() {
|
||||||
|
@ -97,8 +97,11 @@ class ChromeLauncher {
|
|||||||
/// `headless` defaults to false, and controls whether we open a headless or
|
/// `headless` defaults to false, and controls whether we open a headless or
|
||||||
/// a `headfull` browser.
|
/// a `headfull` browser.
|
||||||
///
|
///
|
||||||
|
/// `debugPort` is Chrome's debugging protocol port. If null, a random free
|
||||||
|
/// port is picked automatically.
|
||||||
|
///
|
||||||
/// `skipCheck` does not attempt to make a devtools connection before returning.
|
/// `skipCheck` does not attempt to make a devtools connection before returning.
|
||||||
Future<Chrome> launch(String url, { bool headless = false, bool skipCheck = false, Directory dataDir }) async {
|
Future<Chrome> launch(String url, { bool headless = false, int debugPort, bool skipCheck = false, Directory dataDir }) async {
|
||||||
// This is a JSON file which contains configuration from the
|
// This is a JSON file which contains configuration from the
|
||||||
// browser session, such as window position. It is located
|
// browser session, such as window position. It is located
|
||||||
// under the Chrome data-dir folder.
|
// under the Chrome data-dir folder.
|
||||||
@ -117,7 +120,7 @@ class ChromeLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final int port = await globals.os.findFreePort();
|
final int port = debugPort ?? await globals.os.findFreePort();
|
||||||
final List<String> args = <String>[
|
final List<String> args = <String>[
|
||||||
chromeExecutable,
|
chromeExecutable,
|
||||||
// Using a tmp directory ensures that a new instance of chrome launches
|
// Using a tmp directory ensures that a new instance of chrome launches
|
||||||
|
@ -134,10 +134,14 @@ class ChromeDevice extends Device {
|
|||||||
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
|
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
|
||||||
// for the web initialization and server logic.
|
// for the web initialization and server logic.
|
||||||
final String url = platformArgs['uri'] as String;
|
final String url = platformArgs['uri'] as String;
|
||||||
_chrome = await chromeLauncher.launch(url,
|
_chrome = await chromeLauncher.launch(
|
||||||
|
url,
|
||||||
dataDir: globals.fs.currentDirectory
|
dataDir: globals.fs.currentDirectory
|
||||||
.childDirectory('.dart_tool')
|
.childDirectory('.dart_tool')
|
||||||
.childDirectory('chrome-device'));
|
.childDirectory('chrome-device'),
|
||||||
|
headless: debuggingOptions.webRunHeadless,
|
||||||
|
debugPort: debuggingOptions.webBrowserDebugPort,
|
||||||
|
);
|
||||||
|
|
||||||
globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': true});
|
globals.logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': true});
|
||||||
|
|
||||||
|
@ -54,10 +54,10 @@ void main() {
|
|||||||
resetChromeForTesting();
|
resetChromeForTesting();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can launch chrome and connect to the devtools', () => testbed.run(() async {
|
List<String> expectChromeArgs({int debugPort = 1234}) {
|
||||||
const List<String> expected = <String>[
|
return <String>[
|
||||||
'example_chrome',
|
'example_chrome',
|
||||||
'--remote-debugging-port=1234',
|
'--remote-debugging-port=$debugPort',
|
||||||
'--disable-background-timer-throttling',
|
'--disable-background-timer-throttling',
|
||||||
'--disable-extensions',
|
'--disable-extensions',
|
||||||
'--disable-popup-blocking',
|
'--disable-popup-blocking',
|
||||||
@ -68,11 +68,18 @@ void main() {
|
|||||||
'--disable-translate',
|
'--disable-translate',
|
||||||
'example_url',
|
'example_url',
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
test('can launch chrome and connect to the devtools', () => testbed.run(() async {
|
||||||
await chromeLauncher.launch('example_url', skipCheck: true);
|
await chromeLauncher.launch('example_url', skipCheck: true);
|
||||||
final VerificationResult result = verify(globals.processManager.start(captureAny));
|
final VerificationResult result = verify(globals.processManager.start(captureAny));
|
||||||
|
expect(result.captured.single, containsAll(expectChromeArgs()));
|
||||||
|
}));
|
||||||
|
|
||||||
expect(result.captured.single, containsAll(expected));
|
test('can launch chrome with a custom debug port', () => testbed.run(() async {
|
||||||
|
await chromeLauncher.launch('example_url', skipCheck: true, debugPort: 10000);
|
||||||
|
final VerificationResult result = verify(globals.processManager.start(captureAny));
|
||||||
|
expect(result.captured.single, containsAll(expectChromeArgs(debugPort: 10000)));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('can seed chrome temp directory with existing preferences', () => testbed.run(() async {
|
test('can seed chrome temp directory with existing preferences', () => testbed.run(() async {
|
||||||
|
@ -498,7 +498,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
// fast.
|
// fast.
|
||||||
unawaited(_process.exitCode.then((_) {
|
unawaited(_process.exitCode.then((_) {
|
||||||
if (!prematureExitGuard.isCompleted) {
|
if (!prematureExitGuard.isCompleted) {
|
||||||
prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}: $_errorBuffer');
|
prematureExitGuard.completeError('Process exited prematurely: ${args.join(' ')}: $_errorBuffer');
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user