[reload_test] Adding support for VM hot reload tests.

VM hot reload are run via:
1) We first emit a dill for every generation ahead of time (full dill on gen 0, incremental deltas subsequently).
2) We start a VM process at generation 0.
3) The VM process runs until it hits a `hotReload` command. It then uses the VM service protocol to connect to itself and reload the next generation.
4) The VM exits when the next generation isn't found.

* Adds config files to reload tests that allow runtime filtering.
* Implements VM-side hot reloading
* Adds several VM-specific hot reload tests

Change-Id: I1c6ad5c4eed426a0189c1b4af31297c9c1dba717
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/359200
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
MarkZ 2024-04-07 16:39:49 +00:00 committed by Commit Queue
parent 6c2819d8ba
commit df75a619c8
35 changed files with 1002 additions and 41 deletions

View file

@ -17,14 +17,10 @@ import 'package:reload_test/frontend_server_controller.dart';
import 'package:reload_test/hot_reload_memory_filesystem.dart';
import 'package:reload_test/test_helpers.dart';
enum RuntimePlatforms {
chrome('chrome'),
d8('d8'),
vm('vm');
// Set an arbitrary cap on generations.
final globalMaxGenerations = 100;
const RuntimePlatforms(this.text);
final String text;
}
final testTimeoutSeconds = 10;
final argParser = ArgParser()
..addOption('runtime',
@ -65,6 +61,7 @@ Future<void> main(List<String> args) async {
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 vmPlatformDillUri = buildRootUri.resolve('vm_platform_strong.dill');
final sdkRoot = Platform.script.resolve('../../../');
final packageConfigUri = sdkRoot.resolve('.dart_tool/package_config.json');
@ -88,6 +85,7 @@ Future<void> main(List<String> args) async {
// Contains files emitted from Frontend Server compiles and recompiles.
final frontendServerEmittedFilesDirUri = generatedCodeUri.resolve('.fes/');
Directory.fromUri(frontendServerEmittedFilesDirUri).createSync();
final outputDillUri = frontendServerEmittedFilesDirUri.resolve('output.dill');
final outputIncrementalDillUri =
frontendServerEmittedFilesDirUri.resolve('output_incremental.dill');
@ -100,24 +98,44 @@ Future<void> main(List<String> args) async {
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',
_print('Initializing the Frontend Server.');
HotReloadFrontendServerController controller;
final commonArgs = [
'--incremental',
'--filesystem-root=${snapshotUri.toFilePath()}',
'--filesystem-scheme=$filesystemScheme',
'--output-dill=${outputDillUri.toFilePath()}',
'--output-incremental-dill=${outputIncrementalDillUri.toFilePath()}',
'--packages=${packageConfigUri.toFilePath()}',
'--platform=$ddcPlatformDillFromSdkRoot',
'--sdk-root=${sdkRoot.toFilePath()}',
'--target=dartdevc',
'--verbosity=${verbose ? 'all' : 'info'}',
];
_print('Initializing the Frontend Server.');
var controller = HotReloadFrontendServerController(ddcArgs);
switch (runtimePlatform) {
case RuntimePlatforms.d8:
final ddcPlatformDillFromSdkRoot = fe_shared.relativizeUri(
sdkRoot, ddcPlatformDillUri, fe_shared.isWindows);
final fesArgs = [
...commonArgs,
'--dartdevc-module-format=ddc',
'--platform=$ddcPlatformDillFromSdkRoot',
'--target=dartdevc',
];
controller = HotReloadFrontendServerController(fesArgs);
break;
case RuntimePlatforms.chrome:
throw Exception('Unsupported platform: $runtimePlatform');
case RuntimePlatforms.vm:
final vmPlatformDillFromSdkRoot = fe_shared.relativizeUri(
sdkRoot, vmPlatformDillUri, fe_shared.isWindows);
final fesArgs = [
...commonArgs,
'--platform=$vmPlatformDillFromSdkRoot',
'--target=vm',
];
controller = HotReloadFrontendServerController(fesArgs);
break;
}
controller.start();
Future<void> shutdown() async {
@ -130,6 +148,7 @@ Future<void> main(List<String> args) async {
}
final testOutcomes = <TestResultOutcome>[];
final validTestSourceName = RegExp(r'.*[a-zA-Z0-9]+.[0-9]+.dart');
for (var testDir in Directory.fromUri(allTestsUri).listSync()) {
if (testDir is! Directory) {
if (testDir is File) {
@ -157,16 +176,25 @@ Future<void> main(List<String> args) async {
var filesystem = HotReloadMemoryFilesystem(tempUri);
var maxGenerations = 0;
// Count the number of generations for this test.
//
// Assumes all files are named like '$name.$integer.dart', where 0 is the
// first generation.
//
// TODO(markzipan): Account for subdirectories.
var maxGenerations = 0;
var testConfig = ReloadTestConfiguration();
for (var file in testDir.listSync()) {
if (file is File) {
if (file.path.endsWith('.dart')) {
final fileName = file.uri.pathSegments.last;
// Process config files.
if (fileName == 'config.json') {
testConfig = ReloadTestConfiguration.fromJsonFile(file.uri);
} else if (fileName.endsWith('.dart')) {
if (!validTestSourceName.hasMatch(fileName)) {
throw Exception('Invalid test source file name: $fileName\n'
'Valid names look like "file_name.10.dart".');
}
var strippedName =
file.path.substring(0, file.path.length - '.dart'.length);
var parts = strippedName.split('.');
@ -175,6 +203,17 @@ Future<void> main(List<String> args) async {
}
}
}
if (maxGenerations > globalMaxGenerations) {
throw Exception('Too many generations specified in test '
'(requested: $maxGenerations, max: $globalMaxGenerations).');
}
// Skip this test directory if this platform is excluded.
if (testConfig.excludedPlaforms.contains(runtimePlatform)) {
_print('Skipping test on platform: ${runtimePlatform.text}',
label: testName);
continue;
}
// TODO(markzipan): replace this with a test-configurable main entrypoint.
final mainDartFilePath = testDir.uri.resolve('main.dart').toFilePath();
@ -248,26 +287,40 @@ Future<void> main(List<String> args) async {
'$outputDirectoryPath',
label: testName);
// Update the memory filesystem with the newly-created JS files
_print(
'Loading generation $currentGeneration files '
'into the memory filesystem.',
label: testName);
final codeFile = File('$outputDirectoryPath.sources');
final manifestFile = File('$outputDirectoryPath.json');
final sourcemapFile = File('$outputDirectoryPath.map');
filesystem.update(
codeFile,
manifestFile,
sourcemapFile,
generation: '$currentGeneration',
);
if (runtimePlatform.emitsJS) {
// Update the memory filesystem with the newly-created JS files
_print(
'Loading generation $currentGeneration files '
'into the memory filesystem.',
label: testName);
final codeFile = File('$outputDirectoryPath.sources');
final manifestFile = File('$outputDirectoryPath.json');
final sourcemapFile = File('$outputDirectoryPath.map');
filesystem.update(
codeFile,
manifestFile,
sourcemapFile,
generation: '$currentGeneration',
);
// Write JS files and sourcemaps to their respective generation.
_print('Writing generation $currentGeneration assets.', label: testName);
_debugPrint('Writing JS assets to ${tempUri.toFilePath()}',
label: testName);
filesystem.writeToDisk(tempUri, generation: '$currentGeneration');
// Write JS files and sourcemaps to their respective generation.
_print('Writing generation $currentGeneration assets.',
label: testName);
_debugPrint('Writing JS assets to ${tempUri.toFilePath()}',
label: testName);
filesystem.writeToDisk(tempUri, generation: '$currentGeneration');
} else {
final dillOutputDir =
Directory.fromUri(tempUri.resolve('generation$currentGeneration'));
dillOutputDir.createSync();
final dillOutputUri = dillOutputDir.uri.resolve('$testName.dill');
File(outputDirectoryPath).copySync(dillOutputUri.toFilePath());
// Write dills their respective generation.
_print('Writing generation $currentGeneration assets.',
label: testName);
_debugPrint('Writing dill to ${dillOutputUri.toFilePath()}',
label: testName);
}
currentGeneration++;
}
@ -305,7 +358,44 @@ Future<void> main(List<String> args) async {
break;
case RuntimePlatforms.chrome:
case RuntimePlatforms.vm:
throw Exception('Unsupported platform: $runtimePlatform');
final firstGenerationDillUri =
tempUri.resolve('generation0/$testName.dill');
// Start the VM at generation 0.
final vmArgs = [
'--enable-vm-service=0', // 0 avoids port collisions.
'--disable-service-auth-codes',
'--disable-dart-dev',
firstGenerationDillUri.toFilePath(),
];
final vm = await Process.start(Platform.executable, vmArgs);
_debugPrint(
'Starting VM with command: ${Platform.executable} ${vmArgs.join(" ")}',
label: testName);
vm.stdout
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((String line) {
_debugPrint('VM stdout: $line', label: testName);
testOutputBuffer.writeln(line);
});
vm.stderr
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((String err) {
_debugPrint('VM stderr: $err', label: testName);
testOutputBuffer.writeln(err);
});
_print('Executing VM test.', label: testName);
final vmExitCode = await vm.exitCode
.timeout(Duration(seconds: testTimeoutSeconds), onTimeout: () {
final timeoutText =
'Test timed out after $testTimeoutSeconds seconds.';
_print(timeoutText, label: testName);
testOutputBuffer.writeln(timeoutText);
vm.kill();
return 1;
});
testPassed = vmExitCode == 0;
}
stopwatch.stop();

View file

@ -4,12 +4,104 @@
// Helper functions for the hot reload test suite.
import 'dart:developer' show Service;
import 'dart:io' as io;
import 'package:vm_service/vm_service.dart' show VmService, ReloadReport;
import 'package:vm_service/vm_service_io.dart' as vm_service_io;
int get hotRestartGeneration =>
throw Exception('Not implemented on this platform.');
void hotRestart() => throw Exception('Not implemented on this platform.');
int get hotReloadGeneration =>
throw Exception('Not implemented on this platform.');
int _reloadCounter = 0;
int get hotReloadGeneration => _reloadCounter;
void hotReload() => throw Exception('Not implemented on this platform.');
HotReloadHelper? _hotReloadHelper;
Future<void> hotReload() async {
_hotReloadHelper ??= await HotReloadHelper.create();
_reloadCounter++;
await _hotReloadHelper!.reloadNextGeneration();
}
/// Helper to mediate with the vm service protocol.
///
/// Contains logic to initiate a connection with the vm service protocol on the
/// Dart VM running the current program and for requesting a hot-reload request
/// on the current isolate.
///
/// Adapted from:
/// https://github.com/dart-lang/sdk/blob/dbcf24cedbe4d3a8eccaa51712f0c98b92173ad2/pkg/dev_compiler/tool/hotreload/hot_reload_helper.dart#L77
class HotReloadHelper {
/// ID for the isolate running the test.
final String _id;
final VmService _vmService;
/// The output directory under which generation directories are saved.
final Uri testOutputDirUri;
/// File name of the dill (full or fragment) to be reloaded.
///
/// We assume that:
/// * Every generation only loads one dill
/// * All dill files have the same name across every generation
final String dillName;
/// The current generation being executed by the VM.
int generation = 0;
HotReloadHelper._(
this._vmService, this._id, this.testOutputDirUri, this.dillName);
/// 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 observatoryUri = info.serverUri;
if (observatoryUri == null) {
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 dillUri = Uri.file(currentIsolateGroup.name!);
final generationPart =
dillUri.pathSegments[dillUri.pathSegments.length - 2];
if (!generationPart.startsWith('generation')) {
print('Error: Unable to find generation in dill file: $dillUri.');
io.exit(1);
}
return HotReloadHelper._(
vmService, id, dillUri.resolve('../'), dillUri.pathSegments.last);
}
/// Trigger a hot-reload on the current isolate for the next generation.
///
/// Also checks that the generation aftewards exists. If not, the VM service
/// is disconnected to allow the VM to complete.
Future<ReloadReport> reloadNextGeneration() async {
generation += 1;
final nextGenerationDillUri =
testOutputDirUri.resolve('generation$generation/$dillName');
print('Reloading: $nextGenerationDillUri');
var reloadReport = await _vmService.reloadSources(_id,
rootLibUri: nextGenerationDillUri.path);
final nextNextGenerationDillUri =
testOutputDirUri.resolve('generation${generation + 1}/$dillName');
final hasNextNextGeneration =
io.File.fromUri(nextNextGenerationDillUri).existsSync();
if (!hasNextNextGeneration) {
await _vmService.dispose();
}
return reloadReport;
}
}

View file

@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
class TestResultOutcome {
// This encoder must generate each output element on its own line.
@ -48,3 +49,47 @@ class TestResultOutcome {
String escapedString(String unescaped) {
return unescaped.replaceAll(r'\', r'\\');
}
enum RuntimePlatforms {
chrome('chrome', true),
d8('d8', true),
vm('vm', false);
const RuntimePlatforms(this.text, this.emitsJS);
final String text;
final bool emitsJS;
}
/// Encodes information provided in a hot_reload test's configuration file.
///
/// Example structure:
/// {
/// "exclude": ["vm", "chrome"]
/// }
class ReloadTestConfiguration {
final Map<String, dynamic> _values;
final Set<RuntimePlatforms> excludedPlaforms;
ReloadTestConfiguration._(this._values, this.excludedPlaforms);
factory ReloadTestConfiguration() => ReloadTestConfiguration._(
const <String, dynamic>{}, <RuntimePlatforms>{});
factory ReloadTestConfiguration.fromJsonFile(Uri file) {
final Map<String, dynamic> jsonData =
jsonDecode(File.fromUri(file).readAsStringSync());
final excludedPlaforms = <RuntimePlatforms>{};
var rawExcludedPlatforms = jsonData['exclude'];
if (rawExcludedPlatforms != null) {
for (final String platform in rawExcludedPlatforms) {
final runtimePlatform = RuntimePlatforms.values.byName(platform);
excludedPlaforms.add(runtimePlatform);
}
}
return ReloadTestConfiguration._(jsonData, excludedPlaforms);
}
String toJson() {
return JsonEncoder().convert(_values);
}
}

View file

@ -11,6 +11,7 @@ environment:
dependencies:
dev_compiler: any
frontend_server: any
vm_service: any
# Use 'any' constraints here; we get our versions from the DEPS file.
dev_dependencies:

View file

@ -0,0 +1,3 @@
{
"exclude": ["d8", "chrome"]
}

View file

@ -0,0 +1,17 @@
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'dart:math';
void validate() {
// Initial program is valid. Symbols in 'dart:math' are visible.
Expect.equals(0, hotReloadGeneration);
Expect.equals(e, 2.718281828459045);
Expect.type<double>(e);
}
Future<void> main() async {
validate();
await hotReload();
validate();
}

View file

@ -0,0 +1,20 @@
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'dart:math';
import 'dart:convert';
void validate() {
// Symbols in 'dart:convert' are visible after hot reload.
Expect.equals(1, hotReloadGeneration);
Expect.equals(e, 2.718281828459045);
Expect.type<double>(e);
Expect.type<Codec>(utf8);
Expect.type<Function>(jsonEncode);
}
Future<void> main() async {
validate();
await hotReload();
validate();
}

View file

@ -0,0 +1,3 @@
{
"exclude": ["vm"]
}

View file

@ -0,0 +1 @@
get line => "part1";

View file

@ -0,0 +1 @@
get line => "part3";

View file

@ -0,0 +1 @@
get line => "part5";

View file

@ -0,0 +1 @@
get line => "part4";

View file

@ -0,0 +1 @@
String g() => "";

View file

@ -0,0 +1,10 @@
String g() {
return bField.a("a");
}
class B {
dynamic a;
B({this.a});
}
var bField = B(a: (String s) => "$s");

View file

@ -0,0 +1,11 @@
String g() {
return bField.a("a") + (bField.b ?? "c");
}
class B {
dynamic a;
dynamic b;
B({this.a});
}
var bField = B(a: (String s) => "$s");

View file

@ -0,0 +1,3 @@
{
"exclude": ["d8", "chrome"]
}

View file

@ -0,0 +1,55 @@
// 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.
// Adapted from:
// https://github.com/dart-lang/sdk/blob/8df8de82b38fae8e9ae1c310fef4f8b735649fd4/pkg/front_end/test/hot_reload_e2e_test.dart
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'b.dart';
import 'c.dart';
f() => "$line part2";
Future<void> main() async {
// Initial program is valid.
var last = f();
Expect.equals('part1 part2', last);
Expect.equals(0, hotReloadGeneration);
await hotReload();
// Reload after leaf library modification.
last = f();
Expect.equals('part3 part2', last);
Expect.equals(1, hotReloadGeneration);
await hotReload();
// Reload after non-leaf library modification.
last = f();
Expect.equals('part3 part4', last);
Expect.equals(2, hotReloadGeneration);
await hotReload();
// Reload after whole program modification.
last = f();
Expect.equals('part5 part6', last);
Expect.equals(3, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
var topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('a', topLevel);
Expect.equals(4, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('ac', topLevel);
Expect.equals(5, hotReloadGeneration);
}

View file

@ -0,0 +1,55 @@
// 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.
// Adapted from:
// https://github.com/dart-lang/sdk/blob/8df8de82b38fae8e9ae1c310fef4f8b735649fd4/pkg/front_end/test/hot_reload_e2e_test.dart
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'b.dart';
import 'c.dart';
f() => "$line part2";
Future<void> main() async {
// Initial program is valid.
var last = f();
Expect.equals('part1 part2', last);
Expect.equals(0, hotReloadGeneration);
await hotReload();
// Reload after leaf library modification.
last = f();
Expect.equals('part3 part2', last);
Expect.equals(1, hotReloadGeneration);
await hotReload();
// Reload after non-leaf library modification.
last = f();
Expect.equals('part3 part4', last);
Expect.equals(2, hotReloadGeneration);
await hotReload();
// Reload after whole program modification.
last = f();
Expect.equals('part5 part6', last);
Expect.equals(3, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
var topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('a', topLevel);
Expect.equals(4, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('ac', topLevel);
Expect.equals(5, hotReloadGeneration);
}

View file

@ -0,0 +1,55 @@
// 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.
// Adapted from:
// https://github.com/dart-lang/sdk/blob/8df8de82b38fae8e9ae1c310fef4f8b735649fd4/pkg/front_end/test/hot_reload_e2e_test.dart
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'b.dart';
import 'c.dart';
f() => "$line part4";
Future<void> main() async {
// Initial program is valid.
var last = f();
Expect.equals('part1 part2', last);
Expect.equals(0, hotReloadGeneration);
await hotReload();
// Reload after leaf library modification.
last = f();
Expect.equals('part3 part2', last);
Expect.equals(1, hotReloadGeneration);
await hotReload();
// Reload after non-leaf library modification.
last = f();
Expect.equals('part3 part4', last);
Expect.equals(2, hotReloadGeneration);
await hotReload();
// Reload after whole program modification.
last = f();
Expect.equals('part5 part6', last);
Expect.equals(3, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
var topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('a', topLevel);
Expect.equals(4, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('ac', topLevel);
Expect.equals(5, hotReloadGeneration);
}

View file

@ -0,0 +1,55 @@
// 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.
// Adapted from:
// https://github.com/dart-lang/sdk/blob/8df8de82b38fae8e9ae1c310fef4f8b735649fd4/pkg/front_end/test/hot_reload_e2e_test.dart
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'b.dart';
import 'c.dart';
f() => "$line part6";
Future<void> main() async {
// Initial program is valid.
var last = f();
Expect.equals('part1 part2', last);
Expect.equals(0, hotReloadGeneration);
await hotReload();
// Reload after leaf library modification.
last = f();
Expect.equals('part3 part2', last);
Expect.equals(1, hotReloadGeneration);
await hotReload();
// Reload after non-leaf library modification.
last = f();
Expect.equals('part3 part4', last);
Expect.equals(2, hotReloadGeneration);
await hotReload();
// Reload after whole program modification.
last = f();
Expect.equals('part5 part6', last);
Expect.equals(3, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
var topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('a', topLevel);
Expect.equals(4, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('ac', topLevel);
Expect.equals(5, hotReloadGeneration);
}

View file

@ -0,0 +1,55 @@
// 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.
// Adapted from:
// https://github.com/dart-lang/sdk/blob/8df8de82b38fae8e9ae1c310fef4f8b735649fd4/pkg/front_end/test/hot_reload_e2e_test.dart
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'b.dart';
import 'c.dart';
f() => "$line part6";
Future<void> main() async {
// Initial program is valid.
var last = f();
Expect.equals('part1 part2', last);
Expect.equals(0, hotReloadGeneration);
await hotReload();
// Reload after leaf library modification.
last = f();
Expect.equals('part3 part2', last);
Expect.equals(1, hotReloadGeneration);
await hotReload();
// Reload after non-leaf library modification.
last = f();
Expect.equals('part3 part4', last);
Expect.equals(2, hotReloadGeneration);
await hotReload();
// Reload after whole program modification.
last = f();
Expect.equals('part5 part6', last);
Expect.equals(3, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
var topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('a', topLevel);
Expect.equals(4, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('ac', topLevel);
Expect.equals(5, hotReloadGeneration);
}

View file

@ -0,0 +1,55 @@
// 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.
// Adapted from:
// https://github.com/dart-lang/sdk/blob/8df8de82b38fae8e9ae1c310fef4f8b735649fd4/pkg/front_end/test/hot_reload_e2e_test.dart
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'b.dart';
import 'c.dart';
f() => "$line part6";
Future<void> main() async {
// Initial program is valid.
var last = f();
Expect.equals('part1 part2', last);
Expect.equals(0, hotReloadGeneration);
await hotReload();
// Reload after leaf library modification.
last = f();
Expect.equals('part3 part2', last);
Expect.equals(1, hotReloadGeneration);
await hotReload();
// Reload after non-leaf library modification.
last = f();
Expect.equals('part3 part4', last);
Expect.equals(2, hotReloadGeneration);
await hotReload();
// Reload after whole program modification.
last = f();
Expect.equals('part5 part6', last);
Expect.equals(3, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
var topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('a', topLevel);
Expect.equals(4, hotReloadGeneration);
await hotReload();
// Reload top-level field.
last = f();
topLevel = g();
Expect.equals('part4 part6', last);
Expect.equals('ac', topLevel);
Expect.equals(5, hotReloadGeneration);
}

View file

@ -0,0 +1,3 @@
{
"exclude": ["vm"]
}

View file

@ -0,0 +1,3 @@
{
"exclude": ["d8", "chrome", "vm"]
}

View file

@ -0,0 +1,27 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
var value = "unused";
Future<void> main() async {
// Declare an unreferenced lazy static field.
Expect.equals(0, hotReloadGeneration);
await hotReload();
// The lazy static field changes value but remains unread.
Expect.equals(1, hotReloadGeneration);
await hotReload();
// The lazy static is now read and contains the updated value.
Expect.equals(2, hotReloadGeneration);
Expect.equals("before", value);
await hotReload();
// The lazy static is updated and read but retains the old value.
Expect.equals(3, hotReloadGeneration);
Expect.equals("before", value);
}

View file

@ -0,0 +1,27 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
var value = "before";
Future<void> main() async {
// Declare an unreferenced lazy static field.
Expect.equals(0, hotReloadGeneration);
await hotReload();
// The lazy static field changes value but remains unread.
Expect.equals(1, hotReloadGeneration);
await hotReload();
// The lazy static is now read and contains the updated value.
Expect.equals(2, hotReloadGeneration);
Expect.equals("before", value);
await hotReload();
// The lazy static is updated and read but retains the old value.
Expect.equals(3, hotReloadGeneration);
Expect.equals("before", value);
}

View file

@ -0,0 +1,28 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
var value = "before";
Future<void> main() async {
// Declare an unreferenced lazy static field.
Expect.equals(0, hotReloadGeneration);
await hotReload();
// The lazy static field changes value but remains unread.
Expect.equals(1, hotReloadGeneration);
await hotReload();
// The lazy static is now read and contains the updated value.
print(value);
Expect.equals(2, hotReloadGeneration);
Expect.equals("before", value);
await hotReload();
// The lazy static is updated and read but retains the old value.
Expect.equals(3, hotReloadGeneration);
Expect.equals("before", value);
}

View file

@ -0,0 +1,27 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
var value = "after";
Future<void> main() async {
// Declare an unreferenced lazy static field.
Expect.equals(0, hotReloadGeneration);
await hotReload();
// The lazy static field changes value but remains unread.
Expect.equals(1, hotReloadGeneration);
await hotReload();
// The lazy static is now read and contains the updated value.
Expect.equals(2, hotReloadGeneration);
Expect.equals("before", value);
await hotReload();
// The lazy static is updated and read but retains the old value.
Expect.equals(3, hotReloadGeneration);
Expect.equals("before", value);
}

View file

@ -0,0 +1,3 @@
{
"exclude": ["d8", "chrome"]
}

View file

@ -0,0 +1,33 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
// Adapted from:
// https://github.com/dart-lang/sdk/blob/36c0788137d55c6c77f4b9a8be12e557bc764b1c/runtime/vm/isolate_reload_test.cc#L4810
int myInitialValue = 8 * 7;
class Foo {
int x = 4;
}
late Foo value;
late Foo value1;
void validate() {
Expect.equals(0, hotReloadGeneration);
value = Foo();
value1 = Foo();
Expect.equals(4, value.x);
Expect.equals(4, value1.x);
Expect.equals(56, myInitialValue);
}
Future<void> main() async {
validate();
await hotReload();
validate();
}

View file

@ -0,0 +1,36 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
// Adapted from:
// https://github.com/dart-lang/sdk/blob/36c0788137d55c6c77f4b9a8be12e557bc764b1c/runtime/vm/isolate_reload_test.cc#L4810
int myInitialValue = 8 * 7;
class Foo {
int x = 4;
int y = myInitialValue++;
}
late Foo value;
late Foo value1;
void validate() {
// Add field 'y' with side effects and verify that initializers ran lazily.
Expect.equals(1, hotReloadGeneration);
Expect.equals(4, value.x);
Expect.equals(4, value1.x);
Expect.equals(56, myInitialValue);
Expect.equals(56, value.y);
Expect.equals(57, value1.y);
Expect.equals(58, myInitialValue);
}
Future<void> main() async {
validate();
await hotReload();
validate();
}

View file

@ -0,0 +1,3 @@
{
"exclude": ["d8", "chrome"]
}

View file

@ -0,0 +1,47 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'dart:async';
var counter = 0;
Future<void> main() async {
final periodicTimerDone = Completer();
Expect.equals(0, hotReloadGeneration);
Expect.equals(0, counter++);
Timer.periodic(Duration(milliseconds: 400), (timer) {
// Runs in the final generation.
Expect.equals(2, hotReloadGeneration);
periodicTimerDone.complete();
timer.cancel();
});
await Future.delayed(Duration(milliseconds: 20), () {
Expect.equals(1, counter++);
});
Expect.equals(0, hotReloadGeneration);
Expect.isFalse(periodicTimerDone.isCompleted);
await hotReload();
Expect.equals(1, hotReloadGeneration);
Expect.equals(2, counter++);
await Future.delayed(Duration(milliseconds: 20), () {
Expect.equals(3, counter++);
});
Expect.equals(1, hotReloadGeneration);
Expect.isFalse(periodicTimerDone.isCompleted);
await hotReload();
Expect.isFalse(periodicTimerDone.isCompleted);
Expect.equals(2, hotReloadGeneration);
Expect.equals(4, counter++);
await periodicTimerDone.future;
}

View file

@ -0,0 +1,47 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'dart:async';
var counter = 0;
Future<void> main() async {
final periodicTimerDone = Completer();
Expect.equals(0, hotReloadGeneration);
Expect.equals(0, counter++);
Timer.periodic(Duration(milliseconds: 400), (timer) {
// Runs in the final generation.
Expect.equals(2, hotReloadGeneration);
periodicTimerDone.complete();
timer.cancel();
});
await Future.delayed(Duration(milliseconds: 20), () {
Expect.equals(1, counter++);
});
Expect.equals(0, hotReloadGeneration);
Expect.isFalse(periodicTimerDone.isCompleted);
await hotReload();
Expect.equals(1, hotReloadGeneration);
Expect.equals(2, counter++);
await Future.delayed(Duration(milliseconds: 20), () {
Expect.equals(3, counter++);
});
Expect.equals(1, hotReloadGeneration);
Expect.isFalse(periodicTimerDone.isCompleted);
await hotReload();
Expect.isFalse(periodicTimerDone.isCompleted);
Expect.equals(2, hotReloadGeneration);
Expect.equals(4, counter++);
await periodicTimerDone.future;
}

View file

@ -0,0 +1,47 @@
// 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
import 'dart:async';
var counter = 0;
Future<void> main() async {
final periodicTimerDone = Completer();
Expect.equals(0, hotReloadGeneration);
Expect.equals(0, counter++);
Timer.periodic(Duration(milliseconds: 400), (timer) {
// Runs in the final generation.
Expect.equals(2, hotReloadGeneration);
periodicTimerDone.complete();
timer.cancel();
});
await Future.delayed(Duration(milliseconds: 20), () {
Expect.equals(1, counter++);
});
Expect.equals(0, hotReloadGeneration);
Expect.isFalse(periodicTimerDone.isCompleted);
await hotReload();
Expect.equals(1, hotReloadGeneration);
Expect.equals(2, counter++);
await Future.delayed(Duration(milliseconds: 20), () {
Expect.equals(3, counter++);
});
Expect.equals(1, hotReloadGeneration);
Expect.isFalse(periodicTimerDone.isCompleted);
await hotReload();
Expect.isFalse(periodicTimerDone.isCompleted);
Expect.equals(2, hotReloadGeneration);
Expect.equals(4, counter++);
await periodicTimerDone.future;
}