diff --git a/pkg/dev_compiler/test/hot_reload_suite.dart b/pkg/dev_compiler/test/hot_reload_suite.dart index eed333b24c1..10553567a4a 100644 --- a/pkg/dev_compiler/test/hot_reload_suite.dart +++ b/pkg/dev_compiler/test/hot_reload_suite.dart @@ -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 main(List 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 main(List 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 main(List 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 shutdown() async { @@ -130,6 +148,7 @@ Future main(List args) async { } final testOutcomes = []; + 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 main(List 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 main(List 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 main(List 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 main(List 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(); diff --git a/pkg/reload_test/lib/src/_vm_reload_utils.dart b/pkg/reload_test/lib/src/_vm_reload_utils.dart index 772cc014355..70d362e89a9 100644 --- a/pkg/reload_test/lib/src/_vm_reload_utils.dart +++ b/pkg/reload_test/lib/src/_vm_reload_utils.dart @@ -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 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 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 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; + } +} diff --git a/pkg/reload_test/lib/test_helpers.dart b/pkg/reload_test/lib/test_helpers.dart index 665e8a6b039..3db6bcaf84c 100644 --- a/pkg/reload_test/lib/test_helpers.dart +++ b/pkg/reload_test/lib/test_helpers.dart @@ -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 _values; + final Set excludedPlaforms; + + ReloadTestConfiguration._(this._values, this.excludedPlaforms); + + factory ReloadTestConfiguration() => ReloadTestConfiguration._( + const {}, {}); + + factory ReloadTestConfiguration.fromJsonFile(Uri file) { + final Map jsonData = + jsonDecode(File.fromUri(file).readAsStringSync()); + final excludedPlaforms = {}; + 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); + } +} diff --git a/pkg/reload_test/pubspec.yaml b/pkg/reload_test/pubspec.yaml index 7b9311b0212..1d31a6a021e 100644 --- a/pkg/reload_test/pubspec.yaml +++ b/pkg/reload_test/pubspec.yaml @@ -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: diff --git a/tests/hot_reload/add_library_imports/config.json b/tests/hot_reload/add_library_imports/config.json new file mode 100644 index 00000000000..908c53e2938 --- /dev/null +++ b/tests/hot_reload/add_library_imports/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["d8", "chrome"] +} diff --git a/tests/hot_reload/add_library_imports/main.0.dart b/tests/hot_reload/add_library_imports/main.0.dart new file mode 100644 index 00000000000..3d08cadab51 --- /dev/null +++ b/tests/hot_reload/add_library_imports/main.0.dart @@ -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(e); +} + +Future main() async { + validate(); + await hotReload(); + validate(); +} diff --git a/tests/hot_reload/add_library_imports/main.1.dart b/tests/hot_reload/add_library_imports/main.1.dart new file mode 100644 index 00000000000..34a12852337 --- /dev/null +++ b/tests/hot_reload/add_library_imports/main.1.dart @@ -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(e); + Expect.type(utf8); + Expect.type(jsonEncode); +} + +Future main() async { + validate(); + await hotReload(); + validate(); +} diff --git a/tests/hot_reload/framework_timing_test/config.json b/tests/hot_reload/framework_timing_test/config.json new file mode 100644 index 00000000000..aa7ec6ee021 --- /dev/null +++ b/tests/hot_reload/framework_timing_test/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["vm"] +} diff --git a/tests/hot_reload/general_test/b.0.dart b/tests/hot_reload/general_test/b.0.dart new file mode 100644 index 00000000000..1baefc37ad2 --- /dev/null +++ b/tests/hot_reload/general_test/b.0.dart @@ -0,0 +1 @@ +get line => "part1"; diff --git a/tests/hot_reload/general_test/b.1.dart b/tests/hot_reload/general_test/b.1.dart new file mode 100644 index 00000000000..bbfaf01482d --- /dev/null +++ b/tests/hot_reload/general_test/b.1.dart @@ -0,0 +1 @@ +get line => "part3"; diff --git a/tests/hot_reload/general_test/b.3.dart b/tests/hot_reload/general_test/b.3.dart new file mode 100644 index 00000000000..b50f4ac21b2 --- /dev/null +++ b/tests/hot_reload/general_test/b.3.dart @@ -0,0 +1 @@ +get line => "part5"; diff --git a/tests/hot_reload/general_test/b.4.dart b/tests/hot_reload/general_test/b.4.dart new file mode 100644 index 00000000000..62af173940d --- /dev/null +++ b/tests/hot_reload/general_test/b.4.dart @@ -0,0 +1 @@ +get line => "part4"; diff --git a/tests/hot_reload/general_test/c.0.dart b/tests/hot_reload/general_test/c.0.dart new file mode 100644 index 00000000000..59221457e40 --- /dev/null +++ b/tests/hot_reload/general_test/c.0.dart @@ -0,0 +1 @@ +String g() => ""; diff --git a/tests/hot_reload/general_test/c.4.dart b/tests/hot_reload/general_test/c.4.dart new file mode 100644 index 00000000000..b1c871a5b73 --- /dev/null +++ b/tests/hot_reload/general_test/c.4.dart @@ -0,0 +1,10 @@ +String g() { + return bField.a("a"); +} + +class B { + dynamic a; + B({this.a}); +} + +var bField = B(a: (String s) => "$s"); diff --git a/tests/hot_reload/general_test/c.5.dart b/tests/hot_reload/general_test/c.5.dart new file mode 100644 index 00000000000..f0ad10fda4a --- /dev/null +++ b/tests/hot_reload/general_test/c.5.dart @@ -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"); diff --git a/tests/hot_reload/general_test/config.json b/tests/hot_reload/general_test/config.json new file mode 100644 index 00000000000..908c53e2938 --- /dev/null +++ b/tests/hot_reload/general_test/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["d8", "chrome"] +} diff --git a/tests/hot_reload/general_test/main.0.dart b/tests/hot_reload/general_test/main.0.dart new file mode 100644 index 00000000000..2ac4c0a5f42 --- /dev/null +++ b/tests/hot_reload/general_test/main.0.dart @@ -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 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); +} diff --git a/tests/hot_reload/general_test/main.1.dart b/tests/hot_reload/general_test/main.1.dart new file mode 100644 index 00000000000..2ac4c0a5f42 --- /dev/null +++ b/tests/hot_reload/general_test/main.1.dart @@ -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 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); +} diff --git a/tests/hot_reload/general_test/main.2.dart b/tests/hot_reload/general_test/main.2.dart new file mode 100644 index 00000000000..c5f4b167f99 --- /dev/null +++ b/tests/hot_reload/general_test/main.2.dart @@ -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 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); +} diff --git a/tests/hot_reload/general_test/main.3.dart b/tests/hot_reload/general_test/main.3.dart new file mode 100644 index 00000000000..2d6530703a6 --- /dev/null +++ b/tests/hot_reload/general_test/main.3.dart @@ -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 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); +} diff --git a/tests/hot_reload/general_test/main.4.dart b/tests/hot_reload/general_test/main.4.dart new file mode 100644 index 00000000000..2d6530703a6 --- /dev/null +++ b/tests/hot_reload/general_test/main.4.dart @@ -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 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); +} diff --git a/tests/hot_reload/general_test/main.5.dart b/tests/hot_reload/general_test/main.5.dart new file mode 100644 index 00000000000..2d6530703a6 --- /dev/null +++ b/tests/hot_reload/general_test/main.5.dart @@ -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 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); +} diff --git a/tests/hot_reload/hot_restart_constant_equality/config.json b/tests/hot_reload/hot_restart_constant_equality/config.json new file mode 100644 index 00000000000..aa7ec6ee021 --- /dev/null +++ b/tests/hot_reload/hot_restart_constant_equality/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["vm"] +} diff --git a/tests/hot_reload/lazy_static_initializers/config.json b/tests/hot_reload/lazy_static_initializers/config.json new file mode 100644 index 00000000000..910de2a2dbc --- /dev/null +++ b/tests/hot_reload/lazy_static_initializers/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["d8", "chrome", "vm"] +} diff --git a/tests/hot_reload/lazy_static_initializers/main.0.dart b/tests/hot_reload/lazy_static_initializers/main.0.dart new file mode 100644 index 00000000000..df4f43a731e --- /dev/null +++ b/tests/hot_reload/lazy_static_initializers/main.0.dart @@ -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 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); +} diff --git a/tests/hot_reload/lazy_static_initializers/main.1.dart b/tests/hot_reload/lazy_static_initializers/main.1.dart new file mode 100644 index 00000000000..ba2aa97e7f8 --- /dev/null +++ b/tests/hot_reload/lazy_static_initializers/main.1.dart @@ -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 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); +} diff --git a/tests/hot_reload/lazy_static_initializers/main.2.dart b/tests/hot_reload/lazy_static_initializers/main.2.dart new file mode 100644 index 00000000000..574c31f5da9 --- /dev/null +++ b/tests/hot_reload/lazy_static_initializers/main.2.dart @@ -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 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); +} diff --git a/tests/hot_reload/lazy_static_initializers/main.3.dart b/tests/hot_reload/lazy_static_initializers/main.3.dart new file mode 100644 index 00000000000..0fbbf774b3e --- /dev/null +++ b/tests/hot_reload/lazy_static_initializers/main.3.dart @@ -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 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); +} diff --git a/tests/hot_reload/run_lazy_field_initializers/config.json b/tests/hot_reload/run_lazy_field_initializers/config.json new file mode 100644 index 00000000000..908c53e2938 --- /dev/null +++ b/tests/hot_reload/run_lazy_field_initializers/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["d8", "chrome"] +} diff --git a/tests/hot_reload/run_lazy_field_initializers/main.0.dart b/tests/hot_reload/run_lazy_field_initializers/main.0.dart new file mode 100644 index 00000000000..86d194bfdf1 --- /dev/null +++ b/tests/hot_reload/run_lazy_field_initializers/main.0.dart @@ -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 main() async { + validate(); + await hotReload(); + validate(); +} diff --git a/tests/hot_reload/run_lazy_field_initializers/main.1.dart b/tests/hot_reload/run_lazy_field_initializers/main.1.dart new file mode 100644 index 00000000000..ea9fc52db55 --- /dev/null +++ b/tests/hot_reload/run_lazy_field_initializers/main.1.dart @@ -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 main() async { + validate(); + await hotReload(); + validate(); +} diff --git a/tests/hot_reload/timer_test/config.json b/tests/hot_reload/timer_test/config.json new file mode 100644 index 00000000000..908c53e2938 --- /dev/null +++ b/tests/hot_reload/timer_test/config.json @@ -0,0 +1,3 @@ +{ + "exclude": ["d8", "chrome"] +} diff --git a/tests/hot_reload/timer_test/main.0.dart b/tests/hot_reload/timer_test/main.0.dart new file mode 100644 index 00000000000..2a1b897e645 --- /dev/null +++ b/tests/hot_reload/timer_test/main.0.dart @@ -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 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; +} diff --git a/tests/hot_reload/timer_test/main.1.dart b/tests/hot_reload/timer_test/main.1.dart new file mode 100644 index 00000000000..2a1b897e645 --- /dev/null +++ b/tests/hot_reload/timer_test/main.1.dart @@ -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 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; +} diff --git a/tests/hot_reload/timer_test/main.2.dart b/tests/hot_reload/timer_test/main.2.dart new file mode 100644 index 00000000000..2a1b897e645 --- /dev/null +++ b/tests/hot_reload/timer_test/main.2.dart @@ -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 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; +}