@@ -292,31 +292,6 @@ targets:
|
||||
release_build: "true"
|
||||
config_name: linux_web_engine
|
||||
|
||||
- name: Linux Web Engine
|
||||
recipe: engine/web_engine
|
||||
properties:
|
||||
add_recipes_cq: "true"
|
||||
cores: "32"
|
||||
gcs_goldens_bucket: flutter_logs
|
||||
gclient_variables: >-
|
||||
{"download_emsdk": true}
|
||||
dependencies: >-
|
||||
[
|
||||
{"dependency": "chrome_and_driver", "version": "version:111.0"},
|
||||
{"dependency": "firefox", "version": "version:106.0"},
|
||||
{"dependency": "goldctl", "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603"}
|
||||
]
|
||||
no_goma: "true"
|
||||
timeout: 60
|
||||
runIf:
|
||||
- DEPS
|
||||
- .ci.yaml
|
||||
- lib/web_ui/**
|
||||
- web_sdk/**
|
||||
- tools/**
|
||||
- ci/**
|
||||
- flutter_frontend_server/**
|
||||
|
||||
- name: Linux Web Framework tests
|
||||
recipe: engine/web_engine_framework
|
||||
enabled_branches:
|
||||
@@ -448,28 +423,6 @@ targets:
|
||||
ios_debug: "true"
|
||||
timeout: 60
|
||||
|
||||
- name: Mac Web Engine
|
||||
recipe: engine/web_engine
|
||||
properties:
|
||||
add_recipes_cq: "true"
|
||||
gcs_goldens_bucket: flutter_logs
|
||||
gclient_variables: >-
|
||||
{"download_emsdk": true}
|
||||
dependencies: >-
|
||||
[
|
||||
{"dependency": "goldctl", "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603"}
|
||||
]
|
||||
no_goma: "true"
|
||||
timeout: 60
|
||||
runIf:
|
||||
- DEPS
|
||||
- .ci.yaml
|
||||
- lib/web_ui/**
|
||||
- web_sdk/**
|
||||
- tools/**
|
||||
- ci/**
|
||||
- flutter_frontend_server/**
|
||||
|
||||
- name: Mac mac_ios_engine
|
||||
recipe: engine_v2/engine_v2
|
||||
timeout: 60
|
||||
@@ -530,24 +483,6 @@ targets:
|
||||
add_recipes_cq: "true"
|
||||
timeout: 75
|
||||
|
||||
- name: Windows Web Engine
|
||||
recipe: engine/web_engine
|
||||
properties:
|
||||
gclient_variables: >-
|
||||
{"download_emsdk": true}
|
||||
gcs_goldens_bucket: flutter_logs
|
||||
dependencies: >-
|
||||
[
|
||||
{"dependency": "chrome_and_driver", "version": "version:111.0"}
|
||||
]
|
||||
no_goma: "true"
|
||||
timeout: 60
|
||||
runIf:
|
||||
- DEPS
|
||||
- .ci.yaml
|
||||
- lib/web_ui/**
|
||||
- web_sdk/**
|
||||
|
||||
- name: Mac iOS Engine Profile
|
||||
recipe: engine/engine
|
||||
properties:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,75 +22,92 @@ To tell `felt` to do anything you call `felt SUBCOMMAND`, where `SUBCOMMAND` is
|
||||
one of the available subcommands, which can be listed by running `felt help`. To
|
||||
get help for a specific subcommand, run `felt help SUBCOMMAND`.
|
||||
|
||||
The most useful subcommands are:
|
||||
|
||||
- `felt build` - builds a local Flutter Web engine ready to be used by the
|
||||
Flutter framework. To use a locally built web sdk, build with `felt build`,
|
||||
then pass `--local-web-sdk=wasm_release` to the `flutter` command, or to
|
||||
`dev/bots/test.dart` when running a web shard, such as `web_tests`.
|
||||
- `felt test` - runs web engine tests. By default, this runs all tests using
|
||||
Chromium. Passing one or more paths to specific tests would run just the
|
||||
specified tests. Run `felt help test` for more options.
|
||||
|
||||
`build` and `test` take the `--watch` option, which automatically reruns the
|
||||
subcommand when a source file changes. This is handy when you are iterating
|
||||
quickly.
|
||||
|
||||
#### Examples
|
||||
|
||||
Builds the web engine, the runs a Flutter app using it:
|
||||
#### `felt build`
|
||||
The `build` subcommand builds web engine gn/ninja targets. Targets can be
|
||||
individually specified in the command line invocation, or if none are specified,
|
||||
all web engine targets are built. Common targets are as follows:
|
||||
* `sdk` - The flutter_web_sdk itself.
|
||||
* `canvaskit` - Flutter's version of canvakit.
|
||||
* `canvaskit_chromium` - A version of canvaskit optimized for use with
|
||||
chromium-based browsers.
|
||||
* `skwasm` - Builds experimental skia wasm module renderer.
|
||||
The output of these steps is used in unit tests, and can be used with the flutter
|
||||
command via the `--local-web-sdk=wasm_release` command.
|
||||
|
||||
##### Examples
|
||||
Builds all web engine targets, then runs a Flutter app using it:
|
||||
```
|
||||
felt build
|
||||
cd path/to/some/app
|
||||
flutter --local-web-sdk=wasm_release run -d chrome
|
||||
```
|
||||
|
||||
Runs all tests in Chromium:
|
||||
Builds only the `sdk` and the `canvaskit` targets:
|
||||
```
|
||||
felt build sdk canvaskit
|
||||
```
|
||||
|
||||
#### `felt test`
|
||||
The `test` subcommand will compile and/or run web engine unit test suites. For
|
||||
information on how test suites are structured, see the test configuration
|
||||
[readme][2].
|
||||
|
||||
By default, `felt test` compiles and runs all suites that are compatible with the
|
||||
host system. Some useful flags supported by this command:
|
||||
* `--compile` will only perform compilation of these suites without running them.
|
||||
* `--run` will only run the tests and not compile them, and assume they have been
|
||||
compiled in a previous run of the tool.
|
||||
* `--list` will list all the test suites and test bundles and exit without
|
||||
compiling or running anything.
|
||||
* `--verbose` will output some extra information that may be useful for debugging.
|
||||
* `--debug` will open a browser window and pause the tests before starting so that
|
||||
breakpoints can be set before starting the test suites.
|
||||
|
||||
Several other flags can be passed that filter which test suites should be run:
|
||||
* `--browser` runs only the test suites that test on the browsers passed. Valid
|
||||
values for this are `chrome`, `firefox`, `safari`, or `edge`.
|
||||
* `--compiler` runs only the test suites that use a particular compiler. Valid
|
||||
values for this are `dart2js` or `dart2wasm`
|
||||
* `--renderer` runs only the test suites that use a particular renderer. Valid
|
||||
values for this are `html`, `canvakit`, or `skwasm`
|
||||
* `--suite` runs a suite by name.
|
||||
* `--bundle` runs suites that target a particular test bundle.
|
||||
|
||||
Filters of different types are logically ANDed together, but multiple filter flags
|
||||
of the same type are logically ORed together.
|
||||
|
||||
The `test` command will also accept a list of paths to specific test files to be
|
||||
compiled and run. If none of these paths are specified, all tests are run, otherwise
|
||||
only the tests that are specified will run.
|
||||
|
||||
##### Examples
|
||||
Runs all test suites in all compatible browsers:
|
||||
```
|
||||
felt test
|
||||
```
|
||||
|
||||
Runs a specific test:
|
||||
|
||||
Runs a specific test on all compatible browsers:
|
||||
```
|
||||
felt test test/engine/util_test.dart
|
||||
```
|
||||
|
||||
Runs multiple specific tests:
|
||||
|
||||
Runs multiple specific tests on all compatible browsers:
|
||||
```
|
||||
felt test test/engine/util_test.dart test/alarm_clock_test.dart
|
||||
felt test test/engine/util_test.dart test/engine/alarm_clock_test.dart
|
||||
```
|
||||
|
||||
Enable watch mode so that the test re-runs every time a source file changes:
|
||||
|
||||
Runs only test suites that compile via dart2wasm:
|
||||
```
|
||||
felt test --watch test/engine/util_test.dart
|
||||
felt test --compiler dart2wasm
|
||||
```
|
||||
|
||||
Runs tests in Firefox (requires a Linux computer):
|
||||
|
||||
Runs only test suites that run in Chrome and Safari:
|
||||
```
|
||||
felt test --browser=firefox
|
||||
```
|
||||
|
||||
Chromium and Firefox support debugging tests using the browser's developer
|
||||
tools. To run tests in debug mode add `--debug` to the `test` command, e.g.:
|
||||
|
||||
```
|
||||
felt test --debug --browser=firefox test/alarm_clock_test.dart
|
||||
felt test --browser chrome --browser safari
|
||||
```
|
||||
|
||||
### Optimizing local builds
|
||||
|
||||
Concurrency of various build steps can be configured via environment variables:
|
||||
|
||||
- `FELT_DART2JS_CONCURRENCY` specifies the number of concurrent `dart2js`
|
||||
- `FELT_COMPILE_CONCURRENCY` specifies the number of concurrent compiler
|
||||
processes used to compile tests. Default value is 8.
|
||||
- `FELT_TEST_CONCURRENCY` specifies the number of tests run concurrently.
|
||||
Default value is 10.
|
||||
|
||||
If you are a Google employee, you can use an internal instance of Goma (go/ma)
|
||||
to parallelize your ninja builds. Because Goma compiles code on remote servers,
|
||||
@@ -99,7 +116,7 @@ this option is particularly effective for building on low-powered laptops.
|
||||
### Test browsers
|
||||
|
||||
Chromium, Firefox, and Safari for iOS are version-locked using the
|
||||
[browser_lock.yaml][2] configuration file. Safari for macOS is supplied by the
|
||||
[browser_lock.yaml][3] configuration file. Safari for macOS is supplied by the
|
||||
computer's operating system. Tests can be run in Edge locally, but Edge is not
|
||||
enabled on LUCI. Chromium is used as a proxy for Chrome, Edge, and other
|
||||
Chromium-based browsers.
|
||||
@@ -273,7 +290,8 @@ Once you know the version for the Emscripten SDK, change the line in
|
||||
|
||||
|
||||
[1]: https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment
|
||||
[2]: https://github.com/flutter/engine/blob/main/lib/web_ui/dev/browser_lock.yaml
|
||||
[2]: https://github.com/flutter/flutter/blob/main/lib/web_ui/test/README
|
||||
[3]: https://github.com/flutter/engine/blob/main/lib/web_ui/dev/browser_lock.yaml
|
||||
[4]: https://chrome-infra-packages.appspot.com/p/flutter_internal
|
||||
[5]: https://cs.opensource.google/flutter/recipes/+/master:recipes/engine/web_engine.py
|
||||
[6]: https://chromium.googlesource.com/chromium/src.git/+/main/docs/cipd_and_3pp.md#What-is-CIPD
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'browser_lock.dart';
|
||||
import 'chrome.dart';
|
||||
import 'edge.dart';
|
||||
import 'environment.dart';
|
||||
import 'felt_config.dart';
|
||||
import 'firefox.dart';
|
||||
import 'safari_macos.dart';
|
||||
|
||||
@@ -261,16 +262,15 @@ const List<String> kAllBrowserNames = <String>[
|
||||
/// Creates an environment for a browser.
|
||||
///
|
||||
/// The [browserName] matches the browser name passed as the `--browser` option.
|
||||
BrowserEnvironment getBrowserEnvironment(String browserName, { required bool enableWasmGC }) {
|
||||
BrowserEnvironment getBrowserEnvironment(BrowserName browserName, { required bool enableWasmGC }) {
|
||||
switch (browserName) {
|
||||
case kChrome:
|
||||
case BrowserName.chrome:
|
||||
return ChromeEnvironment(enableWasmGC);
|
||||
case kEdge:
|
||||
case BrowserName.edge:
|
||||
return EdgeEnvironment();
|
||||
case kFirefox:
|
||||
case BrowserName.firefox:
|
||||
return FirefoxEnvironment();
|
||||
case kSafari:
|
||||
case BrowserName.safari:
|
||||
return SafariMacOsEnvironment();
|
||||
}
|
||||
throw UnsupportedError('Browser $browserName is not supported.');
|
||||
}
|
||||
|
||||
@@ -148,12 +148,17 @@ class Environment {
|
||||
|
||||
/// Path to the "build" directory, generated by "package:build_runner".
|
||||
///
|
||||
/// This is where compiled output goes.
|
||||
/// This is where compiled test output goes.
|
||||
io.Directory get webUiBuildDir => io.Directory(pathlib.join(
|
||||
outDir.path,
|
||||
'web_tests',
|
||||
));
|
||||
|
||||
io.Directory get webTestsArtifactsDir => io.Directory(pathlib.join(
|
||||
webUiBuildDir.path,
|
||||
'artifacts',
|
||||
));
|
||||
|
||||
/// Path to the ".dart_tool" directory, generated by various Dart tools.
|
||||
io.Directory get webUiDartToolDir => io.Directory(pathlib.join(
|
||||
webUiRootDir.path,
|
||||
|
||||
@@ -12,7 +12,6 @@ import 'clean.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'generate_fallback_font_data.dart';
|
||||
import 'licenses.dart';
|
||||
import 'run.dart';
|
||||
import 'test_runner.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
@@ -25,7 +24,6 @@ CommandRunner<bool> runner = CommandRunner<bool>(
|
||||
..addCommand(CleanCommand())
|
||||
..addCommand(GenerateFallbackFontDataCommand())
|
||||
..addCommand(LicensesCommand())
|
||||
..addCommand(RunCommand())
|
||||
..addCommand(TestCommand());
|
||||
|
||||
Future<void> main(List<String> rawArgs) async {
|
||||
|
||||
248
engine/src/flutter/lib/web_ui/dev/felt_config.dart
Normal file
248
engine/src/flutter/lib/web_ui/dev/felt_config.dart
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright 2013 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:yaml/yaml.dart';
|
||||
|
||||
enum Compiler {
|
||||
dart2js,
|
||||
dart2wasm
|
||||
}
|
||||
|
||||
enum Renderer {
|
||||
html,
|
||||
canvaskit,
|
||||
skwasm,
|
||||
}
|
||||
|
||||
class CompileConfiguration {
|
||||
CompileConfiguration(this.name, this.compiler, this.renderer);
|
||||
|
||||
final String name;
|
||||
final Compiler compiler;
|
||||
final Renderer renderer;
|
||||
}
|
||||
|
||||
class TestSet {
|
||||
TestSet(this.name, this.directory);
|
||||
|
||||
final String name;
|
||||
final String directory;
|
||||
}
|
||||
|
||||
class TestBundle {
|
||||
TestBundle(this.name, this.testSet, this.compileConfig);
|
||||
|
||||
final String name;
|
||||
final TestSet testSet;
|
||||
final CompileConfiguration compileConfig;
|
||||
}
|
||||
|
||||
enum CanvasKitVariant {
|
||||
full,
|
||||
chromium,
|
||||
}
|
||||
|
||||
enum BrowserName {
|
||||
chrome,
|
||||
edge,
|
||||
firefox,
|
||||
safari,
|
||||
}
|
||||
|
||||
class RunConfiguration {
|
||||
RunConfiguration(this.name, this.browser, this.variant);
|
||||
|
||||
final String name;
|
||||
final BrowserName browser;
|
||||
final CanvasKitVariant? variant;
|
||||
}
|
||||
|
||||
class ArtifactDependencies {
|
||||
ArtifactDependencies({
|
||||
required this.canvasKit,
|
||||
required this.canvasKitChromium,
|
||||
required this.skwasm
|
||||
});
|
||||
|
||||
ArtifactDependencies.none() :
|
||||
canvasKit = false,
|
||||
canvasKitChromium = false,
|
||||
skwasm = false;
|
||||
final bool canvasKit;
|
||||
final bool canvasKitChromium;
|
||||
final bool skwasm;
|
||||
|
||||
ArtifactDependencies operator|(ArtifactDependencies other) {
|
||||
return ArtifactDependencies(
|
||||
canvasKit: canvasKit || other.canvasKit,
|
||||
canvasKitChromium: canvasKitChromium || other.canvasKitChromium,
|
||||
skwasm: skwasm || other.skwasm,
|
||||
);
|
||||
}
|
||||
|
||||
ArtifactDependencies operator&(ArtifactDependencies other) {
|
||||
return ArtifactDependencies(
|
||||
canvasKit: canvasKit && other.canvasKit,
|
||||
canvasKitChromium: canvasKitChromium && other.canvasKitChromium,
|
||||
skwasm: skwasm && other.skwasm,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestSuite {
|
||||
TestSuite(
|
||||
this.name,
|
||||
this.testBundle,
|
||||
this.runConfig,
|
||||
this.artifactDependencies
|
||||
);
|
||||
|
||||
String name;
|
||||
TestBundle testBundle;
|
||||
RunConfiguration runConfig;
|
||||
ArtifactDependencies artifactDependencies;
|
||||
}
|
||||
|
||||
class FeltConfig {
|
||||
FeltConfig(
|
||||
this.compileConfigs,
|
||||
this.testSets,
|
||||
this.testBundles,
|
||||
this.runConfigs,
|
||||
this.testSuites,
|
||||
);
|
||||
|
||||
factory FeltConfig.fromFile(String filePath) {
|
||||
final io.File configFile = io.File(filePath);
|
||||
final YamlMap yaml = loadYaml(configFile.readAsStringSync()) as YamlMap;
|
||||
|
||||
final List<CompileConfiguration> compileConfigs = <CompileConfiguration>[];
|
||||
final Map<String, CompileConfiguration> compileConfigsByName = <String, CompileConfiguration>{};
|
||||
for (final dynamic node in yaml['compile-configs'] as YamlList) {
|
||||
final YamlMap configYaml = node as YamlMap;
|
||||
final String name = configYaml['name'] as String;
|
||||
final Compiler compiler = Compiler.values.byName(configYaml['compiler'] as String);
|
||||
final Renderer renderer = Renderer.values.byName(configYaml['renderer'] as String);
|
||||
final CompileConfiguration config = CompileConfiguration(name, compiler, renderer);
|
||||
compileConfigs.add(config);
|
||||
if (compileConfigsByName.containsKey(name)) {
|
||||
throw AssertionError('Duplicate compile config name: $name');
|
||||
}
|
||||
compileConfigsByName[name] = config;
|
||||
}
|
||||
|
||||
final List<TestSet> testSets = <TestSet>[];
|
||||
final Map<String, TestSet> testSetsByName = <String, TestSet>{};
|
||||
for (final dynamic node in yaml['test-sets'] as YamlList) {
|
||||
final YamlMap testSetYaml = node as YamlMap;
|
||||
final String name = testSetYaml['name'] as String;
|
||||
final String directory = testSetYaml['directory'] as String;
|
||||
final TestSet testSet = TestSet(name, directory);
|
||||
testSets.add(testSet);
|
||||
if (testSetsByName.containsKey(name)) {
|
||||
throw AssertionError('Duplicate test set name: $name');
|
||||
}
|
||||
testSetsByName[name] = testSet;
|
||||
}
|
||||
|
||||
final List<TestBundle> testBundles = <TestBundle>[];
|
||||
final Map<String, TestBundle> testBundlesByName = <String, TestBundle>{};
|
||||
for (final dynamic node in yaml['test-bundles'] as YamlList) {
|
||||
final YamlMap testBundleYaml = node as YamlMap;
|
||||
final String name = testBundleYaml['name'] as String;
|
||||
final String testSetName = testBundleYaml['test-set'] as String;
|
||||
final TestSet? testSet = testSetsByName[testSetName];
|
||||
if (testSet == null) {
|
||||
throw AssertionError('Test set not found with name: `$testSetName` (referenced by test bundle: `$name`)');
|
||||
}
|
||||
final String compileConfigName = testBundleYaml['compile-config'] as String;
|
||||
final CompileConfiguration? compileConfig = compileConfigsByName[compileConfigName];
|
||||
if (compileConfig == null) {
|
||||
throw AssertionError('Compile config not found with name: `$compileConfigName` (referenced by test bundle: `$name`)');
|
||||
}
|
||||
final TestBundle bundle = TestBundle(name, testSet, compileConfig);
|
||||
testBundles.add(bundle);
|
||||
if (testBundlesByName.containsKey(name)) {
|
||||
throw AssertionError('Duplicate test bundle name: $name');
|
||||
}
|
||||
testBundlesByName[name] = bundle;
|
||||
}
|
||||
|
||||
final List<RunConfiguration> runConfigs = <RunConfiguration>[];
|
||||
final Map<String, RunConfiguration> runConfigsByName = <String, RunConfiguration>{};
|
||||
for (final dynamic node in yaml['run-configs'] as YamlList) {
|
||||
final YamlMap runConfigYaml = node as YamlMap;
|
||||
final String name = runConfigYaml['name'] as String;
|
||||
final BrowserName browser = BrowserName.values.byName(runConfigYaml['browser'] as String);
|
||||
final dynamic variantNode = runConfigYaml['canvaskit-variant'];
|
||||
final CanvasKitVariant? variant = variantNode == null
|
||||
? null
|
||||
: CanvasKitVariant.values.byName(variantNode as String);
|
||||
final RunConfiguration runConfig = RunConfiguration(name, browser, variant);
|
||||
runConfigs.add(runConfig);
|
||||
if (runConfigsByName.containsKey(name)) {
|
||||
throw AssertionError('Duplicate run config name: $name');
|
||||
}
|
||||
runConfigsByName[name] = runConfig;
|
||||
}
|
||||
|
||||
final List<TestSuite> testSuites = <TestSuite>[];
|
||||
for (final dynamic node in yaml['test-suites'] as YamlList) {
|
||||
final YamlMap testSuiteYaml = node as YamlMap;
|
||||
final String name = testSuiteYaml['name'] as String;
|
||||
final String testBundleName = testSuiteYaml['test-bundle'] as String;
|
||||
final TestBundle? bundle = testBundlesByName[testBundleName];
|
||||
if (bundle == null) {
|
||||
throw AssertionError('Test bundle not found with name: `$testBundleName` (referenced by test suite: `$name`)');
|
||||
}
|
||||
final String runConfigName = testSuiteYaml['run-config'] as String;
|
||||
final RunConfiguration? runConfig = runConfigsByName[runConfigName];
|
||||
if (runConfig == null) {
|
||||
throw AssertionError('Run config not found with name: `$runConfigName` (referenced by test suite: `$name`)');
|
||||
}
|
||||
bool canvasKit = false;
|
||||
bool canvasKitChromium = false;
|
||||
bool skwasm = false;
|
||||
final dynamic depsNode = testSuiteYaml['artifact-deps'];
|
||||
if (depsNode != null) {
|
||||
for (final dynamic dep in depsNode as YamlList) {
|
||||
switch (dep as String) {
|
||||
case 'canvaskit':
|
||||
if (canvasKit) {
|
||||
throw AssertionError('Artifact dep $dep listed twice in suite $name.');
|
||||
}
|
||||
canvasKit = true;
|
||||
case 'canvaskit_chromium':
|
||||
if (canvasKitChromium) {
|
||||
throw AssertionError('Artifact dep $dep listed twice in suite $name.');
|
||||
}
|
||||
canvasKitChromium = true;
|
||||
case 'skwasm':
|
||||
if (skwasm) {
|
||||
throw AssertionError('Artifact dep $dep listed twice in suite $name.');
|
||||
}
|
||||
skwasm = true;
|
||||
default:
|
||||
throw AssertionError('Unrecognized artifact dependency: $dep');
|
||||
}
|
||||
}
|
||||
}
|
||||
final ArtifactDependencies artifactDeps = ArtifactDependencies(
|
||||
canvasKit: canvasKit,
|
||||
canvasKitChromium: canvasKitChromium,
|
||||
skwasm: skwasm
|
||||
);
|
||||
final TestSuite suite = TestSuite(name, bundle, runConfig, artifactDeps);
|
||||
testSuites.add(suite);
|
||||
}
|
||||
return FeltConfig(compileConfigs, testSets, testBundles, runConfigs, testSuites);
|
||||
}
|
||||
|
||||
List<CompileConfiguration> compileConfigs;
|
||||
List<TestSet> testSets;
|
||||
List<TestBundle> testBundles;
|
||||
List<RunConfiguration> runConfigs;
|
||||
List<TestSuite> testSuites;
|
||||
}
|
||||
173
engine/src/flutter/lib/web_ui/dev/generate_builder_json.dart
Normal file
173
engine/src/flutter/lib/web_ui/dev/generate_builder_json.dart
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2013 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:convert';
|
||||
|
||||
import 'felt_config.dart';
|
||||
|
||||
String generateBuilderJson(FeltConfig config) {
|
||||
final Map<String, dynamic> outputJson = <String, dynamic>{
|
||||
'builds': <dynamic>[
|
||||
_getArtifactBuildStep(),
|
||||
for (final TestBundle bundle in config.testBundles)
|
||||
_getBundleBuildStep(bundle),
|
||||
],
|
||||
'tests': _getAllTestSteps(config.testSuites)
|
||||
};
|
||||
return const JsonEncoder.withIndent(' ').convert(outputJson);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getArtifactBuildStep() {
|
||||
return <String, dynamic>{
|
||||
'name': 'web_tests/artifacts',
|
||||
'drone_dimensions': <String>[
|
||||
'device_type=none',
|
||||
'os=Linux',
|
||||
'cores=32'
|
||||
],
|
||||
'gclient_variables': <String, dynamic>{
|
||||
'download_android_deps': false,
|
||||
'download_emsdk': true,
|
||||
},
|
||||
'gn': <String>[
|
||||
'--web',
|
||||
'--runtime-mode=release',
|
||||
'--no-goma',
|
||||
],
|
||||
'ninja': <String, dynamic>{
|
||||
'config': 'wasm_release',
|
||||
'targets': <String>[
|
||||
'flutter/web_sdk:flutter_web_sdk_archive'
|
||||
]
|
||||
},
|
||||
'archives': <dynamic>[
|
||||
<String, dynamic>{
|
||||
'name': 'wasm_release',
|
||||
'base_path': 'out/wasm_release/zip_archives/',
|
||||
'type': 'gcs',
|
||||
'include_paths': <String>[
|
||||
'out/wasm_release/zip_archives/flutter-web-sdk.zip'
|
||||
],
|
||||
'realm': 'production',
|
||||
}
|
||||
],
|
||||
'generators': <String, dynamic>{
|
||||
'tasks': <dynamic>[
|
||||
<String, dynamic>{
|
||||
'name': 'check licenses',
|
||||
'parameters': <String>[
|
||||
'check-licenses'
|
||||
],
|
||||
'scripts': <String>[ 'flutter/lib/web_ui/dev/felt' ],
|
||||
|
||||
},
|
||||
<String, dynamic>{
|
||||
'name': 'web engine analysis',
|
||||
'parameters': <String>[
|
||||
'analyze'
|
||||
],
|
||||
'scripts': <String>[ 'flutter/lib/web_ui/dev/felt' ],
|
||||
},
|
||||
<String, dynamic>{
|
||||
'name': 'copy artifacts for web tests',
|
||||
'parameters': <String>[
|
||||
'test',
|
||||
'--copy-artifacts',
|
||||
],
|
||||
'scripts': <String>[ 'flutter/lib/web_ui/dev/felt' ],
|
||||
},
|
||||
]
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getBundleBuildStep(TestBundle bundle) {
|
||||
return <String, dynamic>{
|
||||
'name': 'web_tests/test_bundles/${bundle.name}',
|
||||
'drone_dimensions': <String>[
|
||||
'device_type=none',
|
||||
'os=Linux',
|
||||
'cores=32',
|
||||
],
|
||||
'generators': <String, dynamic>{
|
||||
'tasks': <dynamic>[
|
||||
<String, dynamic>{
|
||||
'name': 'compile bundle ${bundle.name}',
|
||||
'parameters': <String>[
|
||||
'test',
|
||||
'--compile',
|
||||
'--bundle=${bundle.name}',
|
||||
],
|
||||
'scripts': <String>[ 'flutter/lib/web_ui/dev/felt' ],
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Iterable<dynamic> _getAllTestSteps(List<TestSuite> suites) {
|
||||
return <dynamic>[
|
||||
..._getTestStepsForPlatform(suites, 'Linux', <BrowserName>{
|
||||
BrowserName.chrome,
|
||||
BrowserName.firefox,
|
||||
}),
|
||||
..._getTestStepsForPlatform(suites, 'Mac', <BrowserName>{
|
||||
BrowserName.safari,
|
||||
}),
|
||||
..._getTestStepsForPlatform(suites, 'Windows', <BrowserName>{
|
||||
BrowserName.chrome,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
Iterable<dynamic> _getTestStepsForPlatform(
|
||||
List<TestSuite> suites,
|
||||
String platform,
|
||||
Set<BrowserName> browsers) {
|
||||
return suites
|
||||
.where((TestSuite suite) => browsers.contains(suite.runConfig.browser))
|
||||
.map((TestSuite suite) => <String, dynamic>{
|
||||
'name': '$platform run ${suite.name} suite',
|
||||
'recipe': 'engine_v2/tester_engine',
|
||||
'drone_dimensions': <String>[
|
||||
'device_type=none',
|
||||
'os=$platform',
|
||||
],
|
||||
'gclient_variables': <String, dynamic>{
|
||||
'download_android_deps': false,
|
||||
},
|
||||
'dependencies': <String>[
|
||||
'web_tests/artifacts',
|
||||
'web_tests/test_bundles/${suite.testBundle.name}',
|
||||
],
|
||||
'test_dependencies': <dynamic>[
|
||||
<String, dynamic>{
|
||||
'dependency': 'goldctl',
|
||||
'version': 'git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603',
|
||||
},
|
||||
if (suite.runConfig.browser == BrowserName.chrome)
|
||||
<String, dynamic>{
|
||||
'dependency': 'chrome_and_driver',
|
||||
'version': 'version:111.0',
|
||||
},
|
||||
if (suite.runConfig.browser == BrowserName.firefox)
|
||||
<String, dynamic>{
|
||||
'dependency': 'firefox',
|
||||
'version': 'version:106.0',
|
||||
}
|
||||
],
|
||||
'tasks': <dynamic>[
|
||||
<String, dynamic>{
|
||||
'name': 'run suite ${suite.name}',
|
||||
'parameters': <String>[
|
||||
'test',
|
||||
'--run',
|
||||
'--suite=${suite.name}'
|
||||
],
|
||||
'script': 'flutter/lib/web_ui/dev/felt',
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -82,7 +82,6 @@ class LicensesCommand extends Command<bool> {
|
||||
// This is the old path that tests used to be built into. Ignore anything
|
||||
// within this path.
|
||||
final String legacyBuildPath = path.join(environment.webUiRootDir.path, 'build');
|
||||
|
||||
return directory.listSync(recursive: true).whereType<io.File>().where((io.File f) {
|
||||
if (!f.path.endsWith('.dart') && !f.path.endsWith('.js')) {
|
||||
// Not a source file we're checking.
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'dart:io' as io;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:watcher/watcher.dart';
|
||||
|
||||
import 'exceptions.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/// Describes what [Pipeline] is currently doing.
|
||||
@@ -87,6 +88,13 @@ abstract class ProcessStep implements PipelineStep {
|
||||
}
|
||||
}
|
||||
|
||||
class _PipelineStepFailure {
|
||||
_PipelineStepFailure(this.step, this.error);
|
||||
|
||||
final PipelineStep step;
|
||||
final Object error;
|
||||
}
|
||||
|
||||
/// Executes a sequence of asynchronous tasks, typically as part of a build/test
|
||||
/// process.
|
||||
///
|
||||
@@ -112,27 +120,34 @@ class Pipeline {
|
||||
///
|
||||
/// Returns a future that resolves after all steps have been performed.
|
||||
///
|
||||
/// The future resolves to an error as soon as any of the steps fails.
|
||||
/// If any steps fail, the pipeline attempts to continue to subsequent steps,
|
||||
/// but will fail at the end.
|
||||
///
|
||||
/// The pipeline may be interrupted by calling [stop] before the future
|
||||
/// resolves.
|
||||
Future<void> run() async {
|
||||
_status = PipelineStatus.started;
|
||||
try {
|
||||
for (final PipelineStep step in steps) {
|
||||
if (status != PipelineStatus.started) {
|
||||
break;
|
||||
}
|
||||
_currentStep = step;
|
||||
_currentStepFuture = step.run();
|
||||
final List<_PipelineStepFailure> failures = <_PipelineStepFailure>[];
|
||||
for (final PipelineStep step in steps) {
|
||||
_currentStep = step;
|
||||
_currentStepFuture = step.run();
|
||||
try {
|
||||
await _currentStepFuture;
|
||||
} catch (e) {
|
||||
failures.add(_PipelineStepFailure(step, e));
|
||||
} finally {
|
||||
_currentStep = null;
|
||||
}
|
||||
}
|
||||
if (failures.isEmpty) {
|
||||
_status = PipelineStatus.done;
|
||||
} catch (_) {
|
||||
} else {
|
||||
_status = PipelineStatus.error;
|
||||
rethrow;
|
||||
} finally {
|
||||
_currentStep = null;
|
||||
print('Pipeline experienced the following failures:');
|
||||
for (final _PipelineStepFailure failure in failures) {
|
||||
print(' "${failure.step.description}": ${failure.error}');
|
||||
}
|
||||
throw ToolExit('Test pipeline failed.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright 2013 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:args/command_runner.dart';
|
||||
|
||||
import 'common.dart';
|
||||
import 'pipeline.dart';
|
||||
import 'steps/compile_tests_step.dart';
|
||||
import 'steps/run_tests_step.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/// Runs build and test steps.
|
||||
///
|
||||
/// This command is designed to be invoked by the LUCI build graph. However, it
|
||||
/// is also usable locally.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// felt run name_of_build_step
|
||||
class RunCommand extends Command<bool> with ArgUtils<bool> {
|
||||
RunCommand() {
|
||||
argParser.addFlag(
|
||||
'list',
|
||||
abbr: 'l',
|
||||
help: 'Lists all available build steps.',
|
||||
);
|
||||
argParser.addFlag(
|
||||
'require-skia-gold',
|
||||
help: 'Whether we require Skia Gold to be available or not. When this '
|
||||
'flag is true, the tests will fail if Skia Gold is not available.',
|
||||
);
|
||||
argParser.addFlag(
|
||||
'wasm',
|
||||
help: 'Whether the test we are running are compiled to webassembly.'
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => 'run';
|
||||
|
||||
bool get isWasm => boolArg('wasm');
|
||||
|
||||
bool get isListSteps => boolArg('list');
|
||||
|
||||
/// When running screenshot tests, require Skia Gold to be available and
|
||||
/// reachable.
|
||||
bool get requireSkiaGold => boolArg('require-skia-gold');
|
||||
|
||||
@override
|
||||
String get description => 'Runs a build step.';
|
||||
|
||||
/// Build steps to run, in order specified.
|
||||
List<String> get stepNames => argResults!.rest;
|
||||
|
||||
@override
|
||||
FutureOr<bool> run() async {
|
||||
// All available build steps.
|
||||
final Map<String, PipelineStep> buildSteps = <String, PipelineStep>{
|
||||
'compile_tests': CompileTestsStep(),
|
||||
for (final String browserName in kAllBrowserNames)
|
||||
'run_tests_$browserName': RunTestsStep(
|
||||
browserName: browserName,
|
||||
isDebug: false,
|
||||
isWasm: isWasm,
|
||||
doUpdateScreenshotGoldens: false,
|
||||
requireSkiaGold: requireSkiaGold,
|
||||
overridePathToCanvasKit: null,
|
||||
),
|
||||
};
|
||||
|
||||
if (isListSteps) {
|
||||
buildSteps.keys.forEach(print);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stepNames.isEmpty) {
|
||||
throw UsageException('No build steps specified.', argParser.usage);
|
||||
}
|
||||
|
||||
final List<String> unrecognizedStepNames = <String>[];
|
||||
for (final String stepName in stepNames) {
|
||||
if (!buildSteps.containsKey(stepName)) {
|
||||
unrecognizedStepNames.add(stepName);
|
||||
}
|
||||
}
|
||||
if (unrecognizedStepNames.isNotEmpty) {
|
||||
io.stderr.writeln(
|
||||
'Unknown build steps specified: ${unrecognizedStepNames.join(', ')}',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<PipelineStep> steps = <PipelineStep>[];
|
||||
print('Running steps ${steps.join(', ')}');
|
||||
for (final String stepName in stepNames) {
|
||||
steps.add(buildSteps[stepName]!);
|
||||
}
|
||||
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
final Pipeline pipeline = Pipeline(steps: steps);
|
||||
await pipeline.run();
|
||||
stopwatch.stop();
|
||||
print('Finished running steps in ${stopwatch.elapsedMilliseconds / 1000} seconds.');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
308
engine/src/flutter/lib/web_ui/dev/steps/compile_bundle_step.dart
Normal file
308
engine/src/flutter/lib/web_ui/dev/steps/compile_bundle_step.dart
Normal file
@@ -0,0 +1,308 @@
|
||||
// Copyright 2013 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:convert';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:path/path.dart' as pathlib;
|
||||
import 'package:pool/pool.dart';
|
||||
|
||||
import '../environment.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../felt_config.dart';
|
||||
import '../pipeline.dart';
|
||||
import '../utils.dart' show AnsiColors, FilePath, ProcessManager, cleanup, getBundleBuildDirectory, startProcess;
|
||||
|
||||
/// Compiles a web test bundle into web_ui/build/test_bundles/<bundle-name>.
|
||||
class CompileBundleStep implements PipelineStep {
|
||||
CompileBundleStep({
|
||||
required this.bundle,
|
||||
required this.isVerbose,
|
||||
this.testFiles,
|
||||
});
|
||||
|
||||
final TestBundle bundle;
|
||||
final bool isVerbose;
|
||||
final Set<FilePath>? testFiles;
|
||||
|
||||
// Maximum number of concurrent compile processes to use.
|
||||
static final int _compileConcurrency = int.parse(io.Platform.environment['FELT_COMPILE_CONCURRENCY'] ?? '8');
|
||||
final Pool compilePool = Pool(_compileConcurrency);
|
||||
|
||||
@override
|
||||
String get description => 'compile_bundle';
|
||||
|
||||
@override
|
||||
bool get isSafeToInterrupt => true;
|
||||
|
||||
@override
|
||||
Future<void> interrupt() async {
|
||||
await cleanup();
|
||||
}
|
||||
|
||||
io.Directory get testSetDirectory => io.Directory(
|
||||
pathlib.join(environment.webUiTestDir.path, bundle.testSet.directory)
|
||||
);
|
||||
|
||||
io.Directory get outputBundleDirectory => getBundleBuildDirectory(bundle);
|
||||
|
||||
List<FilePath> _findTestFiles() {
|
||||
final io.Directory testDirectory = testSetDirectory;
|
||||
if (!testDirectory.existsSync()) {
|
||||
throw ToolExit('Test directory "${testDirectory.path}" for bundle ${bundle.name.ansiMagenta} does not exist.');
|
||||
}
|
||||
return testDirectory
|
||||
.listSync(recursive: true)
|
||||
.whereType<io.File>()
|
||||
.where((io.File f) => f.path.endsWith('_test.dart'))
|
||||
.map<FilePath>((io.File f) => FilePath.fromWebUi(
|
||||
pathlib.relative(f.path, from: environment.webUiRootDir.path)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
TestCompiler _createCompiler() {
|
||||
switch (bundle.compileConfig.compiler) {
|
||||
case Compiler.dart2js:
|
||||
return Dart2JSCompiler(
|
||||
testSetDirectory,
|
||||
outputBundleDirectory,
|
||||
renderer: bundle.compileConfig.renderer,
|
||||
isVerbose: isVerbose,
|
||||
);
|
||||
case Compiler.dart2wasm:
|
||||
return Dart2WasmCompiler(
|
||||
testSetDirectory,
|
||||
outputBundleDirectory,
|
||||
renderer: bundle.compileConfig.renderer,
|
||||
isVerbose: isVerbose,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
print('Compiling test bundle ${bundle.name.ansiMagenta}...');
|
||||
final List<FilePath> allTests = _findTestFiles();
|
||||
final TestCompiler compiler = _createCompiler();
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
final String testSetDirectoryPath = testSetDirectory.path;
|
||||
|
||||
// Clear out old bundle compilations, if they exist
|
||||
if (outputBundleDirectory.existsSync()) {
|
||||
outputBundleDirectory.deleteSync(recursive: true );
|
||||
}
|
||||
|
||||
final List<Future<MapEntry<String, CompileResult>>> pendingResults = <Future<MapEntry<String, CompileResult>>>[];
|
||||
for (final FilePath testFile in allTests) {
|
||||
final String relativePath = pathlib.relative(
|
||||
testFile.absolute,
|
||||
from: testSetDirectoryPath);
|
||||
final Future<MapEntry<String, CompileResult>> result = compilePool.withResource(() async {
|
||||
if (testFiles != null && !testFiles!.contains(testFile)) {
|
||||
return MapEntry<String, CompileResult>(relativePath, CompileResult.filtered);
|
||||
}
|
||||
final bool success = await compiler.compileTest(testFile);
|
||||
const int maxTestNameLength = 80;
|
||||
final String truncatedPath = relativePath.length > maxTestNameLength
|
||||
? relativePath.replaceRange(maxTestNameLength - 3, relativePath.length, '...')
|
||||
: relativePath;
|
||||
final String expandedPath = truncatedPath.padRight(maxTestNameLength);
|
||||
io.stdout.write('\r ${success ? expandedPath.ansiGreen : expandedPath.ansiRed}');
|
||||
return success
|
||||
? MapEntry<String, CompileResult>(relativePath, CompileResult.success)
|
||||
: MapEntry<String, CompileResult>(relativePath, CompileResult.compilationFailure);
|
||||
});
|
||||
pendingResults.add(result);
|
||||
}
|
||||
final Map<String, CompileResult> results = Map<String, CompileResult>.fromEntries(await Future.wait(pendingResults));
|
||||
stopwatch.stop();
|
||||
|
||||
final String resultsJson = const JsonEncoder.withIndent(' ').convert(<String, dynamic>{
|
||||
'name': bundle.name,
|
||||
'directory': bundle.testSet.directory,
|
||||
'compiler': bundle.compileConfig.compiler.name,
|
||||
'renderer': bundle.compileConfig.renderer.name,
|
||||
'compileTimeInMs': stopwatch.elapsedMilliseconds,
|
||||
'results': results.map((String k, CompileResult v) => MapEntry<String, String>(k, v.name)),
|
||||
});
|
||||
final io.File outputResultsFile = io.File(pathlib.join(
|
||||
outputBundleDirectory.path,
|
||||
'results.json',
|
||||
));
|
||||
outputResultsFile.writeAsStringSync(resultsJson);
|
||||
final List<String> failedFiles = <String>[];
|
||||
results.forEach((String fileName, CompileResult result) {
|
||||
if (result == CompileResult.compilationFailure) {
|
||||
failedFiles.add(fileName);
|
||||
}
|
||||
});
|
||||
if (failedFiles.isEmpty) {
|
||||
print('\rCompleted compilation of ${bundle.name.ansiMagenta} in ${stopwatch.elapsedMilliseconds}ms.'.padRight(82));
|
||||
} else {
|
||||
print('\rThe bundle ${bundle.name.ansiMagenta} compiled with some failures in ${stopwatch.elapsedMilliseconds}ms.');
|
||||
print('Compilation failures:');
|
||||
for (final String fileName in failedFiles) {
|
||||
print(' $fileName');
|
||||
}
|
||||
throw ToolExit('Failed to compile ${bundle.name.ansiMagenta}.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CompileResult {
|
||||
success,
|
||||
compilationFailure,
|
||||
filtered,
|
||||
}
|
||||
|
||||
abstract class TestCompiler {
|
||||
TestCompiler(
|
||||
this.inputTestSetDirectory,
|
||||
this.outputTestBundleDirectory,
|
||||
{
|
||||
required this.renderer,
|
||||
required this.isVerbose,
|
||||
}
|
||||
);
|
||||
|
||||
final io.Directory inputTestSetDirectory;
|
||||
final io.Directory outputTestBundleDirectory;
|
||||
final Renderer renderer;
|
||||
final bool isVerbose;
|
||||
|
||||
Future<bool> compileTest(FilePath input);
|
||||
}
|
||||
|
||||
class Dart2JSCompiler extends TestCompiler {
|
||||
Dart2JSCompiler(
|
||||
super.inputTestSetDirectory,
|
||||
super.outputTestBundleDirectory,
|
||||
{
|
||||
required super.renderer,
|
||||
required super.isVerbose,
|
||||
}
|
||||
);
|
||||
|
||||
@override
|
||||
Future<bool> compileTest(FilePath input) async {
|
||||
final String relativePath = pathlib.relative(
|
||||
input.absolute,
|
||||
from: inputTestSetDirectory.path
|
||||
);
|
||||
|
||||
final String targetFileName = pathlib.join(
|
||||
outputTestBundleDirectory.path,
|
||||
'$relativePath.browser_test.dart.js',
|
||||
);
|
||||
|
||||
final io.Directory outputDirectory = io.File(targetFileName).parent;
|
||||
if (!outputDirectory.existsSync()) {
|
||||
outputDirectory.createSync(recursive: true);
|
||||
}
|
||||
|
||||
final List<String> arguments = <String>[
|
||||
'compile',
|
||||
'js',
|
||||
'--no-minify',
|
||||
'--disable-inlining',
|
||||
'--enable-asserts',
|
||||
|
||||
// We do not want to auto-select a renderer in tests. As of today, tests
|
||||
// are designed to run in one specific mode. So instead, we specify the
|
||||
// renderer explicitly.
|
||||
'-DFLUTTER_WEB_AUTO_DETECT=false',
|
||||
'-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvaskit}',
|
||||
'-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}',
|
||||
|
||||
'-O2',
|
||||
'-o',
|
||||
targetFileName, // target path.
|
||||
relativePath, // current path.
|
||||
];
|
||||
|
||||
final ProcessManager process = await startProcess(
|
||||
environment.dartExecutable,
|
||||
arguments,
|
||||
workingDirectory: inputTestSetDirectory.path,
|
||||
failureIsSuccess: true,
|
||||
evalOutput: !isVerbose,
|
||||
);
|
||||
final int exitCode = await process.wait();
|
||||
if (exitCode != 0) {
|
||||
io.stderr.writeln('ERROR: Failed to compile test $input. '
|
||||
'Dart2js exited with exit code $exitCode');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Dart2WasmCompiler extends TestCompiler {
|
||||
Dart2WasmCompiler(
|
||||
super.inputTestSetDirectory,
|
||||
super.outputTestBundleDirectory,
|
||||
{
|
||||
required super.renderer,
|
||||
required super.isVerbose,
|
||||
}
|
||||
);
|
||||
|
||||
@override
|
||||
Future<bool> compileTest(FilePath input) async {
|
||||
final String relativePath = pathlib.relative(
|
||||
input.absolute,
|
||||
from: inputTestSetDirectory.path
|
||||
);
|
||||
|
||||
final String targetFileName = pathlib.join(
|
||||
outputTestBundleDirectory.path,
|
||||
'$relativePath.browser_test.dart.wasm',
|
||||
);
|
||||
|
||||
final io.Directory outputDirectory = io.File(targetFileName).parent;
|
||||
if (!outputDirectory.existsSync()) {
|
||||
outputDirectory.createSync(recursive: true);
|
||||
}
|
||||
|
||||
final List<String> arguments = <String>[
|
||||
environment.dart2wasmSnapshotPath,
|
||||
|
||||
'--dart-sdk=${environment.dartSdkDir.path}',
|
||||
'--enable-asserts',
|
||||
|
||||
// We do not want to auto-select a renderer in tests. As of today, tests
|
||||
// are designed to run in one specific mode. So instead, we specify the
|
||||
// renderer explicitly.
|
||||
'-DFLUTTER_WEB_AUTO_DETECT=false',
|
||||
'-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvaskit}',
|
||||
'-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}',
|
||||
|
||||
if (renderer == Renderer.skwasm) ...<String>[
|
||||
'--import-shared-memory',
|
||||
'--shared-memory-max-pages=32768',
|
||||
],
|
||||
|
||||
relativePath, // current path.
|
||||
targetFileName, // target path.
|
||||
];
|
||||
|
||||
final ProcessManager process = await startProcess(
|
||||
environment.dartAotRuntimePath,
|
||||
arguments,
|
||||
workingDirectory: inputTestSetDirectory.path,
|
||||
failureIsSuccess: true,
|
||||
evalOutput: true,
|
||||
);
|
||||
final int exitCode = await process.wait();
|
||||
|
||||
if (exitCode != 0) {
|
||||
io.stderr.writeln('ERROR: Failed to compile test $input. '
|
||||
'dart2wasm exited with exit code $exitCode');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
// Copyright 2013 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:convert' show JsonEncoder;
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:path/path.dart' as pathlib;
|
||||
import 'package:pool/pool.dart';
|
||||
|
||||
import '../environment.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../pipeline.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
/// Compiles web tests and their dependencies into web_ui/build/.
|
||||
///
|
||||
/// Outputs of this step:
|
||||
///
|
||||
/// * canvaskit/ - CanvasKit artifacts
|
||||
/// * assets/ - test fonts
|
||||
/// * host/ - compiled test host page and static artifacts
|
||||
/// * test/ - compiled test code
|
||||
/// * test_images/ - test images copied from Skis sources.
|
||||
class CompileTestsStep implements PipelineStep {
|
||||
CompileTestsStep({
|
||||
this.testFiles,
|
||||
this.useLocalCanvasKit = false,
|
||||
this.isWasm = false
|
||||
});
|
||||
|
||||
final List<FilePath>? testFiles;
|
||||
final bool isWasm;
|
||||
|
||||
final bool useLocalCanvasKit;
|
||||
|
||||
@override
|
||||
String get description => 'compile_tests';
|
||||
|
||||
@override
|
||||
bool get isSafeToInterrupt => true;
|
||||
|
||||
@override
|
||||
Future<void> interrupt() async {
|
||||
await cleanup();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
await environment.webUiBuildDir.create(recursive: true);
|
||||
if (isWasm) {
|
||||
await copyDart2WasmTestScript();
|
||||
await copySkwasm();
|
||||
}
|
||||
await copyCanvasKitFiles(useLocalCanvasKit: useLocalCanvasKit);
|
||||
await buildHostPage();
|
||||
await copyTestFonts();
|
||||
await copySkiaTestImages();
|
||||
await compileTests(testFiles ?? findAllTests(), isWasm);
|
||||
}
|
||||
}
|
||||
|
||||
const Map<String, String> _kTestFonts = <String, String>{
|
||||
'Ahem': 'ahem.ttf',
|
||||
'Roboto': 'Roboto-Regular.ttf',
|
||||
'RobotoVariable': 'RobotoSlab-VariableFont_wght.ttf',
|
||||
'Noto Naskh Arabic UI': 'NotoNaskhArabic-Regular.ttf',
|
||||
'Noto Color Emoji': 'NotoColorEmoji.ttf',
|
||||
};
|
||||
|
||||
Future<void> copyTestFonts() async {
|
||||
final String fontsPath = pathlib.join(
|
||||
environment.flutterDirectory.path,
|
||||
'third_party',
|
||||
'txt',
|
||||
'third_party',
|
||||
'fonts',
|
||||
);
|
||||
|
||||
final List<dynamic> fontManifest = <dynamic>[];
|
||||
for (final MapEntry<String, String> fontEntry in _kTestFonts.entries) {
|
||||
final String family = fontEntry.key;
|
||||
final String fontFile = fontEntry.value;
|
||||
|
||||
fontManifest.add(<String, dynamic>{
|
||||
'family': family,
|
||||
'fonts': <dynamic>[
|
||||
<String, String>{
|
||||
'asset': 'fonts/$fontFile',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
final io.File sourceTtf = io.File(pathlib.join(fontsPath, fontFile));
|
||||
final io.File destinationTtf = io.File(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'assets',
|
||||
'fonts',
|
||||
fontFile,
|
||||
));
|
||||
await destinationTtf.create(recursive: true);
|
||||
await sourceTtf.copy(destinationTtf.path);
|
||||
}
|
||||
|
||||
final io.File fontManifestFile = io.File(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'assets',
|
||||
'FontManifest.json',
|
||||
));
|
||||
await fontManifestFile.create(recursive: true);
|
||||
await fontManifestFile.writeAsString(
|
||||
const JsonEncoder.withIndent(' ').convert(fontManifest),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> copySkiaTestImages() async {
|
||||
final io.Directory testImagesDir = io.Directory(pathlib.join(
|
||||
environment.engineSrcDir.path,
|
||||
'third_party',
|
||||
'skia',
|
||||
'resources',
|
||||
'images',
|
||||
));
|
||||
|
||||
for (final io.File imageFile in testImagesDir.listSync(recursive: true).whereType<io.File>()) {
|
||||
final io.File destination = io.File(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'test_images',
|
||||
pathlib.relative(imageFile.path, from: testImagesDir.path),
|
||||
));
|
||||
destination.createSync(recursive: true);
|
||||
await imageFile.copy(destination.path);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> copyDart2WasmTestScript() async {
|
||||
final io.File sourceFile = io.File(pathlib.join(
|
||||
environment.webUiDevDir.path,
|
||||
'test_dart2wasm.js',
|
||||
));
|
||||
final io.File targetFile = io.File(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'test_dart2wasm.js',
|
||||
));
|
||||
await sourceFile.copy(targetFile.path);
|
||||
}
|
||||
|
||||
Future<void> copySkwasm() async {
|
||||
final io.Directory targetDir = io.Directory(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'skwasm',
|
||||
));
|
||||
|
||||
await targetDir.create(recursive: true);
|
||||
|
||||
for (final String fileName in <String>[
|
||||
'skwasm.wasm',
|
||||
'skwasm.js',
|
||||
'skwasm.worker.js',
|
||||
]) {
|
||||
final io.File sourceFile = io.File(pathlib.join(
|
||||
environment.wasmReleaseOutDir.path,
|
||||
fileName,
|
||||
));
|
||||
final io.File targetFile = io.File(pathlib.join(
|
||||
targetDir.path,
|
||||
fileName,
|
||||
));
|
||||
await sourceFile.copy(targetFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
final io.Directory _localCanvasKitDir = io.Directory(pathlib.join(
|
||||
environment.wasmReleaseOutDir.path,
|
||||
'canvaskit',
|
||||
));
|
||||
final io.File _localCanvasKitWasm = io.File(pathlib.join(
|
||||
_localCanvasKitDir.path,
|
||||
'canvaskit.wasm',
|
||||
));
|
||||
|
||||
Future<void> copyCanvasKitFiles({bool useLocalCanvasKit = false}) async {
|
||||
// If CanvasKit has been built locally, use that instead of the CIPD version.
|
||||
final bool localCanvasKitExists = _localCanvasKitWasm.existsSync();
|
||||
if (useLocalCanvasKit && !localCanvasKitExists) {
|
||||
throw ArgumentError('Requested to use local CanvasKit but could not find the '
|
||||
'built CanvasKit at ${_localCanvasKitWasm.path}. Falling back to '
|
||||
'CanvasKit from CIPD.');
|
||||
}
|
||||
|
||||
final io.Directory targetDir = io.Directory(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'canvaskit',
|
||||
));
|
||||
|
||||
if (useLocalCanvasKit) {
|
||||
final Iterable<io.File> canvasKitFiles =
|
||||
_localCanvasKitDir.listSync(recursive: true).whereType<io.File>();
|
||||
for (final io.File file in canvasKitFiles) {
|
||||
if (!file.path.endsWith('.wasm') && !file.path.endsWith('.js')) {
|
||||
// We only need the .wasm and .js files.
|
||||
continue;
|
||||
}
|
||||
final String relativePath =
|
||||
pathlib.relative(file.path, from: _localCanvasKitDir.path);
|
||||
final io.File normalTargetFile =
|
||||
io.File(pathlib.join(targetDir.path, relativePath));
|
||||
await normalTargetFile.create(recursive: true);
|
||||
await file.copy(normalTargetFile.path);
|
||||
}
|
||||
} else {
|
||||
final io.Directory canvasKitDir = io.Directory(pathlib.join(
|
||||
environment.engineSrcDir.path,
|
||||
'third_party',
|
||||
'web_dependencies',
|
||||
'canvaskit',
|
||||
));
|
||||
|
||||
final Iterable<io.File> canvasKitFiles = canvasKitDir
|
||||
.listSync(recursive: true)
|
||||
.whereType<io.File>();
|
||||
|
||||
for (final io.File file in canvasKitFiles) {
|
||||
final String relativePath =
|
||||
pathlib.relative(file.path, from: canvasKitDir.path);
|
||||
final io.File targetFile = io.File(pathlib.join(
|
||||
targetDir.path,
|
||||
relativePath,
|
||||
));
|
||||
await targetFile.create(recursive: true);
|
||||
await file.copy(targetFile.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiles the specified unit tests.
|
||||
Future<void> compileTests(List<FilePath> testFiles, bool isWasm) async {
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
|
||||
final TestsByRenderer sortedTests = sortTestsByRenderer(testFiles, isWasm);
|
||||
|
||||
await Future.wait(<Future<void>>[
|
||||
if (sortedTests.htmlTests.isNotEmpty)
|
||||
_compileTestsInParallel(targets: sortedTests.htmlTests, isWasm: isWasm),
|
||||
if (sortedTests.canvasKitTests.isNotEmpty)
|
||||
_compileTestsInParallel(targets: sortedTests.canvasKitTests, renderer: Renderer.canvasKit, isWasm: isWasm),
|
||||
if (sortedTests.skwasmTests.isNotEmpty)
|
||||
_compileTestsInParallel(targets: sortedTests.skwasmTests, renderer: Renderer.skwasm, isWasm: isWasm),
|
||||
]);
|
||||
|
||||
stopwatch.stop();
|
||||
|
||||
final int targetCount = sortedTests.numTargetsToCompile;
|
||||
print(
|
||||
'Built $targetCount tests in ${stopwatch.elapsedMilliseconds ~/ 1000} '
|
||||
'seconds using $_dart2jsConcurrency concurrent compile processes.',
|
||||
);
|
||||
}
|
||||
|
||||
// Maximum number of concurrent dart2js processes to use.
|
||||
int _dart2jsConcurrency = int.parse(io.Platform.environment['FELT_DART2JS_CONCURRENCY'] ?? '8');
|
||||
|
||||
final Pool _dart2jsPool = Pool(_dart2jsConcurrency);
|
||||
|
||||
/// Spawns multiple dart2js processes to compile [targets] in parallel.
|
||||
Future<void> _compileTestsInParallel({
|
||||
required List<FilePath> targets,
|
||||
Renderer renderer = Renderer.html,
|
||||
bool isWasm = false,
|
||||
}) async {
|
||||
final Stream<bool> results = _dart2jsPool.forEach(
|
||||
targets,
|
||||
(FilePath file) => compileUnitTest(file, renderer: renderer, isWasm: isWasm),
|
||||
);
|
||||
await for (final bool isSuccess in results) {
|
||||
if (!isSuccess) {
|
||||
throw ToolExit('Failed to compile tests.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> compileUnitTest(FilePath input, {required Renderer renderer, required bool isWasm}) async {
|
||||
return isWasm ? compileUnitTestToWasm(input, renderer: renderer)
|
||||
: compileUnitTestToJS(input, renderer: renderer);
|
||||
}
|
||||
|
||||
/// Compiles one unit test using `dart2js`.
|
||||
///
|
||||
/// When building for CanvasKit we have to use extra argument
|
||||
/// `DFLUTTER_WEB_USE_SKIA=true`.
|
||||
///
|
||||
/// Dart2js creates the following outputs:
|
||||
/// - target.browser_test.dart.js
|
||||
/// - target.browser_test.dart.js.deps
|
||||
/// - target.browser_test.dart.js.map
|
||||
/// under the same directory with test file. If all these files are not in
|
||||
/// the same directory, Chrome dev tools cannot load the source code during
|
||||
/// debug.
|
||||
///
|
||||
/// All the files under test already copied from /test directory to /build
|
||||
/// directory before test are build. See [_copyFilesFromTestToBuild].
|
||||
///
|
||||
/// Later the extra files will be deleted in [_cleanupExtraFilesUnderTestDir].
|
||||
Future<bool> compileUnitTestToJS(FilePath input, {required Renderer renderer}) async {
|
||||
// Compile to different directories for different renderers. This allows us
|
||||
// to run the same test in multiple renderers.
|
||||
final String targetFileName = pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
getBuildDirForRenderer(renderer),
|
||||
'${input.relativeToWebUi}.browser_test.dart.js',
|
||||
);
|
||||
|
||||
final io.Directory directoryToTarget = io.Directory(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
getBuildDirForRenderer(renderer),
|
||||
pathlib.dirname(input.relativeToWebUi)));
|
||||
|
||||
if (!directoryToTarget.existsSync()) {
|
||||
directoryToTarget.createSync(recursive: true);
|
||||
}
|
||||
|
||||
final List<String> arguments = <String>[
|
||||
'compile',
|
||||
'js',
|
||||
'--no-minify',
|
||||
'--disable-inlining',
|
||||
'--enable-asserts',
|
||||
|
||||
// We do not want to auto-select a renderer in tests. As of today, tests
|
||||
// are designed to run in one specific mode. So instead, we specify the
|
||||
// renderer explicitly.
|
||||
'-DFLUTTER_WEB_AUTO_DETECT=false',
|
||||
'-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvasKit}',
|
||||
'-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}',
|
||||
|
||||
'-O2',
|
||||
'-o',
|
||||
targetFileName, // target path.
|
||||
input.relativeToWebUi, // current path.
|
||||
];
|
||||
|
||||
final int exitCode = await runProcess(
|
||||
environment.dartExecutable,
|
||||
arguments,
|
||||
workingDirectory: environment.webUiRootDir.path,
|
||||
);
|
||||
|
||||
if (exitCode != 0) {
|
||||
io.stderr.writeln('ERROR: Failed to compile test $input. '
|
||||
'Dart2js exited with exit code $exitCode');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> compileUnitTestToWasm(FilePath input, {required Renderer renderer}) async {
|
||||
final String targetFileName = pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
getBuildDirForRenderer(renderer),
|
||||
'${input.relativeToWebUi}.browser_test.dart.wasm',
|
||||
);
|
||||
|
||||
final io.Directory directoryToTarget = io.Directory(pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
getBuildDirForRenderer(renderer),
|
||||
pathlib.dirname(input.relativeToWebUi)));
|
||||
|
||||
if (!directoryToTarget.existsSync()) {
|
||||
directoryToTarget.createSync(recursive: true);
|
||||
}
|
||||
|
||||
final List<String> arguments = <String>[
|
||||
environment.dart2wasmSnapshotPath,
|
||||
|
||||
'--dart-sdk=${environment.dartSdkDir.path}',
|
||||
'--enable-asserts',
|
||||
|
||||
// We do not want to auto-select a renderer in tests. As of today, tests
|
||||
// are designed to run in one specific mode. So instead, we specify the
|
||||
// renderer explicitly.
|
||||
'-DFLUTTER_WEB_AUTO_DETECT=false',
|
||||
'-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvasKit}',
|
||||
'-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}',
|
||||
|
||||
if (renderer == Renderer.skwasm)
|
||||
...<String>[
|
||||
'--import-shared-memory',
|
||||
'--shared-memory-max-pages=32768',
|
||||
],
|
||||
|
||||
input.relativeToWebUi, // current path.
|
||||
targetFileName, // target path.
|
||||
];
|
||||
|
||||
final int exitCode = await runProcess(
|
||||
environment.dartAotRuntimePath,
|
||||
arguments,
|
||||
workingDirectory: environment.webUiRootDir.path,
|
||||
);
|
||||
|
||||
if (exitCode != 0) {
|
||||
io.stderr.writeln('ERROR: Failed to compile test $input. '
|
||||
'dart2wasm exited with exit code $exitCode');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> buildHostPage() async {
|
||||
final String hostDartPath = pathlib.join('lib', 'static', 'host.dart');
|
||||
final io.File hostDartFile = io.File(pathlib.join(
|
||||
environment.webEngineTesterRootDir.path,
|
||||
hostDartPath,
|
||||
));
|
||||
final String targetDirectoryPath = pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'host',
|
||||
);
|
||||
io.Directory(targetDirectoryPath).createSync(recursive: true);
|
||||
final String targetFilePath = pathlib.join(
|
||||
targetDirectoryPath,
|
||||
'host.dart',
|
||||
);
|
||||
|
||||
const List<String> staticFiles = <String>[
|
||||
'favicon.ico',
|
||||
'host.css',
|
||||
'index.html',
|
||||
];
|
||||
for (final String staticFilePath in staticFiles) {
|
||||
final io.File source = io.File(pathlib.join(
|
||||
environment.webEngineTesterRootDir.path,
|
||||
'lib',
|
||||
'static',
|
||||
staticFilePath,
|
||||
));
|
||||
final io.File destination = io.File(pathlib.join(
|
||||
targetDirectoryPath,
|
||||
staticFilePath,
|
||||
));
|
||||
await source.copy(destination.path);
|
||||
}
|
||||
|
||||
final io.File timestampFile = io.File(pathlib.join(
|
||||
environment.webEngineTesterRootDir.path,
|
||||
'$targetFilePath.js.timestamp',
|
||||
));
|
||||
|
||||
final String timestamp =
|
||||
hostDartFile.statSync().modified.millisecondsSinceEpoch.toString();
|
||||
if (timestampFile.existsSync()) {
|
||||
final String lastBuildTimestamp = timestampFile.readAsStringSync();
|
||||
if (lastBuildTimestamp == timestamp) {
|
||||
// The file is still fresh. No need to rebuild.
|
||||
return;
|
||||
} else {
|
||||
// Record new timestamp, but don't return. We need to rebuild.
|
||||
print('${hostDartFile.path} timestamp changed. Rebuilding.');
|
||||
}
|
||||
} else {
|
||||
print('Building ${hostDartFile.path}.');
|
||||
}
|
||||
|
||||
int exitCode = await runProcess(
|
||||
environment.dartExecutable,
|
||||
<String>[
|
||||
'pub',
|
||||
'get',
|
||||
],
|
||||
workingDirectory: environment.webEngineTesterRootDir.path
|
||||
);
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw ToolExit(
|
||||
'Failed to run pub get for web_engine_tester, exit code $exitCode',
|
||||
exitCode: exitCode,
|
||||
);
|
||||
}
|
||||
|
||||
exitCode = await runProcess(
|
||||
environment.dartExecutable,
|
||||
<String>[
|
||||
'compile',
|
||||
'js',
|
||||
hostDartPath,
|
||||
'-o',
|
||||
'$targetFilePath.js',
|
||||
],
|
||||
workingDirectory: environment.webEngineTesterRootDir.path,
|
||||
);
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw ToolExit(
|
||||
'Failed to compile ${hostDartFile.path}. Compiler '
|
||||
'exited with exit code $exitCode',
|
||||
exitCode: exitCode,
|
||||
);
|
||||
}
|
||||
|
||||
// Record the timestamp to avoid rebuilding unless the file changes.
|
||||
timestampFile.writeAsStringSync(timestamp);
|
||||
}
|
||||
293
engine/src/flutter/lib/web_ui/dev/steps/copy_artifacts_step.dart
Normal file
293
engine/src/flutter/lib/web_ui/dev/steps/copy_artifacts_step.dart
Normal file
@@ -0,0 +1,293 @@
|
||||
// Copyright 2013 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:convert' show JsonEncoder;
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:path/path.dart' as pathlib;
|
||||
|
||||
import '../environment.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../felt_config.dart';
|
||||
import '../pipeline.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
class CopyArtifactsStep implements PipelineStep {
|
||||
CopyArtifactsStep(this.artifactDeps, { required this.isProfile });
|
||||
|
||||
final ArtifactDependencies artifactDeps;
|
||||
final bool isProfile;
|
||||
|
||||
@override
|
||||
String get description => 'copy_artifacts';
|
||||
|
||||
@override
|
||||
bool get isSafeToInterrupt => true;
|
||||
|
||||
@override
|
||||
Future<void> interrupt() async {
|
||||
await cleanup();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
await environment.webTestsArtifactsDir.create(recursive: true);
|
||||
await copyDart2WasmTestScript();
|
||||
await buildHostPage();
|
||||
await copyTestFonts();
|
||||
await copySkiaTestImages();
|
||||
if (artifactDeps.canvasKit) {
|
||||
print('Copying CanvasKit...');
|
||||
await copyCanvasKitFiles('canvaskit', 'canvaskit');
|
||||
}
|
||||
if (artifactDeps.canvasKitChromium) {
|
||||
print('Copying CanvasKit (Chromium)...');
|
||||
await copyCanvasKitFiles('canvaskit_chromium', 'canvaskit/chromium');
|
||||
}
|
||||
if (artifactDeps.skwasm) {
|
||||
print('Copying Skwasm...');
|
||||
await copySkwasm();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> copyDart2WasmTestScript() async {
|
||||
final io.File sourceFile = io.File(pathlib.join(
|
||||
environment.webUiDevDir.path,
|
||||
'test_dart2wasm.js',
|
||||
));
|
||||
final io.File targetFile = io.File(pathlib.join(
|
||||
environment.webTestsArtifactsDir.path,
|
||||
'test_dart2wasm.js',
|
||||
));
|
||||
await sourceFile.copy(targetFile.path);
|
||||
}
|
||||
|
||||
Future<void> copyTestFonts() async {
|
||||
const Map<String, String> testFonts = <String, String>{
|
||||
'Ahem': 'ahem.ttf',
|
||||
'Roboto': 'Roboto-Regular.ttf',
|
||||
'RobotoVariable': 'RobotoSlab-VariableFont_wght.ttf',
|
||||
'Noto Naskh Arabic UI': 'NotoNaskhArabic-Regular.ttf',
|
||||
'Noto Color Emoji': 'NotoColorEmoji.ttf',
|
||||
};
|
||||
|
||||
final String fontsPath = pathlib.join(
|
||||
environment.flutterDirectory.path,
|
||||
'third_party',
|
||||
'txt',
|
||||
'third_party',
|
||||
'fonts',
|
||||
);
|
||||
|
||||
final List<dynamic> fontManifest = <dynamic>[];
|
||||
for (final MapEntry<String, String> fontEntry in testFonts.entries) {
|
||||
final String family = fontEntry.key;
|
||||
final String fontFile = fontEntry.value;
|
||||
|
||||
fontManifest.add(<String, dynamic>{
|
||||
'family': family,
|
||||
'fonts': <dynamic>[
|
||||
<String, String>{
|
||||
'asset': 'fonts/$fontFile',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
final io.File sourceTtf = io.File(pathlib.join(fontsPath, fontFile));
|
||||
final io.File destinationTtf = io.File(pathlib.join(
|
||||
environment.webTestsArtifactsDir.path,
|
||||
'assets',
|
||||
'fonts',
|
||||
fontFile,
|
||||
));
|
||||
await destinationTtf.create(recursive: true);
|
||||
await sourceTtf.copy(destinationTtf.path);
|
||||
}
|
||||
|
||||
final io.File fontManifestFile = io.File(pathlib.join(
|
||||
environment.webTestsArtifactsDir.path,
|
||||
'assets',
|
||||
'FontManifest.json',
|
||||
));
|
||||
await fontManifestFile.create(recursive: true);
|
||||
await fontManifestFile.writeAsString(
|
||||
const JsonEncoder.withIndent(' ').convert(fontManifest),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> copySkiaTestImages() async {
|
||||
final io.Directory testImagesDir = io.Directory(pathlib.join(
|
||||
environment.engineSrcDir.path,
|
||||
'third_party',
|
||||
'skia',
|
||||
'resources',
|
||||
'images',
|
||||
));
|
||||
|
||||
for (final io.File imageFile in testImagesDir.listSync(recursive: true).whereType<io.File>()) {
|
||||
final io.File destination = io.File(pathlib.join(
|
||||
environment.webTestsArtifactsDir.path,
|
||||
'test_images',
|
||||
pathlib.relative(imageFile.path, from: testImagesDir.path),
|
||||
));
|
||||
destination.createSync(recursive: true);
|
||||
await imageFile.copy(destination.path);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> copyCanvasKitFiles(String sourcePath, String destinationPath) async {
|
||||
final String sourceDirectoryPath = pathlib.join(
|
||||
outBuildPath,
|
||||
sourcePath,
|
||||
);
|
||||
|
||||
final String targetDirectoryPath = pathlib.join(
|
||||
environment.webTestsArtifactsDir.path,
|
||||
destinationPath,
|
||||
);
|
||||
|
||||
for (final String filename in <String>[
|
||||
'canvaskit.js',
|
||||
'canvaskit.wasm',
|
||||
]) {
|
||||
final io.File sourceFile = io.File(pathlib.join(
|
||||
sourceDirectoryPath,
|
||||
filename,
|
||||
));
|
||||
final io.File targetFile = io.File(pathlib.join(
|
||||
targetDirectoryPath,
|
||||
filename,
|
||||
));
|
||||
if (!sourceFile.existsSync()) {
|
||||
throw ToolExit('Built CanvasKit artifact not found at path "$sourceFile".');
|
||||
}
|
||||
await targetFile.create(recursive: true);
|
||||
await sourceFile.copy(targetFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
String get outBuildPath => isProfile
|
||||
? environment.wasmProfileOutDir.path
|
||||
: environment.wasmReleaseOutDir.path;
|
||||
|
||||
Future<void> copySkwasm() async {
|
||||
final io.Directory targetDir = io.Directory(pathlib.join(
|
||||
environment.webTestsArtifactsDir.path,
|
||||
'canvaskit',
|
||||
));
|
||||
|
||||
await targetDir.create(recursive: true);
|
||||
|
||||
for (final String fileName in <String>[
|
||||
'skwasm.wasm',
|
||||
'skwasm.js',
|
||||
'skwasm.worker.js',
|
||||
]) {
|
||||
final io.File sourceFile = io.File(pathlib.join(
|
||||
outBuildPath,
|
||||
fileName,
|
||||
));
|
||||
final io.File targetFile = io.File(pathlib.join(
|
||||
targetDir.path,
|
||||
fileName,
|
||||
));
|
||||
await sourceFile.copy(targetFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> buildHostPage() async {
|
||||
final String hostDartPath = pathlib.join('lib', 'static', 'host.dart');
|
||||
final io.File hostDartFile = io.File(pathlib.join(
|
||||
environment.webEngineTesterRootDir.path,
|
||||
hostDartPath,
|
||||
));
|
||||
final String targetDirectoryPath = pathlib.join(
|
||||
environment.webTestsArtifactsDir.path,
|
||||
'host',
|
||||
);
|
||||
io.Directory(targetDirectoryPath).createSync(recursive: true);
|
||||
final String targetFilePath = pathlib.join(
|
||||
targetDirectoryPath,
|
||||
'host.dart',
|
||||
);
|
||||
|
||||
const List<String> staticFiles = <String>[
|
||||
'favicon.ico',
|
||||
'host.css',
|
||||
'index.html',
|
||||
];
|
||||
for (final String staticFilePath in staticFiles) {
|
||||
final io.File source = io.File(pathlib.join(
|
||||
environment.webEngineTesterRootDir.path,
|
||||
'lib',
|
||||
'static',
|
||||
staticFilePath,
|
||||
));
|
||||
final io.File destination = io.File(pathlib.join(
|
||||
targetDirectoryPath,
|
||||
staticFilePath,
|
||||
));
|
||||
await source.copy(destination.path);
|
||||
}
|
||||
|
||||
final io.File timestampFile = io.File(pathlib.join(
|
||||
environment.webEngineTesterRootDir.path,
|
||||
'$targetFilePath.js.timestamp',
|
||||
));
|
||||
|
||||
final String timestamp =
|
||||
hostDartFile.statSync().modified.millisecondsSinceEpoch.toString();
|
||||
if (timestampFile.existsSync()) {
|
||||
final String lastBuildTimestamp = timestampFile.readAsStringSync();
|
||||
if (lastBuildTimestamp == timestamp) {
|
||||
// The file is still fresh. No need to rebuild.
|
||||
return;
|
||||
} else {
|
||||
// Record new timestamp, but don't return. We need to rebuild.
|
||||
print('${hostDartFile.path} timestamp changed. Rebuilding.');
|
||||
}
|
||||
} else {
|
||||
print('Building ${hostDartFile.path}.');
|
||||
}
|
||||
|
||||
int exitCode = await runProcess(
|
||||
environment.dartExecutable,
|
||||
<String>[
|
||||
'pub',
|
||||
'get',
|
||||
],
|
||||
workingDirectory: environment.webEngineTesterRootDir.path
|
||||
);
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw ToolExit(
|
||||
'Failed to run pub get for web_engine_tester, exit code $exitCode',
|
||||
exitCode: exitCode,
|
||||
);
|
||||
}
|
||||
|
||||
exitCode = await runProcess(
|
||||
environment.dartExecutable,
|
||||
<String>[
|
||||
'compile',
|
||||
'js',
|
||||
hostDartPath,
|
||||
'-o',
|
||||
'$targetFilePath.js',
|
||||
],
|
||||
workingDirectory: environment.webEngineTesterRootDir.path,
|
||||
);
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw ToolExit(
|
||||
'Failed to compile ${hostDartFile.path}. Compiler '
|
||||
'exited with exit code $exitCode',
|
||||
exitCode: exitCode,
|
||||
);
|
||||
}
|
||||
|
||||
// Record the timestamp to avoid rebuilding unless the file changes.
|
||||
timestampFile.writeAsStringSync(timestamp);
|
||||
}
|
||||
}
|
||||
216
engine/src/flutter/lib/web_ui/dev/steps/run_suite_step.dart
Normal file
216
engine/src/flutter/lib/web_ui/dev/steps/run_suite_step.dart
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2013 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:convert';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:path/path.dart' as pathlib;
|
||||
// TODO(yjbanov): remove hacks when this is fixed:
|
||||
// https://github.com/dart-lang/test/issues/1521
|
||||
import 'package:skia_gold_client/skia_gold_client.dart';
|
||||
import 'package:test_api/src/backend/runtime.dart' as hack;
|
||||
import 'package:test_core/src/executable.dart' as test;
|
||||
import 'package:test_core/src/runner/hack_register_platform.dart' as hack;
|
||||
|
||||
import '../browser.dart';
|
||||
import '../common.dart';
|
||||
import '../environment.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../felt_config.dart';
|
||||
import '../pipeline.dart';
|
||||
import '../test_platform.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
/// Runs a test suite.
|
||||
///
|
||||
/// Assumes the artifacts from previous steps are available, either from
|
||||
/// running them prior to this step locally, or by having the build graph copy
|
||||
/// them from another bot.
|
||||
class RunSuiteStep implements PipelineStep {
|
||||
RunSuiteStep(this.suite, {
|
||||
required this.isDebug,
|
||||
required this.isVerbose,
|
||||
required this.doUpdateScreenshotGoldens,
|
||||
required this.requireSkiaGold,
|
||||
this.testFiles,
|
||||
required this.overridePathToCanvasKit,
|
||||
});
|
||||
|
||||
final TestSuite suite;
|
||||
final Set<FilePath>? testFiles;
|
||||
final bool isDebug;
|
||||
final bool isVerbose;
|
||||
final bool doUpdateScreenshotGoldens;
|
||||
final String? overridePathToCanvasKit;
|
||||
|
||||
/// Require Skia Gold to be available and reachable.
|
||||
final bool requireSkiaGold;
|
||||
|
||||
bool get isWasm => suite.testBundle.compileConfig.compiler == Compiler.dart2wasm;
|
||||
|
||||
@override
|
||||
String get description => 'run_suite';
|
||||
|
||||
@override
|
||||
bool get isSafeToInterrupt => true;
|
||||
|
||||
@override
|
||||
Future<void> interrupt() async {}
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
_prepareTestResultsDirectory();
|
||||
final BrowserEnvironment browserEnvironment = getBrowserEnvironment(
|
||||
suite.runConfig.browser,
|
||||
enableWasmGC: isWasm);
|
||||
await browserEnvironment.prepare();
|
||||
|
||||
final SkiaGoldClient? skiaClient = await _createSkiaClient();
|
||||
final String configurationFilePath = pathlib.join(
|
||||
environment.webUiRootDir.path,
|
||||
browserEnvironment.packageTestConfigurationYamlFile,
|
||||
);
|
||||
final String bundleBuildPath = getBundleBuildDirectory(suite.testBundle).path;
|
||||
final List<String> testArgs = <String>[
|
||||
...<String>['-r', 'compact'],
|
||||
// Disable concurrency. Running with concurrency proved to be flaky.
|
||||
'--concurrency=1',
|
||||
if (isDebug) '--pause-after-load',
|
||||
'--platform=${browserEnvironment.packageTestRuntime.identifier}',
|
||||
'--precompiled=$bundleBuildPath',
|
||||
'--configuration=$configurationFilePath',
|
||||
'--',
|
||||
..._collectTestPaths(),
|
||||
];
|
||||
|
||||
hack.registerPlatformPlugin(<hack.Runtime>[
|
||||
browserEnvironment.packageTestRuntime,
|
||||
], () {
|
||||
return BrowserPlatform.start(
|
||||
suite,
|
||||
browserEnvironment: browserEnvironment,
|
||||
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
|
||||
skiaClient: skiaClient,
|
||||
overridePathToCanvasKit: overridePathToCanvasKit,
|
||||
isWasm: isWasm,
|
||||
isVerbose: isVerbose,
|
||||
);
|
||||
});
|
||||
|
||||
print('[${suite.name.ansiCyan}] Running...');
|
||||
|
||||
// We want to run tests with the test set's directory as a working directory.
|
||||
final io.Directory testSetDirectory = io.Directory(pathlib.join(
|
||||
environment.webUiTestDir.path,
|
||||
suite.testBundle.testSet.directory,
|
||||
));
|
||||
final dynamic originalCwd = io.Directory.current;
|
||||
io.Directory.current = testSetDirectory;
|
||||
try {
|
||||
await test.main(testArgs);
|
||||
} finally {
|
||||
io.Directory.current = originalCwd;
|
||||
}
|
||||
|
||||
await browserEnvironment.cleanup();
|
||||
|
||||
if (io.exitCode != 0) {
|
||||
print('[${suite.name.ansiCyan}] ${'Some tests failed.'.ansiRed}');
|
||||
io.exitCode = 0;
|
||||
} else {
|
||||
print('[${suite.name.ansiCyan}] ${'All tests passed!'.ansiGreen}');
|
||||
}
|
||||
}
|
||||
|
||||
io.Directory _prepareTestResultsDirectory() {
|
||||
final io.Directory resultsDirectory = io.Directory(pathlib.join(
|
||||
environment.webUiTestResultsDirectory.path,
|
||||
suite.name,
|
||||
));
|
||||
if (resultsDirectory.existsSync()) {
|
||||
resultsDirectory.deleteSync(recursive: true);
|
||||
}
|
||||
resultsDirectory.createSync(recursive: true);
|
||||
return resultsDirectory;
|
||||
}
|
||||
|
||||
List<String> _collectTestPaths() {
|
||||
final io.Directory bundleBuild = getBundleBuildDirectory(suite.testBundle);
|
||||
final io.File resultsJsonFile = io.File(pathlib.join(
|
||||
bundleBuild.path,
|
||||
'results.json',
|
||||
));
|
||||
if (!resultsJsonFile.existsSync()) {
|
||||
throw ToolExit('Could not find built bundle ${suite.testBundle.name.ansiMagenta} for suite ${suite.name.ansiCyan}.');
|
||||
}
|
||||
final String jsonString = resultsJsonFile.readAsStringSync();
|
||||
final dynamic jsonContents = const JsonDecoder().convert(jsonString);
|
||||
final dynamic results = jsonContents['results'];
|
||||
final List<String> testPaths = <String>[];
|
||||
results.forEach((dynamic k, dynamic v) {
|
||||
final String result = v as String;
|
||||
final String testPath = k as String;
|
||||
if (testFiles != null) {
|
||||
if (!testFiles!.contains(FilePath.fromTestSet(suite.testBundle.testSet, testPath))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (result == 'success') {
|
||||
testPaths.add(testPath);
|
||||
}
|
||||
});
|
||||
return testPaths;
|
||||
}
|
||||
|
||||
Future<SkiaGoldClient?> _createSkiaClient() async {
|
||||
final Renderer renderer = suite.testBundle.compileConfig.renderer;
|
||||
final CanvasKitVariant? variant = suite.runConfig.variant;
|
||||
final SkiaGoldClient skiaClient = SkiaGoldClient(
|
||||
environment.webUiSkiaGoldDirectory,
|
||||
dimensions: <String, String> {
|
||||
'Browser': suite.runConfig.browser.name,
|
||||
if (isWasm) 'Wasm': 'true',
|
||||
'Renderer': renderer.name,
|
||||
if (variant != null) 'CanvasKitVariant': variant.name,
|
||||
},
|
||||
);
|
||||
|
||||
if (await _checkSkiaClient(skiaClient)) {
|
||||
return skiaClient;
|
||||
}
|
||||
|
||||
if (requireSkiaGold) {
|
||||
throw ToolExit('Skia Gold is required but is unavailable.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Checks whether the Skia Client is usable in this environment.
|
||||
Future<bool> _checkSkiaClient(SkiaGoldClient skiaClient) async {
|
||||
// Now let's check whether Skia Gold is reachable or not.
|
||||
if (isLuci) {
|
||||
if (isSkiaGoldClientAvailable) {
|
||||
try {
|
||||
await skiaClient.auth();
|
||||
return true;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// Check if we can reach Gold.
|
||||
await skiaClient.getExpectationForTest('');
|
||||
return true;
|
||||
} on io.OSError catch (_) {
|
||||
print('OSError occurred, could not reach Gold.');
|
||||
} on io.SocketException catch (_) {
|
||||
print('SocketException occurred, could not reach Gold.');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
// Copyright 2013 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:path/path.dart' as pathlib;
|
||||
// TODO(yjbanov): remove hacks when this is fixed:
|
||||
// https://github.com/dart-lang/test/issues/1521
|
||||
import 'package:skia_gold_client/skia_gold_client.dart';
|
||||
import 'package:test_api/src/backend/group.dart' as hack;
|
||||
import 'package:test_api/src/backend/live_test.dart' as hack;
|
||||
import 'package:test_api/src/backend/runtime.dart' as hack;
|
||||
import 'package:test_core/src/executable.dart' as test;
|
||||
import 'package:test_core/src/runner/configuration/reporters.dart' as hack;
|
||||
import 'package:test_core/src/runner/engine.dart' as hack;
|
||||
import 'package:test_core/src/runner/hack_register_platform.dart' as hack;
|
||||
import 'package:test_core/src/runner/reporter.dart' as hack;
|
||||
|
||||
import '../browser.dart';
|
||||
import '../common.dart';
|
||||
import '../environment.dart';
|
||||
import '../exceptions.dart';
|
||||
import '../pipeline.dart';
|
||||
import '../test_platform.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
/// Runs web tests.
|
||||
///
|
||||
/// Assumes the artifacts from [CompileTestsStep] are available, either from
|
||||
/// running it prior to this step locally, or by having the build graph copy
|
||||
/// them from another bot.
|
||||
class RunTestsStep implements PipelineStep {
|
||||
RunTestsStep({
|
||||
required this.browserName,
|
||||
required this.isDebug,
|
||||
required this.doUpdateScreenshotGoldens,
|
||||
required this.requireSkiaGold,
|
||||
this.testFiles,
|
||||
required this.overridePathToCanvasKit,
|
||||
required this.isWasm
|
||||
});
|
||||
|
||||
final String browserName;
|
||||
final List<FilePath>? testFiles;
|
||||
final bool isDebug;
|
||||
final bool isWasm;
|
||||
final bool doUpdateScreenshotGoldens;
|
||||
final String? overridePathToCanvasKit;
|
||||
|
||||
/// Require Skia Gold to be available and reachable.
|
||||
final bool requireSkiaGold;
|
||||
|
||||
@override
|
||||
String get description => 'run_tests';
|
||||
|
||||
@override
|
||||
bool get isSafeToInterrupt => true;
|
||||
|
||||
@override
|
||||
Future<void> interrupt() async {}
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
await _prepareTestResultsDirectory();
|
||||
|
||||
final BrowserEnvironment browserEnvironment = getBrowserEnvironment(browserName, enableWasmGC: isWasm);
|
||||
await browserEnvironment.prepare();
|
||||
|
||||
final SkiaGoldClient? skiaClient = await _createSkiaClient();
|
||||
final List<FilePath> testFiles = this.testFiles ?? findAllTests();
|
||||
|
||||
final TestsByRenderer sortedTests = sortTestsByRenderer(testFiles, isWasm);
|
||||
|
||||
bool testsPassed = true;
|
||||
|
||||
if (sortedTests.htmlTests.isNotEmpty) {
|
||||
await _runTestBatch(
|
||||
testFiles: sortedTests.htmlTests,
|
||||
renderer: Renderer.html,
|
||||
browserEnvironment: browserEnvironment,
|
||||
expectFailure: false,
|
||||
isDebug: isDebug,
|
||||
isWasm: isWasm,
|
||||
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
|
||||
skiaClient: skiaClient,
|
||||
overridePathToCanvasKit: overridePathToCanvasKit,
|
||||
);
|
||||
testsPassed &= io.exitCode == 0;
|
||||
}
|
||||
|
||||
if (sortedTests.canvasKitTests.isNotEmpty) {
|
||||
await _runTestBatch(
|
||||
testFiles: sortedTests.canvasKitTests,
|
||||
renderer: Renderer.canvasKit,
|
||||
browserEnvironment: browserEnvironment,
|
||||
expectFailure: false,
|
||||
isDebug: isDebug,
|
||||
isWasm: isWasm,
|
||||
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
|
||||
skiaClient: skiaClient,
|
||||
overridePathToCanvasKit: overridePathToCanvasKit,
|
||||
);
|
||||
testsPassed &= io.exitCode == 0;
|
||||
}
|
||||
|
||||
// TODO(jacksongardner): enable this test suite on safari
|
||||
// For some reason, Safari is flaky when running the Skwasm test suite
|
||||
// See https://github.com/flutter/flutter/issues/115312
|
||||
if (browserName != kSafari && sortedTests.skwasmTests.isNotEmpty) {
|
||||
await _runTestBatch(
|
||||
testFiles: sortedTests.skwasmTests,
|
||||
renderer: Renderer.skwasm,
|
||||
browserEnvironment: browserEnvironment,
|
||||
expectFailure: false,
|
||||
isDebug: isDebug,
|
||||
isWasm: isWasm,
|
||||
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
|
||||
skiaClient: skiaClient,
|
||||
overridePathToCanvasKit: overridePathToCanvasKit,
|
||||
);
|
||||
testsPassed &= io.exitCode == 0;
|
||||
}
|
||||
|
||||
await browserEnvironment.cleanup();
|
||||
|
||||
if (!testsPassed) {
|
||||
throw ToolExit('Some tests failed');
|
||||
}
|
||||
}
|
||||
|
||||
Future<SkiaGoldClient?> _createSkiaClient() async {
|
||||
final SkiaGoldClient skiaClient = SkiaGoldClient(
|
||||
environment.webUiSkiaGoldDirectory,
|
||||
dimensions: <String, String> {
|
||||
'Browser': browserName,
|
||||
if (isWasm) 'Wasm': 'true',
|
||||
},
|
||||
);
|
||||
|
||||
if (await _checkSkiaClient(skiaClient)) {
|
||||
return skiaClient;
|
||||
}
|
||||
|
||||
if (requireSkiaGold) {
|
||||
throw ToolExit('Skia Gold is required but is unavailable.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Checks whether the Skia Client is usable in this environment.
|
||||
Future<bool> _checkSkiaClient(SkiaGoldClient skiaClient) async {
|
||||
// Now let's check whether Skia Gold is reachable or not.
|
||||
if (isLuci) {
|
||||
if (isSkiaGoldClientAvailable) {
|
||||
try {
|
||||
await skiaClient.auth();
|
||||
return true;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// Check if we can reach Gold.
|
||||
await skiaClient.getExpectationForTest('');
|
||||
return true;
|
||||
} on io.OSError catch (_) {
|
||||
print('OSError occurred, could not reach Gold.');
|
||||
} on io.SocketException catch (_) {
|
||||
print('SocketException occurred, could not reach Gold.');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _prepareTestResultsDirectory() async {
|
||||
if (environment.webUiTestResultsDirectory.existsSync()) {
|
||||
environment.webUiTestResultsDirectory.deleteSync(recursive: true);
|
||||
}
|
||||
environment.webUiTestResultsDirectory.createSync(recursive: true);
|
||||
}
|
||||
|
||||
/// Runs a batch of tests.
|
||||
///
|
||||
/// Unless [expectFailure] is set to false, sets [io.exitCode] to a non-zero
|
||||
/// value if any tests fail.
|
||||
Future<void> _runTestBatch({
|
||||
required List<FilePath> testFiles,
|
||||
required Renderer renderer,
|
||||
required bool isDebug,
|
||||
required bool isWasm,
|
||||
required BrowserEnvironment browserEnvironment,
|
||||
required bool doUpdateScreenshotGoldens,
|
||||
required bool expectFailure,
|
||||
required SkiaGoldClient? skiaClient,
|
||||
required String? overridePathToCanvasKit,
|
||||
}) async {
|
||||
final String configurationFilePath = pathlib.join(
|
||||
environment.webUiRootDir.path,
|
||||
browserEnvironment.packageTestConfigurationYamlFile,
|
||||
);
|
||||
final String precompiledBuildDir = pathlib.join(
|
||||
environment.webUiBuildDir.path,
|
||||
getBuildDirForRenderer(renderer),
|
||||
);
|
||||
final List<String> testArgs = <String>[
|
||||
...<String>['-r', 'compact'],
|
||||
// Disable concurrency. Running with concurrency proved to be flaky.
|
||||
'--concurrency=1',
|
||||
if (isDebug) '--pause-after-load',
|
||||
// Don't pollute logs with output from tests that are expected to fail.
|
||||
if (expectFailure)
|
||||
'--reporter=name-only',
|
||||
'--platform=${browserEnvironment.packageTestRuntime.identifier}',
|
||||
'--precompiled=$precompiledBuildDir',
|
||||
'--configuration=$configurationFilePath',
|
||||
'--',
|
||||
...testFiles.map((FilePath f) => f.relativeToWebUi),
|
||||
];
|
||||
|
||||
if (expectFailure) {
|
||||
hack.registerReporter(
|
||||
'name-only',
|
||||
hack.ReporterDetails(
|
||||
'Prints the name of the test, but suppresses all other test output.',
|
||||
(_, hack.Engine engine, __) => NameOnlyReporter(engine)),
|
||||
);
|
||||
}
|
||||
|
||||
hack.registerPlatformPlugin(<hack.Runtime>[
|
||||
browserEnvironment.packageTestRuntime,
|
||||
], () {
|
||||
return BrowserPlatform.start(
|
||||
browserEnvironment: browserEnvironment,
|
||||
renderer: renderer,
|
||||
// It doesn't make sense to update a screenshot for a test that is
|
||||
// expected to fail.
|
||||
doUpdateScreenshotGoldens: !expectFailure && doUpdateScreenshotGoldens,
|
||||
skiaClient: skiaClient,
|
||||
overridePathToCanvasKit: overridePathToCanvasKit,
|
||||
isWasm: isWasm,
|
||||
);
|
||||
});
|
||||
|
||||
// We want to run tests with `web_ui` as a working directory.
|
||||
final dynamic originalCwd = io.Directory.current;
|
||||
io.Directory.current = environment.webUiRootDir.path;
|
||||
try {
|
||||
await test.main(testArgs);
|
||||
} finally {
|
||||
io.Directory.current = originalCwd;
|
||||
}
|
||||
|
||||
if (expectFailure) {
|
||||
if (io.exitCode != 0) {
|
||||
// It failed, as expected.
|
||||
print('Test successfully failed, as expected.');
|
||||
io.exitCode = 0;
|
||||
} else {
|
||||
io.stderr.writeln(
|
||||
'Tests ${testFiles.join(', ')} did not fail. Expected failure.',
|
||||
);
|
||||
io.exitCode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the name of the test, but suppresses all other test output.
|
||||
///
|
||||
/// This is useful to prevent pollution of logs by tests that are expected to
|
||||
/// fail.
|
||||
class NameOnlyReporter implements hack.Reporter {
|
||||
NameOnlyReporter(hack.Engine testEngine) {
|
||||
testEngine.onTestStarted.listen(_printTestName);
|
||||
}
|
||||
|
||||
void _printTestName(hack.LiveTest test) {
|
||||
print('Running ${test.groups.map((hack.Group group) => group.name).join(' ')} ${test.individualName}');
|
||||
}
|
||||
|
||||
@override
|
||||
void pause() {}
|
||||
|
||||
@override
|
||||
void resume() {}
|
||||
}
|
||||
123
engine/src/flutter/lib/web_ui/dev/suite_filter.dart
Normal file
123
engine/src/flutter/lib/web_ui/dev/suite_filter.dart
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2013 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 'felt_config.dart';
|
||||
|
||||
class SuiteFilterResult {
|
||||
SuiteFilterResult.accepted();
|
||||
SuiteFilterResult.rejected(String reason) : rejectReason = reason;
|
||||
|
||||
String? rejectReason;
|
||||
|
||||
bool get isAccepted => rejectReason == null;
|
||||
}
|
||||
|
||||
abstract class SuiteFilter {
|
||||
SuiteFilterResult filterSuite(TestSuite suite);
|
||||
}
|
||||
|
||||
abstract class AllowListSuiteFilter<T> implements SuiteFilter {
|
||||
AllowListSuiteFilter({ required this.allowList });
|
||||
|
||||
final Set<T> allowList;
|
||||
|
||||
T getAttributeForSuite(TestSuite suite);
|
||||
|
||||
String rejectReason(TestSuite suite) {
|
||||
return '${getAttributeForSuite(suite)} does not match filter.';
|
||||
}
|
||||
|
||||
@override
|
||||
SuiteFilterResult filterSuite(TestSuite suite) {
|
||||
if (allowList.contains(getAttributeForSuite(suite))) {
|
||||
return SuiteFilterResult.accepted();
|
||||
} else {
|
||||
return SuiteFilterResult.rejected(rejectReason(suite));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserSuiteFilter extends AllowListSuiteFilter<BrowserName> {
|
||||
BrowserSuiteFilter({required super.allowList});
|
||||
|
||||
@override
|
||||
BrowserName getAttributeForSuite(TestSuite suite) => suite.runConfig.browser;
|
||||
}
|
||||
|
||||
class SuiteNameFilter extends AllowListSuiteFilter<String> {
|
||||
SuiteNameFilter({required super.allowList});
|
||||
|
||||
@override
|
||||
String getAttributeForSuite(TestSuite suite) => suite.name;
|
||||
}
|
||||
|
||||
class BundleNameFilter extends AllowListSuiteFilter<String> {
|
||||
BundleNameFilter({required super.allowList});
|
||||
|
||||
@override
|
||||
String getAttributeForSuite(TestSuite suite) => suite.testBundle.name;
|
||||
}
|
||||
|
||||
class FileFilter extends BundleNameFilter {
|
||||
FileFilter({required super.allowList});
|
||||
|
||||
@override
|
||||
String rejectReason(TestSuite suite) {
|
||||
return "Doesn't contain any of the indicated files.";
|
||||
}
|
||||
}
|
||||
|
||||
class CompilerFilter extends AllowListSuiteFilter<Compiler> {
|
||||
CompilerFilter({required super.allowList});
|
||||
|
||||
@override
|
||||
Compiler getAttributeForSuite(TestSuite suite) => suite.testBundle.compileConfig.compiler;
|
||||
}
|
||||
|
||||
class RendererFilter extends AllowListSuiteFilter<Renderer> {
|
||||
RendererFilter({required super.allowList});
|
||||
|
||||
@override
|
||||
Renderer getAttributeForSuite(TestSuite suite) => suite.testBundle.compileConfig.renderer;
|
||||
}
|
||||
|
||||
class CanvasKitVariantFilter extends AllowListSuiteFilter<CanvasKitVariant> {
|
||||
CanvasKitVariantFilter({required super.allowList});
|
||||
|
||||
@override
|
||||
// TODO(jackson): Is this the right default?
|
||||
CanvasKitVariant getAttributeForSuite(TestSuite suite) => suite.runConfig.variant ?? CanvasKitVariant.full;
|
||||
}
|
||||
|
||||
Set<BrowserName> get _supportedPlatformBrowsers {
|
||||
if (io.Platform.isLinux) {
|
||||
return <BrowserName>{
|
||||
BrowserName.chrome,
|
||||
BrowserName.firefox
|
||||
};
|
||||
} else if (io.Platform.isMacOS) {
|
||||
return <BrowserName>{
|
||||
BrowserName.chrome,
|
||||
BrowserName.firefox,
|
||||
BrowserName.safari,
|
||||
};
|
||||
} else if (io.Platform.isWindows) {
|
||||
return <BrowserName>{
|
||||
BrowserName.chrome,
|
||||
BrowserName.edge,
|
||||
};
|
||||
} else {
|
||||
throw AssertionError('Unsupported OS: ${io.Platform.operatingSystem}');
|
||||
}
|
||||
}
|
||||
|
||||
class PlatformBrowserFilter extends BrowserSuiteFilter {
|
||||
PlatformBrowserFilter() : super(allowList: _supportedPlatformBrowsers);
|
||||
|
||||
@override
|
||||
String rejectReason(TestSuite suite) =>
|
||||
'Current platform (${io.Platform.operatingSystem}) does not support browser ${suite.runConfig.browser}';
|
||||
}
|
||||
@@ -57,7 +57,7 @@ window.onload = async function () {
|
||||
const isSkwasm = link.hasAttribute('skwasm');
|
||||
const imports = isSkwasm ? new Promise((resolve) => {
|
||||
const skwasmScript = document.createElement('script');
|
||||
skwasmScript.src = '/skwasm/skwasm.js';
|
||||
skwasmScript.src = '/canvaskit/skwasm.js';
|
||||
|
||||
document.body.appendChild(skwasmScript);
|
||||
skwasmScript.addEventListener('load', async () => {
|
||||
|
||||
@@ -38,6 +38,7 @@ import 'package:web_test_utils/image_compare.dart';
|
||||
|
||||
import 'browser.dart';
|
||||
import 'environment.dart' as env;
|
||||
import 'felt_config.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
const Map<String, String> coopCoepHeaders = <String, String>{
|
||||
@@ -47,12 +48,11 @@ const Map<String, String> coopCoepHeaders = <String, String>{
|
||||
|
||||
/// Custom test platform that serves web engine unit tests.
|
||||
class BrowserPlatform extends PlatformPlugin {
|
||||
BrowserPlatform._({
|
||||
BrowserPlatform._(this.suite, {
|
||||
required this.browserEnvironment,
|
||||
required this.server,
|
||||
required this.renderer,
|
||||
required this.isDebug,
|
||||
required this.isWasm,
|
||||
required this.isVerbose,
|
||||
required this.doUpdateScreenshotGoldens,
|
||||
required this.packageConfig,
|
||||
required this.skiaClient,
|
||||
@@ -74,8 +74,14 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
.add(_packageUrlHandler)
|
||||
.add(_canvasKitOverrideHandler)
|
||||
|
||||
// Serves files from the out/web_tests/ directory at the root (/) URL path.
|
||||
.add(buildDirectoryHandler)
|
||||
// Serves files from the bundle's output build directory
|
||||
.add(createSimpleDirectoryHandler(getBundleBuildDirectory(suite.testBundle)))
|
||||
|
||||
// Serves files from the out/web_tests/artifacts directory at the root (/) URL path.
|
||||
.add(createSimpleDirectoryHandler(env.environment.webTestsArtifactsDir))
|
||||
|
||||
// Serves files from thes test set directory
|
||||
.add(createSimpleDirectoryHandler(getTestSetDirectory(suite.testBundle.testSet)))
|
||||
.add(_testImageListingHandler)
|
||||
|
||||
// Serves the initial HTML for the test.
|
||||
@@ -112,22 +118,22 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
///
|
||||
/// If [doUpdateScreenshotGoldens] is true updates screenshot golden files
|
||||
/// instead of failing the test on screenshot mismatches.
|
||||
static Future<BrowserPlatform> start({
|
||||
static Future<BrowserPlatform> start(TestSuite suite, {
|
||||
required BrowserEnvironment browserEnvironment,
|
||||
required Renderer renderer,
|
||||
required bool doUpdateScreenshotGoldens,
|
||||
required SkiaGoldClient? skiaClient,
|
||||
required String? overridePathToCanvasKit,
|
||||
required bool isWasm,
|
||||
required bool isVerbose,
|
||||
}) async {
|
||||
final shelf_io.IOServer server =
|
||||
shelf_io.IOServer(await HttpMultiServer.loopback(0));
|
||||
return BrowserPlatform._(
|
||||
suite,
|
||||
browserEnvironment: browserEnvironment,
|
||||
renderer: renderer,
|
||||
server: server,
|
||||
isDebug: Configuration.current.pauseAfterLoad,
|
||||
isWasm: isWasm,
|
||||
isVerbose: isVerbose,
|
||||
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
|
||||
packageConfig: await loadPackageConfigUri((await Isolate.packageConfig)!),
|
||||
skiaClient: skiaClient,
|
||||
@@ -135,12 +141,14 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
);
|
||||
}
|
||||
|
||||
final TestSuite suite;
|
||||
|
||||
/// If true, runs the browser with a visible windows (i.e. not headless) and
|
||||
/// pauses before running the tests to give the developer a chance to set
|
||||
/// breakpoints in the code.
|
||||
final bool isDebug;
|
||||
|
||||
final bool isWasm;
|
||||
final bool isVerbose;
|
||||
|
||||
/// The underlying server.
|
||||
final shelf.Server server;
|
||||
@@ -148,12 +156,12 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
/// Provides the environment for the browser running tests.
|
||||
final BrowserEnvironment browserEnvironment;
|
||||
|
||||
/// The renderer that tests are running under.
|
||||
final Renderer renderer;
|
||||
|
||||
/// The URL for this server.
|
||||
Uri get url => server.url.resolve('/');
|
||||
|
||||
bool get isWasm => suite.testBundle.compileConfig.compiler == Compiler.dart2wasm;
|
||||
bool get needsCrossOriginIsolated => isWasm && suite.testBundle.compileConfig.renderer == Renderer.skwasm;
|
||||
|
||||
/// A [OneOffHandler] for servicing WebSocket connections for
|
||||
/// [BrowserManager]s.
|
||||
///
|
||||
@@ -230,7 +238,7 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
}
|
||||
|
||||
final Directory testImageDirectory = Directory(p.join(
|
||||
env.environment.webUiBuildDir.path,
|
||||
env.environment.webTestsArtifactsDir.path,
|
||||
'test_images',
|
||||
));
|
||||
|
||||
@@ -252,7 +260,9 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
}
|
||||
|
||||
Future<shelf.Response> _fileNotFoundCatcher(shelf.Request request) async {
|
||||
print('HTTP 404: ${request.url}');
|
||||
if (isVerbose) {
|
||||
print('HTTP 404: ${request.url}');
|
||||
}
|
||||
return shelf.Response.notFound('File not found');
|
||||
}
|
||||
|
||||
@@ -383,10 +393,12 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
final String filename = requestData['filename'] as String;
|
||||
|
||||
if (!(await browserManager).supportsScreenshots) {
|
||||
print(
|
||||
'Skipping screenshot check for $filename. Current browser/OS '
|
||||
'combination does not support screenshots.',
|
||||
);
|
||||
if (isVerbose) {
|
||||
print(
|
||||
'Skipping screenshot check for $filename. Current browser/OS '
|
||||
'combination does not support screenshots.',
|
||||
);
|
||||
}
|
||||
return shelf.Response.ok(json.encode('OK'));
|
||||
}
|
||||
|
||||
@@ -419,6 +431,7 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
filename,
|
||||
skiaClient,
|
||||
isCanvaskitTest: isCanvaskitTest,
|
||||
verbose: isVerbose,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -443,52 +456,55 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
'.woff2': 'font/woff2',
|
||||
};
|
||||
|
||||
/// A simple file handler that serves files whose URLs and paths are
|
||||
/// Creates a simple file handler that serves files whose URLs and paths are
|
||||
/// statically known.
|
||||
///
|
||||
/// This is used for trivial use-cases, such as `favicon.ico`, host pages, etc.
|
||||
shelf.Response buildDirectoryHandler(shelf.Request request) {
|
||||
File fileInBuild = File(p.join(
|
||||
env.environment.webUiBuildDir.path,
|
||||
getBuildDirForRenderer(renderer),
|
||||
request.url.path,
|
||||
));
|
||||
|
||||
// If we can't find the file in the renderer-specific `build` subdirectory,
|
||||
// then it may be in the top-level `build` subdirectory.
|
||||
if (!fileInBuild.existsSync()) {
|
||||
fileInBuild = File(p.join(
|
||||
env.environment.webUiBuildDir.path,
|
||||
shelf.Handler createSimpleDirectoryHandler(Directory directory) {
|
||||
return (shelf.Request request) {
|
||||
final File fileInDirectory = File(p.join(
|
||||
directory.path,
|
||||
request.url.path,
|
||||
));
|
||||
|
||||
if (!fileInDirectory.existsSync()) {
|
||||
return shelf.Response.notFound('File not found: ${request.url.path}');
|
||||
}
|
||||
|
||||
final String extension = p.extension(fileInDirectory.path);
|
||||
final String? contentType = contentTypes[extension];
|
||||
|
||||
if (contentType == null) {
|
||||
final String error =
|
||||
'Failed to determine Content-Type for "${request.url.path}".';
|
||||
stderr.writeln(error);
|
||||
return shelf.Response.internalServerError(body: error);
|
||||
}
|
||||
|
||||
final bool isScript =
|
||||
extension == '.js' ||
|
||||
extension == '.mjs' ||
|
||||
extension == '.html';
|
||||
return shelf.Response.ok(
|
||||
fileInDirectory.readAsBytesSync(),
|
||||
headers: <String, Object>{
|
||||
HttpHeaders.contentTypeHeader: contentType,
|
||||
if (isScript && needsCrossOriginIsolated)
|
||||
...coopCoepHeaders,
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
String getCanvasKitVariant() {
|
||||
switch (suite.runConfig.variant) {
|
||||
case CanvasKitVariant.full:
|
||||
return 'full';
|
||||
case CanvasKitVariant.chromium:
|
||||
return 'chromium';
|
||||
case null:
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
if (!fileInBuild.existsSync()) {
|
||||
return shelf.Response.notFound('File not found: ${request.url.path}');
|
||||
}
|
||||
|
||||
final String extension = p.extension(fileInBuild.path);
|
||||
final String? contentType = contentTypes[extension];
|
||||
|
||||
if (contentType == null) {
|
||||
final String error =
|
||||
'Failed to determine Content-Type for "${request.url.path}".';
|
||||
stderr.writeln(error);
|
||||
return shelf.Response.internalServerError(body: error);
|
||||
}
|
||||
|
||||
final bool needsCoopCoep =
|
||||
extension == '.js' ||
|
||||
extension == '.mjs' ||
|
||||
extension == '.html';
|
||||
return shelf.Response.ok(
|
||||
fileInBuild.readAsBytesSync(),
|
||||
headers: <String, Object>{
|
||||
HttpHeaders.contentTypeHeader: contentType,
|
||||
if (needsCoopCoep && isWasm && renderer == Renderer.skwasm)
|
||||
...coopCoepHeaders,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Serves the HTML file that bootstraps the test.
|
||||
@@ -498,12 +514,14 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
if (path.endsWith('.html')) {
|
||||
final String test = '${p.withoutExtension(path)}.dart';
|
||||
|
||||
final bool linkSkwasm = suite.testBundle.compileConfig.renderer == Renderer.skwasm;
|
||||
// Link to the Dart wrapper.
|
||||
final String scriptBase = htmlEscape.convert(p.basename(test));
|
||||
final String link = '<link rel="x-dart-test" href="$scriptBase"${renderer == Renderer.skwasm ? " skwasm" : ""}>';
|
||||
final String link = '<link rel="x-dart-test" href="$scriptBase"${linkSkwasm ? " skwasm" : ""}>';
|
||||
|
||||
final String testRunner = isWasm ? '/test_dart2wasm.js' : 'packages/test/dart.js';
|
||||
|
||||
|
||||
return shelf.Response.ok('''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -512,7 +530,8 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
<meta name="assetBase" content="/">
|
||||
<script>
|
||||
window.flutterConfiguration = {
|
||||
canvasKitBaseUrl: "/canvaskit/"
|
||||
canvasKitBaseUrl: "/canvaskit/",
|
||||
canvasKitVariant: "${getCanvasKitVariant()}",
|
||||
};
|
||||
</script>
|
||||
$link
|
||||
@@ -521,7 +540,7 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
</html>
|
||||
''', headers: <String, String>{
|
||||
'Content-Type': 'text/html',
|
||||
if (isWasm && renderer == Renderer.skwasm)
|
||||
if (needsCrossOriginIsolated)
|
||||
...coopCoepHeaders
|
||||
});
|
||||
}
|
||||
@@ -554,8 +573,7 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
}
|
||||
_checkNotClosed();
|
||||
|
||||
final Uri suiteUrl = url.resolveUri(p.toUri('${p.withoutExtension(
|
||||
p.relative(path, from: env.environment.webUiRootDir.path))}.html'));
|
||||
final Uri suiteUrl = url.resolveUri(p.toUri('${p.withoutExtension(path)}.html'));
|
||||
_checkNotClosed();
|
||||
|
||||
final BrowserManager? browserManager = await _startBrowserManager();
|
||||
@@ -565,10 +583,10 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
}
|
||||
_checkNotClosed();
|
||||
|
||||
final RunnerSuite suite =
|
||||
final RunnerSuite runnerSuite =
|
||||
await browserManager.load(path, suiteUrl, suiteConfig, message);
|
||||
_checkNotClosed();
|
||||
return suite;
|
||||
return runnerSuite;
|
||||
}
|
||||
|
||||
Future<BrowserManager?>? _browserManager;
|
||||
@@ -598,9 +616,8 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
url: hostUrl,
|
||||
future: completer.future,
|
||||
packageConfig: packageConfig,
|
||||
isWasm: isWasm,
|
||||
debug: isDebug,
|
||||
renderer: renderer,
|
||||
sourceMapDirectory: isWasm ? null : getBundleBuildDirectory(suite.testBundle),
|
||||
);
|
||||
|
||||
// Store null values for browsers that error out so we know not to load them
|
||||
@@ -696,7 +713,7 @@ class BrowserManager {
|
||||
/// Creates a new BrowserManager that communicates with the browser over
|
||||
/// [webSocket].
|
||||
BrowserManager._(this.packageConfig, this._browser, this._browserEnvironment,
|
||||
this._renderer, this._isWasm, WebSocketChannel webSocket) {
|
||||
this._sourceMapDirectory, WebSocketChannel webSocket) {
|
||||
// The duration should be short enough that the debugging console is open as
|
||||
// soon as the user is done setting breakpoints, but long enough that a test
|
||||
// doing a lot of synchronous work doesn't trigger a false positive.
|
||||
@@ -742,8 +759,8 @@ class BrowserManager {
|
||||
/// The browser environment for this test.
|
||||
final BrowserEnvironment _browserEnvironment;
|
||||
|
||||
/// The renderer for this test.
|
||||
final Renderer _renderer;
|
||||
/// The directory containing sourcemaps for test files
|
||||
final Directory? _sourceMapDirectory;
|
||||
|
||||
/// The channel used to communicate with the browser.
|
||||
///
|
||||
@@ -768,9 +785,6 @@ class BrowserManager {
|
||||
/// Whether the channel to the browser has closed.
|
||||
bool _closed = false;
|
||||
|
||||
/// Whether we are running tests that have been compiled to WebAssembly.
|
||||
final bool _isWasm;
|
||||
|
||||
/// The completer for [_BrowserEnvironment.displayPause].
|
||||
///
|
||||
/// This will be `null` as long as the browser isn't displaying a pause
|
||||
@@ -812,8 +826,7 @@ class BrowserManager {
|
||||
required Uri url,
|
||||
required Future<WebSocketChannel> future,
|
||||
required PackageConfig packageConfig,
|
||||
required Renderer renderer,
|
||||
required bool isWasm,
|
||||
Directory? sourceMapDirectory,
|
||||
bool debug = false,
|
||||
}) async {
|
||||
final Browser browser =
|
||||
@@ -824,8 +837,7 @@ class BrowserManager {
|
||||
future: future,
|
||||
packageConfig: packageConfig,
|
||||
browser: browser,
|
||||
renderer: renderer,
|
||||
isWasm: isWasm,
|
||||
sourceMapDirectory: sourceMapDirectory,
|
||||
debug: debug);
|
||||
}
|
||||
|
||||
@@ -835,8 +847,7 @@ class BrowserManager {
|
||||
required Future<WebSocketChannel> future,
|
||||
required PackageConfig packageConfig,
|
||||
required Browser browser,
|
||||
required Renderer renderer,
|
||||
required bool isWasm,
|
||||
Directory? sourceMapDirectory,
|
||||
bool debug = false,
|
||||
}) {
|
||||
final Completer<BrowserManager> completer = Completer<BrowserManager>();
|
||||
@@ -858,7 +869,7 @@ class BrowserManager {
|
||||
return;
|
||||
}
|
||||
completer.complete(BrowserManager._(
|
||||
packageConfig, browser, browserEnvironment, renderer, isWasm, webSocket));
|
||||
packageConfig, browser, browserEnvironment, sourceMapDirectory, webSocket));
|
||||
}).catchError((Object error, StackTrace stackTrace) {
|
||||
browser.close();
|
||||
if (completer.isCompleted) {
|
||||
@@ -942,7 +953,7 @@ class BrowserManager {
|
||||
suiteChannel,
|
||||
message);
|
||||
|
||||
if (_isWasm) {
|
||||
if (_sourceMapDirectory == null) {
|
||||
// We don't have mapping for wasm yet. But we should send a message
|
||||
// to let the host page move forward.
|
||||
controller!.channel('test.browser.mapper').sink.add(null);
|
||||
@@ -951,8 +962,11 @@ class BrowserManager {
|
||||
'${p.basename(path)}.browser_test.dart.js.map';
|
||||
final String pathToTest = p.dirname(path);
|
||||
|
||||
final String mapPath = p.join(env.environment.webUiBuildDir.path,
|
||||
getBuildDirForRenderer(_renderer), pathToTest, sourceMapFileName);
|
||||
final String mapPath = p.join(
|
||||
_sourceMapDirectory!.path,
|
||||
pathToTest,
|
||||
sourceMapFileName
|
||||
);
|
||||
|
||||
final Map<String, Uri> packageMap = <String, Uri>{
|
||||
for (Package p in packageConfig.packages) p.name: p.packageUriRoot
|
||||
|
||||
@@ -10,9 +10,15 @@ import 'package:path/path.dart' as path;
|
||||
|
||||
import 'package:watcher/src/watch_event.dart';
|
||||
|
||||
import 'environment.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'felt_config.dart';
|
||||
import 'generate_builder_json.dart';
|
||||
import 'pipeline.dart';
|
||||
import 'steps/compile_tests_step.dart';
|
||||
import 'steps/run_tests_step.dart';
|
||||
import 'steps/compile_bundle_step.dart';
|
||||
import 'steps/copy_artifacts_step.dart';
|
||||
import 'steps/run_suite_step.dart';
|
||||
import 'suite_filter.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/// Runs tests.
|
||||
@@ -25,6 +31,11 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
|
||||
'opportunity to add breakpoints or inspect loaded code before '
|
||||
'running the code.',
|
||||
)
|
||||
..addFlag(
|
||||
'verbose',
|
||||
abbr: 'v',
|
||||
help: 'Enable verbose output.'
|
||||
)
|
||||
..addFlag(
|
||||
'watch',
|
||||
abbr: 'w',
|
||||
@@ -32,16 +43,43 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
|
||||
'made.',
|
||||
)
|
||||
..addFlag(
|
||||
'use-system-flutter',
|
||||
help: 'integration tests are using flutter repository for various tasks'
|
||||
', such as flutter drive, flutter pub get. If this flag is set, felt '
|
||||
'will use flutter command without cloning the repository. This flag '
|
||||
'can save internet bandwidth. However use with caution. Note that '
|
||||
'since flutter repo is always synced to youngest commit older than '
|
||||
'the engine commit for the tests running in CI, the tests results '
|
||||
"won't be consistent with CIs when this flag is set. flutter "
|
||||
'command should be set in the PATH for this flag to be useful.'
|
||||
'This flag can also be used to test local Flutter changes.')
|
||||
'list',
|
||||
help:
|
||||
'Lists the bundles that would be compiled and the suites that '
|
||||
'will be run as part of this invocation, without actually '
|
||||
'compiling or running them.'
|
||||
)
|
||||
..addFlag(
|
||||
'generate-builder-json',
|
||||
help:
|
||||
'Generates JSON for the engine_v2 builders to build and copy all'
|
||||
'artifacts, compile all test bundles, and run all test suites on'
|
||||
'all platforms.'
|
||||
)
|
||||
..addFlag(
|
||||
'compile',
|
||||
help:
|
||||
'Compile test bundles. If this is specified on its own, we will '
|
||||
'only compile and not run the suites.'
|
||||
)
|
||||
..addFlag(
|
||||
'run',
|
||||
help:
|
||||
'Run test suites. If this is specified on its own, we will only '
|
||||
'run the suites and not compile the bundles.'
|
||||
)
|
||||
..addFlag(
|
||||
'copy-artifacts',
|
||||
help:
|
||||
'Copy artifacts needed for test suites. If this is specified on '
|
||||
'its own, we will only copy the artifacts and not compile or run'
|
||||
'the tests bundles or suites.'
|
||||
)
|
||||
..addFlag(
|
||||
'profile',
|
||||
help:
|
||||
'Use artifacts from the profile build instead of release.'
|
||||
)
|
||||
..addFlag(
|
||||
'require-skia-gold',
|
||||
help:
|
||||
@@ -55,11 +93,29 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
|
||||
'.dart_tool/goldens. Use this option to bulk-update all screenshots, '
|
||||
'for example, when a new browser version affects pixels.',
|
||||
)
|
||||
..addOption(
|
||||
..addMultiOption(
|
||||
'browser',
|
||||
defaultsTo: 'chrome',
|
||||
help: 'An option to choose a browser to run the tests. By default '
|
||||
'tests run in Chrome.',
|
||||
help: 'Filter test suites by browser.',
|
||||
)
|
||||
..addMultiOption(
|
||||
'compiler',
|
||||
help: 'Filter test suites by compiler.',
|
||||
)
|
||||
..addMultiOption(
|
||||
'renderer',
|
||||
help: 'Filter test suites by renderer.',
|
||||
)
|
||||
..addMultiOption(
|
||||
'canvaskit-variant',
|
||||
help: 'Filter test suites by CanvasKit variant.',
|
||||
)
|
||||
..addMultiOption(
|
||||
'suite',
|
||||
help: 'Filter test suites by suite name.',
|
||||
)
|
||||
..addMultiOption(
|
||||
'bundle',
|
||||
help: 'Filter test suites by bundle name.',
|
||||
)
|
||||
..addFlag(
|
||||
'fail-early',
|
||||
@@ -77,11 +133,6 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
|
||||
..addFlag(
|
||||
'wasm',
|
||||
help: 'Whether the test we are running are compiled to webassembly.'
|
||||
)
|
||||
..addFlag(
|
||||
'use-local-canvaskit',
|
||||
help: 'Optional. Whether or not to use the locally built version of '
|
||||
'CanvasKit in the tests.',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,9 +144,9 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
|
||||
|
||||
bool get isWatchMode => boolArg('watch');
|
||||
|
||||
bool get failEarly => boolArg('fail-early');
|
||||
bool get isList => boolArg('list');
|
||||
|
||||
bool get isWasm => boolArg('wasm');
|
||||
bool get failEarly => boolArg('fail-early');
|
||||
|
||||
/// Whether to start the browser in debug mode.
|
||||
///
|
||||
@@ -103,17 +154,10 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
|
||||
/// you set breakpoints or inspect the code.
|
||||
bool get isDebug => boolArg('debug');
|
||||
|
||||
/// Paths to targets to run, e.g. a single test.
|
||||
List<String> get targets => argResults!.rest;
|
||||
bool get isVerbose => boolArg('verbose');
|
||||
|
||||
/// The target test files to run.
|
||||
List<FilePath> get targetFiles => targets.map((String t) => FilePath.fromCwd(t)).toList();
|
||||
|
||||
/// Whether all tests should run.
|
||||
bool get runAllTests => targets.isEmpty;
|
||||
|
||||
/// The name of the browser to run tests in.
|
||||
String get browserName => stringArg('browser');
|
||||
List<FilePath> get targetFiles => argResults!.rest.map((String t) => FilePath.fromCwd(t)).toList();
|
||||
|
||||
/// When running screenshot tests, require Skia Gold to be available and
|
||||
/// reachable.
|
||||
@@ -126,31 +170,219 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
|
||||
/// Path to a CanvasKit build. Overrides the default CanvasKit.
|
||||
String? get overridePathToCanvasKit => argResults!['canvaskit-path'] as String?;
|
||||
|
||||
/// Whether or not to use the locally built version of CanvasKit.
|
||||
bool get useLocalCanvasKit => boolArg('use-local-canvaskit');
|
||||
final FeltConfig config = FeltConfig.fromFile(
|
||||
path.join(environment.webUiTestDir.path, 'felt_config.yaml')
|
||||
);
|
||||
|
||||
BrowserSuiteFilter? makeBrowserFilter() {
|
||||
final List<String>? browserArgs = argResults!['browser'] as List<String>?;
|
||||
if (browserArgs == null || browserArgs.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final Set<BrowserName> browserNames = Set<BrowserName>.from(browserArgs.map((String arg) => BrowserName.values.byName(arg)));
|
||||
return BrowserSuiteFilter(allowList: browserNames);
|
||||
}
|
||||
|
||||
CompilerFilter? makeCompilerFilter() {
|
||||
final List<String>? compilerArgs = argResults!['compiler'] as List<String>?;
|
||||
if (compilerArgs == null || compilerArgs.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final Set<Compiler> compilers = Set<Compiler>.from(compilerArgs.map((String arg) => Compiler.values.byName(arg)));
|
||||
return CompilerFilter(allowList: compilers);
|
||||
}
|
||||
|
||||
RendererFilter? makeRendererFilter() {
|
||||
final List<String>? rendererArgs = argResults!['renderer'] as List<String>?;
|
||||
if (rendererArgs == null || rendererArgs.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final Set<Renderer> renderers = Set<Renderer>.from(rendererArgs.map((String arg) => Renderer.values.byName(arg)));
|
||||
return RendererFilter(allowList: renderers);
|
||||
}
|
||||
|
||||
CanvasKitVariantFilter? makeCanvasKitVariantFilter() {
|
||||
final List<String>? variantArgs = argResults!['canvaskit-variant'] as List<String>?;
|
||||
if (variantArgs == null || variantArgs.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final Set<CanvasKitVariant> variants = Set<CanvasKitVariant>.from(variantArgs.map((String arg) => CanvasKitVariant.values.byName(arg)));
|
||||
return CanvasKitVariantFilter(allowList: variants);
|
||||
}
|
||||
|
||||
SuiteNameFilter? makeSuiteNameFilter() {
|
||||
final List<String>? suiteNameArgs = argResults!['suite'] as List<String>?;
|
||||
if (suiteNameArgs == null || suiteNameArgs.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Iterable<String> allSuiteNames = config.testSuites.map((TestSuite suite) => suite.name);
|
||||
for (final String suiteName in suiteNameArgs) {
|
||||
if (!allSuiteNames.contains(suiteName)) {
|
||||
throw ToolExit('No suite found named $suiteName');
|
||||
}
|
||||
}
|
||||
return SuiteNameFilter(allowList: Set<String>.from(suiteNameArgs));
|
||||
}
|
||||
|
||||
BundleNameFilter? makeBundleNameFilter() {
|
||||
final List<String>? bundleNameArgs = argResults!['bundle'] as List<String>?;
|
||||
if (bundleNameArgs == null || bundleNameArgs.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Iterable<String> allBundleNames = config.testSuites.map(
|
||||
(TestSuite suite) => suite.testBundle.name
|
||||
);
|
||||
for (final String bundleName in bundleNameArgs) {
|
||||
if (!allBundleNames.contains(bundleName)) {
|
||||
throw ToolExit('No bundle found named $bundleName');
|
||||
}
|
||||
}
|
||||
return BundleNameFilter(allowList: Set<String>.from(bundleNameArgs));
|
||||
}
|
||||
|
||||
FileFilter? makeFileFilter() {
|
||||
final List<FilePath> tests = targetFiles;
|
||||
if (tests.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final Set<String> bundleNames = <String>{};
|
||||
for (final FilePath testPath in tests) {
|
||||
if (!io.File(testPath.absolute).existsSync()) {
|
||||
throw ToolExit('Test path not found: $testPath');
|
||||
}
|
||||
bool bundleFound = false;
|
||||
for (final TestBundle bundle in config.testBundles) {
|
||||
final String testSetPath = getTestSetDirectory(bundle.testSet).path;
|
||||
if (path.isWithin(testSetPath, testPath.absolute)) {
|
||||
bundleFound = true;
|
||||
bundleNames.add(bundle.name);
|
||||
}
|
||||
}
|
||||
if (!bundleFound) {
|
||||
throw ToolExit('Test path not in any known test bundle: $testPath');
|
||||
}
|
||||
}
|
||||
return FileFilter(allowList: bundleNames);
|
||||
}
|
||||
|
||||
List<SuiteFilter> get suiteFilters {
|
||||
final BrowserSuiteFilter? browserFilter = makeBrowserFilter();
|
||||
final CompilerFilter? compilerFilter = makeCompilerFilter();
|
||||
final RendererFilter? rendererFilter = makeRendererFilter();
|
||||
final CanvasKitVariantFilter? canvaskitVariantFilter = makeCanvasKitVariantFilter();
|
||||
final SuiteNameFilter? suiteNameFilter = makeSuiteNameFilter();
|
||||
final BundleNameFilter? bundleNameFilter = makeBundleNameFilter();
|
||||
final FileFilter? fileFilter = makeFileFilter();
|
||||
return <SuiteFilter>[
|
||||
PlatformBrowserFilter(),
|
||||
if (browserFilter != null) browserFilter,
|
||||
if (compilerFilter != null) compilerFilter,
|
||||
if (rendererFilter != null) rendererFilter,
|
||||
if (canvaskitVariantFilter != null) canvaskitVariantFilter,
|
||||
if (suiteNameFilter != null) suiteNameFilter,
|
||||
if (bundleNameFilter != null) bundleNameFilter,
|
||||
if (fileFilter != null) fileFilter,
|
||||
];
|
||||
}
|
||||
|
||||
List<TestSuite> _filterTestSuites() {
|
||||
if (isVerbose) {
|
||||
print('Filtering suites...');
|
||||
}
|
||||
final List<SuiteFilter> filters = suiteFilters;
|
||||
final List<TestSuite> filteredSuites = config.testSuites.where((TestSuite suite) {
|
||||
for (final SuiteFilter filter in filters) {
|
||||
final SuiteFilterResult result = filter.filterSuite(suite);
|
||||
if (!result.isAccepted) {
|
||||
if (isVerbose) {
|
||||
print(' ${suite.name.ansiCyan} rejected for reason: ${result.rejectReason}');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).toList();
|
||||
return filteredSuites;
|
||||
}
|
||||
|
||||
List<TestBundle> _filterBundlesForSuites(List<TestSuite> suites) {
|
||||
final Set<TestBundle> seenBundles =
|
||||
Set<TestBundle>.from(suites.map((TestSuite suite) => suite.testBundle));
|
||||
return config.testBundles.where((TestBundle bundle) => seenBundles.contains(bundle)).toList();
|
||||
}
|
||||
|
||||
ArtifactDependencies _artifactsForSuites(List<TestSuite> suites) {
|
||||
return suites.fold(ArtifactDependencies.none(),
|
||||
(ArtifactDependencies deps, TestSuite suite) => deps | suite.artifactDependencies);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> run() async {
|
||||
final List<FilePath> testFiles = runAllTests
|
||||
? findAllTests()
|
||||
: targetFiles;
|
||||
final List<TestSuite> filteredSuites = _filterTestSuites();
|
||||
final List<TestBundle> bundles = _filterBundlesForSuites(filteredSuites);
|
||||
final ArtifactDependencies artifacts = _artifactsForSuites(filteredSuites);
|
||||
if (boolArg('generate-builder-json')) {
|
||||
print(generateBuilderJson(config));
|
||||
return true;
|
||||
}
|
||||
if (isList || isVerbose) {
|
||||
print('Suites:');
|
||||
for (final TestSuite suite in filteredSuites) {
|
||||
print(' ${suite.name.ansiCyan}');
|
||||
}
|
||||
print('Bundles:');
|
||||
for (final TestBundle bundle in bundles) {
|
||||
print(' ${bundle.name.ansiMagenta}');
|
||||
}
|
||||
print('Artifacts:');
|
||||
if (artifacts.canvasKit) {
|
||||
print(' canvaskit'.ansiYellow);
|
||||
}
|
||||
if (artifacts.canvasKitChromium) {
|
||||
print(' canvaskit_chromium'.ansiYellow);
|
||||
}
|
||||
if (artifacts.skwasm) {
|
||||
print(' skwasm'.ansiYellow);
|
||||
}
|
||||
}
|
||||
if (isList) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shouldRun = boolArg('run');
|
||||
bool shouldCompile = boolArg('compile');
|
||||
bool shouldCopyArtifacts = boolArg('copy-artifacts');
|
||||
if (!shouldRun && !shouldCompile && !shouldCopyArtifacts) {
|
||||
// If none of these is specified, we should assume we need to do all of them.
|
||||
shouldRun = true;
|
||||
shouldCompile = true;
|
||||
shouldCopyArtifacts = true;
|
||||
}
|
||||
|
||||
final Set<FilePath>? testFiles = targetFiles.isEmpty ? null : Set<FilePath>.from(targetFiles);
|
||||
final Pipeline testPipeline = Pipeline(steps: <PipelineStep>[
|
||||
if (isWatchMode) ClearTerminalScreenStep(),
|
||||
CompileTestsStep(
|
||||
testFiles: testFiles,
|
||||
useLocalCanvasKit: useLocalCanvasKit,
|
||||
isWasm: isWasm
|
||||
),
|
||||
RunTestsStep(
|
||||
browserName: browserName,
|
||||
testFiles: testFiles,
|
||||
isDebug: isDebug,
|
||||
isWasm: isWasm,
|
||||
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
|
||||
requireSkiaGold: requireSkiaGold,
|
||||
overridePathToCanvasKit: overridePathToCanvasKit,
|
||||
),
|
||||
if (shouldCopyArtifacts) CopyArtifactsStep(artifacts, isProfile: boolArg('profile')),
|
||||
if (shouldCompile)
|
||||
for (final TestBundle bundle in bundles)
|
||||
CompileBundleStep(
|
||||
bundle: bundle,
|
||||
isVerbose: isVerbose,
|
||||
testFiles: testFiles,
|
||||
),
|
||||
if (shouldRun)
|
||||
for (final TestSuite suite in filteredSuites)
|
||||
RunSuiteStep(
|
||||
suite,
|
||||
isDebug: isDebug,
|
||||
isVerbose: isVerbose,
|
||||
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
|
||||
requireSkiaGold: requireSkiaGold,
|
||||
overridePathToCanvasKit: overridePathToCanvasKit,
|
||||
testFiles: testFiles,
|
||||
),
|
||||
]);
|
||||
|
||||
try {
|
||||
|
||||
@@ -12,12 +12,15 @@ import 'package:path/path.dart' as path;
|
||||
|
||||
import 'environment.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'felt_config.dart';
|
||||
|
||||
class FilePath {
|
||||
FilePath.fromCwd(String relativePath)
|
||||
: _absolutePath = path.absolute(relativePath);
|
||||
FilePath.fromWebUi(String relativePath)
|
||||
: _absolutePath = path.join(environment.webUiRootDir.path, relativePath);
|
||||
FilePath.fromTestSet(TestSet testSet, String relativePath)
|
||||
: _absolutePath = path.join(getTestSetDirectory(testSet).path, relativePath);
|
||||
|
||||
final String _absolutePath;
|
||||
|
||||
@@ -368,82 +371,43 @@ Future<void> cleanup() async {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans the test/ directory for test files and returns them.
|
||||
List<FilePath> findAllTests() {
|
||||
return environment.webUiTestDir
|
||||
.listSync(recursive: true)
|
||||
.whereType<io.File>()
|
||||
.where((io.File f) => f.path.endsWith('_test.dart'))
|
||||
.map<FilePath>((io.File f) => FilePath.fromWebUi(
|
||||
path.relative(f.path, from: environment.webUiRootDir.path)))
|
||||
.toList();
|
||||
io.Directory getTestSetDirectory(TestSet testSet) {
|
||||
return io.Directory(
|
||||
path.join(
|
||||
environment.webUiTestDir.path,
|
||||
testSet.directory,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// The renderer used to run the test.
|
||||
enum Renderer {
|
||||
html,
|
||||
canvasKit,
|
||||
skwasm,
|
||||
io.Directory getBundleBuildDirectory(TestBundle bundle) {
|
||||
return io.Directory(
|
||||
path.join(
|
||||
environment.webUiBuildDir.path,
|
||||
'test_bundles',
|
||||
bundle.name,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// The `FilePath`s for all the tests, organized by renderer.
|
||||
class TestsByRenderer {
|
||||
TestsByRenderer(this.htmlTests, this.canvasKitTests, this.skwasmTests);
|
||||
extension AnsiColors on String {
|
||||
static bool shouldEscape = io.stdout.hasTerminal && io.stdout.supportsAnsiEscapes;
|
||||
|
||||
/// Tests which should be run with the HTML renderer.
|
||||
final List<FilePath> htmlTests;
|
||||
static const String _noColorCode = '\u001b[39m';
|
||||
|
||||
/// Tests which should be run with the CanvasKit renderer.
|
||||
final List<FilePath> canvasKitTests;
|
||||
String _wrapText(String prefix, String suffix) => shouldEscape
|
||||
? '$prefix$this$suffix' : this;
|
||||
|
||||
/// Tests which should be run with the Skwasm renderer.
|
||||
final List<FilePath> skwasmTests;
|
||||
String _colorText(String colorCode) => _wrapText(colorCode, _noColorCode);
|
||||
|
||||
/// The total number of targets to compile.
|
||||
///
|
||||
/// The number of uiTests is doubled since they are compiled twice: once for
|
||||
/// the HTML renderer and once for the CanvasKit renderer.
|
||||
int get numTargetsToCompile => htmlTests.length + canvasKitTests.length + skwasmTests.length;
|
||||
}
|
||||
|
||||
/// Given a list of test files, organizes them by which renderer should run them.
|
||||
TestsByRenderer sortTestsByRenderer(List<FilePath> testFiles, bool forWasm) {
|
||||
final List<FilePath> htmlTargets = <FilePath>[];
|
||||
final List<FilePath> canvasKitTargets = <FilePath>[];
|
||||
final List<FilePath> skwasmTargets = <FilePath>[];
|
||||
final String canvasKitTestDirectory =
|
||||
path.join(environment.webUiTestDir.path, 'canvaskit');
|
||||
final String skwasmTestDirectory =
|
||||
path.join(environment.webUiTestDir.path, 'skwasm');
|
||||
final String uiTestDirectory =
|
||||
path.join(environment.webUiTestDir.path, 'ui');
|
||||
for (final FilePath testFile in testFiles) {
|
||||
if (path.isWithin(canvasKitTestDirectory, testFile.absolute)) {
|
||||
canvasKitTargets.add(testFile);
|
||||
} else if (path.isWithin(skwasmTestDirectory, testFile.absolute)) {
|
||||
skwasmTargets.add(testFile);
|
||||
} else if (path.isWithin(uiTestDirectory, testFile.absolute)) {
|
||||
htmlTargets.add(testFile);
|
||||
canvasKitTargets.add(testFile);
|
||||
if (forWasm) {
|
||||
// Only add these tests in wasm mode, since JS mode has a stub renderer.
|
||||
skwasmTargets.add(testFile);
|
||||
}
|
||||
} else {
|
||||
htmlTargets.add(testFile);
|
||||
}
|
||||
}
|
||||
return TestsByRenderer(htmlTargets, canvasKitTargets, skwasmTargets);
|
||||
}
|
||||
|
||||
/// The build directory to compile a test into given the renderer.
|
||||
String getBuildDirForRenderer(Renderer renderer) {
|
||||
switch (renderer) {
|
||||
case Renderer.html:
|
||||
return 'html_tests';
|
||||
case Renderer.canvasKit:
|
||||
return 'canvaskit_tests';
|
||||
case Renderer.skwasm:
|
||||
return 'skwasm_tests';
|
||||
}
|
||||
String get ansiBlack => _colorText('\u001b[30m');
|
||||
String get ansiRed => _colorText('\u001b[31m');
|
||||
String get ansiGreen => _colorText('\u001b[32m');
|
||||
String get ansiYellow => _colorText('\u001b[33m');
|
||||
String get ansiBlue => _colorText('\u001b[34m');
|
||||
String get ansiMagenta => _colorText('\u001b[35m');
|
||||
String get ansiCyan => _colorText('\u001b[36m');
|
||||
String get ansiWhite => _colorText('\u001b[37m');
|
||||
|
||||
String get ansiBold => _wrapText('\u001b[1m', '\u001b[0m');
|
||||
}
|
||||
|
||||
50
engine/src/flutter/lib/web_ui/test/README.md
Normal file
50
engine/src/flutter/lib/web_ui/test/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
...............................................................................
|
||||
# Flutter Web Engine Test Suites
|
||||
The flutter engine unit tests can be run with a number of different
|
||||
configuration options that affect both compile time and run time. The
|
||||
permutations of these options are specified in the `felt_config.yaml` file that
|
||||
is colocated with this README. Here is an overview of the way the test suite
|
||||
configurations are structured:
|
||||
|
||||
## `compile-configs`
|
||||
Specifies how the tests should be compiled. Each compile config specifies the
|
||||
following:
|
||||
* `name` - The name of the compile configuration.
|
||||
* `compiler` - What compiler is used to compile the tests. Currently we support
|
||||
`dart2js` and `dart2wasm` as values.
|
||||
* `renderer` - Which renderer to use when compiling the tests. Currently we
|
||||
support `html`, `canvaskit`, and `skwasm`.
|
||||
|
||||
## `test-sets`
|
||||
A group of files that contain unit tests. Each test set specifies the following:
|
||||
* `name` - The name of the test set.
|
||||
* `directory` - The name of the directory under `flutter/lib/web_ui/test` that
|
||||
contains all the test files.
|
||||
|
||||
## `test-bundles`
|
||||
Specifies a group of tests and a compile configuration of those tests. The output
|
||||
of the test bundles appears in `flutter/lib/web_ui/build/test_bundles/<name>`
|
||||
where `<name>` is replaced by the name of the bundle. Each test bundle may be used
|
||||
by multiple test suites. Each test bundle specifies the following:
|
||||
* `name` - The name of the test bundle.
|
||||
* `test-set` - The name of the test set that contains the tests to be compiled.
|
||||
* `compile-config` - The name of the compile configuration to use.
|
||||
|
||||
## `run-configs`
|
||||
Specifies the test environment that should be provided to a unit test. Each run
|
||||
config specifies the following:
|
||||
* `name` - Name of the run configuration.
|
||||
* `browser` - The browser with which to run the tests. Valid values for this are
|
||||
`chrome`, `firefox`, `safari` or `edge`.
|
||||
* `canvaskit-variant` - An optionally supplied argument that forces the tests to
|
||||
use a particular variant of CanvasKit, either `full` or `chromium`. If none
|
||||
is specified, the engine will select the variant based on its normal selection
|
||||
logic.
|
||||
|
||||
## `test-suites`
|
||||
This is a fully specified run of a group of unit tests. They specify the following:
|
||||
* `name` - Name of the test suite.
|
||||
* `test-bundle` - Which compiled test bundle to use when running the suite.
|
||||
* `run-config` - Which run configuration to use when runnin the tests.
|
||||
* `artifact-deps` - Which gn/ninja build artifacts are needed to run the suite.
|
||||
Valid values are `canvaskit`, `canvaskit_chromium` or `skwasm`.
|
||||
@@ -13,7 +13,7 @@ import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'common.dart';
|
||||
import 'test_data.dart';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'canvaskit_api_test.dart';
|
||||
|
||||
final bool isBlink = browserEngine == BrowserEngine.blink;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../frame_timings_common.dart';
|
||||
import '../common/frame_timings_common.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'common.dart';
|
||||
import 'test_data.dart';
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../spy.dart';
|
||||
import '../common/matchers.dart';
|
||||
import '../common/spy.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import 'mock_engine_canvas.dart';
|
||||
import '../common/mock_engine_canvas.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -10,7 +10,7 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:test/test.dart';
|
||||
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -16,7 +16,7 @@ import 'package:ui/src/engine/navigation.dart';
|
||||
import 'package:ui/src/engine/services.dart';
|
||||
import 'package:ui/src/engine/test_embedding.dart';
|
||||
|
||||
import '../spy.dart';
|
||||
import '../common/spy.dart';
|
||||
|
||||
Map<String, dynamic> _wrapOriginState(dynamic state) {
|
||||
return <String, dynamic>{'origin': true, 'state': state};
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import 'keyboard_test_common.dart';
|
||||
import '../common/keyboard_test_common.dart';
|
||||
|
||||
const int kLocationStandard = 0;
|
||||
const int kLocationLeft = 1;
|
||||
@@ -7,7 +7,7 @@ import 'package:test/test.dart';
|
||||
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../../matchers.dart';
|
||||
import '../../common/matchers.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../keyboard_converter_test.dart';
|
||||
import 'keyboard_converter_test.dart';
|
||||
|
||||
const int _kNoButtonChange = -1;
|
||||
const PointerSupportDetector _defaultSupportDetector = PointerSupportDetector();
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../spy.dart';
|
||||
import '../common/spy.dart';
|
||||
|
||||
@JS('window._flutter_internal_on_benchmark')
|
||||
external set _onBenchmark (JSAny? object);
|
||||
|
||||
@@ -7,8 +7,8 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import '../common/mock_engine_canvas.dart';
|
||||
import '../html/screenshot.dart';
|
||||
import '../mock_engine_canvas.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
|
||||
@@ -10,8 +10,8 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart' hide window;
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import 'engine/history_test.dart';
|
||||
import 'matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'history_test.dart';
|
||||
|
||||
const MethodCodec codec = JSONMethodCodec();
|
||||
|
||||
@@ -14,7 +14,7 @@ import 'package:ui/src/engine/util.dart';
|
||||
import 'package:ui/src/engine/vector_math.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../../matchers.dart';
|
||||
import '../../common/matchers.dart';
|
||||
|
||||
/// Gets the DOM host where the Flutter app is being rendered.
|
||||
///
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../../frame_timings_common.dart';
|
||||
import '../../common/frame_timings_common.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import '../../matchers.dart';
|
||||
import '../../common/matchers.dart';
|
||||
|
||||
const MethodCodec codec = StandardMethodCodec();
|
||||
final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance);
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../../matchers.dart';
|
||||
import '../../common/matchers.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
|
||||
@@ -21,7 +21,7 @@ import 'package:ui/src/engine/text_editing/text_editing.dart';
|
||||
import 'package:ui/src/engine/util.dart';
|
||||
import 'package:ui/src/engine/vector_math.dart';
|
||||
|
||||
import 'spy.dart';
|
||||
import '../common/spy.dart';
|
||||
|
||||
/// The `keyCode` of the "Enter" key.
|
||||
const int _kReturnKeyCode = 13;
|
||||
270
engine/src/flutter/lib/web_ui/test/felt_config.yaml
Normal file
270
engine/src/flutter/lib/web_ui/test/felt_config.yaml
Normal file
@@ -0,0 +1,270 @@
|
||||
# See the `README.md` in this directory for documentation on the structure of
|
||||
# this file.
|
||||
compile-configs:
|
||||
- name: dart2js-html
|
||||
compiler: dart2js
|
||||
renderer: html
|
||||
|
||||
- name: dart2js-canvaskit
|
||||
compiler: dart2js
|
||||
renderer: canvaskit
|
||||
|
||||
- name: dart2js-skwasm
|
||||
compiler: dart2js
|
||||
renderer: skwasm
|
||||
|
||||
- name: dart2wasm-html
|
||||
compiler: dart2wasm
|
||||
renderer: html
|
||||
|
||||
- name: dart2wasm-canvaskit
|
||||
compiler: dart2wasm
|
||||
renderer: canvaskit
|
||||
|
||||
- name: dart2wasm-skwasm
|
||||
compiler: dart2wasm
|
||||
renderer: skwasm
|
||||
|
||||
test-sets:
|
||||
# Tests for non-renderer logic
|
||||
- name: engine
|
||||
directory: engine
|
||||
|
||||
# Tests for canvaskit-renderer-specific functionality
|
||||
- name: canvaskit
|
||||
directory: canvaskit
|
||||
|
||||
# Tests for html-renderer-specific functionality
|
||||
- name: html
|
||||
directory: html
|
||||
|
||||
# Tests for renderer functionality that can be run on any renderer
|
||||
- name: ui
|
||||
directory: ui
|
||||
|
||||
# This just has a single test that makes sure the skwasm stub renderer is
|
||||
# included when compiling to JS
|
||||
- name: skwasm_stub
|
||||
directory: skwasm_stub
|
||||
|
||||
test-bundles:
|
||||
- name: dart2js-html-engine
|
||||
test-set: engine
|
||||
compile-config: dart2js-html
|
||||
|
||||
- name: dart2js-html-html
|
||||
test-set: html
|
||||
compile-config: dart2js-html
|
||||
|
||||
- name: dart2js-html-ui
|
||||
test-set: ui
|
||||
compile-config: dart2js-html
|
||||
|
||||
- name: dart2js-canvaskit-canvaskit
|
||||
test-set: canvaskit
|
||||
compile-config: dart2js-canvaskit
|
||||
|
||||
- name: dart2js-canvaskit-ui
|
||||
test-set: ui
|
||||
compile-config: dart2js-canvaskit
|
||||
|
||||
- name: dart2js-skwasm-skwasm_stub
|
||||
test-set: skwasm_stub
|
||||
compile-config: dart2js-skwasm
|
||||
|
||||
- name: dart2wasm-html-engine
|
||||
test-set: engine
|
||||
compile-config: dart2wasm-html
|
||||
|
||||
- name: dart2wasm-html-html
|
||||
test-set: html
|
||||
compile-config: dart2wasm-html
|
||||
|
||||
- name: dart2wasm-html-ui
|
||||
test-set: ui
|
||||
compile-config: dart2wasm-html
|
||||
|
||||
- name: dart2wasm-canvaskit-canvaskit
|
||||
test-set: canvaskit
|
||||
compile-config: dart2wasm-canvaskit
|
||||
|
||||
- name: dart2wasm-canvaskit-ui
|
||||
test-set: ui
|
||||
compile-config: dart2wasm-canvaskit
|
||||
|
||||
- name: dart2wasm-skwasm-ui
|
||||
test-set: ui
|
||||
compile-config: dart2wasm-skwasm
|
||||
|
||||
run-configs:
|
||||
- name: chrome
|
||||
browser: chrome
|
||||
canvaskit-variant: chromium
|
||||
|
||||
- name: chrome-full
|
||||
browser: chrome
|
||||
canvaskit-variant: full
|
||||
|
||||
- name: edge
|
||||
browser: edge
|
||||
canvaskit-variant: chromium
|
||||
|
||||
- name: edge-full
|
||||
browser: edge
|
||||
canvaskit-variant: full
|
||||
|
||||
- name: firefox
|
||||
browser: firefox
|
||||
|
||||
- name: safari
|
||||
browser: safari
|
||||
|
||||
test-suites:
|
||||
- name: chrome-dart2js-html-engine
|
||||
test-bundle: dart2js-html-engine
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2js-canvaskit-canvaskit
|
||||
test-bundle: dart2js-canvaskit-canvaskit
|
||||
run-config: chrome
|
||||
artifact-deps: [ canvaskit_chromium ]
|
||||
|
||||
- name: chrome-dart2js-canvaskit-ui
|
||||
test-bundle: dart2js-canvaskit-ui
|
||||
run-config: chrome
|
||||
artifact-deps: [ canvaskit_chromium ]
|
||||
|
||||
- name: chrome-dart2js-skwasm-skwasm_stub
|
||||
test-bundle: dart2js-skwasm-skwasm_stub
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-full-dart2js-canvaskit-canvaskit
|
||||
test-bundle: dart2js-canvaskit-canvaskit
|
||||
run-config: chrome-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: chrome-full-dart2js-canvaskit-ui
|
||||
test-bundle: dart2js-canvaskit-ui
|
||||
run-config: chrome-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: edge-dart2js-html-engine
|
||||
test-bundle: dart2js-html-engine
|
||||
run-config: edge
|
||||
|
||||
- name: edge-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: edge
|
||||
|
||||
- name: edge-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: edge
|
||||
|
||||
- name: edge-dart2js-canvaskit-canvaskit
|
||||
test-bundle: dart2js-canvaskit-canvaskit
|
||||
run-config: edge
|
||||
artifact-deps: [ canvaskit_chromium ]
|
||||
|
||||
- name: edge-dart2js-canvaskit-ui
|
||||
test-bundle: dart2js-canvaskit-ui
|
||||
run-config: edge
|
||||
artifact-deps: [ canvaskit_chromium ]
|
||||
|
||||
- name: edge-full-dart2js-canvaskit-canvaskit
|
||||
test-bundle: dart2js-canvaskit-canvaskit
|
||||
run-config: edge-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: edge-full-dart2js-canvaskit-ui
|
||||
test-bundle: dart2js-canvaskit-ui
|
||||
run-config: edge-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: firefox-dart2js-html-engine
|
||||
test-bundle: dart2js-html-engine
|
||||
run-config: firefox
|
||||
|
||||
- name: firefox-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: firefox
|
||||
|
||||
- name: firefox-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: firefox
|
||||
|
||||
- name: firefox-dart2js-canvaskit-canvaskit
|
||||
test-bundle: dart2js-canvaskit-canvaskit
|
||||
run-config: firefox
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: firefox-dart2js-canvaskit-ui
|
||||
test-bundle: dart2js-canvaskit-ui
|
||||
run-config: firefox
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: safari-dart2js-html-engine
|
||||
test-bundle: dart2js-html-engine
|
||||
run-config: safari
|
||||
|
||||
- name: safari-dart2js-html-html
|
||||
test-bundle: dart2js-html-html
|
||||
run-config: safari
|
||||
|
||||
- name: safari-dart2js-html-ui
|
||||
test-bundle: dart2js-html-ui
|
||||
run-config: safari
|
||||
|
||||
- name: safari-dart2js-canvaskit-canvaskit
|
||||
test-bundle: dart2js-canvaskit-canvaskit
|
||||
run-config: safari
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: safari-dart2js-canvaskit-ui
|
||||
test-bundle: dart2js-canvaskit-ui
|
||||
run-config: safari
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: chrome-dart2wasm-html-engine
|
||||
test-bundle: dart2wasm-html-engine
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2wasm-html-html
|
||||
test-bundle: dart2wasm-html-html
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2wasm-html-ui
|
||||
test-bundle: dart2wasm-html-ui
|
||||
run-config: chrome
|
||||
|
||||
- name: chrome-dart2wasm-canvaskit-canvaskit
|
||||
test-bundle: dart2wasm-canvaskit-canvaskit
|
||||
run-config: chrome
|
||||
artifact-deps: [ canvaskit_chromium ]
|
||||
|
||||
- name: chrome-dart2wasm-canvaskit-ui
|
||||
test-bundle: dart2wasm-canvaskit-ui
|
||||
run-config: chrome
|
||||
artifact-deps: [ canvaskit_chromium ]
|
||||
|
||||
- name: chrome-dart2wasm-skwasm-ui
|
||||
test-bundle: dart2wasm-skwasm-ui
|
||||
run-config: chrome
|
||||
artifact-deps: [ skwasm ]
|
||||
|
||||
- name: chrome-full-dart2wasm-canvaskit-canvaskit
|
||||
test-bundle: dart2wasm-canvaskit-canvaskit
|
||||
run-config: chrome-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
|
||||
- name: chrome-full-dart2wasm-canvaskit-ui
|
||||
test-bundle: dart2wasm-canvaskit-ui
|
||||
run-config: chrome-full
|
||||
artifact-deps: [ canvaskit ]
|
||||
@@ -10,7 +10,7 @@ import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../../matchers.dart';
|
||||
import '../../common/matchers.dart';
|
||||
|
||||
const ui.Rect region = ui.Rect.fromLTWH(0, 0, 500, 100);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'screenshot.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' hide window;
|
||||
|
||||
import 'matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -11,7 +11,7 @@ import 'package:ui/src/engine.dart' hide ColorSpace;
|
||||
import 'package:ui/ui.dart' hide TextStyle;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'screenshot.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -11,8 +11,8 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'html/paragraph/helper.dart';
|
||||
import 'matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'paragraph/helper.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@@ -8,7 +8,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine/browser_detection.dart';
|
||||
import 'package:ui/src/engine/renderer.dart';
|
||||
import 'package:ui/src/engine/skwasm/skwasm_stub/renderer.dart';
|
||||
|
||||
@@ -23,8 +22,6 @@ Future<void> testMain() async {
|
||||
expect(() {
|
||||
renderer.initialize();
|
||||
}, throwsUnimplementedError);
|
||||
}, skip: isWasm);
|
||||
// This test is specifically designed for the JS case, to make sure we
|
||||
// compile to the skwasm stub renderer.
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
@@ -18,7 +18,7 @@ void main() {
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUiTest();
|
||||
await setUpUiTest();
|
||||
|
||||
final bool deviceClipRoundsOut = renderer is! HtmlRenderer;
|
||||
runCanvasTests(deviceClipRoundsOut: deviceClipRoundsOut);
|
||||
|
||||
@@ -16,8 +16,8 @@ class NotAColor extends Color {
|
||||
const NotAColor(super.value);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpUiTest();
|
||||
Future<void> testMain() async {
|
||||
await setUpUiTest();
|
||||
|
||||
test('color accessors should work', () {
|
||||
const Color foo = Color(0x12345678);
|
||||
|
||||
@@ -7,11 +7,15 @@ import 'package:test/test.dart';
|
||||
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
Future<void> testMain() async {
|
||||
await setUpUiTest();
|
||||
|
||||
test('Gradient.radial with no focal point', () {
|
||||
expect(
|
||||
Gradient.radial(
|
||||
@@ -22,7 +26,7 @@ void testMain() {
|
||||
TileMode.mirror),
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
|
||||
// this is just a radial gradient, focal point is discarded.
|
||||
test('radial center and focal == Offset.zero and focalRadius == 0.0 is ok',
|
||||
@@ -38,7 +42,7 @@ void testMain() {
|
||||
Offset.zero,
|
||||
),
|
||||
isNotNull);
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
|
||||
test('radial center != focal and focalRadius == 0.0 is ok', () {
|
||||
expect(
|
||||
@@ -52,7 +56,7 @@ void testMain() {
|
||||
const Offset(2.0, 2.0),
|
||||
),
|
||||
isNotNull);
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
|
||||
// this would result in div/0 on skia side.
|
||||
test('radial center and focal == Offset.zero and focalRadius != 0.0 assert',
|
||||
@@ -70,5 +74,5 @@ void testMain() {
|
||||
),
|
||||
throwsA(const TypeMatcher<AssertionError>()),
|
||||
);
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
}
|
||||
@@ -6,12 +6,14 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
await webOnlyInitializePlatform();
|
||||
await setUpUiTest();
|
||||
|
||||
test('Should be able to build and layout a paragraph', () {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle());
|
||||
@@ -22,14 +24,7 @@ Future<void> testMain() async {
|
||||
paragraph.layout(const ParagraphConstraints(width: 800.0));
|
||||
expect(paragraph.width, isNonZero);
|
||||
expect(paragraph.height, isNonZero);
|
||||
});
|
||||
|
||||
test('pushStyle should not segfault after build()', () {
|
||||
final ParagraphBuilder paragraphBuilder =
|
||||
ParagraphBuilder(ParagraphStyle());
|
||||
paragraphBuilder.build();
|
||||
paragraphBuilder.pushStyle(TextStyle());
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
|
||||
test('the presence of foreground style should not throw', () {
|
||||
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle());
|
||||
@@ -39,5 +34,5 @@ Future<void> testMain() async {
|
||||
builder.addText('hi');
|
||||
|
||||
expect(() => builder.build(), returnsNormally);
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import '../matchers.dart';
|
||||
import '../common/matchers.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
const double kTolerance = 0.1;
|
||||
@@ -17,8 +17,8 @@ void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpUiTest();
|
||||
Future<void> testMain() async {
|
||||
await setUpUiTest();
|
||||
group('PathMetric length', () {
|
||||
test('empty path', () {
|
||||
final Path path = Path();
|
||||
|
||||
@@ -14,8 +14,8 @@ void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpUiTest();
|
||||
Future<void> testMain() async {
|
||||
await setUpUiTest();
|
||||
test('path getBounds', () {
|
||||
const Rect r = Rect.fromLTRB(1.0, 3.0, 5.0, 7.0);
|
||||
final Path p = Path()..addRect(r);
|
||||
|
||||
@@ -13,7 +13,7 @@ void main() {
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUiTest();
|
||||
await setUpUiTest();
|
||||
|
||||
test('Picture construction invokes onCreate once', () async {
|
||||
int onCreateInvokedCount = 0;
|
||||
|
||||
@@ -12,8 +12,8 @@ void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpUiTest();
|
||||
Future<void> testMain() async {
|
||||
await setUpUiTest();
|
||||
test('rect accessors', () {
|
||||
const Rect r = Rect.fromLTRB(1.0, 3.0, 5.0, 7.0);
|
||||
expect(r.left, equals(1.0));
|
||||
|
||||
@@ -12,8 +12,8 @@ void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpUiTest();
|
||||
Future<void> testMain() async {
|
||||
await setUpUiTest();
|
||||
test('RRect.contains()', () {
|
||||
final RRect rrect = RRect.fromRectAndCorners(
|
||||
const Rect.fromLTRB(1.0, 1.0, 2.0, 2.0),
|
||||
|
||||
@@ -13,8 +13,8 @@ void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
setUpUiTest();
|
||||
Future<void> testMain() async {
|
||||
await setUpUiTest();
|
||||
|
||||
const MethodCodec codec = JSONMethodCodec();
|
||||
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/src/engine/skwasm/skwasm_stub.dart' if (dart.library.ffi) 'package:ui/src/engine/skwasm/skwasm_impl.dart';
|
||||
|
||||
import '../canvaskit/common.dart';
|
||||
|
||||
/// Initializes the renderer for this test.
|
||||
void setUpUiTest() {
|
||||
if (renderer is CanvasKitRenderer) {
|
||||
Future<void> setUpUiTest() async {
|
||||
if (isCanvasKit) {
|
||||
setUpCanvasKitTest();
|
||||
} else if (isHtml) {
|
||||
await initializeEngine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,3 +21,5 @@ bool get isCanvasKit => renderer is CanvasKitRenderer;
|
||||
|
||||
/// Returns [true] if this test is running in the HTML renderer.
|
||||
bool get isHtml => renderer is HtmlRenderer;
|
||||
|
||||
bool get isSkwasm => renderer is SkwasmRenderer;
|
||||
|
||||
@@ -24,6 +24,7 @@ Future<String> compareImage(
|
||||
String filename,
|
||||
SkiaGoldClient? skiaClient, {
|
||||
required bool isCanvaskitTest,
|
||||
required bool verbose,
|
||||
}) async {
|
||||
if (skiaClient == null) {
|
||||
return 'OK';
|
||||
@@ -69,12 +70,13 @@ Future<String> compareImage(
|
||||
// At the moment, we don't support local screenshot testing because we use
|
||||
// Skia Gold to handle our screenshots and diffing. In the future, we might
|
||||
// implement local screenshot testing if there's a need.
|
||||
print('Screenshot generated: file://$screenshotPath'); // ignore: avoid_print
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
// TODO(mdebbar): Use the Gold tool to locally diff the golden.
|
||||
|
||||
if (verbose) {
|
||||
print('Screenshot generated: file://$screenshotPath'); // ignore: avoid_print
|
||||
}
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user