blob: bb795c9c62cf93a61cde593024e656230f0e92c1 [file] [edit]
// Copyright (c) 2026, 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 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:dart_data_home/dart_data_home.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
const verboseSubprocesses = false;
final packageRoot = p.dirname(
p.dirname(
Isolate.resolvePackageUriSync(
Uri.parse('package:dart_data_home/dart_data_home.dart'),
)!.toFilePath(),
),
);
final testsDir = p.join(packageRoot, 'test');
// Note: on Windows in JIT mode we have dart.exe spawning dartvm.exe, and
// underlying pid files will be created using the pid of the dartvm.exe while
// process.pid gives us access to the process id of the dart.exe. To accomodate
// for this in tests we send underlying PID from child process to the parent
// over stdout.
typedef TestScriptProcess = ({Process process, int pid});
Future<TestScriptProcess> startTestScript(
String processName,
String packageName,
String pidFileContent,
) async {
final scriptPath = p.join(testsDir, 'common', 'test_script.dart');
final process = await Process.start(Platform.resolvedExecutable, [
scriptPath,
packageName,
pidFileContent,
]);
final ready = Completer<int>();
process.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen(
(line) {
if (verboseSubprocesses) {
print('[$processName] $line');
}
if (line.startsWith('OK:')) {
ready.complete(int.parse(line.split(':')[1]));
}
},
);
process.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen(
(line) {
if (verboseSubprocesses) {
print('[$processName] $line');
}
},
);
return (process: process, pid: await ready.future);
}
Future<void> killProcess(TestScriptProcess p) async {
p.process.kill();
await p.process.exitCode;
if (Platform.isWindows) {
// On Windows in JIT mode we have dart.exe spawning dartvm.exe. Which
// means dart.exe exiting does not imply that dartvm.exe has already
// terminated as well. We don't have Dart API to wait for a specific
// process by PID so just give it a second to terminate.
await Future<void>.delayed(const Duration(seconds: 1));
}
}
void main() {
late Directory tempDir;
setUp(() {
tempDir = Directory.systemTemp.createTempSync();
});
tearDown(() {
try {
tempDir.deleteSync(recursive: true);
} catch (_) {
// Ignore any exceptions.
}
});
test('pid files', () async {
({Process process, int pid})? p1;
({Process process, int pid})? p2;
try {
p1 = await startTestScript(
'p1',
tempDir.path,
'test_content_from_script_p1',
);
p2 = await startTestScript(
'p2',
tempDir.path,
'test_content_from_script_p2',
);
// Both processes should be found by listPidFiles.
expect(
listPidFiles(tempDir.path),
equals({
p1.pid: 'test_content_from_script_p1',
p2.pid: 'test_content_from_script_p2',
}),
);
// Kill one process and check that only one process is now found.
await killProcess(p1);
expect(
listPidFiles(tempDir.path),
equals({p2.pid: 'test_content_from_script_p2'}),
);
// Kill the second process and check that no processes are found.
await killProcess(p2);
expect(listPidFiles(tempDir.path).isEmpty, isTrue);
} finally {
p1?.process.kill();
p2?.process.kill();
}
});
}