[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>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 @@ Future<void> main(List<String> 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<void> main(List<String> args) async {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ Future<void> main(List<String> 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<void> main(List<String> 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<void> main(List<String> args) async {
|
||||
final snapshotPathWithScheme =
|
||||
'$filesystemScheme:///$relativeSnapshotPath';
|
||||
updatedFilesInCurrentGeneration.add(snapshotPathWithScheme);
|
||||
file.copySync(fileSnapshotUri.path);
|
||||
file.copySync(fileSnapshotUri.toFilePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,13 +220,13 @@ Future<void> main(List<String> 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<void> main(List<String> 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<void> main(List<String> 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<void> main(List<String> 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<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 @@ Future<void> main(List<String> args) async {
|
||||
/// 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 @@ Future<Process> startProcess(String name, String command, List<String> 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.
|
||||
|
||||
@@ -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<Map<String, String?>> scriptDescriptors,
|
||||
required Map<String, List<String>> 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 = '';
|
||||
|
||||
@@ -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<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 @@ class HotReloadFrontendServerController {
|
||||
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 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'\\');
|
||||
}
|
||||
@@ -15,3 +15,4 @@ dependencies:
|
||||
# Use 'any' constraints here; we get our versions from the DEPS file.
|
||||
dev_dependencies:
|
||||
lints: any
|
||||
test: any
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user