[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 <nshahan@google.com>
Reviewed-by: Nate Bosch <nbosch@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
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 58354a2..c2f37e7 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 @@
// 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 @@
// 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 74e984d..968d66a 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<void> main(List<String> 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 @@
// 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 @@
}
}
- for (var testDir in allTestsDir.listSync()) {
+ final testOutcomes = <TestResultOutcome>[];
+ 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 @@
}
// 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 @@
// 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 @@
final snapshotPathWithScheme =
'$filesystemScheme:///$relativeSnapshotPath';
updatedFilesInCurrentGeneration.add(snapshotPathWithScheme);
- file.copySync(fileSnapshotUri.path);
+ file.copySync(fileSnapshotUri.toFilePath());
}
}
}
@@ -184,13 +220,13 @@
_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 @@
// 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 @@
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 @@
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<List<int>>();
+ 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 @@
/// Will echo the commands to the console before running them when running in
/// `verbose` mode.
Future<Process> startProcess(String name, String command, List<String> args,
- [Map<String, String> environment = const {}]) {
+ {Map<String, String> environment = const {},
+ ProcessStartMode mode = ProcessStartMode.normal}) {
if (verbose) {
print('Running $name:\n$command ${args.join(' ')}\n');
if (environment.isNotEmpty) {
@@ -276,8 +364,7 @@
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 6750d5a..c7865d4 100644
--- a/pkg/reload_test/lib/ddc_helpers.dart
+++ b/pkg/reload_test/lib/ddc_helpers.dart
@@ -44,14 +44,13 @@
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<Map<String, String?>> scriptDescriptors,
required Map<String, List<String>> modifiedFilesPerGeneration,
}) {
final d8BootstrapJS = '''
-
load("$ddcModuleLoaderJsPath");
load("$dartSdkJsPath");
@@ -70,6 +69,7 @@
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 60a7316..49981aa6 100644
--- a/pkg/reload_test/lib/frontend_server_controller.dart
+++ b/pkg/reload_test/lib/frontend_server_controller.dart
@@ -77,6 +77,7 @@
.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 @@
}
Future<void> 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 @@
Future<void> sendRecompile(String entrypointPath,
{List<String> 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 @@
}
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 501c9cc..58e03b0 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 @@
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 0000000..665e8a6
--- /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 30b08a7..7b9311b 100644
--- a/pkg/reload_test/pubspec.yaml
+++ b/pkg/reload_test/pubspec.yaml
@@ -15,3 +15,4 @@
# 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 0000000..e6e057f
--- /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 1db3e40..7350e1d 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 ac9d4ec..1575140 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 475e0f8..df0a846 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -1779,6 +1779,16 @@
]
},
{
+ "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",
"arguments": [
@@ -1866,6 +1876,16 @@
]
},
{
+ "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",
"arguments": [