[reload_test] Reformat pkg/reload_test

Change-Id: Idb0d35f552aad7c512fd7e19317c4cd1af1e9980
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500241
Commit-Queue: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Bob Nystrom <rnystrom@google.com>
This commit is contained in:
Nicholas Shahan
2026-05-04 12:11:51 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent c0df8bae2e
commit 3e0828907c
8 changed files with 483 additions and 341 deletions
+24 -10
View File
@@ -23,14 +23,20 @@ class D8Configuration {
final Uri preamblesScript;
final Uri sealNativeObjectScript;
D8Configuration._(this.sdkRoot, this.binary, this.preamblesScript,
this.sealNativeObjectScript);
D8Configuration._(
this.sdkRoot,
this.binary,
this.preamblesScript,
this.sealNativeObjectScript,
);
factory D8Configuration(Uri sdkRoot) {
final preamblesScript = sdkRoot
.resolve('sdk/lib/_internal/js_dev_runtime/private/preambles/d8.js');
final preamblesScript = sdkRoot.resolve(
'sdk/lib/_internal/js_dev_runtime/private/preambles/d8.js',
);
final sealNativeObjectScript = sdkRoot.resolve(
'sdk/lib/_internal/js_runtime/lib/preambles/seal_native_object.js');
'sdk/lib/_internal/js_runtime/lib/preambles/seal_native_object.js',
);
final arch = Abi.current().toString().split('_')[1];
final Uri binaryFromRoot;
if (Platform.isWindows) {
@@ -40,12 +46,18 @@ class D8Configuration {
} else if (Platform.isMacOS) {
binaryFromRoot = Uri.file('third_party/d8/macos/$arch/d8');
} else {
throw UnsupportedError('Unsupported platform for running d8: '
'${Platform.operatingSystem}');
throw UnsupportedError(
'Unsupported platform for running d8: '
'${Platform.operatingSystem}',
);
}
final binary = sdkRoot.resolveUri(binaryFromRoot);
return D8Configuration._(
sdkRoot, binary, preamblesScript, sealNativeObjectScript);
sdkRoot,
binary,
preamblesScript,
sealNativeObjectScript,
);
}
}
@@ -80,7 +92,8 @@ String generateD8Bootstrapper({
required List<Map<String, String?>> scriptDescriptors,
required FileDataPerGeneration modifiedFilesPerGeneration,
}) {
final d8BootstrapJS = '''
final d8BootstrapJS =
'''
load("$ddcModuleLoaderJsPath");
load("$dartSdkJsPath");
@@ -343,7 +356,8 @@ String generateChromeBootstrapper({
required List<Map<String, String?>> scriptDescriptors,
required FileDataPerGeneration modifiedFilesPerGeneration,
}) {
final bootstrapJS = '''
final bootstrapJS =
'''
var _currentDirectory = "$jsFileRoot";
window.\$dartCreateScript = (function() {
@@ -24,10 +24,7 @@ class CompilerOutput {
/// Output for a 'reject' response.
factory CompilerOutput.rejectOutput() {
return CompilerOutput(
outputDillPath: '',
errorCount: 0,
);
return CompilerOutput(outputDillPath: '', errorCount: 0);
}
final String outputDillPath;
@@ -102,16 +99,26 @@ class HotReloadFrontendServerController {
FrontendServerState _state = FrontendServerState.awaitingResult;
HotReloadFrontendServerController._(this.frontendServerArgs, this.input,
this.output, this.compileCommandOutputChannel, this.synchronizer);
HotReloadFrontendServerController._(
this.frontendServerArgs,
this.input,
this.output,
this.compileCommandOutputChannel,
this.synchronizer,
);
factory HotReloadFrontendServerController(List<String> frontendServerArgs) {
var input = StreamController<List<int>>();
var output = StreamController<List<int>>();
var compileCommandOutputChannel = StreamController<CompilerOutput>();
var synchronizer = StreamIterator(compileCommandOutputChannel.stream);
return HotReloadFrontendServerController._(frontendServerArgs, input,
output, compileCommandOutputChannel, synchronizer);
return HotReloadFrontendServerController._(
frontendServerArgs,
input,
output,
compileCommandOutputChannel,
synchronizer,
);
}
/// Runs the Frontend Server in-memory in incremental mode.
@@ -126,70 +133,77 @@ class HotReloadFrontendServerController {
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String s) {
if (debug) print('Frontend Server Response: $s');
switch (_state) {
case FrontendServerState.awaitingReject:
if (!s.startsWith(frontEndResponsePrefix)) {
throw Exception('Unexpected Frontend Server response: $s');
if (debug) print('Frontend Server Response: $s');
switch (_state) {
case FrontendServerState.awaitingReject:
if (!s.startsWith(frontEndResponsePrefix)) {
throw Exception('Unexpected Frontend Server response: $s');
}
_boundaryKey = s.substring(frontEndResponsePrefix.length);
_state = FrontendServerState.awaitingRejectKey;
break;
case FrontendServerState.awaitingRejectKey:
if (s != _boundaryKey) {
throw Exception(
'Unexpected Frontend Server response for reject '
'(expected just a key): $s',
);
}
_state = FrontendServerState.finished;
compileCommandOutputChannel.add(CompilerOutput.rejectOutput());
_clearState();
break;
case FrontendServerState.awaitingResult:
if (!s.startsWith(frontEndResponsePrefix)) {
throw Exception('Unexpected Frontend Server response: $s');
}
_boundaryKey = s.substring(frontEndResponsePrefix.length);
_state = FrontendServerState.awaitingKey;
break;
case FrontendServerState.awaitingKey:
// Advance to the next state when we encounter a lone boundary
// key.
if (s == _boundaryKey) {
_state = FrontendServerState.collectingResultSources;
} else {
accumulatedOutput.add(s);
}
case FrontendServerState.collectingResultSources:
// Stop and record the result when we encounter a boundary key.
if (s.startsWith(_boundaryKey)) {
final compilationReportOutput = s.split(' ');
final outputDillPath = compilationReportOutput[1];
final errorCount = int.parse(compilationReportOutput[2]);
// The FrontendServer accumulates all errors seen so far, so we
// need to correct for errors from previous compilations.
final actualErrorCount = errorCount - totalErrors;
final compilerOutput = CompilerOutput(
outputDillPath: outputDillPath,
errorCount: actualErrorCount,
sources: sources,
outputText: accumulatedOutput.join('\n'),
);
totalErrors = errorCount;
_state = FrontendServerState.finished;
compileCommandOutputChannel.add(compilerOutput);
_clearState();
} else if (s.startsWith('+')) {
sources.add(Uri.parse(s.substring(1)));
} else if (s.startsWith('-')) {
sources.remove(Uri.parse(s.substring(1)));
} else {
throw Exception(
"Unexpected Frontend Server response "
"(expected '+' or '-')'): $s",
);
}
break;
case FrontendServerState.finished:
throw StateError(
'Frontend Server reached an unexpected state: $s',
);
}
_boundaryKey = s.substring(frontEndResponsePrefix.length);
_state = FrontendServerState.awaitingRejectKey;
break;
case FrontendServerState.awaitingRejectKey:
if (s != _boundaryKey) {
throw Exception('Unexpected Frontend Server response for reject '
'(expected just a key): $s');
}
_state = FrontendServerState.finished;
compileCommandOutputChannel.add(CompilerOutput.rejectOutput());
_clearState();
break;
case FrontendServerState.awaitingResult:
if (!s.startsWith(frontEndResponsePrefix)) {
throw Exception('Unexpected Frontend Server response: $s');
}
_boundaryKey = s.substring(frontEndResponsePrefix.length);
_state = FrontendServerState.awaitingKey;
break;
case FrontendServerState.awaitingKey:
// Advance to the next state when we encounter a lone boundary key.
if (s == _boundaryKey) {
_state = FrontendServerState.collectingResultSources;
} else {
accumulatedOutput.add(s);
}
case FrontendServerState.collectingResultSources:
// Stop and record the result when we encounter a boundary key.
if (s.startsWith(_boundaryKey)) {
final compilationReportOutput = s.split(' ');
final outputDillPath = compilationReportOutput[1];
final errorCount = int.parse(compilationReportOutput[2]);
// The FrontendServer accumulates all errors seen so far, so we
// need to correct for errors from previous compilations.
final actualErrorCount = errorCount - totalErrors;
final compilerOutput = CompilerOutput(
outputDillPath: outputDillPath,
errorCount: actualErrorCount,
sources: sources,
outputText: accumulatedOutput.join('\n'),
);
totalErrors = errorCount;
_state = FrontendServerState.finished;
compileCommandOutputChannel.add(compilerOutput);
_clearState();
} else if (s.startsWith('+')) {
sources.add(Uri.parse(s.substring(1)));
} else if (s.startsWith('-')) {
sources.remove(Uri.parse(s.substring(1)));
} else {
throw Exception("Unexpected Frontend Server response "
"(expected '+' or '-')'): $s");
}
break;
case FrontendServerState.finished:
throw StateError('Frontend Server reached an unexpected state: $s');
}
});
});
frontendServerExitCode = starter(
frontendServerArgs,
@@ -225,10 +239,12 @@ class HotReloadFrontendServerController {
sendAccept();
}
Future<CompilerOutput> sendRecompile(String entrypointPath,
{List<String> invalidatedFiles = const [],
String boundaryKey = fakeBoundaryKey,
required bool recompileRestart}) async {
Future<CompilerOutput> sendRecompile(
String entrypointPath, {
List<String> invalidatedFiles = const [],
String boundaryKey = fakeBoundaryKey,
required bool recompileRestart,
}) async {
// Currently the `FrontendCompiler` used in this test suite clears errors
// before performing a recompile but not an initial compile. Since we reuse
// the same instance and issue an initial compile request for each test we
@@ -239,7 +255,8 @@ class HotReloadFrontendServerController {
if (!started) throw Exception('Frontend Server has not been started yet.');
_state = FrontendServerState.awaitingResult;
final instruction = recompileRestart ? 'recompile-restart' : 'recompile';
final command = '$instruction $entrypointPath $boundaryKey\n'
final command =
'$instruction $entrypointPath $boundaryKey\n'
'${invalidatedFiles.join('\n')}\n$boundaryKey\n';
if (debug) print('Sending instruction to Frontend Server:\n$command');
input.add(command.codeUnits);
@@ -247,14 +264,18 @@ class HotReloadFrontendServerController {
return synchronizer.current;
}
Future<void> sendRecompileAndAccept(String entrypointPath,
{List<String> invalidatedFiles = const [],
String boundaryKey = fakeBoundaryKey,
required bool recompileRestart}) async {
await sendRecompile(entrypointPath,
invalidatedFiles: invalidatedFiles,
boundaryKey: boundaryKey,
recompileRestart: recompileRestart);
Future<void> sendRecompileAndAccept(
String entrypointPath, {
List<String> invalidatedFiles = const [],
String boundaryKey = fakeBoundaryKey,
required bool recompileRestart,
}) async {
await sendRecompile(
entrypointPath,
invalidatedFiles: invalidatedFiles,
boundaryKey: boundaryKey,
recompileRestart: recompileRestart,
);
sendAccept();
}
@@ -6,7 +6,8 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:dev_compiler/dev_compiler.dart' as ddc_names
import 'package:dev_compiler/dev_compiler.dart'
as ddc_names
show libraryUriToJsIdentifier;
import 'package:reload_test/ddc_helpers.dart' show FileDataPerGeneration;
@@ -48,13 +49,19 @@ class HotReloadMemoryFilesystem implements FileResolver {
///
/// [clearWritableState] clears generation-specific state so that old
/// generations' files aren't rewritten.
void writeToDisk(Uri outputDirectoryUri,
{required String generation, bool clearWritableState = true}) {
assert(Directory.fromUri(outputDirectoryUri).existsSync(),
'$outputDirectoryUri does not exist.');
void writeToDisk(
Uri outputDirectoryUri, {
required String generation,
bool clearWritableState = true,
}) {
assert(
Directory.fromUri(outputDirectoryUri).existsSync(),
'$outputDirectoryUri does not exist.',
);
files.forEach((path, content) {
final outputFileUri =
outputDirectoryUri.resolve('generation$generation/').resolve(path);
final outputFileUri = outputDirectoryUri
.resolve('generation$generation/')
.resolve(path);
final outputFile = File.fromUri(outputFileUri);
outputFile.createSync(recursive: true);
outputFile.writeAsBytesSync(content);
@@ -68,11 +75,11 @@ class HotReloadMemoryFilesystem implements FileResolver {
@override
FileDataPerGeneration get generationsToModifiedFilePaths => {
for (var e in generationChanges.entries)
e.key: e.value
.map((info) => [info.libraryName, info.jsSourcePath])
.toList()
};
for (var e in generationChanges.entries)
e.key: e.value
.map((info) => [info.libraryName, info.jsSourcePath])
.toList(),
};
@override
List<Map<String, String?>> get scriptDescriptorForBootstrap {
@@ -103,17 +110,19 @@ class HotReloadMemoryFilesystem implements FileResolver {
final codeBytes = codeFile.readAsBytesSync();
final sourcemapBytes = sourcemapFile.readAsBytesSync();
final manifest = Map.castFrom<dynamic, dynamic, String, Object?>(
json.decode(manifestFile.readAsStringSync()) as Map);
json.decode(manifestFile.readAsStringSync()) as Map,
);
generationChanges[generation] = [];
for (final filePath in manifest.keys) {
final fileUri = Uri.file(filePath);
final Map<String, dynamic> offsets =
Map.castFrom<dynamic, dynamic, String, Object?>(
manifest[filePath] as Map);
manifest[filePath] as Map,
);
final codeOffsets = (offsets['code'] as List<dynamic>).cast<int>();
final sourcemapOffsets =
(offsets['sourcemap'] as List<dynamic>).cast<int>();
final sourcemapOffsets = (offsets['sourcemap'] as List<dynamic>)
.cast<int>();
if (codeOffsets.length != 2 || sourcemapOffsets.length != 2) {
continue;
@@ -129,19 +138,23 @@ class HotReloadMemoryFilesystem implements FileResolver {
codeStart,
codeEnd - codeStart,
);
final fileName =
filePath.startsWith('/') ? filePath.substring(1) : filePath;
final fileName = filePath.startsWith('/')
? filePath.substring(1)
: filePath;
files[fileName] = byteView;
final moduleName = ddc_names.libraryUriToJsIdentifier(fileUri);
// TODO(markzipan): This is an overly simple heuristic to resolve the
// original Dart file. Replace this if it no longer holds.
var dartFileName = fileName;
if (dartFileName.endsWith('.lib.js')) {
dartFileName =
fileName.substring(0, fileName.length - '.lib.js'.length);
dartFileName = fileName.substring(
0,
fileName.length - '.lib.js'.length,
);
}
final fullyResolvedFileUri =
jsRootUri.resolve('generation$generation/$fileName');
final fullyResolvedFileUri = jsRootUri.resolve(
'generation$generation/$fileName',
);
// This is a simple hack to resolve kernel library URIs from JS files.
// This should be safe for hot reload tests but won't generalize.
var libraryName = dartFileName;
@@ -152,10 +165,11 @@ class HotReloadMemoryFilesystem implements FileResolver {
libraryName = 'hot-reload-test:///$libraryName';
}
final libraryInfo = LibraryInfo(
moduleName: moduleName,
libraryName: libraryName,
dartSourcePath: dartFileName,
jsSourcePath: fullyResolvedFileUri.toFilePath());
moduleName: moduleName,
libraryName: libraryName,
dartSourcePath: dartFileName,
jsSourcePath: fullyResolvedFileUri.toFilePath(),
);
libraries.add(libraryInfo);
if (generation == '0') {
firstGenerationLibraries.add(libraryInfo);
@@ -187,11 +201,12 @@ class LibraryInfo {
final String dartSourcePath;
final String jsSourcePath;
LibraryInfo(
{required this.moduleName,
required this.libraryName,
required this.dartSourcePath,
required this.jsSourcePath});
LibraryInfo({
required this.moduleName,
required this.libraryName,
required this.dartSourcePath,
required this.jsSourcePath,
});
@override
String toString() =>
+4 -8
View File
@@ -4,11 +4,7 @@
import 'dart:convert';
enum Status {
accepted,
rejected,
restarted;
}
enum Status { accepted, rejected, restarted }
/// Reports the result of a hot reload or restart at runtime for test validation
/// purposes only.
@@ -35,9 +31,9 @@ class HotReloadReceipt {
String toString() => jsonEncode(toJson());
HotReloadReceipt.fromJson(Map<String, dynamic> json)
: generation = json[_generationKey] as int,
status = Status.values.byName(json[_statusKey] as String),
rejectionMessage = json[_rejectionMessageKey] as String?;
: generation = json[_generationKey] as int,
status = Status.values.byName(json[_statusKey] as String),
rejectionMessage = json[_rejectionMessageKey] as String?;
Map<String, dynamic> toJson() {
return {
+28 -17
View File
@@ -36,7 +36,8 @@ external _DartDevEmbedder get _dartDevEmbedder;
@JS('\$injectedFilesAndLibrariesToReload')
external JSArray<JSArray<JSString>>? injectedFilesAndLibrariesToReload(
JSNumber requestedFileGeneration);
JSNumber requestedFileGeneration,
);
@JS('\$dartLoader')
external _DartLoader get _dartLoader;
@@ -51,8 +52,10 @@ Future<void> hotRestart() async {
generation: _ddcLoader.intendedHotRestartGeneration,
status: Status.restarted,
);
print('${HotReloadReceipt.hotReloadReceiptTag}'
'${jsonEncode(restartReceipt.toJson())}');
print(
'${HotReloadReceipt.hotReloadReceiptTag}'
'${jsonEncode(restartReceipt.toJson())}',
);
await _dartDevEmbedder.hotRestart().toDart;
}
@@ -68,24 +71,29 @@ int _hotReloadFileGeneration = 0;
Future<void> hotReload({bool expectRejection = false}) async {
_hotReloadFileGeneration++;
final generationFileInfo =
injectedFilesAndLibrariesToReload(_hotReloadFileGeneration.toJS);
final generationFileInfo = injectedFilesAndLibrariesToReload(
_hotReloadFileGeneration.toJS,
);
final HotReloadReceipt reloadStatus = expectRejection
? _rejectNextGeneration(generationFileInfo)
: await _reloadNextGeneration(generationFileInfo);
// Write reload receipt with a leading tag to be recognized by the reload
// suite runner and validated.
print('${HotReloadReceipt.hotReloadReceiptTag}'
'${jsonEncode(reloadStatus.toJson())}');
print(
'${HotReloadReceipt.hotReloadReceiptTag}'
'${jsonEncode(reloadStatus.toJson())}',
);
}
HotReloadReceipt _rejectNextGeneration(
JSArray<JSArray<JSString>>? generationFileInfo) {
JSArray<JSArray<JSString>>? generationFileInfo,
) {
if (generationFileInfo != null) {
throw Exception(
'Generation $_hotReloadFileGeneration was not rejected at compile '
'time. Verify the calls of `hotReload(expectRejection: true)` in the '
'test source match the rejected generation files.');
'Generation $_hotReloadFileGeneration was not rejected at compile '
'time. Verify the calls of `hotReload(expectRejection: true)` in the '
'test source match the rejected generation files.',
);
}
// This reload wasn't expected to find any files so this is OK.
// * The correct reason for rejection was already validated at compile time
@@ -100,16 +108,19 @@ HotReloadReceipt _rejectNextGeneration(
}
Future<HotReloadReceipt> _reloadNextGeneration(
JSArray<JSArray<JSString>>? generationFileInfo) async {
JSArray<JSArray<JSString>>? generationFileInfo,
) async {
if (generationFileInfo == null) {
throw Exception(
'No compiled files found for generation $_hotReloadFileGeneration. '
'Verify the calls of `hotReload()` in the test match the accepted '
'generation source files.');
'No compiled files found for generation $_hotReloadFileGeneration. '
'Verify the calls of `hotReload()` in the test match the accepted '
'generation source files.',
);
}
await (_dartDevEmbedder.hotReload(
generationFileInfo[0], generationFileInfo[1]))
.toDart;
generationFileInfo[0],
generationFileInfo[1],
)).toDart;
return HotReloadReceipt(
generation: _hotReloadFileGeneration,
status: Status.accepted,
+63 -32
View File
@@ -34,8 +34,10 @@ Future<void> hotReload({bool expectRejection = false}) async {
}
// Write reload receipt with a leading tag to be recognized by the reload
// suite runner and validated.
print('${HotReloadReceipt.hotReloadReceiptTag}'
'${jsonEncode(reloadReceipt.toJson())}');
print(
'${HotReloadReceipt.hotReloadReceiptTag}'
'${jsonEncode(reloadReceipt.toJson())}',
);
}
/// Helper to mediate with the vm service protocol.
@@ -70,26 +72,37 @@ class HotReloadHelper {
/// The current generation being executed by the VM.
int generation = 0;
HotReloadHelper._(this._vmService, this._id, this.testOutputDirUri,
this.dillName, this.errorDillName);
HotReloadHelper._(
this._vmService,
this._id,
this.testOutputDirUri,
this.dillName,
this.errorDillName,
);
/// Create a helper that is bound to the current VM and isolate.
static Future<HotReloadHelper> create() async {
final info =
await Service.controlWebServer(enable: true, silenceOutput: true);
final info = await Service.controlWebServer(
enable: true,
silenceOutput: true,
);
final observatoryUri = info.serverUri;
if (observatoryUri == null) {
print('Error: no VM service found. '
'Please invoke dart with `--enable-vm-service`.');
print(
'Error: no VM service found. '
'Please invoke dart with `--enable-vm-service`.',
);
io.exit(1);
}
final wsUri = 'ws://${observatoryUri.authority}${observatoryUri.path}ws';
final vmService = await vm_service_io.vmServiceConnectUri(wsUri);
final vm = await vmService.getVM();
final id =
vm.isolates!.firstWhere((isolate) => !isolate.isSystemIsolate!).id!;
final currentIsolateGroup = vm.isolateGroups!
.firstWhere((isolateGroup) => !isolateGroup.isSystemIsolateGroup!);
final id = vm.isolates!
.firstWhere((isolate) => !isolate.isSystemIsolate!)
.id!;
final currentIsolateGroup = vm.isolateGroups!.firstWhere(
(isolateGroup) => !isolateGroup.isSystemIsolateGroup!,
);
final dillUri = Uri.file(currentIsolateGroup.name!);
final generationPart =
dillUri.pathSegments[dillUri.pathSegments.length - 2];
@@ -102,7 +115,12 @@ class HotReloadHelper {
final errorDillName = dillName.replaceAll('.dill', '.error.dill');
return HotReloadHelper._(
vmService, id, dillUri.resolve('../'), dillName, errorDillName);
vmService,
id,
dillUri.resolve('../'),
dillName,
errorDillName,
);
}
/// Trigger a hot-reload on the current isolate for the next generation.
@@ -111,14 +129,19 @@ class HotReloadHelper {
/// is disconnected to allow the VM to complete.
Future<HotReloadReceipt> _reloadNextGeneration() async {
generation += 1;
final nextGenerationDillUri =
testOutputDirUri.resolve('generation$generation/$dillName');
final nextGenerationDillUri = testOutputDirUri.resolve(
'generation$generation/$dillName',
);
print('Reloading: $nextGenerationDillUri');
var reloadReport = await _vmService.reloadSources(_id,
rootLibUri: nextGenerationDillUri.path);
var reloadReport = await _vmService.reloadSources(
_id,
rootLibUri: nextGenerationDillUri.path,
);
if (!reloadReport.success!) {
throw Exception('Reload for generation $generation was rejected.\n'
'${reloadReport.reasonForCancelling}');
throw Exception(
'Reload for generation $generation was rejected.\n'
'${reloadReport.reasonForCancelling}',
);
}
var reloadReceipt = HotReloadReceipt(
generation: generation,
@@ -132,24 +155,31 @@ class HotReloadHelper {
generation += 1;
HotReloadReceipt reloadReceipt;
final errorDillFile = io.File.fromUri(
testOutputDirUri.resolve('generation$generation/$errorDillName'));
testOutputDirUri.resolve('generation$generation/$errorDillName'),
);
if (errorDillFile.existsSync()) {
// This generation contained a compile time error that has already been
// validated and should be rejected.
reloadReceipt = HotReloadReceipt(
generation: generation,
status: Status.rejected,
rejectionMessage: HotReloadReceipt.compileTimeErrorMessage);
generation: generation,
status: Status.rejected,
rejectionMessage: HotReloadReceipt.compileTimeErrorMessage,
);
} else {
final nextGenerationDillUri =
testOutputDirUri.resolve('generation$generation/$dillName');
final nextGenerationDillUri = testOutputDirUri.resolve(
'generation$generation/$dillName',
);
print('Reloading (expecting rejection): $nextGenerationDillUri');
final reloadReport = await _vmService.reloadSources(_id,
rootLibUri: nextGenerationDillUri.path);
final reloadReport = await _vmService.reloadSources(
_id,
rootLibUri: nextGenerationDillUri.path,
);
if (reloadReport.success!) {
throw Exception('Generation $generation was not rejected. Verify the '
'calls of `hotReload(expectRejection: true)` in the test source '
'match the rejected generation files.');
throw Exception(
'Generation $generation was not rejected. Verify the '
'calls of `hotReload(expectRejection: true)` in the test source '
'match the rejected generation files.',
);
}
reloadReceipt = HotReloadReceipt(
generation: generation,
@@ -162,8 +192,9 @@ class HotReloadHelper {
}
bool get hasNextGeneration {
final nextNextGenerationDirUri =
testOutputDirUri.resolve('generation${generation + 1}');
final nextNextGenerationDirUri = testOutputDirUri.resolve(
'generation${generation + 1}',
);
return io.Directory.fromUri(nextNextGenerationDirUri).existsSync();
}
+21 -21
View File
@@ -25,22 +25,22 @@ class TestResultOutcome {
});
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': matchedExpectations,
});
'name': '$suiteName/$testName',
'configuration': configuration,
'suite': suiteName,
'test_name': testName,
'time_ms': elapsedTime.inMilliseconds,
'expected': expectedResult,
'result': matchedExpectations ? 'Pass' : 'Fail',
'matches': matchedExpectations,
});
String toLogJson() => _encoder.convert({
'name': '$suiteName/$testName',
'configuration': configuration,
'result': matchedExpectations ? 'Pass' : 'Fail',
'log': testOutput,
});
'name': '$suiteName/$testName',
'configuration': configuration,
'result': matchedExpectations ? 'Pass' : 'Fail',
'log': testOutput,
});
}
/// Escapes backslashes in [unescaped].
@@ -83,11 +83,14 @@ class ReloadTestConfiguration {
ReloadTestConfiguration._(this.excludedPlatforms, this.expectedErrors);
factory ReloadTestConfiguration() => ReloadTestConfiguration._(
const <RuntimePlatforms>{}, const <int, String>{});
const <RuntimePlatforms>{},
const <int, String>{},
);
factory ReloadTestConfiguration.fromJsonFile(Uri file) {
final Map<String, dynamic> jsonData =
jsonDecode(File.fromUri(file).readAsStringSync());
final Map<String, dynamic> jsonData = jsonDecode(
File.fromUri(file).readAsStringSync(),
);
final excludedPlatforms = <RuntimePlatforms>{};
final rawExcludedPlatforms = jsonData['exclude'];
if (rawExcludedPlatforms != null) {
@@ -104,9 +107,6 @@ class ReloadTestConfiguration {
expectedErrors[int.parse(entry.key)] = entry.value as String;
}
}
return ReloadTestConfiguration._(
excludedPlatforms,
expectedErrors,
);
return ReloadTestConfiguration._(excludedPlatforms, expectedErrors);
}
}
+192 -138
View File
@@ -43,7 +43,8 @@ void main() {
final sourcemapFile = File.fromUri(testDirectory.uri.resolve('test.map'))
..writeAsBytesSync(sourcemap);
final manifest = '''
final manifest =
'''
{
"/file1.ext": {
"code": [0, ${source1.codeUnits.length}],
@@ -61,50 +62,59 @@ void main() {
}
}
'''
.codeUnits;
.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.');
var updatedFiles = filesystem.update(
sourcesFile,
manifestFile,
sourcemapFile,
generation: "0",
);
expect(
filesystem.files,
equals(
{'file1.ext': source1.codeUnits, 'file2.ext': source2.codeUnits}),
reason: 'Filesystem source files are correctly stored.');
updatedFiles,
equals(['file1.ext', 'file2.ext']),
reason: 'Updated files are correctly reported.',
);
expect(
filesystem.sourcemaps,
equals({
'file1.ext.map': sourcemap1.codeUnits,
'file2.ext.map': sourcemap2.codeUnits,
}),
reason: 'Filesystem sourcemaps are correctly stored.');
filesystem.files,
equals({'file1.ext': source1.codeUnits, 'file2.ext': source2.codeUnits}),
reason: 'Filesystem source files are correctly stored.',
);
expect(
filesystem.generationsToModifiedFilePaths,
equals({
'0': [
[
'hot-reload-test:///file1.ext',
jsOutputUri.resolve('generation0/file1.ext').toFilePath()
],
[
'hot-reload-test:///file2.ext',
jsOutputUri.resolve('generation0/file2.ext').toFilePath()
]
]
}),
reason:
'Filesystem emits correct generation to modfied files mapping.');
filesystem.sourcemaps,
equals({
'file1.ext.map': sourcemap1.codeUnits,
'file2.ext.map': sourcemap2.codeUnits,
}),
reason: 'Filesystem sourcemaps are correctly stored.',
);
expect(
filesystem.generationsToModifiedFilePaths,
equals({
'0': [
[
'hot-reload-test:///file1.ext',
jsOutputUri.resolve('generation0/file1.ext').toFilePath(),
],
[
'hot-reload-test:///file2.ext',
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 = '''
final manifest2 =
'''
{
"/file3.ext": {
"code": [0, ${source1.codeUnits.length}],
@@ -122,140 +132,184 @@ void main() {
}
}
'''
.codeUnits;
.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.');
updatedFiles = filesystem.update(
sourcesFile,
manifestFile,
sourcemapFile,
generation: "1",
);
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.');
updatedFiles,
equals(['file3.ext', 'file4.ext']),
reason: 'Updated files are correctly reported.',
);
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.');
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.generationsToModifiedFilePaths,
equals({
'0': [
[
'hot-reload-test:///file1.ext',
jsOutputUri.resolve('generation0/file1.ext').toFilePath()
],
[
'hot-reload-test:///file2.ext',
jsOutputUri.resolve('generation0/file2.ext').toFilePath()
]
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': [
[
'hot-reload-test:///file1.ext',
jsOutputUri.resolve('generation0/file1.ext').toFilePath(),
],
'1': [
[
'hot-reload-test:///file3.ext',
jsOutputUri.resolve('generation1/file3.ext').toFilePath()
],
[
'hot-reload-test:///file4.ext',
jsOutputUri.resolve('generation1/file4.ext').toFilePath()
],
[
'hot-reload-test:///file2.ext',
jsOutputUri.resolve('generation0/file2.ext').toFilePath(),
],
}),
reason:
'Filesystem emits correct generation to modfied files mapping.');
],
'1': [
[
'hot-reload-test:///file3.ext',
jsOutputUri.resolve('generation1/file3.ext').toFilePath(),
],
[
'hot-reload-test:///file4.ext',
jsOutputUri.resolve('generation1/file4.ext').toFilePath(),
],
],
}),
reason: 'Filesystem emits correct generation to modfied files mapping.',
);
expect(
filesystem.scriptDescriptorForBootstrap,
equals([
{
'id': 'hot-reload-test:///file1.ext',
'src': jsOutputUri.resolve('generation0/file1.ext').toFilePath(),
},
{
'id': 'hot-reload-test:///file2.ext',
'src': jsOutputUri.resolve('generation0/file2.ext').toFilePath(),
},
]),
reason: 'Filesystem emits correct script descriptors.');
filesystem.scriptDescriptorForBootstrap,
equals([
{
'id': 'hot-reload-test:///file1.ext',
'src': jsOutputUri.resolve('generation0/file1.ext').toFilePath(),
},
{
'id': 'hot-reload-test:///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);
File(
jsOutputUri.resolve('generation3/file1.ext').toFilePath(),
).existsSync(),
isFalse,
);
expect(
File(jsOutputUri.resolve('generation3/file2.ext').toFilePath())
.existsSync(),
isFalse);
File(
jsOutputUri.resolve('generation3/file2.ext').toFilePath(),
).existsSync(),
isFalse,
);
expect(
File(jsOutputUri.resolve('generation3/file3.ext').toFilePath())
.existsSync(),
isFalse);
File(
jsOutputUri.resolve('generation3/file3.ext').toFilePath(),
).existsSync(),
isFalse,
);
expect(
File(jsOutputUri.resolve('generation3/file4.ext').toFilePath())
.existsSync(),
isFalse);
File(
jsOutputUri.resolve('generation3/file4.ext').toFilePath(),
).existsSync(),
isFalse,
);
filesystem.writeToDisk(jsOutputUri, generation: "3");
expect(
File(jsOutputUri.resolve('generation3/file1.ext').toFilePath())
.existsSync(),
isTrue);
File(
jsOutputUri.resolve('generation3/file1.ext').toFilePath(),
).existsSync(),
isTrue,
);
expect(
File(jsOutputUri.resolve('generation3/file2.ext').toFilePath())
.existsSync(),
isTrue);
File(
jsOutputUri.resolve('generation3/file2.ext').toFilePath(),
).existsSync(),
isTrue,
);
expect(
File(jsOutputUri.resolve('generation3/file3.ext').toFilePath())
.existsSync(),
isTrue);
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.');
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();
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);
File(
jsOutputUri.resolve('generation3/file1.ext').toFilePath(),
).existsSync(),
isFalse,
);
expect(
File(jsOutputUri.resolve('generation3/file2.ext').toFilePath())
.existsSync(),
isFalse);
File(
jsOutputUri.resolve('generation3/file2.ext').toFilePath(),
).existsSync(),
isFalse,
);
expect(
File(jsOutputUri.resolve('generation3/file3.ext').toFilePath())
.existsSync(),
isFalse);
File(
jsOutputUri.resolve('generation3/file3.ext').toFilePath(),
).existsSync(),
isFalse,
);
expect(
File(jsOutputUri.resolve('generation3/file4.ext').toFilePath())
.existsSync(),
isFalse);
File(
jsOutputUri.resolve('generation3/file4.ext').toFilePath(),
).existsSync(),
isFalse,
);
});
}