Unverified Commit a76289bb authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] run web unit tests in sound null safety (#70799)

parent 6d521e9a
...@@ -1126,13 +1126,13 @@ Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) asy ...@@ -1126,13 +1126,13 @@ 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',
'--sound-null-safety', // web tests do not autodetect yet.
...?flutterTestArgs, ...?flutterTestArgs,
...tests, ...tests,
], ],
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
environment: <String, String>{ environment: <String, String>{
'FLUTTER_WEB': 'true', 'FLUTTER_WEB': 'true',
'FLUTTER_LOW_RESOURCE_MODE': 'true',
}, },
); );
} }
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'dart:js' as js; import 'dart:js' as js;
import 'package:flutter_driver/src/extension/_extension_web.dart'; import 'package:flutter_driver/src/extension/_extension_web.dart';
...@@ -10,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -10,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
group('test web_extension', () { group('test web_extension', () {
Future<Map<String, dynamic>> Function(Map<String, String>) call; late Future<Map<String, dynamic>> Function(Map<String, String>) call;
setUp(() { setUp(() {
call = (Map<String, String> args) async { call = (Map<String, String> args) async {
......
...@@ -35,18 +35,9 @@ import '../globals.dart' as globals; ...@@ -35,18 +35,9 @@ import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import '../web/bootstrap.dart'; import '../web/bootstrap.dart';
import '../web/chrome.dart'; import '../web/chrome.dart';
import '../web/compile.dart';
import '../web/memory_fs.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( typedef DwdsLauncher = Future<Dwds> Function(
{@required AssetReader assetReader, {@required AssetReader assetReader,
@required Stream<BuildResult> buildResults, @required Stream<BuildResult> buildResults,
...@@ -549,46 +540,14 @@ class WebAssetServer implements AssetReader { ...@@ -549,46 +540,14 @@ class WebAssetServer implements AssetReader {
return webSdkFile; 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 => File get _resolveDartSdkJsFile =>
globals.fs.file(globals.artifacts.getArtifactPath( globals.fs.file(globals.artifacts.getArtifactPath(
_dartSdkJsArtifactMap[webRenderer][_nullSafetyMode] kDartSdkJsArtifactMap[webRenderer][_nullSafetyMode]
)); ));
File get _resolveDartSdkJsMapFile => File get _resolveDartSdkJsMapFile =>
globals.fs.file(globals.artifacts.getArtifactPath( globals.fs.file(globals.artifacts.getArtifactPath(
_dartSdkJsMapArtifactMap[webRenderer][_nullSafetyMode] kDartSdkJsMapArtifactMap[webRenderer][_nullSafetyMode]
)); ));
@override @override
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
...@@ -10,6 +11,7 @@ import '../base/file_system.dart'; ...@@ -10,6 +11,7 @@ import '../base/file_system.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../bundle.dart'; import '../bundle.dart';
import '../compile.dart'; import '../compile.dart';
import '../dart/language_version.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../web/compile.dart'; import '../web/compile.dart';
import '../web/memory_fs.dart'; import '../web/memory_fs.dart';
...@@ -27,13 +29,24 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { ...@@ -27,13 +29,24 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
@required List<String> testFiles, @required List<String> testFiles,
@required BuildInfo buildInfo, @required BuildInfo buildInfo,
}) async { }) async {
if (buildInfo.nullSafetyMode == NullSafetyMode.sound) { LanguageVersion languageVersion = LanguageVersion(2, 8);
throwToolExit('flutter test --platform=chrome does not currently support sound mode'); 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>[]); final List<String> extraFrontEndOptions = List<String>.of(buildInfo.extraFrontEndOptions ?? <String>[]);
if (!extraFrontEndOptions.contains('--no-sound-null-safety')) { if (buildInfo.nullSafetyMode == NullSafetyMode.unsound || buildInfo.nullSafetyMode == NullSafetyMode.autodetect) {
extraFrontEndOptions.add('--no-sound-null-safety'); 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) final Directory outputDirectory = globals.fs.directory(testOutputDir)
..createSync(recursive: true); ..createSync(recursive: true);
final List<File> generatedFiles = <File>[]; final List<File> generatedFiles = <File>[];
...@@ -44,12 +57,12 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { ...@@ -44,12 +57,12 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
globals.fs.path.join(outputDirectory.path, '${relativeTestSegments.join('_')}.test.dart')); globals.fs.path.join(outputDirectory.path, '${relativeTestSegments.join('_')}.test.dart'));
generatedFile generatedFile
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync(_generateEntrypoint(relativeTestSegments.join('/'), testFilePath)); ..writeAsStringSync(_generateEntrypoint(relativeTestSegments.join('/'), testFilePath, languageVersion));
generatedFiles.add(generatedFile); generatedFiles.add(generatedFile);
} }
// Generate a fake main file that imports all tests to be executed. This will force // Generate a fake main file that imports all tests to be executed. This will force
// each of them to be compiled. // 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) { for (final File generatedFile in generatedFiles) {
buffer.writeln('import "${globals.fs.path.basename(generatedFile.path)}";'); buffer.writeln('import "${globals.fs.path.basename(generatedFile.path)}";');
} }
...@@ -77,7 +90,7 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { ...@@ -77,7 +90,7 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
targetModel: TargetModel.dartdevc, targetModel: TargetModel.dartdevc,
extraFrontEndOptions: extraFrontEndOptions, extraFrontEndOptions: extraFrontEndOptions,
platformDill: globals.fs.file(globals.artifacts platformDill: globals.fs.file(globals.artifacts
.getArtifactPath(Artifact.webPlatformKernelDill, mode: buildInfo.mode)) .getArtifactPath(platformDillArtifact, mode: buildInfo.mode))
.absolute.uri.toString(), .absolute.uri.toString(),
dartDefines: buildInfo.dartDefines, dartDefines: buildInfo.dartDefines,
librariesSpec: globals.fs.file(globals.artifacts librariesSpec: globals.fs.file(globals.artifacts
...@@ -106,9 +119,9 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { ...@@ -106,9 +119,9 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
..write(codeFile, manifestFile, sourcemapFile, metadataFile); ..write(codeFile, manifestFile, sourcemapFile, metadataFile);
} }
String _generateEntrypoint(String relativeTestPath, String absolutePath) { String _generateEntrypoint(String relativeTestPath, String absolutePath, LanguageVersion languageVersion) {
return ''' return '''
// @dart = 2.8 // @dart = ${languageVersion.major}.${languageVersion.minor}
import 'org-dartlang-app:///$relativeTestPath' as test; import 'org-dartlang-app:///$relativeTestPath' as test;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:html'; import 'dart:html';
...@@ -137,7 +150,7 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { ...@@ -137,7 +150,7 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
postMessageChannel().pipe(channel); 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) { StreamChannel suiteChannel(String name) {
var manager = SuiteChannelManager.current; var manager = SuiteChannelManager.current;
......
...@@ -149,7 +149,6 @@ class TestGoldenComparatorProcess { ...@@ -149,7 +149,6 @@ class TestGoldenComparatorProcess {
final File testConfigFile = findTestConfigFile(globals.fs.file(testUri)); final File testConfigFile = findTestConfigFile(globals.fs.file(testUri));
// Generate comparator process for the file. // Generate comparator process for the file.
return ''' return '''
// @dart=2.9
import 'dart:convert'; // ignore: dart_convert_import import 'dart:convert'; // ignore: dart_convert_import
import 'dart:io'; // ignore: dart_io_import import 'dart:io'; // ignore: dart_io_import
...@@ -165,12 +164,12 @@ void main() async { ...@@ -165,12 +164,12 @@ void main() async {
final commands = stdin final commands = stdin
.transform<String>(utf8.decoder) .transform<String>(utf8.decoder)
.transform<String>(const LineSplitter()) .transform<String>(const LineSplitter())
.map<Object>(jsonDecode); .map<dynamic>(jsonDecode);
await for (final Object command in commands) { await for (final dynamic command in commands) {
if (command is Map<String, dynamic>) { if (command is Map<String, dynamic>) {
File imageFile = File(command['imageFile']); File imageFile = File(command['imageFile'] as String);
Uri goldenKey = Uri.parse(command['key']); Uri goldenKey = Uri.parse(command['key'] as String);
bool update = command['update']; bool update = command['update'] as bool;
final bytes = await File(imageFile.path).readAsBytes(); final bytes = await File(imageFile.path).readAsBytes();
if (update) { if (update) {
......
...@@ -32,6 +32,7 @@ import '../convert.dart'; ...@@ -32,6 +32,7 @@ import '../convert.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../project.dart'; import '../project.dart';
import '../web/chrome.dart'; import '../web/chrome.dart';
import '../web/compile.dart';
import '../web/memory_fs.dart'; import '../web/memory_fs.dart';
import 'flutter_web_goldens.dart'; import 'flutter_web_goldens.dart';
import 'test_compiler.dart'; import 'test_compiler.dart';
...@@ -158,13 +159,13 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -158,13 +159,13 @@ class FlutterWebPlatform extends PlatformPlugin {
'dart_stack_trace_mapper.js', 'dart_stack_trace_mapper.js',
)); ));
/// The precompiled dart sdk. File get _dartSdk => _fileSystem.file(_artifacts.getArtifactPath(kDartSdkJsArtifactMap[WebRendererMode.html][
File get _dartSdk => _fileSystem.file(_fileSystem.path.join( buildInfo.nullSafetyMode == NullSafetyMode.sound ? NullSafetyMode.sound : NullSafetyMode.unsound
_artifacts.getArtifactPath(Artifact.flutterWebSdk), ]));
'kernel',
'amd', File get _dartSdkSourcemaps => _fileSystem.file(_artifacts.getArtifactPath(kDartSdkJsMapArtifactMap[WebRendererMode.html][
'dart_sdk.js', buildInfo.nullSafetyMode == NullSafetyMode.sound ? NullSafetyMode.sound : NullSafetyMode.unsound
)); ]));
/// The precompiled test javascript. /// The precompiled test javascript.
File get _testDartJs => _fileSystem.file(_fileSystem.path.join( File get _testDartJs => _fileSystem.file(_fileSystem.path.join(
...@@ -223,6 +224,11 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -223,6 +224,11 @@ class FlutterWebPlatform extends PlatformPlugin {
_dartSdk.openRead(), _dartSdk.openRead(),
headers: <String, String>{'Content-Type': 'text/javascript'}, 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 } else if (request.requestedUri.path
.contains('dart_stack_trace_mapper.js')) { .contains('dart_stack_trace_mapper.js')) {
return shelf.Response.ok( return shelf.Response.ok(
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
...@@ -106,3 +107,45 @@ class WebCompilationProxy { ...@@ -106,3 +107,45 @@ class WebCompilationProxy {
throw UnimplementedError(); 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,
},
};
...@@ -14,6 +14,7 @@ import 'package:flutter_tools/src/isolated/devfs_web.dart'; ...@@ -14,6 +14,7 @@ import 'package:flutter_tools/src/isolated/devfs_web.dart';
import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/web/compile.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:package_config/package_config.dart'; import 'package:package_config/package_config.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
......
...@@ -121,8 +121,8 @@ class _PlatformBinaryMessenger extends BinaryMessenger { ...@@ -121,8 +121,8 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
/// Sends a platform message from the platform side back to the framework. /// Sends a platform message from the platform side back to the framework.
@override @override
Future<ByteData> send(String channel, ByteData? message) { Future<ByteData?> send(String channel, ByteData? message) {
final Completer<ByteData> completer = Completer<ByteData>(); final Completer<ByteData?> completer = Completer<ByteData?>();
ui.window.onPlatformMessage!(channel, message, (ByteData? reply) { ui.window.onPlatformMessage!(channel, message, (ByteData? reply) {
try { try {
completer.complete(reply); completer.complete(reply);
......
...@@ -57,7 +57,7 @@ void main() { ...@@ -57,7 +57,7 @@ void main() {
ServicesBinding.instance!.defaultBinaryMessenger ServicesBinding.instance!.defaultBinaryMessenger
.setMessageHandler('test_send', (ByteData? data) { .setMessageHandler('test_send', (ByteData? data) {
loggedMessages.add(codec.decodeMessage(data) as String); loggedMessages.add(codec.decodeMessage(data) as String);
return null; return Future<ByteData?>.value(null);
}); });
await pluginBinaryMessenger.send( await pluginBinaryMessenger.send(
......
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