Skip to content

Commit

Permalink
Version 3.5.0-33.0.dev
Browse files Browse the repository at this point in the history
Merge df75a61 into dev
  • Loading branch information
Dart CI committed Apr 7, 2024
2 parents 6ef5505 + df75a61 commit ac3b01d
Show file tree
Hide file tree
Showing 36 changed files with 1,004 additions and 43 deletions.
168 changes: 129 additions & 39 deletions pkg/dev_compiler/test/hot_reload_suite.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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('.');
Expand All @@ -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();
Expand Down Expand Up @@ -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',
);

// 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');
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');
} 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++;
}

Expand Down Expand Up @@ -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();
Expand Down
98 changes: 95 additions & 3 deletions pkg/reload_test/lib/src/_vm_reload_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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);
}

void hotReload() => throw Exception('Not implemented on this platform.');
/// 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;
}
}
Loading

0 comments on commit ac3b01d

Please sign in to comment.