Unverified Commit ad7727a2 authored by Yegor's avatar Yegor Committed by GitHub

[web] enable CanvasKit tests using a local bundle fetched from CIPD (#92134)

parent e7c809e8
...@@ -1184,6 +1184,174 @@ targets: ...@@ -1184,6 +1184,174 @@ targets:
- bin/ - bin/
- .ci.yaml - .ci.yaml
- name: Linux web_canvaskit_tests_0
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "0"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_canvaskit_tests_1
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "1"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_canvaskit_tests_2
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "2"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_canvaskit_tests_3
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "3"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_canvaskit_tests_4
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "4"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_canvaskit_tests_5
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "5"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_canvaskit_tests_6
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "6"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_canvaskit_tests_7_last
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk"},
{"dependency": "chrome_and_driver"},
{"dependency": "goldctl"}
]
shard: web_canvaskit_tests
subshard: "7_last"
tags: >
["framework","hostonly","shard"]
scheduler: luci
runIf:
- dev/
- packages/
- bin/
- name: Linux web_tool_tests - name: Linux web_tool_tests
recipe: flutter/flutter_drone recipe: flutter/flutter_drone
timeout: 60 timeout: 60
......
...@@ -225,6 +225,7 @@ ...@@ -225,6 +225,7 @@
# web_integration_tests @yjbanov @flutter/web # web_integration_tests @yjbanov @flutter/web
# web_long_running_tests @yjbanov @flutter/web # web_long_running_tests @yjbanov @flutter/web
# web_tests @yjbanov @flutter/web # web_tests @yjbanov @flutter/web
# web_canvaskit_tests @yjbanov @flutter/web
# web_tool_tests @zanderso @flutter/tool # web_tool_tests @zanderso @flutter/tool
# fuchsia_precache @zanderso @flutter/tool # fuchsia_precache @zanderso @flutter/tool
# skp_generator @Hixie # skp_generator @Hixie
......
...@@ -74,13 +74,66 @@ const String kSubshardKey = 'SUBSHARD'; ...@@ -74,13 +74,66 @@ const String kSubshardKey = 'SUBSHARD';
int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT') int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT')
? int.parse(Platform.environment['WEB_SHARD_COUNT']!) ? int.parse(Platform.environment['WEB_SHARD_COUNT']!)
: 8; : 8;
/// Tests that we don't run on Web for compilation reasons.
/// Tests that we don't run on Web.
///
/// In general avoid adding new tests here. If a test cannot run on the web
/// because it fails at runtime, such as when a piece of functionality is not
/// implemented or not implementable on the web, prefer using `skip` in the
/// test code. Only add tests here that cannot be skipped using `skip`. For
/// example:
///
/// * Test code cannot be compiled because it uses Dart VM-specific
/// functionality. In this case `skip` doesn't help because the code cannot
/// reach the point where it can even run the skipping logic.
/// * Migrations. It is OK to put tests here that need to be temporarily
/// disabled in certain modes because of some migration or initial bringup.
///
/// The key in the map is the renderer type that the list applies to. The value
/// is the list of tests known to fail for that renderer.
// //
// TODO(yjbanov): we're getting rid of this as part of https://github.com/flutter/flutter/projects/60 // TODO(yjbanov): we're getting rid of this as part of https://github.com/flutter/flutter/projects/60
const List<String> kWebTestFileKnownFailures = <String>[ const Map<String, List<String>> kWebTestFileKnownFailures = <String, List<String>>{
'test/services/message_codecs_vm_test.dart', 'html': <String>[
'test/examples/sector_layout_test.dart', // These tests are not compilable on the web due to dependencies on
]; // VM-specific functionality.
'test/services/message_codecs_vm_test.dart',
'test/examples/sector_layout_test.dart',
],
'canvaskit': <String>[
// These tests are not compilable on the web due to dependencies on
// VM-specific functionality.
'test/services/message_codecs_vm_test.dart',
'test/examples/sector_layout_test.dart',
// These tests are broken and need to be fixed.
// TODO(yjbanov): https://github.com/flutter/flutter/issues/71604
'test/painting/decoration_test.dart',
'test/material/text_selection_theme_test.dart',
'test/material/date_picker_test.dart',
'test/rendering/layers_test.dart',
'test/painting/text_style_test.dart',
'test/widgets/image_test.dart',
'test/cupertino/colors_test.dart',
'test/cupertino/slider_test.dart',
'test/material/text_field_test.dart',
'test/rendering/proxy_box_test.dart',
'test/widgets/app_overrides_test.dart',
'test/material/calendar_date_picker_test.dart',
'test/material/ink_paint_test.dart',
'test/rendering/editable_test.dart',
'test/cupertino/dialog_test.dart',
'test/widgets/shape_decoration_test.dart',
'test/material/time_picker_theme_test.dart',
'test/cupertino/picker_test.dart',
'test/material/chip_theme_test.dart',
'test/cupertino/nav_bar_test.dart',
'test/widgets/performance_overlay_test.dart',
'test/widgets/html_element_view_test.dart',
'test/cupertino/scaffold_test.dart',
'test/rendering/platform_view_test.dart',
],
};
const String kSmokeTestShardName = 'smoke_tests'; const String kSmokeTestShardName = 'smoke_tests';
const List<String> _kAllBuildModes = <String>['debug', 'profile', 'release']; const List<String> _kAllBuildModes = <String>['debug', 'profile', 'release'];
...@@ -143,8 +196,10 @@ Future<void> main(List<String> args) async { ...@@ -143,8 +196,10 @@ Future<void> main(List<String> args) async {
// web_tool_tests is also used by HHH: https://dart.googlesource.com/recipes/+/refs/heads/master/recipes/dart/flutter_engine.py // web_tool_tests is also used by HHH: https://dart.googlesource.com/recipes/+/refs/heads/master/recipes/dart/flutter_engine.py
'web_tool_tests': _runWebToolTests, 'web_tool_tests': _runWebToolTests,
'tool_integration_tests': _runIntegrationToolTests, 'tool_integration_tests': _runIntegrationToolTests,
// All the unit/widget tests run using `flutter test --platform=chrome` // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=html`
'web_tests': _runWebUnitTests, 'web_tests': _runWebHtmlUnitTests,
// All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=canvaskit`
'web_canvaskit_tests': _runWebCanvasKitUnitTests,
// All web integration tests // All web integration tests
'web_long_running_tests': _runWebLongRunningTests, 'web_long_running_tests': _runWebLongRunningTests,
'flutter_plugins': _runFlutterPluginsTests, 'flutter_plugins': _runFlutterPluginsTests,
...@@ -802,7 +857,15 @@ Future<void> _runFrameworkCoverage() async { ...@@ -802,7 +857,15 @@ Future<void> _runFrameworkCoverage() async {
} }
} }
Future<void> _runWebUnitTests() async { Future<void> _runWebHtmlUnitTests() {
return _runWebUnitTests('html');
}
Future<void> _runWebCanvasKitUnitTests() {
return _runWebUnitTests('canvaskit');
}
Future<void> _runWebUnitTests(String webRenderer) async {
final Map<String, ShardRunner> subshards = <String, ShardRunner>{}; final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter')); final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
...@@ -817,7 +880,7 @@ Future<void> _runWebUnitTests() async { ...@@ -817,7 +880,7 @@ Future<void> _runWebUnitTests() async {
) )
.whereType<File>() .whereType<File>()
.map<String>((File file) => path.relative(file.path, from: flutterPackageDirectory.path)) .map<String>((File file) => path.relative(file.path, from: flutterPackageDirectory.path))
.where((String filePath) => !kWebTestFileKnownFailures.contains(path.split(filePath).join('/'))) .where((String filePath) => !kWebTestFileKnownFailures[webRenderer]!.contains(path.split(filePath).join('/')))
.toList() .toList()
// Finally we shuffle the list because we want the average cost per file to be uniformly // Finally we shuffle the list because we want the average cost per file to be uniformly
// distributed. If the list is not sorted then different shards and batches may have // distributed. If the list is not sorted then different shards and batches may have
...@@ -832,6 +895,7 @@ Future<void> _runWebUnitTests() async { ...@@ -832,6 +895,7 @@ Future<void> _runWebUnitTests() async {
// This for loop computes all but the last shard. // This for loop computes all but the last shard.
for (int index = 0; index < webShardCount - 1; index += 1) { for (int index = 0; index < webShardCount - 1; index += 1) {
subshards['$index'] = () => _runFlutterWebTest( subshards['$index'] = () => _runFlutterWebTest(
webRenderer,
flutterPackageDirectory.path, flutterPackageDirectory.path,
allTests.sublist( allTests.sublist(
index * testsPerShard, index * testsPerShard,
...@@ -846,6 +910,7 @@ Future<void> _runWebUnitTests() async { ...@@ -846,6 +910,7 @@ Future<void> _runWebUnitTests() async {
// between `.cirrus.yml` and `test.dart`. // between `.cirrus.yml` and `test.dart`.
subshards['${webShardCount - 1}_last'] = () async { subshards['${webShardCount - 1}_last'] = () async {
await _runFlutterWebTest( await _runFlutterWebTest(
webRenderer,
flutterPackageDirectory.path, flutterPackageDirectory.path,
allTests.sublist( allTests.sublist(
(webShardCount - 1) * testsPerShard, (webShardCount - 1) * testsPerShard,
...@@ -853,12 +918,14 @@ Future<void> _runWebUnitTests() async { ...@@ -853,12 +918,14 @@ Future<void> _runWebUnitTests() async {
), ),
); );
await _runFlutterWebTest( await _runFlutterWebTest(
webRenderer,
path.join(flutterRoot, 'packages', 'flutter_web_plugins'), path.join(flutterRoot, 'packages', 'flutter_web_plugins'),
<String>['test'], <String>['test'],
); );
await _runFlutterWebTest( await _runFlutterWebTest(
path.join(flutterRoot, 'packages', 'flutter_driver'), webRenderer,
<String>[path.join('test', 'src', 'web_tests', 'web_extension_test.dart')], path.join(flutterRoot, 'packages', 'flutter_driver'),
<String>[path.join('test', 'src', 'web_tests', 'web_extension_test.dart')],
); );
}; };
...@@ -1421,7 +1488,7 @@ Future<void> _runWebDebugTest(String target, { ...@@ -1421,7 +1488,7 @@ Future<void> _runWebDebugTest(String target, {
} }
} }
Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) async { Future<void> _runFlutterWebTest(String webRenderer, String workingDirectory, List<String> tests) async {
await runCommand( await runCommand(
flutter, flutter,
<String>[ <String>[
...@@ -1430,9 +1497,8 @@ Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) asy ...@@ -1430,9 +1497,8 @@ Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) asy
'--concurrency=1', // do not parallelize on Cirrus, to reduce flakiness '--concurrency=1', // do not parallelize on Cirrus, to reduce flakiness
'-v', '-v',
'--platform=chrome', '--platform=chrome',
// TODO(ferhatb): Run web tests with both rendering backends. '--web-renderer=$webRenderer',
'--web-renderer=html', // use html backend for web tests. '--sound-null-safety',
'--sound-null-safety', // web tests do not autodetect yet.
...flutterTestArgs, ...flutterTestArgs,
...tests, ...tests,
], ],
......
...@@ -19,6 +19,7 @@ import 'package:process/process.dart'; ...@@ -19,6 +19,7 @@ import 'package:process/process.dart';
const String _kFlutterRootKey = 'FLUTTER_ROOT'; const String _kFlutterRootKey = 'FLUTTER_ROOT';
const String _kGoldctlKey = 'GOLDCTL'; const String _kGoldctlKey = 'GOLDCTL';
const String _kTestBrowserKey = 'FLUTTER_TEST_BROWSER'; const String _kTestBrowserKey = 'FLUTTER_TEST_BROWSER';
const String _kWebRendererKey = 'FLUTTER_WEB_RENDERER';
/// A client for uploading image tests and making baseline requests to the /// A client for uploading image tests and making baseline requests to the
/// Flutter Gold Dashboard. /// Flutter Gold Dashboard.
...@@ -368,6 +369,9 @@ class SkiaGoldClient { ...@@ -368,6 +369,9 @@ class SkiaGoldClient {
if (platform.environment[_kTestBrowserKey] != null) { if (platform.environment[_kTestBrowserKey] != null) {
keys['Browser'] = platform.environment[_kTestBrowserKey]; keys['Browser'] = platform.environment[_kTestBrowserKey];
keys['Platform'] = '${keys['Platform']}-browser'; keys['Platform'] = '${keys['Platform']}-browser';
if (platform.environment[_kWebRendererKey] == 'canvaskit') {
keys['WebRenderer'] = 'canvaskit';
}
} }
return json.encode(keys); return json.encode(keys);
} }
...@@ -413,7 +417,10 @@ class SkiaGoldClient { ...@@ -413,7 +417,10 @@ class SkiaGoldClient {
/// the image keys. /// the image keys.
String getTraceID(String testName) { String getTraceID(String testName) {
final Map<String, dynamic> keys = <String, dynamic>{ final Map<String, dynamic> keys = <String, dynamic>{
if (platform.environment[_kTestBrowserKey] != null) 'Browser' : platform.environment[_kTestBrowserKey], if (platform.environment[_kTestBrowserKey] != null)
'Browser' : platform.environment[_kTestBrowserKey],
if (platform.environment[_kTestBrowserKey] != null && platform.environment[_kWebRendererKey] == 'canvaskit')
'WebRenderer' : 'canvaskit',
'CI' : 'luci', 'CI' : 'luci',
'Platform' : platform.operatingSystem, 'Platform' : platform.operatingSystem,
'name' : testName, 'name' : testName,
......
...@@ -404,17 +404,57 @@ class WebReleaseBundle extends Target { ...@@ -404,17 +404,57 @@ class WebReleaseBundle extends Target {
} }
} }
/// Static assets provided by the Flutter SDK that do not change, such as
/// CanvasKit.
///
/// These assets can be cached forever and are only invalidated when the
/// Flutter SDK is upgraded to a new version.
class WebBuiltInAssets extends Target {
const WebBuiltInAssets(this.fileSystem);
final FileSystem fileSystem;
@override
String get name => 'web_static_assets';
@override
List<Target> get dependencies => const <Target>[];
@override
List<String> get depfiles => const <String>[];
@override
List<Source> get inputs => const <Source>[];
@override
List<Source> get outputs => const <Source>[];
@override
Future<void> build(Environment environment) async {
final Directory flutterWebSdk = globals.artifacts.getHostArtifact(HostArtifact.flutterWebSdk) as Directory;
final Directory canvasKitDirectory = flutterWebSdk.childDirectory('canvaskit');
for (final File file in canvasKitDirectory.listSync(recursive: true).whereType<File>()) {
final String relativePath = fileSystem.path.relative(file.path, from: canvasKitDirectory.path);
final String targetPath = fileSystem.path.join(environment.outputDir.path, 'canvaskit', relativePath);
file.copySync(targetPath);
}
}
}
/// Generate a service worker for a web target. /// Generate a service worker for a web target.
class WebServiceWorker extends Target { class WebServiceWorker extends Target {
const WebServiceWorker(); const WebServiceWorker(this.fileSystem);
final FileSystem fileSystem;
@override @override
String get name => 'web_service_worker'; String get name => 'web_service_worker';
@override @override
List<Target> get dependencies => const <Target>[ List<Target> get dependencies => <Target>[
Dart2JSTarget(), const Dart2JSTarget(),
WebReleaseBundle(), const WebReleaseBundle(),
WebBuiltInAssets(fileSystem),
]; ];
@override @override
......
...@@ -49,7 +49,7 @@ List<Target> _kDefaultTargets = <Target>[ ...@@ -49,7 +49,7 @@ List<Target> _kDefaultTargets = <Target>[
const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64), const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64),
const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64), const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64),
// Web targets // Web targets
const WebServiceWorker(), WebServiceWorker(globals.fs),
const ReleaseAndroidApplication(), const ReleaseAndroidApplication(),
// This is a one-off rule for bundle and aot compat. // This is a one-off rule for bundle and aot compat.
const CopyFlutterBundle(), const CopyFlutterBundle(),
......
...@@ -196,6 +196,14 @@ class FlutterWebSdk extends CachedArtifact { ...@@ -196,6 +196,14 @@ class FlutterWebSdk extends CachedArtifact {
entity.copySync(newPath); entity.copySync(newPath);
} }
} }
final String canvasKitVersion = cache.getVersionFor('canvaskit')!;
final String canvasKitUrl = '$_cipdBaseUrl/flutter/web/canvaskit_bundle/+/$canvasKitVersion';
return artifactUpdater.downloadZipArchive(
'Downloading CanvasKit...',
Uri.parse(canvasKitUrl),
location,
);
} }
} }
......
...@@ -15,6 +15,7 @@ import '../base/file_system.dart'; ...@@ -15,6 +15,7 @@ import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../convert.dart'; import '../convert.dart';
import '../web/compile.dart';
import 'test_compiler.dart'; import 'test_compiler.dart';
import 'test_config.dart'; import 'test_config.dart';
...@@ -31,6 +32,7 @@ class TestGoldenComparator { ...@@ -31,6 +32,7 @@ class TestGoldenComparator {
@required Logger logger, @required Logger logger,
@required FileSystem fileSystem, @required FileSystem fileSystem,
@required ProcessManager processManager, @required ProcessManager processManager,
@required this.webRenderer,
}) : tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_web_platform.'), }) : tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_web_platform.'),
_logger = logger, _logger = logger,
_fileSystem = fileSystem, _fileSystem = fileSystem,
...@@ -42,6 +44,7 @@ class TestGoldenComparator { ...@@ -42,6 +44,7 @@ class TestGoldenComparator {
final Logger _logger; final Logger _logger;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final ProcessManager _processManager; final ProcessManager _processManager;
final WebRendererMode webRenderer;
TestCompiler _compiler; TestCompiler _compiler;
TestGoldenComparatorProcess _previousComparator; TestGoldenComparatorProcess _previousComparator;
...@@ -88,6 +91,7 @@ class TestGoldenComparator { ...@@ -88,6 +91,7 @@ class TestGoldenComparator {
final Map<String, String> environment = <String, String>{ final Map<String, String> environment = <String, String>{
// Chrome is the only supported browser currently. // Chrome is the only supported browser currently.
'FLUTTER_TEST_BROWSER': 'chrome', 'FLUTTER_TEST_BROWSER': 'chrome',
'FLUTTER_WEB_RENDERER': webRenderer == WebRendererMode.html ? 'html' : 'canvaskit',
}; };
return _processManager.start(command, environment: environment); return _processManager.start(command, environment: environment);
} }
......
...@@ -53,11 +53,13 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -53,11 +53,13 @@ class FlutterWebPlatform extends PlatformPlugin {
@required Logger logger, @required Logger logger,
@required Artifacts artifacts, @required Artifacts artifacts,
@required ProcessManager processManager, @required ProcessManager processManager,
@required Cache cache,
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_flutterToolPackageConfig = flutterToolPackageConfig, _flutterToolPackageConfig = flutterToolPackageConfig,
_chromiumLauncher = chromiumLauncher, _chromiumLauncher = chromiumLauncher,
_logger = logger, _logger = logger,
_artifacts = artifacts { _artifacts = artifacts,
_cache = cache {
final shelf.Cascade cascade = shelf.Cascade() final shelf.Cascade cascade = shelf.Cascade()
.add(_webSocketHandler.handler) .add(_webSocketHandler.handler)
.add(createStaticHandler( .add(createStaticHandler(
...@@ -65,6 +67,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -65,6 +67,7 @@ class FlutterWebPlatform extends PlatformPlugin {
serveFilesOutsidePath: true, serveFilesOutsidePath: true,
)) ))
.add(_handleStaticArtifact) .add(_handleStaticArtifact)
.add(_localCanvasKitHandler)
.add(_goldenFileHandler) .add(_goldenFileHandler)
.add(_wrapperHandler) .add(_wrapperHandler)
.add(_handleTestRequest) .add(_handleTestRequest)
...@@ -80,6 +83,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -80,6 +83,7 @@ class FlutterWebPlatform extends PlatformPlugin {
fileSystem: _fileSystem, fileSystem: _fileSystem,
logger: _logger, logger: _logger,
processManager: processManager, processManager: processManager,
webRenderer: _rendererMode,
); );
} }
...@@ -95,6 +99,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -95,6 +99,7 @@ class FlutterWebPlatform extends PlatformPlugin {
final OneOffHandler _webSocketHandler = OneOffHandler(); final OneOffHandler _webSocketHandler = OneOffHandler();
final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>(); final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>();
final String _root; final String _root;
final Cache _cache;
/// Allows only one test suite (typically one test file) to be loaded and run /// Allows only one test suite (typically one test file) to be loaded and run
/// at any given point in time. Loading more than one file at a time is known /// at any given point in time. Loading more than one file at a time is known
...@@ -117,6 +122,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -117,6 +122,7 @@ class FlutterWebPlatform extends PlatformPlugin {
@required ChromiumLauncher chromiumLauncher, @required ChromiumLauncher chromiumLauncher,
@required Artifacts artifacts, @required Artifacts artifacts,
@required ProcessManager processManager, @required ProcessManager processManager,
@required Cache cache,
}) async { }) async {
final shelf_io.IOServer server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); final shelf_io.IOServer server = shelf_io.IOServer(await HttpMultiServer.loopback(0));
final PackageConfig packageConfig = await loadPackageConfigWithLogging( final PackageConfig packageConfig = await loadPackageConfigWithLogging(
...@@ -145,6 +151,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -145,6 +151,7 @@ class FlutterWebPlatform extends PlatformPlugin {
logger: logger, logger: logger,
nullAssertions: nullAssertions, nullAssertions: nullAssertions,
processManager: processManager, processManager: processManager,
cache: cache,
); );
} }
...@@ -218,6 +225,23 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -218,6 +225,23 @@ class FlutterWebPlatform extends PlatformPlugin {
'host.dart.js', 'host.dart.js',
)); ));
File _canvasKitFile(String relativePath) {
// TODO(yjbanov): https://github.com/flutter/flutter/issues/52588
//
// Update this when we start building CanvasKit from sources. In the
// meantime, get the Web SDK directory from cache rather than through
// Artifacts. The latter is sensitive to `--local-engine`, which changes
// the directory to point to ENGINE/src/out. However, CanvasKit is not yet
// built as part of the engine, but fetched from CIPD, and so it won't be
// found in ENGINE/src/out.
final Directory webSdkDirectory = _cache.getWebSdkDirectory();
final File canvasKitFile = _fileSystem.file(_fileSystem.path.join(
webSdkDirectory.path,
relativePath,
));
return canvasKitFile;
}
Future<shelf.Response> _handleTestRequest(shelf.Request request) async { Future<shelf.Response> _handleTestRequest(shelf.Request request) async {
if (request.url.path.endsWith('.dart.browser_test.dart.js')) { if (request.url.path.endsWith('.dart.browser_test.dart.js')) {
final String leadingPath = request.url.path.split('.browser_test.dart.js')[0]; final String leadingPath = request.url.path.split('.browser_test.dart.js')[0];
...@@ -365,6 +389,37 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -365,6 +389,37 @@ class FlutterWebPlatform extends PlatformPlugin {
} }
} }
/// Serves a local build of CanvasKit, replacing the CDN build, which can
/// cause test flakiness due to reliance on network.
shelf.Response _localCanvasKitHandler(shelf.Request request) {
final String path = _fileSystem.path.fromUri(request.url);
if (!path.startsWith('canvaskit/')) {
return shelf.Response.notFound('Not a CanvasKit file request');
}
final String extension = _fileSystem.path.extension(path);
String contentType;
switch (extension) {
case '.js':
contentType = 'text/javascript';
break;
case '.wasm':
contentType = 'application/wasm';
break;
default:
final String error = 'Failed to determine Content-Type for "${request.url.path}".';
_logger.printError(error);
return shelf.Response.internalServerError(body: error);
}
return shelf.Response.ok(
_canvasKitFile(path).openRead(),
headers: <String, Object>{
HttpHeaders.contentTypeHeader: contentType,
},
);
}
// A handler that serves wrapper files used to bootstrap tests. // A handler that serves wrapper files used to bootstrap tests.
shelf.Response _wrapperHandler(shelf.Request request) { shelf.Response _wrapperHandler(shelf.Request request) {
final String path = _fileSystem.path.fromUri(request.url); final String path = _fileSystem.path.fromUri(request.url);
...@@ -377,6 +432,11 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -377,6 +432,11 @@ class FlutterWebPlatform extends PlatformPlugin {
<html> <html>
<head> <head>
<title>${htmlEscape.convert(test)} Test</title> <title>${htmlEscape.convert(test)} Test</title>
<script>
window.flutterConfiguration = {
canvasKitBaseUrl: "/canvaskit/"
};
</script>
$link $link
<script src="static/dart.js"></script> <script src="static/dart.js"></script>
</head> </head>
......
...@@ -176,6 +176,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { ...@@ -176,6 +176,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
browserFinder: findChromeExecutable, browserFinder: findChromeExecutable,
logger: globals.logger, logger: globals.logger,
), ),
cache: globals.cache,
); );
}, },
); );
......
...@@ -37,7 +37,7 @@ Future<void> buildWeb( ...@@ -37,7 +37,7 @@ Future<void> buildWeb(
final Status status = globals.logger.startProgress('Compiling $target for the Web...'); final Status status = globals.logger.startProgress('Compiling $target for the Web...');
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
try { try {
final BuildResult result = await globals.buildSystem.build(const WebServiceWorker(), Environment( final BuildResult result = await globals.buildSystem.build(WebServiceWorker(globals.fs), Environment(
projectDir: globals.fs.currentDirectory, projectDir: globals.fs.currentDirectory,
outputDir: outputDirectory, outputDir: outputDirectory,
buildDir: flutterProject.directory buildDir: flutterProject.directory
......
...@@ -637,7 +637,7 @@ void main() { ...@@ -637,7 +637,7 @@ void main() {
environment.outputDir.childDirectory('a').childFile('a.txt') environment.outputDir.childDirectory('a').childFile('a.txt')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('A'); ..writeAsStringSync('A');
await const WebServiceWorker().build(environment); await WebServiceWorker(globals.fs).build(environment);
expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists);
// Contains file hash. // Contains file hash.
...@@ -656,7 +656,7 @@ void main() { ...@@ -656,7 +656,7 @@ void main() {
environment.outputDir environment.outputDir
.childFile('index.html') .childFile('index.html')
.createSync(recursive: true); .createSync(recursive: true);
await const WebServiceWorker().build(environment); await WebServiceWorker(globals.fs).build(environment);
expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists);
// Contains file hash for both `/` and index.html. // Contains file hash for both `/` and index.html.
...@@ -674,7 +674,7 @@ void main() { ...@@ -674,7 +674,7 @@ void main() {
environment.outputDir environment.outputDir
.childFile('main.dart.js.map') .childFile('main.dart.js.map')
.createSync(recursive: true); .createSync(recursive: true);
await const WebServiceWorker().build(environment); await WebServiceWorker(globals.fs).build(environment);
// No caching of source maps. // No caching of source maps.
expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
......
...@@ -728,20 +728,49 @@ void main() { ...@@ -728,20 +728,49 @@ void main() {
expect(logger.errorText, contains('Failed to delete some stamp files')); expect(logger.errorText, contains('Failed to delete some stamp files'));
}); });
testWithoutContext('FlutterWebSdk deletes previous directory contents', () { testWithoutContext('FlutterWebSdk fetches web artifacts and deletes previous directory contents', () async {
final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final File canvasKitVersionFile = fileSystem.currentDirectory
.childDirectory('cache')
.childDirectory('bin')
.childDirectory('internal')
.childFile('canvaskit.version');
canvasKitVersionFile.createSync(recursive: true);
canvasKitVersionFile.writeAsStringSync('abcdefg');
final Cache cache = Cache.test(processManager: FakeProcessManager.any(), fileSystem: fileSystem); final Cache cache = Cache.test(processManager: FakeProcessManager.any(), fileSystem: fileSystem);
final Directory webCacheDirectory = cache.getWebSdkDirectory(); final Directory webCacheDirectory = cache.getWebSdkDirectory();
final FakeArtifactUpdater artifactUpdater = FakeArtifactUpdater(); final FakeArtifactUpdater artifactUpdater = FakeArtifactUpdater();
final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform()); final FlutterWebSdk webSdk = FlutterWebSdk(cache, platform: FakePlatform());
final List<String> messages = <String>[];
final List<String> downloads = <String>[];
final List<String> locations = <String>[];
artifactUpdater.onDownloadZipArchive = (String message, Uri uri, Directory location) { artifactUpdater.onDownloadZipArchive = (String message, Uri uri, Directory location) {
messages.add(message);
downloads.add(uri.toString());
locations.add(location.path);
location.createSync(recursive: true); location.createSync(recursive: true);
location.childFile('foo').createSync(); location.childFile('foo').createSync();
}; };
webCacheDirectory.childFile('bar').createSync(recursive: true); webCacheDirectory.childFile('bar').createSync(recursive: true);
webSdk.updateInner(artifactUpdater, fileSystem, FakeOperatingSystemUtils()); await webSdk.updateInner(artifactUpdater, fileSystem, FakeOperatingSystemUtils());
expect(messages, <String>[
'Downloading Web SDK...',
'Downloading CanvasKit...',
]);
expect(downloads, <String>[
'https://storage.googleapis.com/flutter_infra_release/flutter/null/flutter-web-sdk-linux-x64.zip',
'https://chrome-infra-packages.appspot.com/dl/flutter/web/canvaskit_bundle/+/abcdefg',
]);
expect(locations, <String>[
'cache/bin/cache/flutter_web_sdk',
'cache/bin/cache/flutter_web_sdk',
]);
expect(webCacheDirectory.childFile('foo'), exists); expect(webCacheDirectory.childFile('foo'), exists);
expect(webCacheDirectory.childFile('bar'), isNot(exists)); expect(webCacheDirectory.childFile('bar'), isNot(exists));
......
...@@ -13,6 +13,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -13,6 +13,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/test/flutter_web_goldens.dart'; import 'package:flutter_tools/src/test/flutter_web_goldens.dart';
import 'package:flutter_tools/src/test/test_compiler.dart'; import 'package:flutter_tools/src/test/test_compiler.dart';
import 'package:flutter_tools/src/web/compile.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -45,7 +46,12 @@ void main() { ...@@ -45,7 +46,12 @@ void main() {
'--non-interactive', '--non-interactive',
'--packages=.dart_tool/package_config.json', '--packages=.dart_tool/package_config.json',
'compiler_output' 'compiler_output'
], stdout: '${jsonEncode(expectedResponse)}\n', ],
stdout: '${jsonEncode(expectedResponse)}\n',
environment: const <String, String>{
'FLUTTER_TEST_BROWSER': 'chrome',
'FLUTTER_WEB_RENDERER': 'html',
},
)); ));
final TestGoldenComparator comparator = TestGoldenComparator( final TestGoldenComparator comparator = TestGoldenComparator(
...@@ -54,6 +60,7 @@ void main() { ...@@ -54,6 +60,7 @@ void main() {
processManager: processManager, processManager: processManager,
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
webRenderer: WebRendererMode.html,
); );
final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false); final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
...@@ -82,6 +89,7 @@ void main() { ...@@ -82,6 +89,7 @@ void main() {
processManager: processManager, processManager: processManager,
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
webRenderer: WebRendererMode.canvaskit,
); );
final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false); final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
...@@ -114,6 +122,7 @@ void main() { ...@@ -114,6 +122,7 @@ void main() {
processManager: processManager, processManager: processManager,
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
webRenderer: WebRendererMode.html,
); );
final String result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false); final String result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
...@@ -158,6 +167,7 @@ void main() { ...@@ -158,6 +167,7 @@ void main() {
processManager: processManager, processManager: processManager,
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
webRenderer: WebRendererMode.canvaskit,
); );
final String result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false); final String result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
...@@ -192,6 +202,7 @@ void main() { ...@@ -192,6 +202,7 @@ void main() {
processManager: processManager, processManager: processManager,
fileSystem: fileSystem, fileSystem: fileSystem,
logger: BufferLogger.test(), logger: BufferLogger.test(),
webRenderer: WebRendererMode.html,
); );
final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false); final String result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
......
...@@ -190,7 +190,7 @@ void testWithoutContext(String description, FutureOr<void> Function() body, { ...@@ -190,7 +190,7 @@ void testWithoutContext(String description, FutureOr<void> Function() body, {
List<String>? tags, List<String>? tags,
Map<String, dynamic>? onPlatform, Map<String, dynamic>? onPlatform,
int? retry, int? retry,
}) { }) {
return test( return test(
description, () async { description, () async {
return runZoned(body, zoneValues: <Object, Object>{ return runZoned(body, zoneValues: <Object, Object>{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment