blob: 70d362e89a9900b17844e3c1893d7ef02860fc16 [file] [log] [blame]
// 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.
// 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 _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);
}
/// 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;
}
}