1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import, print_function, unicode_literals
6
7import unittest
8
9from mozunit import main
10
11from mozbuild.configure.options import (
12    CommandLineHelper,
13    ConflictingOptionError,
14    InvalidOptionError,
15    NegativeOptionValue,
16    Option,
17    OptionValue,
18    PositiveOptionValue,
19)
20
21
22class Option(Option):
23    def __init__(self, *args, **kwargs):
24        kwargs['help'] = 'Dummy help'
25        super(Option, self).__init__(*args, **kwargs)
26
27
28class TestOption(unittest.TestCase):
29    def test_option(self):
30        option = Option('--option')
31        self.assertEquals(option.prefix, '')
32        self.assertEquals(option.name, 'option')
33        self.assertEquals(option.env, None)
34        self.assertFalse(option.default)
35
36        option = Option('--enable-option')
37        self.assertEquals(option.prefix, 'enable')
38        self.assertEquals(option.name, 'option')
39        self.assertEquals(option.env, None)
40        self.assertFalse(option.default)
41
42        option = Option('--disable-option')
43        self.assertEquals(option.prefix, 'disable')
44        self.assertEquals(option.name, 'option')
45        self.assertEquals(option.env, None)
46        self.assertTrue(option.default)
47
48        option = Option('--with-option')
49        self.assertEquals(option.prefix, 'with')
50        self.assertEquals(option.name, 'option')
51        self.assertEquals(option.env, None)
52        self.assertFalse(option.default)
53
54        option = Option('--without-option')
55        self.assertEquals(option.prefix, 'without')
56        self.assertEquals(option.name, 'option')
57        self.assertEquals(option.env, None)
58        self.assertTrue(option.default)
59
60        option = Option('--without-option-foo', env='MOZ_OPTION')
61        self.assertEquals(option.env, 'MOZ_OPTION')
62
63        option = Option(env='MOZ_OPTION')
64        self.assertEquals(option.prefix, '')
65        self.assertEquals(option.name, None)
66        self.assertEquals(option.env, 'MOZ_OPTION')
67        self.assertFalse(option.default)
68
69        with self.assertRaises(InvalidOptionError) as e:
70            Option('--option', nargs=0, default=('a',))
71        self.assertEquals(str(e.exception),
72                          "The given `default` doesn't satisfy `nargs`")
73
74        with self.assertRaises(InvalidOptionError) as e:
75            Option('--option', nargs=1, default=())
76        self.assertEquals(
77            str(e.exception),
78            'default must be a bool, a string or a tuple of strings')
79
80        with self.assertRaises(InvalidOptionError) as e:
81            Option('--option', nargs=1, default=True)
82        self.assertEquals(str(e.exception),
83                          "The given `default` doesn't satisfy `nargs`")
84
85        with self.assertRaises(InvalidOptionError) as e:
86            Option('--option', nargs=1, default=('a', 'b'))
87        self.assertEquals(str(e.exception),
88                          "The given `default` doesn't satisfy `nargs`")
89
90        with self.assertRaises(InvalidOptionError) as e:
91            Option('--option', nargs=2, default=())
92        self.assertEquals(
93            str(e.exception),
94            'default must be a bool, a string or a tuple of strings')
95
96        with self.assertRaises(InvalidOptionError) as e:
97            Option('--option', nargs=2, default=True)
98        self.assertEquals(str(e.exception),
99                          "The given `default` doesn't satisfy `nargs`")
100
101        with self.assertRaises(InvalidOptionError) as e:
102            Option('--option', nargs=2, default=('a',))
103        self.assertEquals(str(e.exception),
104                          "The given `default` doesn't satisfy `nargs`")
105
106        with self.assertRaises(InvalidOptionError) as e:
107            Option('--option', nargs='?', default=('a', 'b'))
108        self.assertEquals(str(e.exception),
109                          "The given `default` doesn't satisfy `nargs`")
110
111        with self.assertRaises(InvalidOptionError) as e:
112            Option('--option', nargs='+', default=())
113        self.assertEquals(
114            str(e.exception),
115            'default must be a bool, a string or a tuple of strings')
116
117        with self.assertRaises(InvalidOptionError) as e:
118            Option('--option', nargs='+', default=True)
119        self.assertEquals(str(e.exception),
120                          "The given `default` doesn't satisfy `nargs`")
121
122        # --disable options with a nargs value that requires at least one
123        # argument need to be given a default.
124        with self.assertRaises(InvalidOptionError) as e:
125            Option('--disable-option', nargs=1)
126        self.assertEquals(str(e.exception),
127                          "The given `default` doesn't satisfy `nargs`")
128
129        with self.assertRaises(InvalidOptionError) as e:
130            Option('--disable-option', nargs='+')
131        self.assertEquals(str(e.exception),
132                          "The given `default` doesn't satisfy `nargs`")
133
134        # Test nargs inference from default value
135        option = Option('--with-foo', default=True)
136        self.assertEquals(option.nargs, 0)
137
138        option = Option('--with-foo', default=False)
139        self.assertEquals(option.nargs, 0)
140
141        option = Option('--with-foo', default='a')
142        self.assertEquals(option.nargs, '?')
143
144        option = Option('--with-foo', default=('a',))
145        self.assertEquals(option.nargs, '?')
146
147        option = Option('--with-foo', default=('a', 'b'))
148        self.assertEquals(option.nargs, '*')
149
150        option = Option(env='FOO', default=True)
151        self.assertEquals(option.nargs, 0)
152
153        option = Option(env='FOO', default=False)
154        self.assertEquals(option.nargs, 0)
155
156        option = Option(env='FOO', default='a')
157        self.assertEquals(option.nargs, '?')
158
159        option = Option(env='FOO', default=('a',))
160        self.assertEquals(option.nargs, '?')
161
162        option = Option(env='FOO', default=('a', 'b'))
163        self.assertEquals(option.nargs, '*')
164
165    def test_option_option(self):
166        for option in (
167            '--option',
168            '--enable-option',
169            '--disable-option',
170            '--with-option',
171            '--without-option',
172        ):
173            self.assertEquals(Option(option).option, option)
174            self.assertEquals(Option(option, env='FOO').option, option)
175
176            opt = Option(option, default=False)
177            self.assertEquals(opt.option,
178                              option.replace('-disable-', '-enable-')
179                                    .replace('-without-', '-with-'))
180
181            opt = Option(option, default=True)
182            self.assertEquals(opt.option,
183                              option.replace('-enable-', '-disable-')
184                                    .replace('-with-', '-without-'))
185
186        self.assertEquals(Option(env='FOO').option, 'FOO')
187
188    def test_option_choices(self):
189        with self.assertRaises(InvalidOptionError) as e:
190            Option('--option', nargs=3, choices=('a', 'b'))
191        self.assertEquals(str(e.exception),
192                          'Not enough `choices` for `nargs`')
193
194        with self.assertRaises(InvalidOptionError) as e:
195            Option('--without-option', nargs=1, choices=('a', 'b'))
196        self.assertEquals(str(e.exception),
197                          'A `default` must be given along with `choices`')
198
199        with self.assertRaises(InvalidOptionError) as e:
200            Option('--without-option', nargs='+', choices=('a', 'b'))
201        self.assertEquals(str(e.exception),
202                          'A `default` must be given along with `choices`')
203
204        with self.assertRaises(InvalidOptionError) as e:
205            Option('--without-option', default='c', choices=('a', 'b'))
206        self.assertEquals(str(e.exception),
207                          "The `default` value must be one of 'a', 'b'")
208
209        with self.assertRaises(InvalidOptionError) as e:
210            Option('--without-option', default=('a', 'c',), choices=('a', 'b'))
211        self.assertEquals(str(e.exception),
212                          "The `default` value must be one of 'a', 'b'")
213
214        with self.assertRaises(InvalidOptionError) as e:
215            Option('--without-option', default=('c',), choices=('a', 'b'))
216        self.assertEquals(str(e.exception),
217                          "The `default` value must be one of 'a', 'b'")
218
219        option = Option('--with-option', nargs='+', choices=('a', 'b'))
220        with self.assertRaises(InvalidOptionError) as e:
221            option.get_value('--with-option=c')
222        self.assertEquals(str(e.exception), "'c' is not one of 'a', 'b'")
223
224        value = option.get_value('--with-option=b,a')
225        self.assertTrue(value)
226        self.assertEquals(PositiveOptionValue(('b', 'a')), value)
227
228        option = Option('--without-option', nargs='*', default='a',
229                        choices=('a', 'b'))
230        with self.assertRaises(InvalidOptionError) as e:
231            option.get_value('--with-option=c')
232        self.assertEquals(str(e.exception), "'c' is not one of 'a', 'b'")
233
234        value = option.get_value('--with-option=b,a')
235        self.assertTrue(value)
236        self.assertEquals(PositiveOptionValue(('b', 'a')), value)
237
238        # Test nargs inference from choices
239        option = Option('--with-option', choices=('a', 'b'))
240        self.assertEqual(option.nargs, 1)
241
242        # Test "relative" values
243        option = Option('--with-option', nargs='*', default=('b', 'c'),
244                        choices=('a', 'b', 'c', 'd'))
245
246        value = option.get_value('--with-option=+d')
247        self.assertEquals(PositiveOptionValue(('b', 'c', 'd')), value)
248
249        value = option.get_value('--with-option=-b')
250        self.assertEquals(PositiveOptionValue(('c',)), value)
251
252        value = option.get_value('--with-option=-b,+d')
253        self.assertEquals(PositiveOptionValue(('c', 'd')), value)
254
255        # Adding something that is in the default is fine
256        value = option.get_value('--with-option=+b')
257        self.assertEquals(PositiveOptionValue(('b', 'c')), value)
258
259        # Removing something that is not in the default is fine, as long as it
260        # is one of the choices
261        value = option.get_value('--with-option=-a')
262        self.assertEquals(PositiveOptionValue(('b', 'c')), value)
263
264        with self.assertRaises(InvalidOptionError) as e:
265            option.get_value('--with-option=-e')
266        self.assertEquals(str(e.exception),
267                          "'e' is not one of 'a', 'b', 'c', 'd'")
268
269        # Other "not a choice" errors.
270        with self.assertRaises(InvalidOptionError) as e:
271            option.get_value('--with-option=+e')
272        self.assertEquals(str(e.exception),
273                          "'e' is not one of 'a', 'b', 'c', 'd'")
274
275        with self.assertRaises(InvalidOptionError) as e:
276            option.get_value('--with-option=e')
277        self.assertEquals(str(e.exception),
278                          "'e' is not one of 'a', 'b', 'c', 'd'")
279
280    def test_option_value_compare(self):
281        # OptionValue are tuple and equivalence should compare as tuples.
282        val = PositiveOptionValue(('foo',))
283
284        self.assertEqual(val[0], 'foo')
285        self.assertEqual(val, PositiveOptionValue(('foo',)))
286        self.assertNotEqual(val, PositiveOptionValue(('foo', 'bar')))
287
288        # Can compare a tuple to an OptionValue.
289        self.assertEqual(val, ('foo',))
290        self.assertNotEqual(val, ('foo', 'bar'))
291
292        # Different OptionValue types are never equal.
293        self.assertNotEqual(val, OptionValue(('foo',)))
294
295        # For usability reasons, we raise TypeError when attempting to compare
296        # against a non-tuple.
297        with self.assertRaisesRegexp(TypeError, 'cannot compare a'):
298            val == 'foo'
299
300        # But we allow empty option values to compare otherwise we can't
301        # easily compare value-less types like PositiveOptionValue and
302        # NegativeOptionValue.
303        empty_positive = PositiveOptionValue()
304        empty_negative = NegativeOptionValue()
305        self.assertEqual(empty_positive, ())
306        self.assertEqual(empty_positive, PositiveOptionValue())
307        self.assertEqual(empty_negative, ())
308        self.assertEqual(empty_negative, NegativeOptionValue())
309        self.assertNotEqual(empty_positive, 'foo')
310        self.assertNotEqual(empty_positive, ('foo',))
311        self.assertNotEqual(empty_negative, 'foo')
312        self.assertNotEqual(empty_negative, ('foo',))
313
314    def test_option_value_format(self):
315        val = PositiveOptionValue()
316        self.assertEquals('--with-value', val.format('--with-value'))
317        self.assertEquals('--with-value', val.format('--without-value'))
318        self.assertEquals('--enable-value', val.format('--enable-value'))
319        self.assertEquals('--enable-value', val.format('--disable-value'))
320        self.assertEquals('--value', val.format('--value'))
321        self.assertEquals('VALUE=1', val.format('VALUE'))
322
323        val = PositiveOptionValue(('a',))
324        self.assertEquals('--with-value=a', val.format('--with-value'))
325        self.assertEquals('--with-value=a', val.format('--without-value'))
326        self.assertEquals('--enable-value=a', val.format('--enable-value'))
327        self.assertEquals('--enable-value=a', val.format('--disable-value'))
328        self.assertEquals('--value=a', val.format('--value'))
329        self.assertEquals('VALUE=a', val.format('VALUE'))
330
331        val = PositiveOptionValue(('a', 'b'))
332        self.assertEquals('--with-value=a,b', val.format('--with-value'))
333        self.assertEquals('--with-value=a,b', val.format('--without-value'))
334        self.assertEquals('--enable-value=a,b', val.format('--enable-value'))
335        self.assertEquals('--enable-value=a,b', val.format('--disable-value'))
336        self.assertEquals('--value=a,b', val.format('--value'))
337        self.assertEquals('VALUE=a,b', val.format('VALUE'))
338
339        val = NegativeOptionValue()
340        self.assertEquals('--without-value', val.format('--with-value'))
341        self.assertEquals('--without-value', val.format('--without-value'))
342        self.assertEquals('--disable-value', val.format('--enable-value'))
343        self.assertEquals('--disable-value', val.format('--disable-value'))
344        self.assertEquals('', val.format('--value'))
345        self.assertEquals('VALUE=', val.format('VALUE'))
346
347    def test_option_value(self, name='option', nargs=0, default=None):
348        disabled = name.startswith(('disable-', 'without-'))
349        if disabled:
350            negOptionValue = PositiveOptionValue
351            posOptionValue = NegativeOptionValue
352        else:
353            posOptionValue = PositiveOptionValue
354            negOptionValue = NegativeOptionValue
355        defaultValue = (PositiveOptionValue(default)
356                        if default else negOptionValue())
357
358        option = Option('--%s' % name, nargs=nargs, default=default)
359
360        if nargs in (0, '?', '*') or disabled:
361            value = option.get_value('--%s' % name, 'option')
362            self.assertEquals(value, posOptionValue())
363            self.assertEquals(value.origin, 'option')
364        else:
365            with self.assertRaises(InvalidOptionError) as e:
366                option.get_value('--%s' % name)
367            if nargs == 1:
368                self.assertEquals(str(e.exception),
369                                  '--%s takes 1 value' % name)
370            elif nargs == '+':
371                self.assertEquals(str(e.exception),
372                                  '--%s takes 1 or more values' % name)
373            else:
374                self.assertEquals(str(e.exception),
375                                  '--%s takes 2 values' % name)
376
377        value = option.get_value('')
378        self.assertEquals(value, defaultValue)
379        self.assertEquals(value.origin, 'default')
380
381        value = option.get_value(None)
382        self.assertEquals(value, defaultValue)
383        self.assertEquals(value.origin, 'default')
384
385        with self.assertRaises(AssertionError):
386            value = option.get_value('MOZ_OPTION=', 'environment')
387
388        with self.assertRaises(AssertionError):
389            value = option.get_value('MOZ_OPTION=1', 'environment')
390
391        with self.assertRaises(AssertionError):
392            value = option.get_value('--foo')
393
394        if nargs in (1, '?', '*', '+') and not disabled:
395            value = option.get_value('--%s=' % name, 'option')
396            self.assertEquals(value, PositiveOptionValue(('',)))
397            self.assertEquals(value.origin, 'option')
398        else:
399            with self.assertRaises(InvalidOptionError) as e:
400                option.get_value('--%s=' % name)
401            if disabled:
402                self.assertEquals(str(e.exception),
403                                  'Cannot pass a value to --%s' % name)
404            else:
405                self.assertEquals(str(e.exception),
406                                  '--%s takes %d values' % (name, nargs))
407
408        if nargs in (1, '?', '*', '+') and not disabled:
409            value = option.get_value('--%s=foo' % name, 'option')
410            self.assertEquals(value, PositiveOptionValue(('foo',)))
411            self.assertEquals(value.origin, 'option')
412        else:
413            with self.assertRaises(InvalidOptionError) as e:
414                option.get_value('--%s=foo' % name)
415            if disabled:
416                self.assertEquals(str(e.exception),
417                                  'Cannot pass a value to --%s' % name)
418            else:
419                self.assertEquals(str(e.exception),
420                                  '--%s takes %d values' % (name, nargs))
421
422        if nargs in (2, '*', '+') and not disabled:
423            value = option.get_value('--%s=foo,bar' % name, 'option')
424            self.assertEquals(value, PositiveOptionValue(('foo', 'bar')))
425            self.assertEquals(value.origin, 'option')
426        else:
427            with self.assertRaises(InvalidOptionError) as e:
428                option.get_value('--%s=foo,bar' % name, 'option')
429            if disabled:
430                self.assertEquals(str(e.exception),
431                                  'Cannot pass a value to --%s' % name)
432            elif nargs == '?':
433                self.assertEquals(str(e.exception),
434                                  '--%s takes 0 or 1 values' % name)
435            else:
436                self.assertEquals(str(e.exception),
437                                  '--%s takes %d value%s'
438                                  % (name, nargs, 's' if nargs != 1 else ''))
439
440        option = Option('--%s' % name, env='MOZ_OPTION', nargs=nargs,
441                        default=default)
442        if nargs in (0, '?', '*') or disabled:
443            value = option.get_value('--%s' % name, 'option')
444            self.assertEquals(value, posOptionValue())
445            self.assertEquals(value.origin, 'option')
446        else:
447            with self.assertRaises(InvalidOptionError) as e:
448                option.get_value('--%s' % name)
449            if disabled:
450                self.assertEquals(str(e.exception),
451                                  'Cannot pass a value to --%s' % name)
452            elif nargs == '+':
453                self.assertEquals(str(e.exception),
454                                  '--%s takes 1 or more values' % name)
455            else:
456                self.assertEquals(str(e.exception),
457                                  '--%s takes %d value%s'
458                                  % (name, nargs, 's' if nargs != 1 else ''))
459
460        value = option.get_value('')
461        self.assertEquals(value, defaultValue)
462        self.assertEquals(value.origin, 'default')
463
464        value = option.get_value(None)
465        self.assertEquals(value, defaultValue)
466        self.assertEquals(value.origin, 'default')
467
468        value = option.get_value('MOZ_OPTION=', 'environment')
469        self.assertEquals(value, NegativeOptionValue())
470        self.assertEquals(value.origin, 'environment')
471
472        if nargs in (0, '?', '*'):
473            value = option.get_value('MOZ_OPTION=1', 'environment')
474            self.assertEquals(value, PositiveOptionValue())
475            self.assertEquals(value.origin, 'environment')
476        elif nargs in (1, '+'):
477            value = option.get_value('MOZ_OPTION=1', 'environment')
478            self.assertEquals(value, PositiveOptionValue(('1',)))
479            self.assertEquals(value.origin, 'environment')
480        else:
481            with self.assertRaises(InvalidOptionError) as e:
482                option.get_value('MOZ_OPTION=1', 'environment')
483            self.assertEquals(str(e.exception), 'MOZ_OPTION takes 2 values')
484
485        if nargs in (1, '?', '*', '+') and not disabled:
486            value = option.get_value('--%s=' % name, 'option')
487            self.assertEquals(value, PositiveOptionValue(('',)))
488            self.assertEquals(value.origin, 'option')
489        else:
490            with self.assertRaises(InvalidOptionError) as e:
491                option.get_value('--%s=' % name, 'option')
492            if disabled:
493                self.assertEquals(str(e.exception),
494                                  'Cannot pass a value to --%s' % name)
495            else:
496                self.assertEquals(str(e.exception),
497                                  '--%s takes %d values' % (name, nargs))
498
499        with self.assertRaises(AssertionError):
500            value = option.get_value('--foo', 'option')
501
502        if nargs in (1, '?', '*', '+'):
503            value = option.get_value('MOZ_OPTION=foo', 'environment')
504            self.assertEquals(value, PositiveOptionValue(('foo',)))
505            self.assertEquals(value.origin, 'environment')
506        else:
507            with self.assertRaises(InvalidOptionError) as e:
508                option.get_value('MOZ_OPTION=foo', 'environment')
509            self.assertEquals(str(e.exception),
510                              'MOZ_OPTION takes %d values' % nargs)
511
512        if nargs in (2, '*', '+'):
513            value = option.get_value('MOZ_OPTION=foo,bar', 'environment')
514            self.assertEquals(value, PositiveOptionValue(('foo', 'bar')))
515            self.assertEquals(value.origin, 'environment')
516        else:
517            with self.assertRaises(InvalidOptionError) as e:
518                option.get_value('MOZ_OPTION=foo,bar', 'environment')
519            if nargs == '?':
520                self.assertEquals(str(e.exception),
521                                  'MOZ_OPTION takes 0 or 1 values')
522            else:
523                self.assertEquals(str(e.exception),
524                                  'MOZ_OPTION takes %d value%s'
525                                  % (nargs, 's' if nargs != 1 else ''))
526
527        if disabled:
528            return option
529
530        env_option = Option(env='MOZ_OPTION', nargs=nargs, default=default)
531        with self.assertRaises(AssertionError):
532            env_option.get_value('--%s' % name)
533
534        value = env_option.get_value('')
535        self.assertEquals(value, defaultValue)
536        self.assertEquals(value.origin, 'default')
537
538        value = env_option.get_value('MOZ_OPTION=', 'environment')
539        self.assertEquals(value, negOptionValue())
540        self.assertEquals(value.origin, 'environment')
541
542        if nargs in (0, '?', '*'):
543            value = env_option.get_value('MOZ_OPTION=1', 'environment')
544            self.assertEquals(value, posOptionValue())
545            self.assertTrue(value)
546            self.assertEquals(value.origin, 'environment')
547        elif nargs in (1, '+'):
548            value = env_option.get_value('MOZ_OPTION=1', 'environment')
549            self.assertEquals(value, PositiveOptionValue(('1',)))
550            self.assertEquals(value.origin, 'environment')
551        else:
552            with self.assertRaises(InvalidOptionError) as e:
553                env_option.get_value('MOZ_OPTION=1', 'environment')
554            self.assertEquals(str(e.exception), 'MOZ_OPTION takes 2 values')
555
556        with self.assertRaises(AssertionError) as e:
557            env_option.get_value('--%s' % name)
558
559        with self.assertRaises(AssertionError) as e:
560            env_option.get_value('--foo')
561
562        if nargs in (1, '?', '*', '+'):
563            value = env_option.get_value('MOZ_OPTION=foo', 'environment')
564            self.assertEquals(value, PositiveOptionValue(('foo',)))
565            self.assertEquals(value.origin, 'environment')
566        else:
567            with self.assertRaises(InvalidOptionError) as e:
568                env_option.get_value('MOZ_OPTION=foo', 'environment')
569            self.assertEquals(str(e.exception),
570                              'MOZ_OPTION takes %d values' % nargs)
571
572        if nargs in (2, '*', '+'):
573            value = env_option.get_value('MOZ_OPTION=foo,bar', 'environment')
574            self.assertEquals(value, PositiveOptionValue(('foo', 'bar')))
575            self.assertEquals(value.origin, 'environment')
576        else:
577            with self.assertRaises(InvalidOptionError) as e:
578                env_option.get_value('MOZ_OPTION=foo,bar', 'environment')
579            if nargs == '?':
580                self.assertEquals(str(e.exception),
581                                  'MOZ_OPTION takes 0 or 1 values')
582            else:
583                self.assertEquals(str(e.exception),
584                                  'MOZ_OPTION takes %d value%s'
585                                  % (nargs, 's' if nargs != 1 else ''))
586
587        return option
588
589    def test_option_value_enable(self, enable='enable', disable='disable',
590                                 nargs=0, default=None):
591        option = self.test_option_value('%s-option' % enable, nargs=nargs,
592                                        default=default)
593
594        value = option.get_value('--%s-option' % disable, 'option')
595        self.assertEquals(value, NegativeOptionValue())
596        self.assertEquals(value.origin, 'option')
597
598        option = self.test_option_value('%s-option' % disable, nargs=nargs,
599                                        default=default)
600
601        if nargs in (0, '?', '*'):
602            value = option.get_value('--%s-option' % enable, 'option')
603            self.assertEquals(value, PositiveOptionValue())
604            self.assertEquals(value.origin, 'option')
605        else:
606            with self.assertRaises(InvalidOptionError) as e:
607                option.get_value('--%s-option' % enable, 'option')
608            if nargs == 1:
609                self.assertEquals(str(e.exception),
610                                  '--%s-option takes 1 value' % enable)
611            elif nargs == '+':
612                self.assertEquals(str(e.exception),
613                                  '--%s-option takes 1 or more values'
614                                  % enable)
615            else:
616                self.assertEquals(str(e.exception),
617                                  '--%s-option takes 2 values' % enable)
618
619    def test_option_value_with(self):
620        self.test_option_value_enable('with', 'without')
621
622    def test_option_value_invalid_nargs(self):
623        with self.assertRaises(InvalidOptionError) as e:
624            Option('--option', nargs='foo')
625        self.assertEquals(str(e.exception),
626                          "nargs must be a positive integer, '?', '*' or '+'")
627
628        with self.assertRaises(InvalidOptionError) as e:
629            Option('--option', nargs=-2)
630        self.assertEquals(str(e.exception),
631                          "nargs must be a positive integer, '?', '*' or '+'")
632
633    def test_option_value_nargs_1(self):
634        self.test_option_value(nargs=1)
635        self.test_option_value(nargs=1, default=('a',))
636        self.test_option_value_enable(nargs=1, default=('a',))
637
638        # A default is required
639        with self.assertRaises(InvalidOptionError) as e:
640            Option('--disable-option', nargs=1)
641        self.assertEquals(str(e.exception),
642                          "The given `default` doesn't satisfy `nargs`")
643
644    def test_option_value_nargs_2(self):
645        self.test_option_value(nargs=2)
646        self.test_option_value(nargs=2, default=('a', 'b'))
647        self.test_option_value_enable(nargs=2, default=('a', 'b'))
648
649        # A default is required
650        with self.assertRaises(InvalidOptionError) as e:
651            Option('--disable-option', nargs=2)
652        self.assertEquals(str(e.exception),
653                          "The given `default` doesn't satisfy `nargs`")
654
655    def test_option_value_nargs_0_or_1(self):
656        self.test_option_value(nargs='?')
657        self.test_option_value(nargs='?', default=('a',))
658        self.test_option_value_enable(nargs='?')
659        self.test_option_value_enable(nargs='?', default=('a',))
660
661    def test_option_value_nargs_0_or_more(self):
662        self.test_option_value(nargs='*')
663        self.test_option_value(nargs='*', default=('a',))
664        self.test_option_value(nargs='*', default=('a', 'b'))
665        self.test_option_value_enable(nargs='*')
666        self.test_option_value_enable(nargs='*', default=('a',))
667        self.test_option_value_enable(nargs='*', default=('a', 'b'))
668
669    def test_option_value_nargs_1_or_more(self):
670        self.test_option_value(nargs='+')
671        self.test_option_value(nargs='+', default=('a',))
672        self.test_option_value(nargs='+', default=('a', 'b'))
673        self.test_option_value_enable(nargs='+', default=('a',))
674        self.test_option_value_enable(nargs='+', default=('a', 'b'))
675
676        # A default is required
677        with self.assertRaises(InvalidOptionError) as e:
678            Option('--disable-option', nargs='+')
679        self.assertEquals(str(e.exception),
680                          "The given `default` doesn't satisfy `nargs`")
681
682
683class TestCommandLineHelper(unittest.TestCase):
684    def test_basic(self):
685        helper = CommandLineHelper({}, ['cmd', '--foo', '--bar'])
686
687        self.assertEquals(['--foo', '--bar'], list(helper))
688
689        helper.add('--enable-qux')
690
691        self.assertEquals(['--foo', '--bar', '--enable-qux'], list(helper))
692
693        value, option = helper.handle(Option('--bar'))
694        self.assertEquals(['--foo', '--enable-qux'], list(helper))
695        self.assertEquals(PositiveOptionValue(), value)
696        self.assertEquals('--bar', option)
697
698        value, option = helper.handle(Option('--baz'))
699        self.assertEquals(['--foo', '--enable-qux'], list(helper))
700        self.assertEquals(NegativeOptionValue(), value)
701        self.assertEquals(None, option)
702
703        with self.assertRaises(AssertionError):
704            CommandLineHelper({}, ['--foo', '--bar'])
705
706    def test_precedence(self):
707        foo = Option('--with-foo', nargs='*')
708        helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b'])
709        value, option = helper.handle(foo)
710        self.assertEquals(PositiveOptionValue(('a', 'b')), value)
711        self.assertEquals('command-line', value.origin)
712        self.assertEquals('--with-foo=a,b', option)
713
714        helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b',
715                                        '--without-foo'])
716        value, option = helper.handle(foo)
717        self.assertEquals(NegativeOptionValue(), value)
718        self.assertEquals('command-line', value.origin)
719        self.assertEquals('--without-foo', option)
720
721        helper = CommandLineHelper({}, ['cmd', '--without-foo',
722                                        '--with-foo=a,b'])
723        value, option = helper.handle(foo)
724        self.assertEquals(PositiveOptionValue(('a', 'b')), value)
725        self.assertEquals('command-line', value.origin)
726        self.assertEquals('--with-foo=a,b', option)
727
728        foo = Option('--with-foo', env='FOO', nargs='*')
729        helper = CommandLineHelper({'FOO': ''}, ['cmd', '--with-foo=a,b'])
730        value, option = helper.handle(foo)
731        self.assertEquals(PositiveOptionValue(('a', 'b')), value)
732        self.assertEquals('command-line', value.origin)
733        self.assertEquals('--with-foo=a,b', option)
734
735        helper = CommandLineHelper({'FOO': 'a,b'}, ['cmd', '--without-foo'])
736        value, option = helper.handle(foo)
737        self.assertEquals(NegativeOptionValue(), value)
738        self.assertEquals('command-line', value.origin)
739        self.assertEquals('--without-foo', option)
740
741        helper = CommandLineHelper({'FOO': ''}, ['cmd', '--with-bar=a,b'])
742        value, option = helper.handle(foo)
743        self.assertEquals(NegativeOptionValue(), value)
744        self.assertEquals('environment', value.origin)
745        self.assertEquals('FOO=', option)
746
747        helper = CommandLineHelper({'FOO': 'a,b'}, ['cmd', '--without-bar'])
748        value, option = helper.handle(foo)
749        self.assertEquals(PositiveOptionValue(('a', 'b')), value)
750        self.assertEquals('environment', value.origin)
751        self.assertEquals('FOO=a,b', option)
752
753        helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b', 'FOO='])
754        value, option = helper.handle(foo)
755        self.assertEquals(NegativeOptionValue(), value)
756        self.assertEquals('command-line', value.origin)
757        self.assertEquals('FOO=', option)
758
759        helper = CommandLineHelper({}, ['cmd', '--without-foo', 'FOO=a,b'])
760        value, option = helper.handle(foo)
761        self.assertEquals(PositiveOptionValue(('a', 'b')), value)
762        self.assertEquals('command-line', value.origin)
763        self.assertEquals('FOO=a,b', option)
764
765        helper = CommandLineHelper({}, ['cmd', 'FOO=', '--with-foo=a,b'])
766        value, option = helper.handle(foo)
767        self.assertEquals(PositiveOptionValue(('a', 'b')), value)
768        self.assertEquals('command-line', value.origin)
769        self.assertEquals('--with-foo=a,b', option)
770
771        helper = CommandLineHelper({}, ['cmd', 'FOO=a,b', '--without-foo'])
772        value, option = helper.handle(foo)
773        self.assertEquals(NegativeOptionValue(), value)
774        self.assertEquals('command-line', value.origin)
775        self.assertEquals('--without-foo', option)
776
777    def test_extra_args(self):
778        foo = Option('--with-foo', env='FOO', nargs='*')
779        helper = CommandLineHelper({}, ['cmd'])
780        helper.add('FOO=a,b,c', 'other-origin')
781        value, option = helper.handle(foo)
782        self.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
783        self.assertEquals('other-origin', value.origin)
784        self.assertEquals('FOO=a,b,c', option)
785
786        helper = CommandLineHelper({}, ['cmd'])
787        helper.add('FOO=a,b,c', 'other-origin')
788        helper.add('--with-foo=a,b,c', 'other-origin')
789        value, option = helper.handle(foo)
790        self.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
791        self.assertEquals('other-origin', value.origin)
792        self.assertEquals('--with-foo=a,b,c', option)
793
794        # Adding conflicting options is not allowed.
795        helper = CommandLineHelper({}, ['cmd'])
796        helper.add('FOO=a,b,c', 'other-origin')
797        with self.assertRaises(ConflictingOptionError) as cm:
798            helper.add('FOO=', 'other-origin')
799        self.assertEqual('FOO=', cm.exception.arg)
800        self.assertEqual('other-origin', cm.exception.origin)
801        self.assertEqual('FOO=a,b,c', cm.exception.old_arg)
802        self.assertEqual('other-origin', cm.exception.old_origin)
803        with self.assertRaises(ConflictingOptionError) as cm:
804            helper.add('FOO=a,b', 'other-origin')
805        self.assertEqual('FOO=a,b', cm.exception.arg)
806        self.assertEqual('other-origin', cm.exception.origin)
807        self.assertEqual('FOO=a,b,c', cm.exception.old_arg)
808        self.assertEqual('other-origin', cm.exception.old_origin)
809        # But adding the same is allowed.
810        helper.add('FOO=a,b,c', 'other-origin')
811        value, option = helper.handle(foo)
812        self.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
813        self.assertEquals('other-origin', value.origin)
814        self.assertEquals('FOO=a,b,c', option)
815
816        # The same rule as above applies when using the option form vs. the
817        # variable form. But we can't detect it when .add is called.
818        helper = CommandLineHelper({}, ['cmd'])
819        helper.add('FOO=a,b,c', 'other-origin')
820        helper.add('--without-foo', 'other-origin')
821        with self.assertRaises(ConflictingOptionError) as cm:
822            helper.handle(foo)
823        self.assertEqual('--without-foo', cm.exception.arg)
824        self.assertEqual('other-origin', cm.exception.origin)
825        self.assertEqual('FOO=a,b,c', cm.exception.old_arg)
826        self.assertEqual('other-origin', cm.exception.old_origin)
827        helper = CommandLineHelper({}, ['cmd'])
828        helper.add('FOO=a,b,c', 'other-origin')
829        helper.add('--with-foo=a,b', 'other-origin')
830        with self.assertRaises(ConflictingOptionError) as cm:
831            helper.handle(foo)
832        self.assertEqual('--with-foo=a,b', cm.exception.arg)
833        self.assertEqual('other-origin', cm.exception.origin)
834        self.assertEqual('FOO=a,b,c', cm.exception.old_arg)
835        self.assertEqual('other-origin', cm.exception.old_origin)
836        helper = CommandLineHelper({}, ['cmd'])
837        helper.add('FOO=a,b,c', 'other-origin')
838        helper.add('--with-foo=a,b,c', 'other-origin')
839        value, option = helper.handle(foo)
840        self.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
841        self.assertEquals('other-origin', value.origin)
842        self.assertEquals('--with-foo=a,b,c', option)
843
844        # Conflicts are also not allowed against what is in the
845        # environment/on the command line.
846        helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b'])
847        helper.add('FOO=a,b,c', 'other-origin')
848        with self.assertRaises(ConflictingOptionError) as cm:
849            helper.handle(foo)
850        self.assertEqual('FOO=a,b,c', cm.exception.arg)
851        self.assertEqual('other-origin', cm.exception.origin)
852        self.assertEqual('--with-foo=a,b', cm.exception.old_arg)
853        self.assertEqual('command-line', cm.exception.old_origin)
854
855        helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b'])
856        helper.add('--without-foo', 'other-origin')
857        with self.assertRaises(ConflictingOptionError) as cm:
858            helper.handle(foo)
859        self.assertEqual('--without-foo', cm.exception.arg)
860        self.assertEqual('other-origin', cm.exception.origin)
861        self.assertEqual('--with-foo=a,b', cm.exception.old_arg)
862        self.assertEqual('command-line', cm.exception.old_origin)
863
864    def test_possible_origins(self):
865        with self.assertRaises(InvalidOptionError):
866            Option('--foo', possible_origins='command-line')
867
868        helper = CommandLineHelper({'BAZ': '1'}, ['cmd', '--foo', '--bar'])
869        foo = Option('--foo',
870                     possible_origins=('command-line',))
871        value, option = helper.handle(foo)
872        self.assertEquals(PositiveOptionValue(), value)
873        self.assertEquals('command-line', value.origin)
874        self.assertEquals('--foo', option)
875
876        bar = Option('--bar',
877                     possible_origins=('mozconfig',))
878        with self.assertRaisesRegexp(
879            InvalidOptionError,
880            "--bar can not be set by command-line. Values are accepted from: mozconfig"
881        ):
882            helper.handle(bar)
883
884        baz = Option(env='BAZ',
885                     possible_origins=('implied',))
886        with self.assertRaisesRegexp(
887            InvalidOptionError,
888            "BAZ=1 can not be set by environment. Values are accepted from: implied"
889        ):
890            helper.handle(baz)
891
892
893if __name__ == '__main__':
894    main()
895