Skip to content

Commit 69e7fc1

Browse files
[tools] Fix vm test requirement (#6995)
The logic for handling Dart unit test `test_on` directives was incorrect, causing it to skip packages that required vm testing, even when run in vm mode. This PR: - Adds the missing tests for false negatives. - Reworks the logic to explicitly fail for anything that isn't one of the exact patterns we are expecting, to make it much harder to re-introduce a bug like this in the future.
1 parent 5549eba commit 69e7fc1

File tree

3 files changed

+192
-41
lines changed

3 files changed

+192
-41
lines changed

packages/flutter_migrate/pubspec.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ environment:
1010

1111
dependencies:
1212
args: ^2.3.1
13-
convert: 3.0.2
14-
file: 6.1.4
15-
intl: 0.17.0
16-
meta: 1.8.0
13+
convert: ^3.0.2
14+
file: ">=6.0.0 <8.0.0"
15+
intl: ">=0.17.0 <0.20.0"
16+
meta: ^1.8.0
1717
path: ^1.8.0
18-
process: 4.2.4
19-
vm_service: 9.3.0
20-
yaml: 3.1.1
18+
process: ^4.2.4
19+
vm_service: ^9.3.0
20+
yaml: ^3.1.1
2121

2222
dev_dependencies:
23-
collection: 1.16.0
23+
collection: ^1.16.0
2424
file_testing: 3.0.0
2525
lints: ^2.0.0
2626
test: ^1.16.0

script/tool/lib/src/dart_test_command.dart

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ import 'common/plugin_utils.dart';
1111
import 'common/pub_utils.dart';
1212
import 'common/repository_package.dart';
1313

14+
const int _exitUnknownTestPlatform = 3;
15+
16+
enum _TestPlatform {
17+
// Must run in the command-line VM.
18+
vm,
19+
// Must run in a browser.
20+
browser,
21+
}
22+
1423
/// A command to run Dart unit tests for packages.
1524
class DartTestCommand extends PackageLoopingCommand {
1625
/// Creates an instance of the test command.
@@ -76,7 +85,7 @@ class DartTestCommand extends PackageLoopingCommand {
7685
return PackageResult.skip(
7786
"Non-web plugin tests don't need web testing.");
7887
}
79-
if (_requiresVM(package)) {
88+
if (_testOnTarget(package) == _TestPlatform.vm) {
8089
// This explict skip is necessary because trying to run tests in a mode
8190
// that the package has opted out of returns a non-zero exit code.
8291
return PackageResult.skip('Package has opted out of non-vm testing.');
@@ -85,7 +94,8 @@ class DartTestCommand extends PackageLoopingCommand {
8594
if (isWebOnlyPluginImplementation) {
8695
return PackageResult.skip("Web plugin tests don't need vm testing.");
8796
}
88-
if (_requiresNonVM(package)) {
97+
final _TestPlatform? target = _testOnTarget(package);
98+
if (target != null && _testOnTarget(package) != _TestPlatform.vm) {
8999
// This explict skip is necessary because trying to run tests in a mode
90100
// that the package has opted out of returns a non-zero exit code.
91101
return PackageResult.skip('Package has opted out of vm testing.');
@@ -102,7 +112,8 @@ class DartTestCommand extends PackageLoopingCommand {
102112
final String? webRenderer = (platform == 'chrome') ? 'html' : null;
103113
bool passed;
104114
if (package.requiresFlutter()) {
105-
passed = await _runFlutterTests(package, platform: platform, webRenderer: webRenderer);
115+
passed = await _runFlutterTests(package,
116+
platform: platform, webRenderer: webRenderer);
106117
} else {
107118
passed = await _runDartTests(package, platform: platform);
108119
}
@@ -156,34 +167,39 @@ class DartTestCommand extends PackageLoopingCommand {
156167
return exitCode == 0;
157168
}
158169

159-
bool _requiresVM(RepositoryPackage package) {
160-
final File testConfig = package.directory.childFile('dart_test.yaml');
161-
if (!testConfig.existsSync()) {
162-
return false;
163-
}
164-
// test_on lines can be very complex, but in pratice the packages in this
165-
// repo currently only need the ability to require vm or not, so that
166-
// simple directive is all that is currently supported.
167-
final RegExp vmRequrimentRegex = RegExp(r'^test_on:\s*vm$');
168-
return testConfig
169-
.readAsLinesSync()
170-
.any((String line) => vmRequrimentRegex.hasMatch(line));
171-
}
172-
173-
bool _requiresNonVM(RepositoryPackage package) {
170+
/// Returns the required test environment, or null if none is specified.
171+
///
172+
/// Throws if the target is not recognized.
173+
_TestPlatform? _testOnTarget(RepositoryPackage package) {
174174
final File testConfig = package.directory.childFile('dart_test.yaml');
175175
if (!testConfig.existsSync()) {
176-
return false;
176+
return null;
177177
}
178-
// test_on lines can be very complex, but in pratice the packages in this
179-
// repo currently only need the ability to require vm or not, so a simple
180-
// one-target directive is all that's supported currently. Making it
181-
// deliberately strict avoids the possibility of accidentally skipping vm
182-
// coverage due to a complex expression that's not handled correctly.
183-
final RegExp testOnRegex = RegExp(r'^test_on:\s*([a-z])*\s*$');
184-
return testConfig.readAsLinesSync().any((String line) {
178+
final RegExp testOnRegex = RegExp(r'^test_on:\s*([a-z].*[a-z])\s*$');
179+
for (final String line in testConfig.readAsLinesSync()) {
185180
final RegExpMatch? match = testOnRegex.firstMatch(line);
186-
return match != null && match.group(1) != 'vm';
187-
});
181+
if (match != null) {
182+
final String targetFilter = match.group(1)!;
183+
// test_on lines can be very complex, but in pratice the packages in
184+
// this repo currently only need the ability to require vm or not, so a
185+
// simple one-target directive is all that's supported currently.
186+
// Making it deliberately strict avoids the possibility of accidentally
187+
// skipping vm coverage due to a complex expression that's not handled
188+
// correctly.
189+
switch (targetFilter) {
190+
case 'vm':
191+
return _TestPlatform.vm;
192+
case 'browser':
193+
return _TestPlatform.browser;
194+
default:
195+
printError('Unknown "test_on" value: "$targetFilter"\n'
196+
"If this value needs to be supported for this package's tests, "
197+
'please update the repository tooling to support more test_on '
198+
'modes.');
199+
throw ToolExit(_exitUnknownTestPlatform);
200+
}
201+
}
202+
}
203+
return null;
188204
}
189205
}

script/tool/test/dart_test_command_test.dart

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,68 @@ void main() {
249249
);
250250
});
251251

252+
test('throws for an unrecognized test_on type', () async {
253+
final RepositoryPackage package = createFakePackage(
254+
'a_package',
255+
packagesDir,
256+
extraFiles: <String>['test/empty_test.dart'],
257+
);
258+
package.directory.childFile('dart_test.yaml').writeAsStringSync('''
259+
test_on: unknown
260+
''');
261+
262+
Error? commandError;
263+
final List<String> output = await runCapturingPrint(
264+
runner, <String>['dart-test', '--platform=vm'],
265+
errorHandler: (Error e) {
266+
commandError = e;
267+
});
268+
269+
expect(commandError, isA<ToolExit>());
270+
271+
expect(
272+
output,
273+
containsAllInOrder(
274+
<Matcher>[
275+
contains('Unknown "test_on" value: "unknown"\n'
276+
"If this value needs to be supported for this package's "
277+
'tests, please update the repository tooling to support more '
278+
'test_on modes.'),
279+
],
280+
));
281+
});
282+
283+
test('throws for an valid but complex test_on directive', () async {
284+
final RepositoryPackage package = createFakePackage(
285+
'a_package',
286+
packagesDir,
287+
extraFiles: <String>['test/empty_test.dart'],
288+
);
289+
package.directory.childFile('dart_test.yaml').writeAsStringSync('''
290+
test_on: vm && browser
291+
''');
292+
293+
Error? commandError;
294+
final List<String> output = await runCapturingPrint(
295+
runner, <String>['dart-test', '--platform=vm'],
296+
errorHandler: (Error e) {
297+
commandError = e;
298+
});
299+
300+
expect(commandError, isA<ToolExit>());
301+
302+
expect(
303+
output,
304+
containsAllInOrder(
305+
<Matcher>[
306+
contains('Unknown "test_on" value: "vm && browser"\n'
307+
"If this value needs to be supported for this package's "
308+
'tests, please update the repository tooling to support more '
309+
'test_on modes.'),
310+
],
311+
));
312+
});
313+
252314
test('runs in Chrome when requested for Flutter package', () async {
253315
final RepositoryPackage package = createFakePackage(
254316
'a_package',
@@ -265,7 +327,12 @@ void main() {
265327
orderedEquals(<ProcessCall>[
266328
ProcessCall(
267329
getFlutterCommand(mockPlatform),
268-
const <String>['test', '--color', '--platform=chrome', '--web-renderer=html'],
330+
const <String>[
331+
'test',
332+
'--color',
333+
'--platform=chrome',
334+
'--web-renderer=html'
335+
],
269336
package.path),
270337
]),
271338
);
@@ -289,7 +356,12 @@ void main() {
289356
orderedEquals(<ProcessCall>[
290357
ProcessCall(
291358
getFlutterCommand(mockPlatform),
292-
const <String>['test', '--color', '--platform=chrome', '--web-renderer=html'],
359+
const <String>[
360+
'test',
361+
'--color',
362+
'--platform=chrome',
363+
'--web-renderer=html'
364+
],
293365
plugin.path),
294366
]),
295367
);
@@ -314,7 +386,12 @@ void main() {
314386
orderedEquals(<ProcessCall>[
315387
ProcessCall(
316388
getFlutterCommand(mockPlatform),
317-
const <String>['test', '--color', '--platform=chrome', '--web-renderer=html'],
389+
const <String>[
390+
'test',
391+
'--color',
392+
'--platform=chrome',
393+
'--web-renderer=html'
394+
],
318395
plugin.path),
319396
]),
320397
);
@@ -339,7 +416,12 @@ void main() {
339416
orderedEquals(<ProcessCall>[
340417
ProcessCall(
341418
getFlutterCommand(mockPlatform),
342-
const <String>['test', '--color', '--platform=chrome', '--web-renderer=html'],
419+
const <String>[
420+
'test',
421+
'--color',
422+
'--platform=chrome',
423+
'--web-renderer=html'
424+
],
343425
plugin.path),
344426
]),
345427
);
@@ -409,7 +491,12 @@ void main() {
409491
orderedEquals(<ProcessCall>[
410492
ProcessCall(
411493
getFlutterCommand(mockPlatform),
412-
const <String>['test', '--color', '--platform=chrome', '--web-renderer=html'],
494+
const <String>[
495+
'test',
496+
'--color',
497+
'--platform=chrome',
498+
'--web-renderer=html'
499+
],
413500
plugin.path),
414501
]),
415502
);
@@ -459,6 +546,30 @@ test_on: vm
459546
);
460547
});
461548

549+
test('does not skip running vm in vm mode', () async {
550+
final RepositoryPackage package = createFakePackage(
551+
'a_package',
552+
packagesDir,
553+
extraFiles: <String>['test/empty_test.dart'],
554+
);
555+
package.directory.childFile('dart_test.yaml').writeAsStringSync('''
556+
test_on: vm
557+
''');
558+
559+
final List<String> output = await runCapturingPrint(
560+
runner, <String>['dart-test', '--platform=vm']);
561+
562+
expect(
563+
output,
564+
isNot(containsAllInOrder(<Matcher>[
565+
contains('Package has opted out'),
566+
])));
567+
expect(
568+
processRunner.recordedCalls,
569+
isNotEmpty,
570+
);
571+
});
572+
462573
test('skips running in vm mode if package opts out', () async {
463574
final RepositoryPackage package = createFakePackage(
464575
'a_package',
@@ -483,6 +594,30 @@ test_on: browser
483594
);
484595
});
485596

597+
test('does not skip running browser in browser mode', () async {
598+
final RepositoryPackage package = createFakePackage(
599+
'a_package',
600+
packagesDir,
601+
extraFiles: <String>['test/empty_test.dart'],
602+
);
603+
package.directory.childFile('dart_test.yaml').writeAsStringSync('''
604+
test_on: browser
605+
''');
606+
607+
final List<String> output = await runCapturingPrint(
608+
runner, <String>['dart-test', '--platform=browser']);
609+
610+
expect(
611+
output,
612+
isNot(containsAllInOrder(<Matcher>[
613+
contains('Package has opted out'),
614+
])));
615+
expect(
616+
processRunner.recordedCalls,
617+
isNotEmpty,
618+
);
619+
});
620+
486621
test('tries to run for a test_on that the tool does not recognize',
487622
() async {
488623
final RepositoryPackage package = createFakePackage(

0 commit comments

Comments
 (0)