Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pkgs/dart_mcp_server/test/test_harness.dart
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,35 @@ final class AppDebugSession {
await process.kill();
await process.shouldExit();
}

// On Windows, the process might still be cleaning up even after the exit
// code is received.
// Poll with a delay to ensure it's fully gone before proceeding to cleanup.
if (Platform.isWindows) {
for (var i = 0; i < 20; i++) {
if (!await _pidAlive(process.pid)) break;
await Future<void>.delayed(const Duration(milliseconds: 50));
}
}
}

static Future<bool> _pidAlive(int pid) async {
try {
if (Platform.isWindows) {
final result = await Process.run('tasklist', [
'/FI',
'"PID eq $pid"',
'/NH',
]);
return result.stdout.toString().contains('$pid');
} else if (Platform.isLinux || Platform.isMacOS) {
final result = await Process.run('ps', ['-p', '$pid']);
return result.stdout.toString().contains('$pid');
}
return false;
} catch (_) {
return false;
}
}
Comment thread
goweii marked this conversation as resolved.
}

Expand Down
174 changes: 83 additions & 91 deletions pkgs/dart_mcp_server/test/tools/dtd_multiple_connection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// 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:io';

import 'package:dart_mcp/server.dart';
import 'package:dart_mcp_server/src/mixins/dtd.dart';

Expand Down Expand Up @@ -166,14 +164,12 @@ void main() {
}
});

test(
'can get runtime errors from multiple apps using appUri',
() async {
await testHarness.connectToDtd();
test('can get runtime errors from multiple apps using appUri', () async {
await testHarness.connectToDtd();

await d.dir('dart_app_1', [
d.dir('bin', [
d.file('main.dart', '''
await d.dir('dart_app_1', [
d.dir('bin', [
d.file('main.dart', '''
import 'dart:io';
void main() async {
stderr.writeln('error from app 1');
Expand All @@ -182,24 +178,24 @@ void main() async {
}
}
'''),
]),
]).create();
final session1 = await testHarness.startDebugSession(
p.join(d.sandbox, 'dart_app_1'),
'bin/main.dart',
isFlutter: false,
);
addTearDown(() => testHarness.stopDebugSession(session1));
]),
]).create();
final session1 = await testHarness.startDebugSession(
p.join(d.sandbox, 'dart_app_1'),
'bin/main.dart',
isFlutter: false,
);
addTearDown(() => testHarness.stopDebugSession(session1));

final secondEditorExtension = await FakeEditorExtension.connect(
testHarness.sdk,
);
addTearDown(secondEditorExtension.shutdown);
await testHarness.connectToDtd(dtdUri: secondEditorExtension.dtdUri);
final secondEditorExtension = await FakeEditorExtension.connect(
testHarness.sdk,
);
addTearDown(secondEditorExtension.shutdown);
await testHarness.connectToDtd(dtdUri: secondEditorExtension.dtdUri);

await d.dir('dart_app_2', [
d.dir('bin', [
d.file('main.dart', '''
await d.dir('dart_app_2', [
d.dir('bin', [
d.file('main.dart', '''
import 'dart:io';
void main() async {
stderr.writeln('error from app 2');
Expand All @@ -208,76 +204,72 @@ void main() async {
}
}
'''),
]),
]).create();
final session2 = await testHarness.startDebugSession(
p.join(d.sandbox, 'dart_app_2'),
'bin/main.dart',
isFlutter: false,
]),
]).create();
final session2 = await testHarness.startDebugSession(
p.join(d.sandbox, 'dart_app_2'),
'bin/main.dart',
isFlutter: false,
editorExtension: secondEditorExtension,
);
addTearDown(
() => testHarness.stopDebugSession(
session2,
editorExtension: secondEditorExtension,
);
addTearDown(
() => testHarness.stopDebugSession(
session2,
editorExtension: secondEditorExtension,
),
);
),
);

final listResult = await testHarness.callToolWithRetry(
CallToolRequest(
name: ToolNames.dtd.name,
arguments: {ParameterNames.command: DtdCommand.listConnectedApps},
),
retryUntil: (result) =>
(result.structuredContent as Map<String, Object?>).values
.cast<List<Object?>>()
.fold(0, (count, apps) => count + apps.length) ==
2,
);
expect(listResult.isError, isNot(true));
final dtdEntries = listResult.structuredContent;
expect(dtdEntries, isNotNull);
final connectedAppUris = (dtdEntries as Map<String, Object?>).values
.cast<List<Object?>>()
.fold(
<String>[],
(apps, appInfos) => apps
..addAll(
appInfos.map(
(app) => (app as Map<String, Object?>)['uri'] as String,
),
final listResult = await testHarness.callToolWithRetry(
CallToolRequest(
name: ToolNames.dtd.name,
arguments: {ParameterNames.command: DtdCommand.listConnectedApps},
),
retryUntil: (result) =>
(result.structuredContent as Map<String, Object?>).values
.cast<List<Object?>>()
.fold(0, (count, apps) => count + apps.length) ==
2,
);
expect(listResult.isError, isNot(true));
final dtdEntries = listResult.structuredContent;
expect(dtdEntries, isNotNull);
final connectedAppUris = (dtdEntries as Map<String, Object?>).values
.cast<List<Object?>>()
.fold(
<String>[],
(apps, appInfos) => apps
..addAll(
appInfos.map(
(app) => (app as Map<String, Object?>)['uri'] as String,
),
);
expect(connectedAppUris, hasLength(2));
),
);
expect(connectedAppUris, hasLength(2));

// Verify errors for App 1
final errors1 = await testHarness.callToolWithRetry(
CallToolRequest(
name: ToolNames.getRuntimeErrors.name,
arguments: {ParameterNames.appUri: session1.vmServiceUri},
),
);
expect(
(errors1.content[1] as TextContent).text,
contains('error from app 1'),
);
// Verify errors for App 1
final errors1 = await testHarness.callToolWithRetry(
CallToolRequest(
name: ToolNames.getRuntimeErrors.name,
arguments: {ParameterNames.appUri: session1.vmServiceUri},
),
);
expect(
(errors1.content[1] as TextContent).text,
contains('error from app 1'),
);

// Verify errors for App 2
final errors2 = await testHarness.callToolWithRetry(
CallToolRequest(
name: ToolNames.getRuntimeErrors.name,
arguments: {ParameterNames.appUri: session2.vmServiceUri},
),
retryUntil: (result) => result.content.length > 1,
);
expect(
(errors2.content[1] as TextContent).text,
contains('error from app 2'),
);
},
skip: Platform.isWindows
? 'https://github.com/dart-lang/ai/issues/407'
: false,
);
// Verify errors for App 2
final errors2 = await testHarness.callToolWithRetry(
CallToolRequest(
name: ToolNames.getRuntimeErrors.name,
arguments: {ParameterNames.appUri: session2.vmServiceUri},
),
retryUntil: (result) => result.content.length > 1,
);
expect(
(errors2.content[1] as TextContent).text,
contains('error from app 2'),
);
});
});
}
Loading