Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions pkgs/test_reflective_loader/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.3.0

- Update to `package:test` 1.26.1.
- Pass locations of groups/tests to `package:test` to improve locations reported
in the JSON reporter that may be used for navigation in IDEs.

## 0.2.3

- Require Dart `^3.1.0`.
Expand Down
38 changes: 28 additions & 10 deletions pkgs/test_reflective_loader/lib/test_reflective_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:mirrors';

import 'package:test/scaffolding.dart' as test_package show TestLocation;
import 'package:test/test.dart' as test_package;

/// A marker annotation used to annotate test methods which are expected to fail
Expand Down Expand Up @@ -87,7 +88,8 @@ void defineReflectiveTests(Type type) {
{
var isSolo = _hasAnnotationInstance(classMirror, soloTest);
var className = MirrorSystem.getName(classMirror.simpleName);
group = _Group(isSolo, _combineNames(_currentSuiteName, className));
group = _Group(isSolo, _combineNames(_currentSuiteName, className),
classMirror.testLocation);
_currentGroups.add(group);
}

Expand All @@ -104,7 +106,7 @@ void defineReflectiveTests(Type type) {
// test_
if (memberName.startsWith('test_')) {
if (_hasSkippedTestAnnotation(memberMirror)) {
group.addSkippedTest(memberName);
group.addSkippedTest(memberName, memberMirror.testLocation);
} else {
group.addTest(isSolo, memberName, memberMirror, () {
if (_hasFailingTestAnnotation(memberMirror) ||
Expand Down Expand Up @@ -137,7 +139,7 @@ void defineReflectiveTests(Type type) {
}
// skip_test_
if (memberName.startsWith('skip_test_')) {
group.addSkippedTest(memberName);
group.addSkippedTest(memberName, memberMirror.testLocation);
}
});

Expand All @@ -154,7 +156,9 @@ void _addTestsIfTopLevelSuite() {
for (var test in group.tests) {
if (allTests || test.isSolo) {
test_package.test(test.name, test.function,
timeout: test.timeout, skip: test.isSkipped);
timeout: test.timeout,
skip: test.isSkipped,
location: test.location);
}
}
}
Expand Down Expand Up @@ -304,23 +308,25 @@ class _AssertFailingTest {
class _Group {
final bool isSolo;
final String name;
final test_package.TestLocation? location;
final List<_Test> tests = <_Test>[];

_Group(this.isSolo, this.name);
_Group(this.isSolo, this.name, this.location);

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

void addSkippedTest(String name) {
void addSkippedTest(String name, test_package.TestLocation? location) {
var fullName = _combineNames(this.name, name);
tests.add(_Test.skipped(isSolo, fullName));
tests.add(_Test.skipped(isSolo, fullName, location));
}

void addTest(bool isSolo, String name, MethodMirror memberMirror,
_TestFunction function) {
var fullName = _combineNames(this.name, name);
var timeout =
_getAnnotationInstance(memberMirror, TestTimeout) as TestTimeout?;
tests.add(_Test(isSolo, fullName, function, timeout?._timeout));
tests.add(_Test(isSolo, fullName, function, timeout?._timeout,
memberMirror.testLocation));
}
}

Expand All @@ -341,14 +347,26 @@ class _Test {
final String name;
final _TestFunction function;
final test_package.Timeout? timeout;
final test_package.TestLocation? location;

final bool isSkipped;

_Test(this.isSolo, this.name, this.function, this.timeout)
_Test(this.isSolo, this.name, this.function, this.timeout, this.location)
: isSkipped = false;

_Test.skipped(this.isSolo, this.name)
_Test.skipped(this.isSolo, this.name, this.location)
: isSkipped = true,
function = (() {}),
timeout = null;
}

extension on DeclarationMirror {
test_package.TestLocation? get testLocation {
if (location case var location?) {
return test_package.TestLocation(
location.sourceUri, location.line, location.column);
} else {
return null;
}
}
}
5 changes: 3 additions & 2 deletions pkgs/test_reflective_loader/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: test_reflective_loader
version: 0.2.3
version: 0.3.0
description: Support for discovering tests and test suites using reflection.
repository: https://github.com/dart-lang/tools/tree/main/pkgs/test_reflective_loader
issue_tracker: https://github.com/dart-lang/tools/labels/package%3Atest_reflective_loader
Expand All @@ -8,7 +8,8 @@ environment:
sdk: ^3.1.0

dependencies:
test: ^1.16.0
test: ^1.26.1

dev_dependencies:
dart_flutter_team_lints: ^3.0.0
path: ^1.8.0
69 changes: 69 additions & 0 deletions pkgs/test_reflective_loader/test/location_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2025, 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:convert';
import 'dart:io';
import 'dart:isolate';

import 'package:path/path.dart' as path;
import 'package:test/test.dart';

void main() {
test("reports correct locations in the JSON output from 'dart test'",
() async {
var testPackagePath = (await Isolate.resolvePackageUri(
Uri.parse('package:test_reflective_loader/')))!
.toFilePath();
var testFilePath = path.normalize(path.join(
testPackagePath, '..', 'test', 'test_reflective_loader_test.dart'));
var testFileContent = File(testFilePath).readAsLinesSync();
var result = await Process.run(
Platform.resolvedExecutable, ['test', '-r', 'json', testFilePath]);

var error = result.stderr.toString().trim();
var output = result.stdout.toString().trim();

expect(error, isEmpty);
expect(output, isNotEmpty);

for (var event in LineSplitter.split(output).map(jsonDecode)) {
if (event case {'type': 'testStart', 'test': Map<String, Object?> test}) {
var name = test['name'] as String;

// Skip the "loading" test, it never has a location.
if (name.startsWith('loading')) {
continue;
}

// Split just the method name from the combined test so we can search
// the source code to ensure the locations match up.
name = name.split('|').last.trim();

// Expect locations for all remaining fields.
var url = test['url'] as String;
var line = test['line'] as int;
var column = test['column'] as int;

expect(path.equals(Uri.parse(url).toFilePath(), testFilePath), isTrue);

// Verify the location provided matches where this test appears in the
// file.
var lineContent = testFileContent[line - 1];
// If the line is an annotation, skip to the next line
if (lineContent.trim().startsWith('@')) {
lineContent = testFileContent[line];
}
expect(lineContent, contains(name),
reason: 'JSON reports test $name on line $line, '
'but line content is "$lineContent"');

// Verify the column too.
var columnContent = lineContent.substring(column - 1);
expect(columnContent, contains(name),
reason: 'JSON reports test $name at column $column, '
'but text at column is "$columnContent"');
}
}
});
}
Loading