Skip to content

Commit d90e5b7

Browse files
author
Harry Terkelsen
authored
[web] Allow tests to run with multiple renderers (flutter#37061)
1 parent ec1ba84 commit d90e5b7

File tree

7 files changed

+178
-47
lines changed

7 files changed

+178
-47
lines changed

lib/web_ui/dev/steps/compile_tests_step.dart

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -194,37 +194,20 @@ Future<void> copyCanvasKitFiles({bool useLocalCanvasKit = false}) async {
194194
Future<void> compileTests(List<FilePath> testFiles) async {
195195
final Stopwatch stopwatch = Stopwatch()..start();
196196

197-
// Separate HTML targets from CanvasKit targets because the two use
198-
// different dart2js options.
199-
final List<FilePath> htmlTargets = <FilePath>[];
200-
final List<FilePath> canvasKitTargets = <FilePath>[];
201-
final List<FilePath> skwasmTargets = <FilePath>[];
202-
final String canvasKitTestDirectory =
203-
pathlib.join(environment.webUiTestDir.path, 'canvaskit');
204-
final String skwasmTestDirectory =
205-
pathlib.join(environment.webUiTestDir.path, 'skwasm');
206-
for (final FilePath testFile in testFiles) {
207-
if (pathlib.isWithin(canvasKitTestDirectory, testFile.absolute)) {
208-
canvasKitTargets.add(testFile);
209-
} else if (pathlib.isWithin(skwasmTestDirectory, testFile.absolute)) {
210-
skwasmTargets.add(testFile);
211-
} else {
212-
htmlTargets.add(testFile);
213-
}
214-
}
197+
final TestsByRenderer sortedTests = sortTestsByRenderer(testFiles);
215198

216199
await Future.wait(<Future<void>>[
217-
if (htmlTargets.isNotEmpty)
218-
_compileTestsInParallel(targets: htmlTargets),
219-
if (canvasKitTargets.isNotEmpty)
220-
_compileTestsInParallel(targets: canvasKitTargets, forCanvasKit: true),
221-
if (skwasmTargets.isNotEmpty)
222-
_compileTestsInParallel(targets: skwasmTargets, forSkwasm: true),
200+
if (sortedTests.htmlTests.isNotEmpty)
201+
_compileTestsInParallel(targets: sortedTests.htmlTests),
202+
if (sortedTests.canvasKitTests.isNotEmpty)
203+
_compileTestsInParallel(targets: sortedTests.canvasKitTests, renderer: Renderer.canvasKit),
204+
if (sortedTests.skwasmTests.isNotEmpty)
205+
_compileTestsInParallel(targets: sortedTests.skwasmTests, renderer: Renderer.skwasm),
223206
]);
224207

225208
stopwatch.stop();
226209

227-
final int targetCount = htmlTargets.length + canvasKitTargets.length;
210+
final int targetCount = sortedTests.numTargetsToCompile;
228211
print(
229212
'Built $targetCount tests in ${stopwatch.elapsedMilliseconds ~/ 1000} '
230213
'seconds using $_dart2jsConcurrency concurrent dart2js processes.',
@@ -239,12 +222,11 @@ final Pool _dart2jsPool = Pool(_dart2jsConcurrency);
239222
/// Spawns multiple dart2js processes to compile [targets] in parallel.
240223
Future<void> _compileTestsInParallel({
241224
required List<FilePath> targets,
242-
bool forCanvasKit = false,
243-
bool forSkwasm = false,
225+
Renderer renderer = Renderer.html,
244226
}) async {
245227
final Stream<bool> results = _dart2jsPool.forEach(
246228
targets,
247-
(FilePath file) => compileUnitTest(file, forCanvasKit: forCanvasKit, forSkwasm: forSkwasm),
229+
(FilePath file) => compileUnitTest(file, renderer: renderer),
248230
);
249231
await for (final bool isSuccess in results) {
250232
if (!isSuccess) {
@@ -261,7 +243,7 @@ Future<void> _compileTestsInParallel({
261243
/// Dart2js creates the following outputs:
262244
/// - target.browser_test.dart.js
263245
/// - target.browser_test.dart.js.deps
264-
/// - target.browser_test.dart.js.maps
246+
/// - target.browser_test.dart.js.map
265247
/// under the same directory with test file. If all these files are not in
266248
/// the same directory, Chrome dev tools cannot load the source code during
267249
/// debug.
@@ -270,14 +252,18 @@ Future<void> _compileTestsInParallel({
270252
/// directory before test are build. See [_copyFilesFromTestToBuild].
271253
///
272254
/// Later the extra files will be deleted in [_cleanupExtraFilesUnderTestDir].
273-
Future<bool> compileUnitTest(FilePath input, {required bool forCanvasKit, required bool forSkwasm}) async {
255+
Future<bool> compileUnitTest(FilePath input, {required Renderer renderer}) async {
256+
// Compile to different directories for different renderers. This allows us
257+
// to run the same test in multiple renderers.
274258
final String targetFileName = pathlib.join(
275259
environment.webUiBuildDir.path,
260+
getBuildDirForRenderer(renderer),
276261
'${input.relativeToWebUi}.browser_test.dart.js',
277262
);
278263

279264
final io.Directory directoryToTarget = io.Directory(pathlib.join(
280265
environment.webUiBuildDir.path,
266+
getBuildDirForRenderer(renderer),
281267
pathlib.dirname(input.relativeToWebUi)));
282268

283269
if (!directoryToTarget.existsSync()) {
@@ -295,8 +281,8 @@ Future<bool> compileUnitTest(FilePath input, {required bool forCanvasKit, requir
295281
// are designed to run in one specific mode. So instead, we specify the
296282
// renderer explicitly.
297283
'-DFLUTTER_WEB_AUTO_DETECT=false',
298-
'-DFLUTTER_WEB_USE_SKIA=$forCanvasKit',
299-
'-DFLUTTER_WEB_USE_SKWASM=$forSkwasm',
284+
'-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvasKit}',
285+
'-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}',
300286

301287
'-O2',
302288
'-o',

lib/web_ui/dev/steps/run_tests_step.dart

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,46 @@ class RunTestsStep implements PipelineStep {
6868
final SkiaGoldClient? skiaClient = await _createSkiaClient();
6969
final List<FilePath> testFiles = this.testFiles ?? findAllTests();
7070

71-
await _runTestBatch(
72-
testFiles: testFiles,
73-
browserEnvironment: browserEnvironment,
74-
expectFailure: false,
75-
isDebug: isDebug,
76-
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
77-
skiaClient: skiaClient,
78-
overridePathToCanvasKit: overridePathToCanvasKit,
79-
);
71+
final TestsByRenderer sortedTests = sortTestsByRenderer(testFiles);
72+
73+
if (sortedTests.htmlTests.isNotEmpty) {
74+
await _runTestBatch(
75+
testFiles: sortedTests.htmlTests,
76+
renderer: Renderer.html,
77+
browserEnvironment: browserEnvironment,
78+
expectFailure: false,
79+
isDebug: isDebug,
80+
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
81+
skiaClient: skiaClient,
82+
overridePathToCanvasKit: overridePathToCanvasKit,
83+
);
84+
}
85+
86+
if (sortedTests.canvasKitTests.isNotEmpty) {
87+
await _runTestBatch(
88+
testFiles: sortedTests.canvasKitTests,
89+
renderer: Renderer.canvasKit,
90+
browserEnvironment: browserEnvironment,
91+
expectFailure: false,
92+
isDebug: isDebug,
93+
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
94+
skiaClient: skiaClient,
95+
overridePathToCanvasKit: overridePathToCanvasKit,
96+
);
97+
}
98+
99+
if (sortedTests.skwasmTests.isNotEmpty) {
100+
await _runTestBatch(
101+
testFiles: sortedTests.skwasmTests,
102+
renderer: Renderer.skwasm,
103+
browserEnvironment: browserEnvironment,
104+
expectFailure: false,
105+
isDebug: isDebug,
106+
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
107+
skiaClient: skiaClient,
108+
overridePathToCanvasKit: overridePathToCanvasKit,
109+
);
110+
}
80111

81112
await browserEnvironment.cleanup();
82113

@@ -143,6 +174,7 @@ Future<void> _prepareTestResultsDirectory() async {
143174
/// value if any tests fail.
144175
Future<void> _runTestBatch({
145176
required List<FilePath> testFiles,
177+
required Renderer renderer,
146178
required bool isDebug,
147179
required BrowserEnvironment browserEnvironment,
148180
required bool doUpdateScreenshotGoldens,
@@ -154,6 +186,10 @@ Future<void> _runTestBatch({
154186
environment.webUiRootDir.path,
155187
browserEnvironment.packageTestConfigurationYamlFile,
156188
);
189+
final String precompiledBuildDir = pathlib.join(
190+
environment.webUiBuildDir.path,
191+
getBuildDirForRenderer(renderer),
192+
);
157193
final List<String> testArgs = <String>[
158194
...<String>['-r', 'compact'],
159195
// Disable concurrency. Running with concurrency proved to be flaky.
@@ -163,7 +199,7 @@ Future<void> _runTestBatch({
163199
if (expectFailure)
164200
'--reporter=name-only',
165201
'--platform=${browserEnvironment.packageTestRuntime.identifier}',
166-
'--precompiled=${environment.webUiBuildDir.path}',
202+
'--precompiled=$precompiledBuildDir',
167203
'--configuration=$configurationFilePath',
168204
'--',
169205
...testFiles.map((FilePath f) => f.relativeToWebUi),
@@ -183,6 +219,7 @@ Future<void> _runTestBatch({
183219
], () {
184220
return BrowserPlatform.start(
185221
browserEnvironment: browserEnvironment,
222+
renderer: renderer,
186223
// It doesn't make sense to update a screenshot for a test that is
187224
// expected to fail.
188225
doUpdateScreenshotGoldens: !expectFailure && doUpdateScreenshotGoldens,

lib/web_ui/dev/test_platform.dart

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ import 'package:web_test_utils/image_compare.dart';
3838

3939
import 'browser.dart';
4040
import 'environment.dart' as env;
41+
import 'utils.dart';
4142

4243
/// Custom test platform that serves web engine unit tests.
4344
class BrowserPlatform extends PlatformPlugin {
4445
BrowserPlatform._({
4546
required this.browserEnvironment,
4647
required this.server,
48+
required this.renderer,
4749
required this.isDebug,
4850
required this.doUpdateScreenshotGoldens,
4951
required this.packageConfig,
@@ -100,6 +102,7 @@ class BrowserPlatform extends PlatformPlugin {
100102
/// instead of failing the test on screenshot mismatches.
101103
static Future<BrowserPlatform> start({
102104
required BrowserEnvironment browserEnvironment,
105+
required Renderer renderer,
103106
required bool doUpdateScreenshotGoldens,
104107
required SkiaGoldClient? skiaClient,
105108
required String? overridePathToCanvasKit,
@@ -108,6 +111,7 @@ class BrowserPlatform extends PlatformPlugin {
108111
shelf_io.IOServer(await HttpMultiServer.loopback(0));
109112
return BrowserPlatform._(
110113
browserEnvironment: browserEnvironment,
114+
renderer: renderer,
111115
server: server,
112116
isDebug: Configuration.current.pauseAfterLoad,
113117
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
@@ -128,6 +132,9 @@ class BrowserPlatform extends PlatformPlugin {
128132
/// Provides the environment for the browser running tests.
129133
final BrowserEnvironment browserEnvironment;
130134

135+
/// The renderer that tests are running under.
136+
final Renderer renderer;
137+
131138
/// The URL for this server.
132139
Uri get url => server.url.resolve('/');
133140

@@ -384,11 +391,21 @@ class BrowserPlatform extends PlatformPlugin {
384391
///
385392
/// This is used for trivial use-cases, such as `favicon.ico`, host pages, etc.
386393
shelf.Response buildDirectoryHandler(shelf.Request request) {
387-
final File fileInBuild = File(p.join(
394+
File fileInBuild = File(p.join(
388395
env.environment.webUiBuildDir.path,
389396
request.url.path,
390397
));
391398

399+
// If we can't find the file in the top-level `build` directory, then it
400+
// may be in the renderer-specific `build` subdirectory.
401+
if (!fileInBuild.existsSync()) {
402+
fileInBuild = File(p.join(
403+
env.environment.webUiBuildDir.path,
404+
getBuildDirForRenderer(renderer),
405+
request.url.path,
406+
));
407+
}
408+
392409
if (!fileInBuild.existsSync()) {
393410
return shelf.Response.notFound('File not found: ${request.url.path}');
394411
}
@@ -517,6 +534,7 @@ class BrowserPlatform extends PlatformPlugin {
517534
future: completer.future,
518535
packageConfig: packageConfig,
519536
debug: isDebug,
537+
renderer: renderer,
520538
);
521539

522540
// Store null values for browsers that error out so we know not to load them
@@ -612,7 +630,7 @@ class BrowserManager {
612630
/// Creates a new BrowserManager that communicates with the browser over
613631
/// [webSocket].
614632
BrowserManager._(this.packageConfig, this._browser, this._browserEnvironment,
615-
WebSocketChannel webSocket) {
633+
this._renderer, WebSocketChannel webSocket) {
616634
// The duration should be short enough that the debugging console is open as
617635
// soon as the user is done setting breakpoints, but long enough that a test
618636
// doing a lot of synchronous work doesn't trigger a false positive.
@@ -658,6 +676,9 @@ class BrowserManager {
658676
/// The browser environment for this test.
659677
final BrowserEnvironment _browserEnvironment;
660678

679+
/// The renderer for this test.
680+
final Renderer _renderer;
681+
661682
/// The channel used to communicate with the browser.
662683
///
663684
/// This is connected to a page running `static/host.dart`.
@@ -722,6 +743,7 @@ class BrowserManager {
722743
required Uri url,
723744
required Future<WebSocketChannel> future,
724745
required PackageConfig packageConfig,
746+
required Renderer renderer,
725747
bool debug = false,
726748
}) async {
727749
final Browser browser =
@@ -732,6 +754,7 @@ class BrowserManager {
732754
future: future,
733755
packageConfig: packageConfig,
734756
browser: browser,
757+
renderer: renderer,
735758
debug: debug);
736759
}
737760

@@ -741,6 +764,7 @@ class BrowserManager {
741764
required Future<WebSocketChannel> future,
742765
required PackageConfig packageConfig,
743766
required Browser browser,
767+
required Renderer renderer,
744768
bool debug = false,
745769
}) {
746770
final Completer<BrowserManager> completer = Completer<BrowserManager>();
@@ -762,7 +786,7 @@ class BrowserManager {
762786
return;
763787
}
764788
completer.complete(BrowserManager._(
765-
packageConfig, browser, browserEnvironment, webSocket));
789+
packageConfig, browser, browserEnvironment, renderer, webSocket));
766790
}).catchError((Object error, StackTrace stackTrace) {
767791
browser.close();
768792
if (completer.isCompleted) {
@@ -846,7 +870,7 @@ class BrowserManager {
846870
final String pathToTest = p.dirname(path);
847871

848872
final String mapPath = p.join(env.environment.webUiRootDir.path,
849-
'build', pathToTest, sourceMapFileName);
873+
'build', getBuildDirForRenderer(_renderer), pathToTest, sourceMapFileName);
850874

851875
final Map<String, Uri> packageMap = <String, Uri>{
852876
for (Package p in packageConfig.packages) p.name: p.packageUriRoot

lib/web_ui/dev/utils.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,68 @@ List<FilePath> findAllTests() {
379379
path.relative(f.path, from: environment.webUiRootDir.path)))
380380
.toList();
381381
}
382+
383+
/// The renderer used to run the test.
384+
enum Renderer {
385+
html,
386+
canvasKit,
387+
skwasm,
388+
}
389+
390+
/// The `FilePath`s for all the tests, organized by renderer.
391+
class TestsByRenderer {
392+
TestsByRenderer(this.htmlTests, this.canvasKitTests, this.skwasmTests);
393+
394+
/// Tests which should be run with the HTML renderer.
395+
final List<FilePath> htmlTests;
396+
397+
/// Tests which should be run with the CanvasKit renderer.
398+
final List<FilePath> canvasKitTests;
399+
400+
/// Tests which should be run with the Skwasm renderer.
401+
final List<FilePath> skwasmTests;
402+
403+
/// The total number of targets to compile.
404+
///
405+
/// The number of uiTests is doubled since they are compiled twice: once for
406+
/// the HTML renderer and once for the CanvasKit renderer.
407+
int get numTargetsToCompile => htmlTests.length + canvasKitTests.length + skwasmTests.length;
408+
}
409+
410+
/// Given a list of test files, organizes them by which renderer should run them.
411+
TestsByRenderer sortTestsByRenderer(List<FilePath> testFiles) {
412+
final List<FilePath> htmlTargets = <FilePath>[];
413+
final List<FilePath> canvasKitTargets = <FilePath>[];
414+
final List<FilePath> skwasmTargets = <FilePath>[];
415+
final String canvasKitTestDirectory =
416+
path.join(environment.webUiTestDir.path, 'canvaskit');
417+
final String skwasmTestDirectory =
418+
path.join(environment.webUiTestDir.path, 'skwasm');
419+
final String uiTestDirectory =
420+
path.join(environment.webUiTestDir.path, 'ui');
421+
for (final FilePath testFile in testFiles) {
422+
if (path.isWithin(canvasKitTestDirectory, testFile.absolute)) {
423+
canvasKitTargets.add(testFile);
424+
} else if (path.isWithin(skwasmTestDirectory, testFile.absolute)) {
425+
skwasmTargets.add(testFile);
426+
} else if (path.isWithin(uiTestDirectory, testFile.absolute)) {
427+
htmlTargets.add(testFile);
428+
canvasKitTargets.add(testFile);
429+
} else {
430+
htmlTargets.add(testFile);
431+
}
432+
}
433+
return TestsByRenderer(htmlTargets, canvasKitTargets, skwasmTargets);
434+
}
435+
436+
/// The build directory to compile a test into given the renderer.
437+
String getBuildDirForRenderer(Renderer renderer) {
438+
switch (renderer) {
439+
case Renderer.html:
440+
return 'html_tests';
441+
case Renderer.canvasKit:
442+
return 'canvaskit_tests';
443+
case Renderer.skwasm:
444+
return 'skwasm_tests';
445+
}
446+
}

0 commit comments

Comments
 (0)