[flutter_tools] run web unit tests in sound null safety (#70799)
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 8bd0494..2aca003 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -1126,13 +1126,13 @@
'--concurrency=1', // do not parallelize on Cirrus, to reduce flakiness
'-v',
'--platform=chrome',
+ '--sound-null-safety', // web tests do not autodetect yet.
...?flutterTestArgs,
...tests,
],
workingDirectory: workingDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
- 'FLUTTER_LOW_RESOURCE_MODE': 'true',
},
);
}
diff --git a/packages/flutter_driver/test/src/web_tests/web_extension_test.dart b/packages/flutter_driver/test/src/web_tests/web_extension_test.dart
index 6cdb0c8..c65ef3e 100644
--- a/packages/flutter_driver/test/src/web_tests/web_extension_test.dart
+++ b/packages/flutter_driver/test/src/web_tests/web_extension_test.dart
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.8
import 'dart:js' as js;
import 'package:flutter_driver/src/extension/_extension_web.dart';
@@ -10,7 +9,7 @@
void main() {
group('test web_extension', () {
- Future<Map<String, dynamic>> Function(Map<String, String>) call;
+ late Future<Map<String, dynamic>> Function(Map<String, String>) call;
setUp(() {
call = (Map<String, String> args) async {
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
index c17e3b6..2c24a79 100644
--- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart
+++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -35,18 +35,9 @@
import '../project.dart';
import '../web/bootstrap.dart';
import '../web/chrome.dart';
+import '../web/compile.dart';
import '../web/memory_fs.dart';
-/// Web rendering backend mode.
-enum WebRendererMode {
- /// Auto detects which rendering backend to use.
- autoDetect,
- /// Always uses canvaskit.
- canvaskit,
- /// Always uses html.
- html,
-}
-
typedef DwdsLauncher = Future<Dwds> Function(
{@required AssetReader assetReader,
@required Stream<BuildResult> buildResults,
@@ -549,46 +540,14 @@
return webSdkFile;
}
- static const Map<WebRendererMode, Map<NullSafetyMode, Artifact>> _dartSdkJsArtifactMap =
- <WebRendererMode, Map<NullSafetyMode, Artifact>> {
- WebRendererMode.autoDetect: <NullSafetyMode, Artifact> {
- NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdk,
- NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitAndHtmlSdk,
- },
- WebRendererMode.canvaskit: <NullSafetyMode, Artifact> {
- NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitSoundSdk,
- NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitSdk,
- },
- WebRendererMode.html: <NullSafetyMode, Artifact> {
- NullSafetyMode.sound: Artifact.webPrecompiledSoundSdk,
- NullSafetyMode.unsound: Artifact.webPrecompiledSdk,
- },
- };
-
- static const Map<WebRendererMode, Map<NullSafetyMode, Artifact>> _dartSdkJsMapArtifactMap =
- <WebRendererMode, Map<NullSafetyMode, Artifact>> {
- WebRendererMode.autoDetect: <NullSafetyMode, Artifact> {
- NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps,
- NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps,
- },
- WebRendererMode.canvaskit: <NullSafetyMode, Artifact> {
- NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps,
- NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitSdkSourcemaps,
- },
- WebRendererMode.html: <NullSafetyMode, Artifact> {
- NullSafetyMode.sound: Artifact.webPrecompiledSoundSdkSourcemaps,
- NullSafetyMode.unsound: Artifact.webPrecompiledSdkSourcemaps,
- },
- };
-
File get _resolveDartSdkJsFile =>
globals.fs.file(globals.artifacts.getArtifactPath(
- _dartSdkJsArtifactMap[webRenderer][_nullSafetyMode]
+ kDartSdkJsArtifactMap[webRenderer][_nullSafetyMode]
));
File get _resolveDartSdkJsMapFile =>
globals.fs.file(globals.artifacts.getArtifactPath(
- _dartSdkJsMapArtifactMap[webRenderer][_nullSafetyMode]
+ kDartSdkJsMapArtifactMap[webRenderer][_nullSafetyMode]
));
@override
diff --git a/packages/flutter_tools/lib/src/isolated/web_compilation_delegate.dart b/packages/flutter_tools/lib/src/isolated/web_compilation_delegate.dart
index b092421..ca2d34c 100644
--- a/packages/flutter_tools/lib/src/isolated/web_compilation_delegate.dart
+++ b/packages/flutter_tools/lib/src/isolated/web_compilation_delegate.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:meta/meta.dart';
+import 'package:package_config/package_config.dart';
import '../artifacts.dart';
import '../base/common.dart';
@@ -10,6 +11,7 @@
import '../build_info.dart';
import '../bundle.dart';
import '../compile.dart';
+import '../dart/language_version.dart';
import '../globals.dart' as globals;
import '../web/compile.dart';
import '../web/memory_fs.dart';
@@ -27,13 +29,24 @@
@required List<String> testFiles,
@required BuildInfo buildInfo,
}) async {
- if (buildInfo.nullSafetyMode == NullSafetyMode.sound) {
- throwToolExit('flutter test --platform=chrome does not currently support sound mode');
- }
+ LanguageVersion languageVersion = LanguageVersion(2, 8);
+ Artifact platformDillArtifact;
+ // TODO(jonahwilliams): to support autodetect this would need to partition the source code into a
+ // a sound and unsound set and perform separate compilations.
final List<String> extraFrontEndOptions = List<String>.of(buildInfo.extraFrontEndOptions ?? <String>[]);
- if (!extraFrontEndOptions.contains('--no-sound-null-safety')) {
- extraFrontEndOptions.add('--no-sound-null-safety');
+ if (buildInfo.nullSafetyMode == NullSafetyMode.unsound || buildInfo.nullSafetyMode == NullSafetyMode.autodetect) {
+ platformDillArtifact = Artifact.webPlatformKernelDill;
+ if (!extraFrontEndOptions.contains('--no-sound-null-safety')) {
+ extraFrontEndOptions.add('--no-sound-null-safety');
+ }
+ } else if (buildInfo.nullSafetyMode == NullSafetyMode.sound) {
+ platformDillArtifact = Artifact.webPlatformSoundKernelDill;
+ languageVersion = nullSafeVersion;
+ if (!extraFrontEndOptions.contains('--sound-null-safety')) {
+ extraFrontEndOptions.add('--sound-null-safety');
+ }
}
+
final Directory outputDirectory = globals.fs.directory(testOutputDir)
..createSync(recursive: true);
final List<File> generatedFiles = <File>[];
@@ -44,12 +57,12 @@
globals.fs.path.join(outputDirectory.path, '${relativeTestSegments.join('_')}.test.dart'));
generatedFile
..createSync(recursive: true)
- ..writeAsStringSync(_generateEntrypoint(relativeTestSegments.join('/'), testFilePath));
+ ..writeAsStringSync(_generateEntrypoint(relativeTestSegments.join('/'), testFilePath, languageVersion));
generatedFiles.add(generatedFile);
}
// Generate a fake main file that imports all tests to be executed. This will force
// each of them to be compiled.
- final StringBuffer buffer = StringBuffer('// @dart=2.8\n');
+ final StringBuffer buffer = StringBuffer('// @dart=${languageVersion.major}.${languageVersion.minor}\n');
for (final File generatedFile in generatedFiles) {
buffer.writeln('import "${globals.fs.path.basename(generatedFile.path)}";');
}
@@ -77,7 +90,7 @@
targetModel: TargetModel.dartdevc,
extraFrontEndOptions: extraFrontEndOptions,
platformDill: globals.fs.file(globals.artifacts
- .getArtifactPath(Artifact.webPlatformKernelDill, mode: buildInfo.mode))
+ .getArtifactPath(platformDillArtifact, mode: buildInfo.mode))
.absolute.uri.toString(),
dartDefines: buildInfo.dartDefines,
librariesSpec: globals.fs.file(globals.artifacts
@@ -106,9 +119,9 @@
..write(codeFile, manifestFile, sourcemapFile, metadataFile);
}
- String _generateEntrypoint(String relativeTestPath, String absolutePath) {
+ String _generateEntrypoint(String relativeTestPath, String absolutePath, LanguageVersion languageVersion) {
return '''
- // @dart = 2.8
+ // @dart = ${languageVersion.major}.${languageVersion.minor}
import 'org-dartlang-app:///$relativeTestPath' as test;
import 'dart:ui' as ui;
import 'dart:html';
@@ -137,7 +150,7 @@
postMessageChannel().pipe(channel);
}
- StreamChannel serializeSuite(Function getMain(), {bool hidePrints = true, Future beforeLoad()}) => RemoteListener.start(getMain, hidePrints: hidePrints, beforeLoad: beforeLoad);
+ StreamChannel serializeSuite(Function getMain(), {bool hidePrints = true}) => RemoteListener.start(getMain, hidePrints: hidePrints);
StreamChannel suiteChannel(String name) {
var manager = SuiteChannelManager.current;
diff --git a/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart b/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart
index 5c2e0f8..cbc5456 100644
--- a/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart
@@ -149,7 +149,6 @@
final File testConfigFile = findTestConfigFile(globals.fs.file(testUri));
// Generate comparator process for the file.
return '''
-// @dart=2.9
import 'dart:convert'; // ignore: dart_convert_import
import 'dart:io'; // ignore: dart_io_import
@@ -165,12 +164,12 @@
final commands = stdin
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
- .map<Object>(jsonDecode);
- await for (final Object command in commands) {
+ .map<dynamic>(jsonDecode);
+ await for (final dynamic command in commands) {
if (command is Map<String, dynamic>) {
- File imageFile = File(command['imageFile']);
- Uri goldenKey = Uri.parse(command['key']);
- bool update = command['update'];
+ File imageFile = File(command['imageFile'] as String);
+ Uri goldenKey = Uri.parse(command['key'] as String);
+ bool update = command['update'] as bool;
final bytes = await File(imageFile.path).readAsBytes();
if (update) {
diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
index 8f7786d..375d62d 100644
--- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
@@ -32,6 +32,7 @@
import '../dart/package_map.dart';
import '../project.dart';
import '../web/chrome.dart';
+import '../web/compile.dart';
import '../web/memory_fs.dart';
import 'flutter_web_goldens.dart';
import 'test_compiler.dart';
@@ -158,13 +159,13 @@
'dart_stack_trace_mapper.js',
));
- /// The precompiled dart sdk.
- File get _dartSdk => _fileSystem.file(_fileSystem.path.join(
- _artifacts.getArtifactPath(Artifact.flutterWebSdk),
- 'kernel',
- 'amd',
- 'dart_sdk.js',
- ));
+ File get _dartSdk => _fileSystem.file(_artifacts.getArtifactPath(kDartSdkJsArtifactMap[WebRendererMode.html][
+ buildInfo.nullSafetyMode == NullSafetyMode.sound ? NullSafetyMode.sound : NullSafetyMode.unsound
+ ]));
+
+ File get _dartSdkSourcemaps => _fileSystem.file(_artifacts.getArtifactPath(kDartSdkJsMapArtifactMap[WebRendererMode.html][
+ buildInfo.nullSafetyMode == NullSafetyMode.sound ? NullSafetyMode.sound : NullSafetyMode.unsound
+ ]));
/// The precompiled test javascript.
File get _testDartJs => _fileSystem.file(_fileSystem.path.join(
@@ -223,6 +224,11 @@
_dartSdk.openRead(),
headers: <String, String>{'Content-Type': 'text/javascript'},
);
+ } else if (request.requestedUri.path.contains('dart_sdk.js.map')) {
+ return shelf.Response.ok(
+ _dartSdkSourcemaps.openRead(),
+ headers: <String, String>{'Content-Type': 'text/javascript'},
+ );
} else if (request.requestedUri.path
.contains('dart_stack_trace_mapper.js')) {
return shelf.Response.ok(
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index dcd52e1..465a094 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -4,6 +4,7 @@
import 'package:meta/meta.dart';
+import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
@@ -106,3 +107,45 @@
throw UnimplementedError();
}
}
+
+/// Web rendering backend mode.
+enum WebRendererMode {
+ /// Auto detects which rendering backend to use.
+ autoDetect,
+ /// Always uses canvaskit.
+ canvaskit,
+ /// Always uses html.
+ html,
+}
+
+/// The correct precompiled artifact to use for each build and render mode.
+const Map<WebRendererMode, Map<NullSafetyMode, Artifact>> kDartSdkJsArtifactMap = <WebRendererMode, Map<NullSafetyMode, Artifact>>{
+ WebRendererMode.autoDetect: <NullSafetyMode, Artifact> {
+ NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdk,
+ NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitAndHtmlSdk,
+ },
+ WebRendererMode.canvaskit: <NullSafetyMode, Artifact> {
+ NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitSoundSdk,
+ NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitSdk,
+ },
+ WebRendererMode.html: <NullSafetyMode, Artifact> {
+ NullSafetyMode.sound: Artifact.webPrecompiledSoundSdk,
+ NullSafetyMode.unsound: Artifact.webPrecompiledSdk,
+ },
+};
+
+/// The correct source map artifact to use for each build and render mode.
+const Map<WebRendererMode, Map<NullSafetyMode, Artifact>> kDartSdkJsMapArtifactMap = <WebRendererMode, Map<NullSafetyMode, Artifact>>{
+ WebRendererMode.autoDetect: <NullSafetyMode, Artifact> {
+ NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps,
+ NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps,
+ },
+ WebRendererMode.canvaskit: <NullSafetyMode, Artifact> {
+ NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps,
+ NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitSdkSourcemaps,
+ },
+ WebRendererMode.html: <NullSafetyMode, Artifact> {
+ NullSafetyMode.sound: Artifact.webPrecompiledSoundSdkSourcemaps,
+ NullSafetyMode.unsound: Artifact.webPrecompiledSdkSourcemaps,
+ },
+};
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
index e1e82a7..77228e4 100644
--- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
@@ -14,6 +14,7 @@
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
+import 'package:flutter_tools/src/web/compile.dart';
import 'package:mockito/mockito.dart';
import 'package:package_config/package_config.dart';
import 'package:shelf/shelf.dart';
diff --git a/packages/flutter_web_plugins/lib/src/plugin_registry.dart b/packages/flutter_web_plugins/lib/src/plugin_registry.dart
index f9a2fff..ada8aae 100644
--- a/packages/flutter_web_plugins/lib/src/plugin_registry.dart
+++ b/packages/flutter_web_plugins/lib/src/plugin_registry.dart
@@ -121,8 +121,8 @@
/// Sends a platform message from the platform side back to the framework.
@override
- Future<ByteData> send(String channel, ByteData? message) {
- final Completer<ByteData> completer = Completer<ByteData>();
+ Future<ByteData?> send(String channel, ByteData? message) {
+ final Completer<ByteData?> completer = Completer<ByteData?>();
ui.window.onPlatformMessage!(channel, message, (ByteData? reply) {
try {
completer.complete(reply);
diff --git a/packages/flutter_web_plugins/test/plugin_registry_test.dart b/packages/flutter_web_plugins/test/plugin_registry_test.dart
index 7957100..7b950d7 100644
--- a/packages/flutter_web_plugins/test/plugin_registry_test.dart
+++ b/packages/flutter_web_plugins/test/plugin_registry_test.dart
@@ -57,7 +57,7 @@
ServicesBinding.instance!.defaultBinaryMessenger
.setMessageHandler('test_send', (ByteData? data) {
loggedMessages.add(codec.decodeMessage(data) as String);
- return null;
+ return Future<ByteData?>.value(null);
});
await pluginBinaryMessenger.send(