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

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
« no previous file with comments | « 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 false if missing with no default', () {
84 var parser = new ArgParser();
85 parser.addFlag('verbose');
86
87 var args = parser.parse([]);
88 expect(args['verbose']).isFalse();
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']).isFalse();
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).isFalse();
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']).isFalse();
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.rest).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.rest).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('leading empty lines are ignored in help text', () {
428 var parser = new ArgParser();
429 parser.addFlag('mode', help: '\n\n\n\nAfter newlines');
430
431 validateUsage(parser,
432 '''
433 --[no-]mode After newlines
434 ''');
435 });
436
437 test('trailing empty lines are ignored in help text', () {
438 var parser = new ArgParser();
439 parser.addFlag('mode', help: 'Before newlines\n\n\n\n');
440
441 validateUsage(parser,
442 '''
443 --[no-]mode Before newlines
444 ''');
445 });
446
447 test('options are documented in the order they were added', () {
448 var parser = new ArgParser();
449 parser.addFlag('zebra', help: 'First');
450 parser.addFlag('monkey', help: 'Second');
451 parser.addFlag('wombat', help: 'Third');
452
453 validateUsage(parser,
454 '''
455 --[no-]zebra First
456 --[no-]monkey Second
457 --[no-]wombat Third
458 ''');
459 });
460
461 test('the default value for a flag is shown if on', () {
462 var parser = new ArgParser();
463 parser.addFlag('affirm', help: 'Should be on', defaultsTo: true);
464 parser.addFlag('negate', help: 'Should be off', defaultsTo: false);
465
466 validateUsage(parser,
467 '''
468 --[no-]affirm Should be on
469 (defaults to on)
470
471 --[no-]negate Should be off
472 ''');
473 });
474
475 test('the default value for an option with no allowed list is shown', () {
476 var parser = new ArgParser();
477 parser.addOption('any', help: 'Can be anything', defaultsTo: 'whatevs');
478
479 validateUsage(parser,
480 '''
481 --any Can be anything
482 (defaults to "whatevs")
483 ''');
484 });
485
486 test('the allowed list is shown', () {
487 var parser = new ArgParser();
488 parser.addOption('suit', help: 'Like in cards',
489 allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
490
491 validateUsage(parser,
492 '''
493 --suit Like in cards
494 [spades, clubs, hearts, diamonds]
495 ''');
496 });
497
498 test('the default is highlighted in the allowed list', () {
499 var parser = new ArgParser();
500 parser.addOption('suit', help: 'Like in cards', defaultsTo: 'clubs',
501 allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
502
503 validateUsage(parser,
504 '''
505 --suit Like in cards
506 [spades, clubs (default), hearts, diamonds]
507 ''');
508 });
509
510 test('the allowed help is shown', () {
511 var parser = new ArgParser();
512 parser.addOption('suit', help: 'Like in cards', defaultsTo: 'clubs',
513 allowed: ['spades', 'clubs', 'diamonds', 'hearts'],
514 allowedHelp: {
515 'spades': 'Swords of a soldier',
516 'clubs': 'Weapons of war',
517 'diamonds': 'Money for this art',
518 'hearts': 'The shape of my heart'
519 });
520
521 validateUsage(parser,
522 '''
523 --suit Like in cards
524
525 [clubs] Weapons of war
526 [diamonds] Money for this art
527 [hearts] The shape of my heart
528 [spades] Swords of a soldier
529 ''');
530 });
531 });
532
533 group('ArgResults[]', () {
534 test('throws if the name is not an option', () {
535 var parser = new ArgParser();
536 var results = parser.parse([]);
537 throwsIllegalArg(() => results['unknown']);
538 });
539 });
540 }
541
542 throwsIllegalArg(function) {
543 expectThrow(function, (e) => e is IllegalArgumentException);
544 }
545
546 throwsBadFormat(ArgParser parser, List<String> args) {
547 expectThrow(() => parser.parse(args), (e) => e is ArgFormatException);
548 }
549
550 validateUsage(ArgParser parser, String expected) {
551 expected = unindentString(expected);
552 expect(parser.getUsage()).equals(expected);
553 }
554
555 // TODO(rnystrom): Replace one in test_utils.
556 String unindentString(String text) {
557 var lines = text.split('\n');
558
559 // Count the indentation of the last line.
560 var whitespace = const RegExp('^ *');
561 var indent = whitespace.firstMatch(lines[lines.length - 1])[0].length;
562
563 // Drop the last line. It only exists for specifying indentation.
564 lines.removeLast();
565
566 // Strip indentation from the remaining lines.
567 for (var i = 0; i < lines.length; i++) {
568 var line = lines[i];
569 if (line.length <= indent) {
570 // It's short, so it must be nothing but whitespace.
571 if (line.trim() != '') {
572 throw new IllegalArgumentException(
573 'Line "$line" does not have enough indentation.');
574 }
575
576 lines[i] = '';
577 } else {
578 if (line.substring(0, indent).trim() != '') {
579 throw new IllegalArgumentException(
580 'Line "$line" does not have enough indentation.');
581 }
582
583 lines[i] = line.substring(indent);
584 }
585 }
586
587 return Strings.join(lines, '\n');
588 }
OLDNEW
« no previous file with comments | « lib/unittest/unittest.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698