Files
sdk/pkg/dds/test/devtools_server/server_connection_common.dart
Ben Konyi ad5133b373 [DDS] Fix server_connection_vm_service_test timeouts
In `server_connection_common.dart`, the `server removes clients that
disconnect from the API` test spawned its own Chrome instance using
`package:devtools_shared`'s `Chrome` class without isolated profiles or
essential headless flags.

This caused the test to hang or fail flakily in container environments
(like LUCI bots) and local environments: 1. Without
`--use-mock-keychain`, headless Chrome on macOS blocks on system
credential dialogs. 2. Without `--no-sandbox`, Chrome renderer processes
can crash in restricted container environments. 3. Without
`--user-data-dir`, Chrome uses the default system profile, which can
cause it to attach to an existing open Chrome instance instead of
starting a new one, meaning the process exits immediately and the test
cannot terminate it.

Fixed by directly using `package:browser_launcher`'s `Chrome` class in
the test and passing:
* `--user-data-dir` pointing to a unique temporary directory.
* `--no-first-run` and `--no-default-browser-check` to bypass welcome prompts.
* `--no-sandbox` and `--use-mock-keychain` where appropriate.

Also wrapped the test in `try-finally` to guarantee cleanup of the
temporary profile directory.

Change-Id: I6fbe5a280524b57c635ab11ef54fa07dba2794cf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/507600
Reviewed-by: Alexander Aprelev <aam@google.com>
Auto-Submit: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
2026-05-29 13:48:45 -07:00

146 lines
5.0 KiB
Dart

// Copyright 2022 The Chromium Authors. 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:convert';
import 'dart:io';
import 'package:browser_launcher/browser_launcher.dart';
import 'package:devtools_shared/devtools_test_utils.dart' hide Chrome;
import 'package:test/test.dart';
import 'utils/server_driver.dart';
// Note: this test is broken out from devtools_server_test.dart so that the
// tests run faster and we do not have to mark them as slow.
late final DevToolsServerTestController testController;
void runTest({required bool useVmService}) {
testController = DevToolsServerTestController();
setUp(() async {
await testController.setUp();
});
tearDown(() async {
await testController.tearDown();
});
group('Server (${useVmService ? 'VM Service' : 'API'})', () {
test(
'DevTools connects back to server API and registers that it is connected',
() async {
// Register the VM.
await testController.send(
'vm.register',
{'uri': testController.appFixture.serviceUri.toString()},
);
// Send a request to launch DevTools in a browser.
await testController.sendLaunchDevToolsRequest(
useVmService: useVmService,
);
final serverResponse =
await testController.waitForClients(requiredConnectionState: true);
expect(serverResponse, isNotNull);
expect(serverResponse['clients'], hasLength(1));
expect(serverResponse['clients'][0]['hasConnection'], isTrue);
expect(
serverResponse['clients'][0]['vmServiceUri'],
testController.appFixture.serviceUri.toString(),
);
}, timeout: const Timeout.factor(10));
test('DevTools reports disconnects from a VM', () async {
// Register the VM.
await testController.send(
'vm.register',
{'uri': testController.appFixture.serviceUri.toString()},
);
// Send a request to launch DevTools in a browser.
await testController.sendLaunchDevToolsRequest(
useVmService: useVmService,
);
// Wait for the DevTools to inform server that it's connected.
await testController.waitForClients(requiredConnectionState: true);
// Terminate the VM.
await testController.appFixture.teardown();
// Ensure the client is marked as disconnected.
final serverResponse = await testController.waitForClients(
requiredConnectionState: false,
);
expect(serverResponse['clients'], hasLength(1));
expect(serverResponse['clients'][0]['hasConnection'], isFalse);
expect(serverResponse['clients'][0]['vmServiceUri'], isNull);
}, timeout: const Timeout.factor(20));
test('server removes clients that disconnect from the API', () async {
final event = await testController.serverStartedEvent.future;
// Spawn our own Chrome process so we can terminate it.
final devToolsUri =
'http://${event['params']['host']}:${event['params']['port']}';
// Create a temporary directory for an isolated user profile.
final tempDir = Directory.systemTemp.createTempSync('devtools_chrome_profile');
final chromeProcess = await Chrome.start([devToolsUri], args: [
'--user-data-dir=${tempDir.path}', // Ensures process isolation
'--no-first-run', // Prevents welcome dialogs
'--no-default-browser-check', // Prevents default browser prompts
if (useChromeHeadless && headlessModeIsSupported) ...[
'--headless',
'--disable-gpu',
'--no-sandbox',
],
if (Platform.isMacOS) '--use-mock-keychain',
]);
final stdoutSub =
chromeProcess.stdout.transform(utf8.decoder).listen((e) {
print('[CHROME STDOUT]: $e');
});
final stderrSub =
chromeProcess.stderr.transform(utf8.decoder).listen((e) {
print('[CHROME STDERR]: $e');
});
try {
// Wait for DevTools to inform server that it's connected.
print('Waiting for clients...');
await testController.waitForClients();
// Close the browser, which will disconnect DevTools SSE connection
// back to the server.
print('Killing Chrome...');
chromeProcess.kill();
await chromeProcess.exitCode;
await Future.wait([stdoutSub.cancel(), stderrSub.cancel()]);
// Await a long delay to wait for the SSE client to close.
print('Delaying to wait for SSE connection to cleanup...');
await delay(duration: const Duration(seconds: 15));
// Ensure the client is completely removed from the list.
print('Expecting no clients...');
await testController.waitForClients(expectNone: true);
print('Done!');
} finally {
// Clean up the temporary profile directory.
try {
await tempDir.delete(recursive: true);
} catch (_) {
// Ignore cleanup errors since the OS temp dir is eventually cleaned up.
}
}
}, timeout: const Timeout.factor(20));
});
}