Skip to content

Commit 9d275e5

Browse files
Merge pull request #365 from Workiva/test_tool_trailing_args_v370backpatch
Allow args after separator to TestTool (backpatch to v3.7)
2 parents c1e8ee9 + be06a13 commit 9d275e5

8 files changed

+194
-35
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## [3.7.1](https://github.com/Workiva/dart_dev/compare/3.7.0...3.7.1)
4+
5+
- Update `TestTool` to allow arguments after a separator (`--`). These arguments
6+
will always be passed to the `dart test` process. The main use case for this is
7+
integration with IDE plugins that enable running tests directly from the IDE.
8+
- Update `FunctionTool` to allow arguments after a separator (`--`). There isn't
9+
a strong reason to disallow this since the function tool could do anything it
10+
wants with those args (and now we have a concrete use case for just that).
11+
- Fix a bug in `takeAllArgs` (the arg mapper util used with `CompoundTool`) so
12+
that it now properly restores the first separator (`--`) if present in the
13+
original arguments list.
14+
315
## [3.7.0](https://github.com/Workiva/dart_dev/compare/3.7.0...3.6.7)
416

517
- Export ArgResults utilities.

lib/src/tools/compound_tool.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:dart_dev/dart_dev.dart';
66
import 'package:logging/logging.dart';
77

88
import '../dart_dev_tool.dart';
9+
import '../utils/rest_args_with_separator.dart';
910

1011
final _log = Logger('CompoundTool');
1112

@@ -43,7 +44,7 @@ ArgResults takeOptionArgs(ArgParser parser, ArgResults results) =>
4344
/// };
4445
ArgResults takeAllArgs(ArgParser parser, ArgResults results) => parser.parse([
4546
...optionArgsOnly(results, allowedOptions: parser.options.keys),
46-
...results.rest,
47+
...restArgsWithSeparator(results),
4748
]);
4849

4950
class CompoundTool extends DevTool with CompoundToolMixin {}

lib/src/tools/function_tool.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ class FunctionTool extends DevTool {
3636
assertNoPositionalArgsNorArgsAfterSeparator(
3737
context.argResults, context.usageException,
3838
commandName: context.commandName);
39-
} else {
40-
assertNoArgsAfterSeparator(context.argResults, context.usageException,
41-
commandName: context.commandName);
4239
}
4340
}
4441
final exitCode = await _function(context);

lib/src/tools/test_tool.dart

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,6 @@ TestExecution buildExecution(
273273
List<String> configuredTestArgs,
274274
String path,
275275
}) {
276-
if (context.argResults != null) {
277-
assertNoArgsAfterSeparator(context.argResults, context.usageException,
278-
commandName: context.commandName,
279-
usageFooter:
280-
'Arguments can be passed to the test runner process via the '
281-
'--test-args option.\n'
282-
'If this project runs tests via build_runner, arguments can be '
283-
'passed to that process via the --build-args option.');
284-
}
285-
286276
final hasBuildRunner =
287277
packageIsImmediateDependency('build_runner', path: path);
288278
final hasBuildTest = packageIsImmediateDependency('build_test', path: path);
@@ -333,9 +323,10 @@ TestExecution buildExecution(
333323
// Additionally, consumers need to depend on build_web_compilers AND build_vm_compilers
334324
// We should add some guard-rails (don't use filters if either of those deps are
335325
// missing, and ensure adequate version of build_runner).
336-
Iterable<String> buildFiltersForTestArgs(List<String> testInputs) {
326+
Iterable<String> buildFiltersForTestArgs(List<String> testArgs) {
327+
final testInputs = (testArgs ?? []).where((arg) => arg.startsWith('test'));
337328
final filters = <String>[];
338-
for (final input in testInputs ?? []) {
329+
for (final input in testInputs) {
339330
if (input.endsWith('.dart')) {
340331
filters..add('$input.*_test.dart.js*')..add(dartExtToHtml(input));
341332
} else {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'package:args/args.dart';
2+
3+
/// Returns the "rest" args from [argResults], but with the arg separator "--"
4+
/// restored to its original position if it was included.
5+
///
6+
/// This is necessary because [ArgResults.rest] will _not_ include the separator
7+
/// unless it stopped parsing before it reached the separator.
8+
///
9+
/// The use case for this is a [CompoundTool] that uses the [takeAllArgs] arg
10+
/// mapper, because the goal there is to forward on the original args minus the
11+
/// consumed options and flags. If the separator has also been removed, you may
12+
/// hit an error when trying to parse those args.
13+
///
14+
/// var parser = ArgParser()..addFlag('verbose', abbr: 'v');
15+
/// var results = parser.parse(['a', '-v', 'b', '--', '--unknown', 'c']);
16+
/// print(results.rest);
17+
/// // ['a', 'b', '--unknown', 'c']
18+
/// print(restArgsWithSeparator(results));
19+
/// // ['a', 'b', '--', '--unknown', 'c']
20+
List<String> restArgsWithSeparator(ArgResults argResults) {
21+
// If no separator was used, return the rest args as is.
22+
if (!argResults.arguments.contains('--')) {
23+
return argResults.rest;
24+
}
25+
26+
final args = argResults.arguments;
27+
final rest = argResults.rest;
28+
var restIndex = 0;
29+
for (var argsIndex = 0; argsIndex < args.length; argsIndex++) {
30+
// Iterate through the original args until we hit the first separator.
31+
if (args[argsIndex] == '--') break;
32+
// While doing so, move a cursor through the rest args list each time we
33+
// match up between the original list and the rest args list. This works
34+
// because the rest args list should be an ordered subset of the original
35+
// args list.
36+
if (args[argsIndex] == rest[restIndex]) {
37+
restIndex++;
38+
}
39+
}
40+
41+
// At this point, [restIndex] should be pointing to the spot where the first
42+
// arg separator should be restored.
43+
return [...rest.sublist(0, restIndex), '--', ...rest.sublist(restIndex)];
44+
}

test/tools/function_tool_test.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,10 @@ void main() {
3636
.run(DevToolExecutionContext(argResults: parser.parse(['--flag'])));
3737
});
3838

39-
test(
40-
'throws UsageException with custom ArgParser and args after a separator',
41-
() {
39+
test('allows a custom ArgParser and args after a separator', () async {
4240
final tool = DevTool.fromFunction((_) => 0, argParser: ArgParser());
43-
expect(
44-
() => tool.run(DevToolExecutionContext(
45-
argResults: ArgParser().parse(['--', 'foo']))),
46-
throwsA(isA<UsageException>()));
41+
await tool.run(DevToolExecutionContext(
42+
argResults: ArgParser().parse(['--', 'foo'])));
4743
});
4844

4945
test('logs a warning if no exit code is returned', () {

test/tools/test_tool_test.dart

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -185,21 +185,47 @@ void main() {
185185
verbose: true),
186186
orderedEquals(['run', 'build_runner', 'test', '--verbose']));
187187
});
188-
});
189188

190-
group('buildExecution', () {
191-
test('throws UsageException if args are given after a separator', () {
192-
final argResults = ArgParser().parse(['--', 'a']);
193-
final context = DevToolExecutionContext(
194-
argResults: argResults, commandName: 'test_test');
195-
expect(
196-
() => buildExecution(context),
197-
throwsA(isA<UsageException>()
198-
..having((e) => e.message, 'command name', contains('test_test'))
199-
..having((e) => e.message, 'help',
200-
allOf(contains('--test-args'), contains('--build-args')))));
189+
group('supports test args after a separator', () {
190+
test('with no test file', () {
191+
final argParser = TestTool().toCommand('t').argParser;
192+
final argResults = argParser.parse(['--', '-r', 'json', '-j1']);
193+
expect(
194+
buildArgs(argResults: argResults, useBuildRunner: true),
195+
orderedEquals([
196+
'run',
197+
'build_runner',
198+
'test',
199+
'--',
200+
'-r',
201+
'json',
202+
'-j1',
203+
]));
204+
});
205+
206+
test('with a test file', () {
207+
final argParser = TestTool().toCommand('t').argParser;
208+
final argResults =
209+
argParser.parse(['--', '-r', 'json', '-j1', 'test/foo_test.dart']);
210+
expect(
211+
buildArgs(argResults: argResults, useBuildRunner: true),
212+
orderedEquals([
213+
'run',
214+
'build_runner',
215+
'test',
216+
'--build-filter=test/foo_test.dart.*_test.dart.js*',
217+
'--build-filter=test/foo_test.html',
218+
'--',
219+
'-r',
220+
'json',
221+
'-j1',
222+
'test/foo_test.dart',
223+
]));
224+
});
201225
});
226+
});
202227

228+
group('buildExecution', () {
203229
test(
204230
'throws UsageException if --build-args is used but build_runner is not '
205231
'a direct dependency', () async {
@@ -349,6 +375,16 @@ dev_dependencies:
349375
orderedEquals(['run', 'test', '-P', 'unit', '-n', 'foo']));
350376
});
351377

378+
test('with args after a separator', () {
379+
final argParser = TestTool().toCommand('t').argParser;
380+
final argResults = argParser.parse(['--', '-j1']);
381+
final context = DevToolExecutionContext(argResults: argResults);
382+
final execution = buildExecution(context, path: d.sandbox);
383+
expect(execution.exitCode, isNull);
384+
expect(execution.process.executable, 'pub');
385+
expect(execution.process.args, orderedEquals(['run', 'test', '-j1']));
386+
});
387+
352388
test(
353389
'and logs a warning if --release is used in a non-build project',
354390
() => overrideAnsiOutput(false, () {
@@ -409,6 +445,16 @@ dev_dependencies:
409445
orderedEquals(['run', 'test', '-P', 'unit', '-n', 'foo']));
410446
});
411447

448+
test('with args after a separator', () {
449+
final argParser = TestTool().toCommand('t').argParser;
450+
final argResults = argParser.parse(['--', '-j1']);
451+
final context = DevToolExecutionContext(argResults: argResults);
452+
final execution = buildExecution(context, path: d.sandbox);
453+
expect(execution.exitCode, isNull);
454+
expect(execution.process.executable, 'pub');
455+
expect(execution.process.args, orderedEquals(['run', 'test', '-j1']));
456+
});
457+
412458
test(
413459
'and logs a warning if --release is used in a non-build project',
414460
() => overrideAnsiOutput(false, () {
@@ -487,6 +533,24 @@ dev_dependencies:
487533
]));
488534
});
489535

536+
test('with args after a separator', () {
537+
final argParser = TestTool().toCommand('t').argParser;
538+
final argResults = argParser.parse(['--', '-j1']);
539+
final context = DevToolExecutionContext(argResults: argResults);
540+
final execution = buildExecution(context, path: d.sandbox);
541+
expect(execution.exitCode, isNull);
542+
expect(execution.process.executable, 'pub');
543+
expect(
544+
execution.process.args,
545+
orderedEquals([
546+
'run',
547+
'build_runner',
548+
'test',
549+
'--',
550+
'-j1',
551+
]));
552+
});
553+
490554
test('with verbose=true', () {
491555
final argParser = TestTool().toCommand('t').argParser;
492556
final argResults = argParser.parse(
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import 'package:args/args.dart';
2+
import 'package:dart_dev/src/utils/rest_args_with_separator.dart';
3+
import 'package:test/test.dart';
4+
5+
void main() {
6+
group('restArgsWithSeparator', () {
7+
ArgParser parser;
8+
9+
setUp(() {
10+
parser = ArgParser()
11+
..addOption('output', abbr: 'o')
12+
..addFlag('verbose', abbr: 'v');
13+
});
14+
15+
test('with no args', () {
16+
final results = parser.parse([]);
17+
expect(restArgsWithSeparator(results), <String>[]);
18+
});
19+
20+
test('restores the separator to the correct spot', () {
21+
final results = parser.parse([
22+
'a',
23+
'-o',
24+
'out',
25+
'-v',
26+
'b',
27+
'--',
28+
'c',
29+
'-d',
30+
]);
31+
expect(restArgsWithSeparator(results), [
32+
'a',
33+
'b',
34+
'--',
35+
'c',
36+
'-d',
37+
]);
38+
});
39+
40+
test('with multiple separators', () {
41+
final results = parser
42+
.parse(['a', '-o', 'out', '-v', 'b', '--', 'c', '-d', '--', 'e']);
43+
expect(restArgsWithSeparator(results), [
44+
'a',
45+
'b',
46+
'--',
47+
'c',
48+
'-d',
49+
'--',
50+
'e',
51+
]);
52+
});
53+
});
54+
}

0 commit comments

Comments
 (0)