From d30283f0a190d78b2cb16c5db30ef50868a53740 Mon Sep 17 00:00:00 2001 From: MarkZ Date: Mon, 25 Mar 2024 23:58:26 +0000 Subject: [PATCH] [ddc] Adding tests for pkg:reload_test and enabling reload tests in test bots. * Adds tests for the memory filesystem * Enables hot restart/reload tests * Adds flags to the hot reload suite * Makes path resolution logic windows-friendly * Adds test-reporting logic for trybots to hot reload tests Change-Id: Ic51a0b8a3c6f8b6de20b58b2ac185dacf444cf47 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/353788 Reviewed-by: Nicholas Shahan Reviewed-by: Nate Bosch Commit-Queue: Mark Zhou --- .../lib/js/ddc/ddc_module_loader.js | 11 +- pkg/dev_compiler/test/hot_reload_suite.dart | 161 +++++++++--- pkg/reload_test/lib/ddc_helpers.dart | 4 +- .../lib/frontend_server_controller.dart | 33 +-- .../lib/hot_reload_memory_filesystem.dart | 2 +- pkg/reload_test/lib/test_helpers.dart | 50 ++++ pkg/reload_test/pubspec.yaml | 1 + pkg/reload_test/test/filesystem_test.dart | 243 ++++++++++++++++++ .../hot_restart_constant_equality/main.0.dart | 3 - .../hot_restart_constant_equality/main.1.dart | 3 - tools/bots/test_matrix.json | 20 ++ 11 files changed, 458 insertions(+), 73 deletions(-) create mode 100644 pkg/reload_test/lib/test_helpers.dart create mode 100644 pkg/reload_test/test/filesystem_test.dart diff --git a/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js b/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js index 58354a2f6c0..c2f37e74c0d 100644 --- a/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js +++ b/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js @@ -732,6 +732,8 @@ if (!self.dart_library) { // DDCLoader, which is unique per-app. this.root = trimmedDirectory; + this.isWindows = false; + // Optional event handlers. // Called when modules begin loading. this.onLoadStart = () => { }; @@ -837,21 +839,24 @@ if (!self.dart_library) { // Joins path segments from the root directory to [script]'s path to get a // complete URL. getScriptUrl(script) { + let pathSlash = this.loadConfig.isWindows ? "\\" : "/"; // Get path segments for src - let splitSrc = script.src.toString().toString().split("/"); + let splitSrc = script.src.toString().split(pathSlash); let j = 0; // Count number of relative path segments while (splitSrc[j] == "..") { j++; } // Get path segments for root directory - let splitDir = this.loadConfig.root.split("/"); + let splitDir = !this.loadConfig.root + || this.loadConfig.root == pathSlash ? [] + : this.loadConfig.root.split(pathSlash); // Account for relative path from the root directory let splitPath = splitDir .slice(0, splitDir.length - j) .concat(splitSrc.slice(j)); // Join path segments to get a complete path - return splitPath.join("/"); + return splitPath.join(pathSlash); }; // Adds [script] to the dartLoader's internals as if it had been loaded and diff --git a/pkg/dev_compiler/test/hot_reload_suite.dart b/pkg/dev_compiler/test/hot_reload_suite.dart index 74e984d400a..968d66a60e7 100644 --- a/pkg/dev_compiler/test/hot_reload_suite.dart +++ b/pkg/dev_compiler/test/hot_reload_suite.dart @@ -3,40 +3,65 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:_fe_analyzer_shared/src/util/relativize.dart' as fe_shared; +import 'package:args/args.dart'; import 'package:dev_compiler/dev_compiler.dart' as ddc_names show libraryUriToJsIdentifier; import 'package:front_end/src/compute_platform_binaries_location.dart' as fe; import 'package:reload_test/ddc_helpers.dart' as ddc_helpers; import 'package:reload_test/frontend_server_controller.dart'; import 'package:reload_test/hot_reload_memory_filesystem.dart'; +import 'package:reload_test/test_helpers.dart'; -final verbose = true; -final debug = true; +final argParser = ArgParser() + ..addOption('named-configuration', + abbr: 'n', + defaultsTo: 'no-configuration', + help: 'configuration name to use for emitting test result files.') + ..addOption('output-directory', help: 'Directory to emit test results files.') + ..addFlag('debug', + abbr: 'd', + defaultsTo: true, + negatable: true, + help: 'enables additional debug behavior and logging.') + ..addFlag('verbose', + abbr: 'v', + defaultsTo: true, + negatable: true, + help: 'enables verbose logging.'); + +late final bool verbose; +late final bool debug; /// TODO(markzipan): Add arg parsing for additional execution modes /// (chrome, VM) and diffs across generations. Future main(List args) async { + final argResults = argParser.parse(args); + verbose = argResults['verbose'] as bool; + debug = argResults['debug'] as bool; + + // Used to communicate individual test failures to our test bots. + final emitTestResultsJson = argResults['output-directory'] != null; final buildRootUri = fe.computePlatformBinariesLocation(forceBuildDir: true); // We can use the outline instead of the full SDK dill here. final ddcPlatformDillUri = buildRootUri.resolve('ddc_outline.dill'); final sdkRoot = Platform.script.resolve('../../../'); final packageConfigUri = sdkRoot.resolve('.dart_tool/package_config.json'); - final hotReloadTestUri = sdkRoot.resolve('tests/hot_reload/'); - final soundStableDartSdkJsPath = - buildRootUri.resolve('gen/utils/ddc/stable/sdk/ddc/dart_sdk.js').path; - final ddcModuleLoaderJsPath = - sdkRoot.resolve('pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js').path; + final allTestsUri = sdkRoot.resolve('tests/hot_reload/'); + final soundStableDartSdkJsUri = + buildRootUri.resolve('gen/utils/ddc/stable/sdk/ddc/dart_sdk.js'); + final ddcModuleLoaderJsUri = + sdkRoot.resolve('pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js'); final d8PreamblesUri = sdkRoot .resolve('sdk/lib/_internal/js_dev_runtime/private/preambles/d8.js'); final sealNativeObjectJsUri = sdkRoot.resolve( 'sdk/lib/_internal/js_runtime/lib/preambles/seal_native_object.js'); final d8BinaryUri = sdkRoot.resolveUri(ddc_helpers.d8executableUri); - final allTestsDir = Directory(hotReloadTestUri.path); // Contains generated code for all tests. final generatedCodeDir = Directory.systemTemp.createTempSync(); @@ -57,22 +82,24 @@ Future main(List args) async { // TODO(markzipan): Support custom entrypoints. final snapshotEntrypointUri = snapshotUri.resolve('main.dart'); - final filesystemRootUri = snapshotDir.uri; + final filesystemRootUri = snapshotUri; final filesystemScheme = 'hot-reload-test'; final snapshotEntrypointLibraryName = fe_shared.relativizeUri( filesystemRootUri, snapshotEntrypointUri, fe_shared.isWindows); final snapshotEntrypointWithScheme = '$filesystemScheme:///$snapshotEntrypointLibraryName'; + final ddcPlatformDillFromSdkRoot = + fe_shared.relativizeUri(sdkRoot, ddcPlatformDillUri, fe_shared.isWindows); final ddcArgs = [ '--dartdevc-module-format=ddc', '--incremental', - '--filesystem-root=${snapshotDir.path}', + '--filesystem-root=${snapshotUri.toFilePath()}', '--filesystem-scheme=$filesystemScheme', - '--output-dill=${outputDillUri.path}', - '--output-incremental-dill=${outputIncrementalDillUri.path}', - '--packages=${packageConfigUri.path}', - '--platform=${ddcPlatformDillUri.path}', - '--sdk-root=${sdkRoot.path}', + '--output-dill=${outputDillUri.toFilePath()}', + '--output-incremental-dill=${outputIncrementalDillUri.toFilePath()}', + '--packages=${packageConfigUri.toFilePath()}', + '--platform=$ddcPlatformDillFromSdkRoot', + '--sdk-root=${sdkRoot.toFilePath()}', '--target=dartdevc', '--verbosity=${verbose ? 'all' : 'info'}', ]; @@ -90,22 +117,31 @@ Future main(List args) async { } } - for (var testDir in allTestsDir.listSync()) { + final testOutcomes = []; + for (var testDir in Directory.fromUri(allTestsUri).listSync()) { if (testDir is! Directory) { if (testDir is File) { // Ignore Dart source files, which may be imported as helpers continue; } throw Exception( - 'Non-directory or file entity found in ${allTestsDir.path}: $testDir'); + 'Non-directory or file entity found in ${allTestsUri.toFilePath()}: $testDir'); } final testDirParts = testDir.uri.pathSegments; final testName = testDirParts[testDirParts.length - 2]; + + var outcome = TestResultOutcome( + configuration: argResults['named-configuration'] as String, + testName: testName, + ); + var stopwatch = Stopwatch()..start(); + final tempUri = generatedCodeUri.resolve('$testName/'); Directory.fromUri(tempUri).createSync(); _print('Generating test assets.', label: testName); - _debugPrint('Emitting JS code to ${tempUri.path}.', label: testName); + _debugPrint('Emitting JS code to ${tempUri.toFilePath()}.', + label: testName); var filesystem = HotReloadMemoryFilesystem(tempUri); @@ -129,7 +165,7 @@ Future main(List args) async { } // TODO(markzipan): replace this with a test-configurable main entrypoint. - final mainDartFilePath = testDir.uri.resolve('main.dart').path; + final mainDartFilePath = testDir.uri.resolve('main.dart').toFilePath(); _debugPrint('Test entrypoint: $mainDartFilePath', label: testName); _print('Generating code over ${maxGenerations + 1} generations.', label: testName); @@ -144,7 +180,7 @@ Future main(List args) async { // names restored (e.g., path/to/main' from 'path/to/main.0.dart). // TODO(markzipan): support subdirectories. _debugPrint( - 'Copying Dart files to snapshot directory: ${snapshotDir.path}', + 'Copying Dart files to snapshot directory: ${snapshotUri.toFilePath()}', label: testName); for (var file in testDir.listSync()) { // Convert a name like `/path/foo.bar.25.dart` to `/path/foo.bar.dart`. @@ -164,7 +200,7 @@ Future main(List args) async { final snapshotPathWithScheme = '$filesystemScheme:///$relativeSnapshotPath'; updatedFilesInCurrentGeneration.add(snapshotPathWithScheme); - file.copySync(fileSnapshotUri.path); + file.copySync(fileSnapshotUri.toFilePath()); } } } @@ -184,13 +220,13 @@ Future main(List args) async { _debugPrint( 'Compiling snapshot entrypoint: $snapshotEntrypointWithScheme', label: testName); - outputDirectoryPath = outputDillUri.path; + outputDirectoryPath = outputDillUri.toFilePath(); await controller.sendCompileAndAccept(snapshotEntrypointWithScheme); } else { _debugPrint( 'Recompiling snapshot entrypoint: $snapshotEntrypointWithScheme', label: testName); - outputDirectoryPath = outputIncrementalDillUri.path; + outputDirectoryPath = outputIncrementalDillUri.toFilePath(); // TODO(markzipan): Add logic to reject bad compiles. await controller.sendRecompileAndAccept(snapshotEntrypointWithScheme, invalidatedFiles: updatedFilesInCurrentGeneration); @@ -217,7 +253,8 @@ Future main(List args) async { // Write JS files and sourcemaps to their respective generation. _print('Writing generation $currentGeneration assets.', label: testName); - _debugPrint('Writing JS assets to ${tempUri.path}', label: testName); + _debugPrint('Writing JS assets to ${tempUri.toFilePath()}', + label: testName); filesystem.writeToDisk(tempUri, generation: '$currentGeneration'); currentGeneration++; } @@ -233,10 +270,10 @@ Future main(List args) async { final d8BootstrapJsUri = tempUri.resolve('generation0/bootstrap.js'); final d8BootstrapJS = ddc_helpers.generateD8Bootstrapper( - ddcModuleLoaderJsPath: ddcModuleLoaderJsPath, - dartSdkJsPath: soundStableDartSdkJsPath, - entrypointModuleName: entrypointModuleName, - entrypointLibraryExportName: entrypointLibraryExportName, + ddcModuleLoaderJsPath: escapedString(ddcModuleLoaderJsUri.toFilePath()), + dartSdkJsPath: escapedString(soundStableDartSdkJsUri.toFilePath()), + entrypointModuleName: escapedString(entrypointModuleName), + entrypointLibraryExportName: escapedString(entrypointLibraryExportName), scriptDescriptors: filesystem.scriptDescriptorForBootstrap, modifiedFilesPerGeneration: filesystem.generationsToModifiedFilePaths, ); @@ -244,22 +281,72 @@ Future main(List args) async { File.fromUri(d8BootstrapJsUri).writeAsStringSync(d8BootstrapJS); _debugPrint('Writing D8 bootstrapper: $d8BootstrapJsUri', label: testName); - var process = await startProcess('D8', d8BinaryUri.path, [ - sealNativeObjectJsUri.path, - d8PreamblesUri.path, - d8BootstrapJsUri.path + final d8OutputStreamController = StreamController>(); + final process = await startProcess('D8', d8BinaryUri.toFilePath(), [ + sealNativeObjectJsUri.toFilePath(), + d8PreamblesUri.toFilePath(), + d8BootstrapJsUri.toFilePath() ]); + // Send D8's output to a local buffer so we can write or process it later. + final d8OutputBuffer = StringBuffer(); + unawaited(process.stdout.pipe(d8OutputStreamController.sink)); + d8OutputStreamController.stream + .transform(utf8.decoder) + .listen(d8OutputBuffer.write); + final d8ExitCode = await process.exitCode; - if (d8ExitCode != 0) { + final testPassed = d8ExitCode == 0; + + stopwatch.stop(); + outcome.elapsedTime = stopwatch.elapsed; + outcome.matchedExpectations = testPassed; + outcome.testOutput = d8OutputBuffer.toString(); + testOutcomes.add(outcome); + if (testPassed) { + _print('D8 passed with:\n$d8OutputBuffer', label: testName); + } else { + _print('TEST FAILURE: D8 exited with:\n$d8OutputBuffer', label: testName); + } + _print(outcome.testOutput, label: testName); + + if (!testPassed) { await shutdown(); exit(d8ExitCode); } - _print('Test passed in D8.', label: testName); } await shutdown(); _print('Testing complete.'); + + if (emitTestResultsJson) { + final testOutcomeResults = testOutcomes.map((o) => o.toRecordJson()); + final testOutcomeLogs = testOutcomes.map((o) => o.toLogJson()); + final testResultsOutputDir = + Uri.directory(argResults['output-directory'] as String); + _print('Saving test results to ${testResultsOutputDir.toFilePath()}.'); + + // Test outputs must have one JSON blob per line and be newline-terminated. + final testResultsUri = testResultsOutputDir.resolve('results.json'); + final testResultsSink = File.fromUri(testResultsUri).openWrite(); + testOutcomeResults.forEach(testResultsSink.writeln); + await testResultsSink.flush(); + await testResultsSink.close(); + + final testLogsUri = testResultsOutputDir.resolve('logs.json'); + if (Platform.isWindows) { + // TODO(55297): Logs are disabled on windows until this but is fixed. + _print('Logs are not written on Windows. ' + 'See: https://github.com/dart-lang/sdk/issues/55297'); + } else { + final testLogsSink = File.fromUri(testLogsUri).openWrite(); + testOutcomeLogs.forEach(testLogsSink.writeln); + await testLogsSink.flush(); + await testLogsSink.close(); + } + _print('Emitted logs to ${testResultsUri.toFilePath()} ' + 'and ${testLogsUri.toFilePath()}.'); + } } /// Runs the [command] with [args] in [environment]. @@ -267,7 +354,8 @@ Future main(List args) async { /// Will echo the commands to the console before running them when running in /// `verbose` mode. Future startProcess(String name, String command, List args, - [Map environment = const {}]) { + {Map environment = const {}, + ProcessStartMode mode = ProcessStartMode.normal}) { if (verbose) { print('Running $name:\n$command ${args.join(' ')}\n'); if (environment.isNotEmpty) { @@ -276,8 +364,7 @@ Future startProcess(String name, String command, List args, print('With environment:\n$environmentVariables\n'); } } - return Process.start(command, args, - mode: ProcessStartMode.inheritStdio, environment: environment); + return Process.start(command, args, mode: mode, environment: environment); } /// Prints messages if 'verbose' mode is enabled. diff --git a/pkg/reload_test/lib/ddc_helpers.dart b/pkg/reload_test/lib/ddc_helpers.dart index 6750d5a0774..c7865d451a0 100644 --- a/pkg/reload_test/lib/ddc_helpers.dart +++ b/pkg/reload_test/lib/ddc_helpers.dart @@ -44,14 +44,13 @@ String generateD8Bootstrapper({ required String ddcModuleLoaderJsPath, required String dartSdkJsPath, required String entrypointModuleName, - String jsFileRoot = '/', + String jsFileRoot = '', String uuid = '00000000-0000-0000-0000-000000000000', required String entrypointLibraryExportName, required List> scriptDescriptors, required Map> modifiedFilesPerGeneration, }) { final d8BootstrapJS = ''' - load("$ddcModuleLoaderJsPath"); load("$dartSdkJsPath"); @@ -70,6 +69,7 @@ let sdk = dart_library.import('dart_sdk'); let scripts = ${_encoder.convert(scriptDescriptors)}; let loadConfig = new self.\$dartLoader.LoadConfiguration(); +loadConfig.isWindows = ${Platform.isWindows}; loadConfig.root = '$jsFileRoot'; // Loading the entrypoint late is only necessary in Chrome. loadConfig.bootstrapScript = ''; diff --git a/pkg/reload_test/lib/frontend_server_controller.dart b/pkg/reload_test/lib/frontend_server_controller.dart index 60a731644e0..49981aa6b16 100644 --- a/pkg/reload_test/lib/frontend_server_controller.dart +++ b/pkg/reload_test/lib/frontend_server_controller.dart @@ -77,6 +77,7 @@ class HotReloadFrontendServerController { .transform(utf8.decoder) .transform(const LineSplitter()) .listen((String s) { + if (debug) print('Frontend Server Response: $s'); if (_boundaryKey == null) { if (s.startsWith(frontEndResponsePrefix)) { _boundaryKey = s.substring(frontEndResponsePrefix.length); @@ -99,13 +100,9 @@ class HotReloadFrontendServerController { } Future sendCompile(String dartSourcePath) async { - if (!started) { - throw Exception('Frontend Server has not been started yet.'); - } + if (!started) throw Exception('Frontend Server has not been started yet.'); final command = 'compile $dartSourcePath\n'; - if (debug) { - print('Sending instruction to Frontend Server:\n$command'); - } + if (debug) print('Sending instruction to Frontend Server:\n$command'); input.add(command.codeUnits); await synchronizer.moveNext(); } @@ -118,14 +115,10 @@ class HotReloadFrontendServerController { Future sendRecompile(String entrypointPath, {List invalidatedFiles = const [], String boundaryKey = fakeBoundaryKey}) async { - if (!started) { - throw Exception('Frontend Server has not been started yet.'); - } + if (!started) throw Exception('Frontend Server has not been started yet.'); final command = 'recompile $entrypointPath $boundaryKey\n' '${invalidatedFiles.join('\n')}\n$boundaryKey\n'; - if (debug) { - print('Sending instruction to Frontend Server:\n$command'); - } + if (debug) print('Sending instruction to Frontend Server:\n$command'); input.add(command.codeUnits); await synchronizer.moveNext(); } @@ -139,26 +132,18 @@ class HotReloadFrontendServerController { } void sendAccept() { - if (!started) { - throw Exception('Frontend Server has not been started yet.'); - } + if (!started) throw Exception('Frontend Server has not been started yet.'); final command = 'accept\n'; // TODO(markzipan): We should reject certain invalid compiles (e.g., those // with unimplemented or invalid nodes). - if (debug) { - print('Sending instruction to Frontend Server:\n$command'); - } + if (debug) print('Sending instruction to Frontend Server:\n$command'); input.add(command.codeUnits); } void _sendQuit() { - if (!started) { - throw Exception('Frontend Server has not been started yet.'); - } + if (!started) throw Exception('Frontend Server has not been started yet.'); final command = 'quit\n'; - if (debug) { - print('Sending instruction to Frontend Server:\n$command'); - } + if (debug) print('Sending instruction to Frontend Server:\n$command'); input.add(command.codeUnits); } diff --git a/pkg/reload_test/lib/hot_reload_memory_filesystem.dart b/pkg/reload_test/lib/hot_reload_memory_filesystem.dart index 501c9ccd01f..58e03b02862 100644 --- a/pkg/reload_test/lib/hot_reload_memory_filesystem.dart +++ b/pkg/reload_test/lib/hot_reload_memory_filesystem.dart @@ -136,7 +136,7 @@ class HotReloadMemoryFilesystem { moduleName: libraryName, libraryName: libraryName, dartSourcePath: dartFileName, - jsSourcePath: fullyResolvedFileUri.path); + jsSourcePath: fullyResolvedFileUri.toFilePath()); libraries.add(libraryInfo); if (generation == '0') { firstGenerationLibraries.add(libraryInfo); diff --git a/pkg/reload_test/lib/test_helpers.dart b/pkg/reload_test/lib/test_helpers.dart new file mode 100644 index 00000000000..665e8a6b039 --- /dev/null +++ b/pkg/reload_test/lib/test_helpers.dart @@ -0,0 +1,50 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. 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'; + +class TestResultOutcome { + // This encoder must generate each output element on its own line. + final _encoder = JsonEncoder(); + final String configuration; + final String suiteName; + final String testName; + late Duration elapsedTime; + final String expectedResult; + late bool matchedExpectations; + String testOutput; + + TestResultOutcome({ + required this.configuration, + this.suiteName = 'tests/reload', + required this.testName, + this.expectedResult = 'Pass', + this.testOutput = '', + }); + + String toRecordJson() => _encoder.convert({ + 'name': '$suiteName/$testName', + 'configuration': configuration, + 'suite': suiteName, + 'test_name': testName, + 'time_ms': elapsedTime.inMilliseconds, + 'expected': expectedResult, + 'result': matchedExpectations ? 'Pass' : 'Fail', + 'matches': expectedResult == expectedResult, + }); + + String toLogJson() => _encoder.convert({ + 'name': '$suiteName/$testName', + 'configuration': configuration, + 'result': matchedExpectations ? 'Pass' : 'Fail', + 'log': testOutput, + }); +} + +/// Escapes backslashes in [unescaped]. +/// +/// Used for wrapping Windows-style paths. +String escapedString(String unescaped) { + return unescaped.replaceAll(r'\', r'\\'); +} diff --git a/pkg/reload_test/pubspec.yaml b/pkg/reload_test/pubspec.yaml index 30b08a73945..7b9311b0212 100644 --- a/pkg/reload_test/pubspec.yaml +++ b/pkg/reload_test/pubspec.yaml @@ -15,3 +15,4 @@ dependencies: # Use 'any' constraints here; we get our versions from the DEPS file. dev_dependencies: lints: any + test: any diff --git a/pkg/reload_test/test/filesystem_test.dart b/pkg/reload_test/test/filesystem_test.dart new file mode 100644 index 00000000000..e6e057fe943 --- /dev/null +++ b/pkg/reload_test/test/filesystem_test.dart @@ -0,0 +1,243 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. 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'; + +import 'package:test/test.dart'; +import 'package:reload_test/hot_reload_memory_filesystem.dart'; + +void main() { + late Directory testDirectory; + late Uri jsOutputUri; + + setUp(() { + testDirectory = Directory.systemTemp.createTempSync('test_file_system'); + jsOutputUri = testDirectory.uri.resolve('js/'); + Directory.fromUri(jsOutputUri).createSync(); + }); + tearDown(() { + testDirectory.deleteSync(recursive: true); + }); + test("Web filesystem behaves correctly across generations.", () { + // Initialize the filesystem and register two test files with their + // corresponding sourcemaps. + final filesystem = HotReloadMemoryFilesystem(jsOutputUri); + + final source1 = ''' + file1() { + foo(); + } + '''; + final source2 = ''' + file2() { + bar(); + } + '''; + final sources = '$source1$source2'.codeUnits; + final sourcesFile = File.fromUri(testDirectory.uri.resolve('test.sources')) + ..writeAsBytesSync(sources); + final sourcemap1 = '{map1}'; + final sourcemap2 = '{map2}'; + final sourcemap = '$sourcemap1$sourcemap2'.codeUnits; + final sourcemapFile = File.fromUri(testDirectory.uri.resolve('test.map')) + ..writeAsBytesSync(sourcemap); + + final manifest = ''' + { + "/file1.ext": { + "code": [0, ${source1.codeUnits.length}], + "sourcemap": [0, ${sourcemap1.codeUnits.length}] + }, + "/file2.ext": { + "code": [ + ${source1.codeUnits.length}, + ${source1.codeUnits.length + source2.codeUnits.length} + ], + "sourcemap":[ + ${sourcemap1.codeUnits.length}, + ${sourcemap1.codeUnits.length + sourcemap2.codeUnits.length} + ] + } + } + ''' + .codeUnits; + final manifestFile = File.fromUri(testDirectory.uri.resolve('test.json')) + ..writeAsBytesSync(manifest); + + var updatedFiles = filesystem + .update(sourcesFile, manifestFile, sourcemapFile, generation: "0"); + + expect(updatedFiles, equals(['file1.ext', 'file2.ext']), + reason: 'Updated files are correctly reported.'); + + expect( + filesystem.files, + equals( + {'file1.ext': source1.codeUnits, 'file2.ext': source2.codeUnits}), + reason: 'Filesystem source files are correctly stored.'); + + expect( + filesystem.sourcemaps, + equals({ + 'file1.ext.map': sourcemap1.codeUnits, + 'file2.ext.map': sourcemap2.codeUnits, + }), + reason: 'Filesystem sourcemaps are correctly stored.'); + + expect( + filesystem.generationsToModifiedFilePaths, + equals({ + '0': [ + jsOutputUri.resolve('generation0/file1.ext').toFilePath(), + jsOutputUri.resolve('generation0/file2.ext').toFilePath() + ] + }), + reason: + 'Filesystem emits correct generation to modfied files mapping.'); + + // Update the filesystem with two more files in the next generation. + + final manifest2 = ''' + { + "/file3.ext": { + "code": [0, ${source1.codeUnits.length}], + "sourcemap": [0, ${sourcemap1.codeUnits.length}] + }, + "/file4.ext": { + "code": [ + ${source1.codeUnits.length}, + ${source1.codeUnits.length + source2.codeUnits.length} + ], + "sourcemap":[ + ${sourcemap1.codeUnits.length}, + ${sourcemap1.codeUnits.length + sourcemap2.codeUnits.length} + ] + } + } + ''' + .codeUnits; + manifestFile.writeAsBytesSync(manifest2); + + updatedFiles = filesystem.update(sourcesFile, manifestFile, sourcemapFile, + generation: "1"); + + expect(updatedFiles, equals(['file3.ext', 'file4.ext']), + reason: 'Updated files are correctly reported.'); + + expect( + filesystem.files, + equals({ + 'file1.ext': source1.codeUnits, + 'file2.ext': source2.codeUnits, + 'file3.ext': source1.codeUnits, + 'file4.ext': source2.codeUnits, + }), + reason: 'Filesystem source files are correctly stored.'); + + expect( + filesystem.sourcemaps, + equals({ + 'file1.ext.map': sourcemap1.codeUnits, + 'file2.ext.map': sourcemap2.codeUnits, + 'file3.ext.map': sourcemap1.codeUnits, + 'file4.ext.map': sourcemap2.codeUnits, + }), + reason: 'Filesystem sourcemaps are correctly stored.'); + + expect( + filesystem.generationsToModifiedFilePaths, + equals({ + '0': [ + jsOutputUri.resolve('generation0/file1.ext').toFilePath(), + jsOutputUri.resolve('generation0/file2.ext').toFilePath() + ], + '1': [ + jsOutputUri.resolve('generation1/file3.ext').toFilePath(), + jsOutputUri.resolve('generation1/file4.ext').toFilePath(), + ], + }), + reason: + 'Filesystem emits correct generation to modfied files mapping.'); + + expect( + filesystem.scriptDescriptorForBootstrap, + equals([ + { + 'id': 'file1.ext', + 'src': jsOutputUri.resolve('generation0/file1.ext').toFilePath(), + }, + { + 'id': 'file2.ext', + 'src': jsOutputUri.resolve('generation0/file2.ext').toFilePath(), + }, + ]), + reason: 'Filesystem emits correct script descriptors.'); + + // Write files and check that the filesystem's state is properly cleared. + expect( + File(jsOutputUri.resolve('generation3/file1.ext').toFilePath()) + .existsSync(), + isFalse); + expect( + File(jsOutputUri.resolve('generation3/file2.ext').toFilePath()) + .existsSync(), + isFalse); + expect( + File(jsOutputUri.resolve('generation3/file3.ext').toFilePath()) + .existsSync(), + isFalse); + expect( + File(jsOutputUri.resolve('generation3/file4.ext').toFilePath()) + .existsSync(), + isFalse); + filesystem.writeToDisk(jsOutputUri, generation: "3"); + expect( + File(jsOutputUri.resolve('generation3/file1.ext').toFilePath()) + .existsSync(), + isTrue); + expect( + File(jsOutputUri.resolve('generation3/file2.ext').toFilePath()) + .existsSync(), + isTrue); + expect( + File(jsOutputUri.resolve('generation3/file3.ext').toFilePath()) + .existsSync(), + isTrue); + expect( + File(jsOutputUri.resolve('generation3/file4.ext').toFilePath()) + .existsSync(), + isTrue); + expect(filesystem.files, isEmpty, + reason: 'Filesystem clears files after writing to disk.'); + expect(filesystem.sourcemaps, isEmpty, + reason: 'Filesystem clears sourcemaps after writing to disk.'); + + // Check that subsequent writes don't emit already-emitted files. + File(jsOutputUri.resolve('generation3/file1.ext').toFilePath()) + .deleteSync(); + File(jsOutputUri.resolve('generation3/file2.ext').toFilePath()) + .deleteSync(); + File(jsOutputUri.resolve('generation3/file3.ext').toFilePath()) + .deleteSync(); + File(jsOutputUri.resolve('generation3/file4.ext').toFilePath()) + .deleteSync(); + filesystem.writeToDisk(jsOutputUri, generation: "3"); + expect( + File(jsOutputUri.resolve('generation3/file1.ext').toFilePath()) + .existsSync(), + isFalse); + expect( + File(jsOutputUri.resolve('generation3/file2.ext').toFilePath()) + .existsSync(), + isFalse); + expect( + File(jsOutputUri.resolve('generation3/file3.ext').toFilePath()) + .existsSync(), + isFalse); + expect( + File(jsOutputUri.resolve('generation3/file4.ext').toFilePath()) + .existsSync(), + isFalse); + }); +} diff --git a/tests/hot_reload/hot_restart_constant_equality/main.0.dart b/tests/hot_reload/hot_restart_constant_equality/main.0.dart index 1db3e409003..7350e1d9263 100644 --- a/tests/hot_reload/hot_restart_constant_equality/main.0.dart +++ b/tests/hot_reload/hot_restart_constant_equality/main.0.dart @@ -2,9 +2,6 @@ // for details. 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:core'; -import 'dart:html'; - import 'package:expect/expect.dart'; import 'package:reload_test/reload_test_utils.dart'; diff --git a/tests/hot_reload/hot_restart_constant_equality/main.1.dart b/tests/hot_reload/hot_restart_constant_equality/main.1.dart index ac9d4ec0af2..15751403886 100644 --- a/tests/hot_reload/hot_restart_constant_equality/main.1.dart +++ b/tests/hot_reload/hot_restart_constant_equality/main.1.dart @@ -2,9 +2,6 @@ // for details. 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:core'; -import 'dart:html'; - import 'package:expect/expect.dart'; import 'package:reload_test/reload_test_utils.dart'; diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json index 475e0f88cba..df0a8465077 100644 --- a/tools/bots/test_matrix.json +++ b/tools/bots/test_matrix.json @@ -1778,6 +1778,16 @@ "--use-sdk" ] }, + { + "name": "ddc hot reload tests", + "script": "out/ReleaseX64/dart-sdk/bin/dart", + "testRunner": true, + "arguments": [ + "pkg/dev_compiler/test/hot_reload_suite.dart", + "-nddc-${system}-chrome", + "--verbose" + ] + }, { "name": "ddc sourcemap tests", "script": "out/ReleaseX64/dart", @@ -1865,6 +1875,16 @@ "--use-sdk" ] }, + { + "name": "ddc hot reload tests", + "script": "xcodebuild/ReleaseARM64/dart-sdk/bin/dart", + "testRunner": true, + "arguments": [ + "pkg/dev_compiler/test/hot_reload_suite.dart", + "-nddc-mac-chrome", + "--verbose" + ] + }, { "name": "ddc sourcemap tests", "script": "xcodebuild/ReleaseARM64/dart",