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