Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(235)

Side by Side Diff: tests/lib/args/args_test.dart

Issue 10335007: First pass at a generic reusable command line option parser. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Respond to review. Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« lib/unittest/unittest.dart ('K') | « lib/unittest/unittest.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, 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 #library('args_test');
6
7 #import('../../../lib/unittest/unittest.dart');
8 #import('../../../lib/args/args.dart');
9
10 main() {
11 group('ArgParser.addFlag()', () {
12 test('throws IllegalArgumentException if the flag already exists', () {
13 var parser = new ArgParser();
14 parser.addFlag('foo');
15 throwsIllegalArg(() => parser.addFlag('foo'));
16 });
17
18 test('throws IllegalArgumentException if the option already exists', () {
19 var parser = new ArgParser();
20 parser.addOption('foo');
21 throwsIllegalArg(() => parser.addFlag('foo'));
22 });
23
24 test('throws IllegalArgumentException if the abbreviation exists', () {
25 var parser = new ArgParser();
26 parser.addFlag('foo', abbr: 'f');
27 throwsIllegalArg(() => parser.addFlag('flummox', abbr: 'f'));
28 });
29
30 test('throws IllegalArgumentException if the abbreviation is longer '
31 'than one character', () {
32 var parser = new ArgParser();
33 throwsIllegalArg(() => parser.addFlag('flummox', abbr: 'flu'));
34 });
35 });
36
37 group('ArgParser.addOption()', () {
38 test('throws IllegalArgumentException if the flag already exists', () {
39 var parser = new ArgParser();
40 parser.addFlag('foo');
41 throwsIllegalArg(() => parser.addOption('foo'));
42 });
43
44 test('throws IllegalArgumentException if the option already exists', () {
45 var parser = new ArgParser();
46 parser.addOption('foo');
47 throwsIllegalArg(() => parser.addOption('foo'));
48 });
49
50 test('throws IllegalArgumentException if the abbreviation exists', () {
51 var parser = new ArgParser();
52 parser.addFlag('foo', abbr: 'f');
53 throwsIllegalArg(() => parser.addOption('flummox', abbr: 'f'));
54 });
55
56 test('throws IllegalArgumentException if the abbreviation is longer '
57 'than one character', () {
58 var parser = new ArgParser();
59 throwsIllegalArg(() => parser.addOption('flummox', abbr: 'flu'));
60 });
61 });
62
63 group('ArgParser.parse()', () {
64 group('flags', () {
65 test('are true if present', () {
66 var parser = new ArgParser();
67 parser.addFlag('verbose');
68
69 var args = parser.parse(['--verbose']);
70 expect(args['verbose']).isTrue();
71 });
72
73 test('default if missing', () {
74 var parser = new ArgParser();
75 parser.addFlag('a', defaultsTo: true);
76 parser.addFlag('b', defaultsTo: false);
77
78 var args = parser.parse([]);
79 expect(args['a']).isTrue();
80 expect(args['b']).isFalse();
81 });
82
83 test('are null if missing with no default', () {
nweiz 2012/05/03 21:07:08 I'm not convinced this behavior is correct, especi
Bob Nystrom 2012/05/03 22:31:20 If we do that, we lose the ability to have three-s
nweiz 2012/05/03 23:47:52 As I mentioned elsewhere, I don't think three-stat
84 var parser = new ArgParser();
85 parser.addFlag('verbose');
86
87 var args = parser.parse([]);
88 expect(args['verbose']).isNull();
89 });
90
91 test('throws if given a value', () {
92 var parser = new ArgParser();
93 parser.addFlag('verbose');
94
95 throwsBadFormat(parser, ['--verbose=true']);
96 });
97 });
98
99 group('flags negated with "no-"', () {
100 test('set the flag to false', () {
101 var parser = new ArgParser();
102 parser.addFlag('verbose');
103
104 var args = parser.parse(['--no-verbose']);
105 expect(args['verbose']).isFalse();
106 });
107
108 test('set the flag to true if the flag actually starts with "no-"', () {
109 var parser = new ArgParser();
110 parser.addFlag('no-body');
111
112 var args = parser.parse(['--no-body']);
113 expect(args['no-body']).isTrue();
114 });
115
116 test('are not preferred over a colliding one without', () {
117 var parser = new ArgParser();
118 parser.addFlag('no-strum');
119 parser.addFlag('strum');
120
121 var args = parser.parse(['--no-strum']);
122 expect(args['no-strum']).isTrue();
123 expect(args['strum']).isNull();
124 });
125 });
126
127 group('callbacks', () {
128 test('for present flags are invoked with the value', () {
129 var a;
130 var parser = new ArgParser();
131 parser.addFlag('a', callback: (value) => a = value);
132
133 var args = parser.parse(['--a']);
134 expect(a).isTrue();
135 });
136
137 test('for absent flags are invoked with the default value', () {
138 var a;
139 var parser = new ArgParser();
140 parser.addFlag('a', defaultsTo: false,
141 callback: (value) => a = value);
142
143 var args = parser.parse([]);
144 expect(a).isFalse();
145 });
146
147 test('are invoked even if the flag is not present', () {
148 var a = 'not called';
149 var parser = new ArgParser();
150 parser.addFlag('a', callback: (value) => a = value);
151
152 var args = parser.parse([]);
153 expect(a).isNull();
154 });
155
156 test('for present options are invoked with the value', () {
157 var a;
158 var parser = new ArgParser();
159 parser.addOption('a', callback: (value) => a = value);
160
161 var args = parser.parse(['--a=v']);
162 expect(a).equals('v');
163 });
164
165 test('for absent options are invoked with the default value', () {
166 var a;
167 var parser = new ArgParser();
168 parser.addOption('a', defaultsTo: 'v',
169 callback: (value) => a = value);
170
171 var args = parser.parse([]);
172 expect(a).equals('v');
173 });
174
175 test('are invoked even if the option is not present', () {
176 var a = 'not called';
177 var parser = new ArgParser();
178 parser.addOption('a', callback: (value) => a = value);
179
180 var args = parser.parse([]);
181 expect(a).isNull();
182 });
183 });
184
185 group('abbreviations', () {
186 test('are parsed with a preceding "-"', () {
187 var parser = new ArgParser();
188 parser.addFlag('arg', abbr: 'a');
189
190 var args = parser.parse(['-a']);
191 expect(args['arg']).isTrue();
192 });
193
194 test('can use multiple after a single "-"', () {
195 var parser = new ArgParser();
196 parser.addFlag('first', abbr: 'f');
197 parser.addFlag('second', abbr: 's');
198 parser.addFlag('third', abbr: 't');
199
200 var args = parser.parse(['-tf']);
201 expect(args['first']).isTrue();
202 expect(args['second']).isNull();
203 expect(args['third']).isTrue();
204 });
205
206 test('can have multiple "-" args', () {
207 var parser = new ArgParser();
208 parser.addFlag('first', abbr: 'f');
209 parser.addFlag('second', abbr: 's');
210 parser.addFlag('third', abbr: 't');
211
212 var args = parser.parse(['-s', '-tf']);
213 expect(args['first']).isTrue();
214 expect(args['second']).isTrue();
215 expect(args['third']).isTrue();
216 });
217
218 test('can take arguments without a space separating', () {
219 var parser = new ArgParser();
220 parser.addOption('file', abbr: 'f');
221
222 var args = parser.parse(['-flip']);
223 expect(args['file']).equals('lip');
224 });
225
226 test('can take arguments with a space separating', () {
227 var parser = new ArgParser();
228 parser.addOption('file', abbr: 'f');
229
230 var args = parser.parse(['-f', 'name']);
231 expect(args['file']).equals('name');
232 });
233
234 test('allow non-option characters in the value', () {
235 var parser = new ArgParser();
236 parser.addOption('apple', abbr: 'a');
237
238 var args = parser.parse(['-ab?!c']);
239 expect(args['apple']).equals('b?!c');
240 });
241
242 test('throw if unknown', () {
243 var parser = new ArgParser();
244 throwsBadFormat(parser, ['-f']);
245 });
246
247 test('throw if the value is missing', () {
248 var parser = new ArgParser();
249 parser.addOption('file', abbr: 'f');
250
251 throwsBadFormat(parser, ['-f']);
252 });
253
254 test('throw if the value looks like an option', () {
255 var parser = new ArgParser();
256 parser.addOption('file', abbr: 'f');
257 parser.addOption('other');
258
259 throwsBadFormat(parser, ['-f', '--other']);
260 throwsBadFormat(parser, ['-f', '--unknown']);
261 throwsBadFormat(parser, ['-f', '-abbr']);
262 });
263
264 test('throw if the value is not allowed', () {
265 var parser = new ArgParser();
266 parser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']);
267
268 throwsBadFormat(parser, ['-mprofile']);
269 });
270
271 test('throw if any but the first is not a flag', () {
272 var parser = new ArgParser();
273 parser.addFlag('apple', abbr: 'a');
274 parser.addOption('banana', abbr: 'b'); // Takes an argument.
275 parser.addFlag('cherry', abbr: 'c');
276
277 throwsBadFormat(parser, ['-abc']);
278 });
279
280 test('throw if it has a value but the option is a flag', () {
281 var parser = new ArgParser();
282 parser.addFlag('apple', abbr: 'a');
283 parser.addFlag('banana', abbr: 'b');
284
285 // The '?!' means this can only be understood as '--apple b?!c'.
286 throwsBadFormat(parser, ['-ab?!c']);
287 });
288 });
289
290 group('options', () {
291 test('are parsed if present', () {
292 var parser = new ArgParser();
293 parser.addOption('mode');
294 var args = parser.parse(['--mode=release']);
295 expect(args['mode']).equals('release');
296 });
297
298 test('are null if not present', () {
299 var parser = new ArgParser();
300 parser.addOption('mode');
301 var args = parser.parse([]);
302 expect(args['mode']).isNull();
303 });
304
305 test('default if missing', () {
306 var parser = new ArgParser();
307 parser.addOption('mode', defaultsTo: 'debug');
308 var args = parser.parse([]);
309 expect(args['mode']).equals('debug');
310 });
311
312 test('allow the value to be separated by whitespace', () {
313 var parser = new ArgParser();
314 parser.addOption('mode');
315 var args = parser.parse(['--mode', 'release']);
316 expect(args['mode']).equals('release');
317 });
318
319 test('throw if unknown', () {
320 var parser = new ArgParser();
321 throwsBadFormat(parser, ['--unknown']);
322 throwsBadFormat(parser, ['--nobody']); // Starts with "no".
323 });
324
325 test('throw if the arg does not include a value', () {
326 var parser = new ArgParser();
327 parser.addOption('mode');
328 throwsBadFormat(parser, ['--mode']);
329 });
330
331 test('throw if the value looks like an option', () {
332 var parser = new ArgParser();
333 parser.addOption('mode');
334 parser.addOption('other');
335
336 throwsBadFormat(parser, ['--mode', '--other']);
337 throwsBadFormat(parser, ['--mode', '--unknown']);
338 throwsBadFormat(parser, ['--mode', '-abbr']);
339 });
340
341 test('do not throw if the value is in the allowed set', () {
342 var parser = new ArgParser();
343 parser.addOption('mode', allowed: ['debug', 'release']);
344 var args = parser.parse(['--mode=debug']);
345 expect(args['mode']).equals('debug');
346 });
347
348 test('throw if the value is not in the allowed set', () {
349 var parser = new ArgParser();
350 parser.addOption('mode', allowed: ['debug', 'release']);
351 throwsBadFormat(parser, ['--mode=profile']);
352 });
353 });
354
355 group('remaining args', () {
356 test('stops parsing args when a non-option-like arg is encountered', () {
357 var parser = new ArgParser();
358 parser.addFlag('woof');
359 parser.addOption('meow');
360 parser.addOption('tweet', defaultsTo: 'bird');
361
362 var results = parser.parse(['--woof', '--meow', 'v', 'not', 'option']);
363 expect(results['woof']).isTrue();
364 expect(results['meow']).equals('v');
365 expect(results['tweet']).equals('bird');
366 expect(results.remainingArgs).equalsCollection(['not', 'option']);
367 });
368
369 test('stops parsing at "--"', () {
370 var parser = new ArgParser();
371 parser.addFlag('woof', defaultsTo: false);
372 parser.addOption('meow', defaultsTo: 'kitty');
373
374 var results = parser.parse(['--woof', '--', '--meow']);
375 expect(results['woof']).isTrue();
376 expect(results['meow']).equals('kitty');
377 expect(results.remainingArgs).equalsCollection(['--meow']);
378 });
379 });
380 });
381
382 group('ArgParser.getUsage()', () {
383 test('flags show "no-" in title', () {
384 var parser = new ArgParser();
385 parser.addFlag('mode', help: 'The mode');
386
387 validateUsage(parser,
388 '''
389 --[no-]mode The mode
390 ''');
391 });
392
393 test('if there are no abbreviations, there is no column for them', () {
394 var parser = new ArgParser();
395 parser.addFlag('mode', help: 'The mode');
396
397 validateUsage(parser,
398 '''
399 --[no-]mode The mode
400 ''');
401 });
402
403 test('options are lined up past abbreviations', () {
404 var parser = new ArgParser();
405 parser.addFlag('mode', abbr: 'm', help: 'The mode');
406 parser.addOption('long', help: 'Lacks an abbreviation');
407
408 validateUsage(parser,
409 '''
410 -m, --[no-]mode The mode
411 --long Lacks an abbreviation
412 ''');
413 });
414
415 test('help text is lined up past the longest option', () {
416 var parser = new ArgParser();
417 parser.addFlag('mode', abbr: 'm', help: 'Lined up with below');
418 parser.addOption('a-really-long-name', help: 'Its help text');
419
420 validateUsage(parser,
421 '''
422 -m, --[no-]mode Lined up with below
423 --a-really-long-name Its help text
424 ''');
425 });
426
427 test('options are documented in the order they were added', () {
428 var parser = new ArgParser();
429 parser.addFlag('zebra', help: 'First');
430 parser.addFlag('monkey', help: 'Second');
431 parser.addFlag('wombat', help: 'Third');
432
433 validateUsage(parser,
434 '''
435 --[no-]zebra First
436 --[no-]monkey Second
437 --[no-]wombat Third
438 ''');
439 });
440
441 test('the default value for a flag is shown', () {
442 var parser = new ArgParser();
443 parser.addFlag('affirm', help: 'Should be on', defaultsTo: true);
444 parser.addFlag('negate', help: 'Should be off', defaultsTo: false);
445
446 validateUsage(parser,
447 '''
448 --[no-]affirm Should be on
449 (defaults to on)
450
451 --[no-]negate Should be off
452 (defaults to off)
453 ''');
454 });
455
456 test('the default value for an option with no allowed list is shown', () {
457 var parser = new ArgParser();
458 parser.addOption('any', help: 'Can be anything', defaultsTo: 'whatevs');
459
460 validateUsage(parser,
461 '''
462 --any Can be anything
463 (defaults to "whatevs")
464 ''');
465 });
466
467 test('the allowed list is shown', () {
468 var parser = new ArgParser();
469 parser.addOption('suit', help: 'Like in cards',
470 allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
471
472 validateUsage(parser,
473 '''
474 --suit Like in cards
475 [spades, clubs, hearts, diamonds]
476 ''');
477 });
478
479 test('the default is highlighted in the allowed list', () {
480 var parser = new ArgParser();
481 parser.addOption('suit', help: 'Like in cards', defaultsTo: 'clubs',
482 allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
483
484 validateUsage(parser,
485 '''
486 --suit Like in cards
487 [spades, clubs (default), hearts, diamonds]
488 ''');
489 });
490
491 test('the allowed help is shown', () {
492 var parser = new ArgParser();
493 parser.addOption('suit', help: 'Like in cards', defaultsTo: 'clubs',
494 allowed: ['spades', 'clubs', 'diamonds', 'hearts'],
495 allowedHelp: {
496 'spades': 'Swords of a soldier',
497 'clubs': 'Weapons of war',
498 'diamonds': 'Money for this art',
499 'hearts': 'The shape of my heart'
500 });
501
502 validateUsage(parser,
503 '''
504 --suit Like in cards
505
506 [clubs] Weapons of war
507 [diamonds] Money for this art
508 [hearts] The shape of my heart
509 [spades] Swords of a soldier
510 ''');
511 });
512 });
513
514 group('ArgResults[]', () {
515 test('throws if the name is not an option', () {
516 var parser = new ArgParser();
517 var results = parser.parse([]);
518 throwsIllegalArg(() => results['unknown']);
519 });
520 });
521 }
522
523 throwsIllegalArg(function) {
524 expectThrow(function, (e) => e is IllegalArgumentException);
525 }
526
527 throwsBadFormat(ArgParser parser, List<String> args) {
528 expectThrow(() => parser.parse(args), (e) => e is ArgFormatException);
529 }
530
531 validateUsage(ArgParser parser, String expected) {
532 expected = unindentString(expected);
533 expect(parser.getUsage()).equals(expected);
534 }
535
536 // TODO(rnystrom): Replace one in test_utils.
537 String unindentString(String text) {
538 var lines = text.split('\n');
539
540 // Count the indentation of the last line.
541 var indent = 0;
542 var last = lines[lines.length - 1];
nweiz 2012/05/03 21:07:08 var indent = const RegExp("^ *").match(lines[lines
Bob Nystrom 2012/05/03 22:31:20 Done.
543 for (indent = 0; indent < last.length; indent++) {
544 if (last[indent] != ' ') break;
545 }
546
547 // Drop the last line. It only exists for specifying indentation.
548 lines.removeLast();
549
550 // Strip indentation from the remaining lines.
551 for (var i = 0; i < lines.length; i++) {
552 var line = lines[i];
553 if (line.length <= indent) {
554 // It's short, so it must be nothing but whitespace.
555 if (line.trim() != '') {
556 throw new IllegalArgumentException(
557 'Line "$line" does not have enough indentation.');
558 }
559
560 lines[i] = '';
561 } else {
562 if (line.substring(0, indent).trim() != '') {
563 throw new IllegalArgumentException(
564 'Line "$line" does not have enough indentation.');
565 }
566
567 lines[i] = line.substring(indent);
568 }
569 }
570
571 return Strings.join(lines, '\n');
572 }
OLDNEW
« lib/unittest/unittest.dart ('K') | « lib/unittest/unittest.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698