Skip to content

Commit a433200

Browse files
authored
[test_reflective_loader] Pass test locations to pkg:test to improve IDE navigation (#2090)
1 parent be0bd20 commit a433200

File tree

5 files changed

+108
-14
lines changed

5 files changed

+108
-14
lines changed

.github/workflows/test_reflective_loader.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
strategy:
2727
fail-fast: false
2828
matrix:
29-
sdk: [dev, 3.1]
29+
sdk: [dev, 3.5]
3030

3131
steps:
3232
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

pkgs/test_reflective_loader/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.3.0
2+
3+
- Require Dart `^3.5.0`.
4+
- Update to `package:test` 1.26.1.
5+
- Pass locations of groups/tests to `package:test` to improve locations reported
6+
in the JSON reporter that may be used for navigation in IDEs.
7+
18
## 0.2.3
29

310
- Require Dart `^3.1.0`.

pkgs/test_reflective_loader/lib/test_reflective_loader.dart

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ void defineReflectiveTests(Type type) {
8787
{
8888
var isSolo = _hasAnnotationInstance(classMirror, soloTest);
8989
var className = MirrorSystem.getName(classMirror.simpleName);
90-
group = _Group(isSolo, _combineNames(_currentSuiteName, className));
90+
group = _Group(isSolo, _combineNames(_currentSuiteName, className),
91+
classMirror.testLocation);
9192
_currentGroups.add(group);
9293
}
9394

@@ -104,7 +105,7 @@ void defineReflectiveTests(Type type) {
104105
// test_
105106
if (memberName.startsWith('test_')) {
106107
if (_hasSkippedTestAnnotation(memberMirror)) {
107-
group.addSkippedTest(memberName);
108+
group.addSkippedTest(memberName, memberMirror.testLocation);
108109
} else {
109110
group.addTest(isSolo, memberName, memberMirror, () {
110111
if (_hasFailingTestAnnotation(memberMirror) ||
@@ -137,7 +138,7 @@ void defineReflectiveTests(Type type) {
137138
}
138139
// skip_test_
139140
if (memberName.startsWith('skip_test_')) {
140-
group.addSkippedTest(memberName);
141+
group.addSkippedTest(memberName, memberMirror.testLocation);
141142
}
142143
});
143144

@@ -154,7 +155,9 @@ void _addTestsIfTopLevelSuite() {
154155
for (var test in group.tests) {
155156
if (allTests || test.isSolo) {
156157
test_package.test(test.name, test.function,
157-
timeout: test.timeout, skip: test.isSkipped);
158+
timeout: test.timeout,
159+
skip: test.isSkipped,
160+
location: test.location);
158161
}
159162
}
160163
}
@@ -304,23 +307,25 @@ class _AssertFailingTest {
304307
class _Group {
305308
final bool isSolo;
306309
final String name;
310+
final test_package.TestLocation? location;
307311
final List<_Test> tests = <_Test>[];
308312

309-
_Group(this.isSolo, this.name);
313+
_Group(this.isSolo, this.name, this.location);
310314

311315
bool get hasSoloTest => tests.any((test) => test.isSolo);
312316

313-
void addSkippedTest(String name) {
317+
void addSkippedTest(String name, test_package.TestLocation? location) {
314318
var fullName = _combineNames(this.name, name);
315-
tests.add(_Test.skipped(isSolo, fullName));
319+
tests.add(_Test.skipped(isSolo, fullName, location));
316320
}
317321

318322
void addTest(bool isSolo, String name, MethodMirror memberMirror,
319323
_TestFunction function) {
320324
var fullName = _combineNames(this.name, name);
321325
var timeout =
322326
_getAnnotationInstance(memberMirror, TestTimeout) as TestTimeout?;
323-
tests.add(_Test(isSolo, fullName, function, timeout?._timeout));
327+
tests.add(_Test(isSolo, fullName, function, timeout?._timeout,
328+
memberMirror.testLocation));
324329
}
325330
}
326331

@@ -341,14 +346,26 @@ class _Test {
341346
final String name;
342347
final _TestFunction function;
343348
final test_package.Timeout? timeout;
349+
final test_package.TestLocation? location;
344350

345351
final bool isSkipped;
346352

347-
_Test(this.isSolo, this.name, this.function, this.timeout)
353+
_Test(this.isSolo, this.name, this.function, this.timeout, this.location)
348354
: isSkipped = false;
349355

350-
_Test.skipped(this.isSolo, this.name)
356+
_Test.skipped(this.isSolo, this.name, this.location)
351357
: isSkipped = true,
352358
function = (() {}),
353359
timeout = null;
354360
}
361+
362+
extension on DeclarationMirror {
363+
test_package.TestLocation? get testLocation {
364+
if (location case var location?) {
365+
return test_package.TestLocation(
366+
location.sourceUri, location.line, location.column);
367+
} else {
368+
return null;
369+
}
370+
}
371+
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
name: test_reflective_loader
2-
version: 0.2.3
2+
version: 0.3.0
33
description: Support for discovering tests and test suites using reflection.
44
repository: https://github.com/dart-lang/tools/tree/main/pkgs/test_reflective_loader
55
issue_tracker: https://github.com/dart-lang/tools/labels/package%3Atest_reflective_loader
66

77
environment:
8-
sdk: ^3.1.0
8+
sdk: ^3.5.0
99

1010
dependencies:
11-
test: ^1.16.0
11+
test: ^1.26.1
1212

1313
dev_dependencies:
1414
dart_flutter_team_lints: ^3.0.0
15+
path: ^1.8.0
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
import 'dart:io';
7+
import 'dart:isolate';
8+
9+
import 'package:path/path.dart' as path;
10+
import 'package:test/test.dart';
11+
12+
void main() {
13+
test("reports correct locations in the JSON output from 'dart test'",
14+
() async {
15+
var testPackagePath = (await Isolate.resolvePackageUri(
16+
Uri.parse('package:test_reflective_loader/')))!
17+
.toFilePath();
18+
var testFilePath = path.normalize(path.join(
19+
testPackagePath, '..', 'test', 'test_reflective_loader_test.dart'));
20+
var testFileContent = File(testFilePath).readAsLinesSync();
21+
var result = await Process.run(
22+
Platform.resolvedExecutable, ['test', '-r', 'json', testFilePath]);
23+
24+
var error = result.stderr.toString().trim();
25+
var output = result.stdout.toString().trim();
26+
27+
expect(error, isEmpty);
28+
expect(output, isNotEmpty);
29+
30+
for (var event in LineSplitter.split(output).map(jsonDecode)) {
31+
if (event case {'type': 'testStart', 'test': Map<String, Object?> test}) {
32+
var name = test['name'] as String;
33+
34+
// Skip the "loading" test, it never has a location.
35+
if (name.startsWith('loading')) {
36+
continue;
37+
}
38+
39+
// Split just the method name from the combined test so we can search
40+
// the source code to ensure the locations match up.
41+
name = name.split('|').last.trim();
42+
43+
// Expect locations for all remaining fields.
44+
var url = test['url'] as String;
45+
var line = test['line'] as int;
46+
var column = test['column'] as int;
47+
48+
expect(path.equals(Uri.parse(url).toFilePath(), testFilePath), isTrue);
49+
50+
// Verify the location provided matches where this test appears in the
51+
// file.
52+
var lineContent = testFileContent[line - 1];
53+
// If the line is an annotation, skip to the next line
54+
if (lineContent.trim().startsWith('@')) {
55+
lineContent = testFileContent[line];
56+
}
57+
expect(lineContent, contains(name),
58+
reason: 'JSON reports test $name on line $line, '
59+
'but line content is "$lineContent"');
60+
61+
// Verify the column too.
62+
var columnContent = lineContent.substring(column - 1);
63+
expect(columnContent, contains(name),
64+
reason: 'JSON reports test $name at column $column, '
65+
'but text at column is "$columnContent"');
66+
}
67+
}
68+
});
69+
}

0 commit comments

Comments
 (0)