Skip to content

Commit 935b8b0

Browse files
authored
Allow specifying an explicit location for test/groups (#2481)
Usually test locations (which are used both to report the location of tests in the json reporter, and also for filtering tests by line/col) are inferred from the call stack when test() is called. This changes adds a new `location` parameter to group/test to allow this information to be provided explicitly. This can be useful where the call to `test()` doesn't contain the actual location of the declaration in the call stack (for example when using `pkg:test_reflective_loader`). See #2029 See Dart-Code/Dart-Code#5480
1 parent 3ac991f commit 935b8b0

27 files changed

+438
-66
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Don’t commit the following directories created by pub.
22
.buildlog
33
.dart_tool/
4+
.vscode/settings.json
45
.pub/
56
build/
67
packages

pkgs/test/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
## 1.25.16-wip
1+
## 1.26.0-wip
2+
3+
* `test()` and `group()` functions now take an optional `TestLocation` that will
4+
be used as the location of the test in JSON reporters instead of being parsed
5+
from the call stack.
26

37
## 1.25.15
48

pkgs/test/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: test
2-
version: 1.25.16-wip
2+
version: 1.26.0-wip
33
description: >-
44
A full featured library for writing and running Dart tests across platforms.
55
repository: https://github.com/dart-lang/test/tree/master/pkgs/test
@@ -36,7 +36,7 @@ dependencies:
3636
stream_channel: ^2.1.0
3737

3838
# Use an exact version until the test_api and test_core package are stable.
39-
test_api: 0.7.4
39+
test_api: 0.7.5-wip
4040
test_core: 0.6.9-wip
4141

4242
typed_data: ^1.3.0

pkgs/test/test/runner/engine_test.dart

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:math';
77

88
import 'package:test/test.dart';
99
import 'package:test_api/src/backend/group.dart';
10+
import 'package:test_api/src/backend/group_entry.dart';
1011
import 'package:test_api/src/backend/state.dart';
1112
import 'package:test_core/src/runner/engine.dart';
1213

@@ -27,8 +28,8 @@ void main() {
2728
});
2829

2930
var engine = Engine.withSuites([
30-
runnerSuite(Group.root(tests.take(2))),
31-
runnerSuite(Group.root(tests.skip(2)))
31+
runnerSuite(tests.take(2).asRootGroup()),
32+
runnerSuite(tests.skip(2).asRootGroup())
3233
]);
3334

3435
await engine.run();
@@ -55,7 +56,7 @@ void main() {
5556
}),
5657
completes);
5758

58-
engine.suiteSink.add(runnerSuite(Group.root(tests)));
59+
engine.suiteSink.add(runnerSuite(tests.asRootGroup()));
5960
engine.suiteSink.close();
6061
});
6162

@@ -207,7 +208,7 @@ void main() {
207208
test('test', () {}, skip: true);
208209
});
209210

210-
var engine = Engine.withSuites([runnerSuite(Group.root(tests))]);
211+
var engine = Engine.withSuites([runnerSuite(tests.asRootGroup())]);
211212

212213
engine.onTestStarted.listen(expectAsync1((liveTest) {
213214
expect(liveTest, same(engine.liveTests.single));
@@ -276,7 +277,7 @@ void main() {
276277
}, skip: true);
277278
});
278279

279-
var engine = Engine.withSuites([runnerSuite(Group.root(entries))]);
280+
var engine = Engine.withSuites([runnerSuite(entries.asRootGroup())]);
280281

281282
engine.onTestStarted.listen(expectAsync1((liveTest) {
282283
expect(liveTest, same(engine.liveTests.single));
@@ -341,7 +342,7 @@ void main() {
341342
for (var i = 0; i < testCount; i++)
342343
loadSuite('group $i', () async {
343344
await updateAndCheckConcurrency(isLoadSuite: true);
344-
return runnerSuite(Group.root([tests[i]]));
345+
return runnerSuite([tests[i]].asRootGroup());
345346
}),
346347
], concurrency: concurrency);
347348

@@ -355,3 +356,11 @@ void main() {
355356
});
356357
});
357358
}
359+
360+
extension on Iterable<GroupEntry> {
361+
/// Clones these entries into a new root group, assigning the new parent group
362+
/// as necessary.
363+
Group asRootGroup() {
364+
return Group.root(map((entry) => entry.filter((_) => true)!));
365+
}
366+
}

pkgs/test/test/runner/json_reporter_test.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,70 @@ void customTest(String name, dynamic Function() testFn) => test(name, testFn);
587587
''',
588588
});
589589
});
590+
591+
test('groups and tests with custom locations', () {
592+
return _expectReport('''
593+
group('group 1 inferred', () {
594+
setUpAll(() {});
595+
test('test 1 inferred', () {});
596+
tearDownAll(() {});
597+
});
598+
group('group 2 custom', location: TestLocation(Uri.parse('file:///foo/group'), 123, 234), () {
599+
setUpAll(location: TestLocation(Uri.parse('file:///foo/setUpAll'), 345, 456), () {});
600+
test('test 2 custom', location: TestLocation(Uri.parse('file:///foo/test'), 567, 789), () {});
601+
tearDownAll(location: TestLocation(Uri.parse('file:///foo/tearDownAll'), 890, 901), () {});
602+
});
603+
''', [
604+
[
605+
suiteJson(0),
606+
testStartJson(1, 'loading test.dart', groupIDs: []),
607+
testDoneJson(1, hidden: true),
608+
],
609+
[
610+
groupJson(2, testCount: 2),
611+
groupJson(3,
612+
name: 'group 1 inferred',
613+
parentID: 2,
614+
line: 6,
615+
column: 7,
616+
testCount: 1),
617+
testStartJson(4, 'group 1 inferred (setUpAll)',
618+
groupIDs: [2, 3], line: 7, column: 9),
619+
testDoneJson(4, hidden: true),
620+
testStartJson(5, 'group 1 inferred test 1 inferred',
621+
groupIDs: [2, 3], line: 8, column: 9),
622+
testDoneJson(5),
623+
testStartJson(6, 'group 1 inferred (tearDownAll)',
624+
groupIDs: [2, 3], line: 9, column: 9),
625+
testDoneJson(6, hidden: true),
626+
groupJson(7,
627+
name: 'group 2 custom',
628+
parentID: 2,
629+
url: 'file:///foo/group',
630+
line: 123,
631+
column: 234,
632+
testCount: 1),
633+
testStartJson(8, 'group 2 custom (setUpAll)',
634+
url: 'file:///foo/setUpAll',
635+
groupIDs: [2, 7],
636+
line: 345,
637+
column: 456),
638+
testDoneJson(8, hidden: true),
639+
testStartJson(9, 'group 2 custom test 2 custom',
640+
url: 'file:///foo/test',
641+
groupIDs: [2, 7],
642+
line: 567,
643+
column: 789),
644+
testDoneJson(9),
645+
testStartJson(10, 'group 2 custom (tearDownAll)',
646+
url: 'file:///foo/tearDownAll',
647+
groupIDs: [2, 7],
648+
line: 890,
649+
column: 901),
650+
testDoneJson(10, hidden: true),
651+
]
652+
], doneJson());
653+
});
590654
});
591655

592656
test(

pkgs/test/test/runner/json_reporter_utils.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,16 @@ Map<String, Object> groupJson(int id,
8989
int? parentID,
9090
Object? skip,
9191
int? testCount,
92+
String? url,
9293
int? line,
9394
int? column}) {
9495
if ((line == null) != (column == null)) {
9596
throw ArgumentError(
9697
'line and column must either both be null or both be passed');
9798
}
9899

100+
url ??=
101+
line == null ? null : p.toUri(p.join(d.sandbox, 'test.dart')).toString();
99102
return {
100103
'type': 'group',
101104
'group': {
@@ -107,17 +110,15 @@ Map<String, Object> groupJson(int id,
107110
'testCount': testCount ?? 1,
108111
'line': line,
109112
'column': column,
110-
'url': line == null
111-
? null
112-
: p.toUri(p.join(d.sandbox, 'test.dart')).toString()
113+
'url': url
113114
}
114115
};
115116
}
116117

117118
/// Returns the event emitted by the JSON reporter indicating that a test has
118119
/// begun running.
119120
///
120-
/// If [parentIDs] is passed, it's the IDs of groups containing this test. If
121+
/// If [groupIDs] is passed, it's the IDs of groups containing this test. If
121122
/// [skip] is `true`, the test is expected to be marked as skipped without a
122123
/// reason. If it's a [String], the test is expected to be marked as skipped
123124
/// with that reason.

pkgs/test/test/runner/line_and_col_test.dart

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,49 @@ void main() {
5656
await test.shouldExit(0);
5757
});
5858

59+
test('additionally selects test with matching custom location', () async {
60+
await d.dir('test').create();
61+
var testFileUri = Uri.file(d.file('test/aaa_test.dart').io.path);
62+
var notTestFileUri = Uri.file(d.file('test/bbb.dart').io.path);
63+
var testFilePackageRelativeUri =
64+
Uri.parse('org-dartlang-app:///test/aaa_test.dart');
65+
await d.file('test/aaa_test.dart', '''
66+
import 'package:test/test.dart';
67+
68+
void main() {
69+
test("a", () {}); // Line 4 from stack trace (match)
70+
71+
// Custom line 4 (match)
72+
test("b", location: TestLocation(Uri.parse('$testFileUri'), 4, 0), () {});
73+
74+
// Custom line 4 match but using org-dartlang-app (match)
75+
test("c", location: TestLocation(Uri.parse('$testFilePackageRelativeUri'), 4, 0), () {});
76+
77+
// Custom different line, same file (no match)
78+
test("d", location: TestLocation(Uri.parse('$testFileUri'), 5, 0), () => throw TestFailure("oh no"));
79+
80+
// Custom line 4 match but different file (no match)
81+
test("e", location: TestLocation(Uri.parse('$notTestFileUri'), 4, 0), () => throw TestFailure("oh no"));
82+
}
83+
''').create();
84+
85+
var test = await runTest(['test/aaa_test.dart?line=4']);
86+
87+
expect(
88+
test.stdout,
89+
emitsThrough(contains('+3: All tests passed!')),
90+
);
91+
92+
await test.shouldExit(0);
93+
});
94+
5995
test('selects groups with a matching line', () async {
6096
await d.file('test.dart', '''
6197
import 'package:test/test.dart';
6298
6399
void main() {
64100
group("a", () {
65-
test("b", () {});
101+
test("a", () {});
66102
});
67103
group("b", () {
68104
test("b", () => throw TestFailure("oh no"));
@@ -80,6 +116,53 @@ void main() {
80116
await test.shouldExit(0);
81117
});
82118

119+
test('additionally selects groups with a matching custom location',
120+
() async {
121+
await d.dir('test').create();
122+
var testFileUri = Uri.file(d.file('test/aaa_test.dart').io.path);
123+
var notTestFileUri = Uri.file(d.file('test/bbb.dart').io.path);
124+
var testFilePackageRelativeUri =
125+
Uri.parse('org-dartlang-app:///test/aaa_test.dart');
126+
await d.file('test/aaa_test.dart', '''
127+
import 'package:test/test.dart';
128+
129+
void main() {
130+
group("a", () { // Line 4 from stack trace (match)
131+
test("a", () {});
132+
});
133+
134+
// Custom line 4 (match)
135+
group("b", location: TestLocation(Uri.parse('$testFileUri'), 4, 0), () {
136+
test("b", () {});
137+
});
138+
139+
// Custom line 4 match but using org-dartlang-app (match)
140+
group("c", location: TestLocation(Uri.parse('$testFilePackageRelativeUri'), 4, 0), () {
141+
test("c", () {});
142+
});
143+
144+
// Custom different line, same file (no match)
145+
group("d", location: TestLocation(Uri.parse('$testFileUri'), 5, 0), () {
146+
test("d", () => throw TestFailure("oh no"));
147+
});
148+
149+
// Custom line 4 match but different file (no match)
150+
group("e", location: TestLocation(Uri.parse('$notTestFileUri'), 4, 0), () {
151+
test("e", () => throw TestFailure("oh no"));
152+
});
153+
}
154+
''').create();
155+
156+
var test = await runTest(['test/aaa_test.dart?line=4']);
157+
158+
expect(
159+
test.stdout,
160+
emitsThrough(contains('+3: All tests passed!')),
161+
);
162+
163+
await test.shouldExit(0);
164+
});
165+
83166
test('No matching tests', () async {
84167
await d.file('test.dart', '''
85168
import 'package:test/test.dart';

pkgs/test_api/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.7.5-wip
2+
3+
* `test()` and `group()` functions now take an optional `TestLocation` that will
4+
be used as the location of the test in JSON reporters instead of being parsed
5+
from the call stack.
6+
17
## 0.7.4
28

39
* Allow `analyzer: '>=6.0.0 <8.0.0'`

pkgs/test_api/lib/backend.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export 'src/backend/runtime.dart' show Runtime;
1111
export 'src/backend/stack_trace_formatter.dart' show StackTraceFormatter;
1212
export 'src/backend/stack_trace_mapper.dart' show StackTraceMapper;
1313
export 'src/backend/suite_platform.dart' show SuitePlatform;
14+
export 'src/backend/test_location.dart' show TestLocation;

pkgs/test_api/lib/scaffolding.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export 'src/backend/configuration/skip.dart' show Skip;
1616
export 'src/backend/configuration/tags.dart' show Tags;
1717
export 'src/backend/configuration/test_on.dart' show TestOn;
1818
export 'src/backend/configuration/timeout.dart' show Timeout;
19+
export 'src/backend/test_location.dart' show TestLocation;
1920
export 'src/scaffolding/spawn_hybrid.dart' show spawnHybridCode, spawnHybridUri;
2021
export 'src/scaffolding/test_structure.dart'
2122
show addTearDown, group, setUp, setUpAll, tearDown, tearDownAll, test;

0 commit comments

Comments
 (0)