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
7from StringIO import StringIO
8import os
9import sys
10import textwrap
11import unittest
12
13from mozunit import (
14    main,
15    MockedOpen,
16)
17
18from mozbuild.configure.options import (
19    InvalidOptionError,
20    NegativeOptionValue,
21    PositiveOptionValue,
22)
23from mozbuild.configure import (
24    ConfigureError,
25    ConfigureSandbox,
26)
27from mozbuild.util import exec_
28
29import mozpack.path as mozpath
30
31test_data_path = mozpath.abspath(mozpath.dirname(__file__))
32test_data_path = mozpath.join(test_data_path, 'data')
33
34
35class TestConfigure(unittest.TestCase):
36    def get_config(self, options=[], env={}, configure='moz.configure',
37                   prog='/bin/configure'):
38        config = {}
39        out = StringIO()
40        sandbox = ConfigureSandbox(config, env, [prog] + options, out, out)
41
42        sandbox.run(mozpath.join(test_data_path, configure))
43
44        if '--help' in options:
45            return out.getvalue(), config
46        self.assertEquals('', out.getvalue())
47        return config
48
49    def moz_configure(self, source):
50        return MockedOpen({
51            os.path.join(test_data_path,
52                         'moz.configure'): textwrap.dedent(source)
53        })
54
55    def test_defaults(self):
56        config = self.get_config()
57        self.maxDiff = None
58        self.assertEquals({
59            'CHOICES': NegativeOptionValue(),
60            'DEFAULTED': PositiveOptionValue(('not-simple',)),
61            'IS_GCC': NegativeOptionValue(),
62            'REMAINDER': (PositiveOptionValue(), NegativeOptionValue(),
63                          NegativeOptionValue(), NegativeOptionValue()),
64            'SIMPLE': NegativeOptionValue(),
65            'VALUES': NegativeOptionValue(),
66            'VALUES2': NegativeOptionValue(),
67            'VALUES3': NegativeOptionValue(),
68            'WITH_ENV': NegativeOptionValue(),
69        }, config)
70
71    def test_help(self):
72        help, config = self.get_config(['--help'], prog='configure')
73
74        self.assertEquals({}, config)
75        self.maxDiff = None
76        self.assertEquals(
77            'Usage: configure [options]\n'
78            '\n'
79            'Options: [defaults in brackets after descriptions]\n'
80            '  --help                    print this message\n'
81            '  --enable-simple           Enable simple\n'
82            '  --enable-with-env         Enable with env\n'
83            '  --enable-values           Enable values\n'
84            '  --without-thing           Build without thing\n'
85            '  --with-stuff              Build with stuff\n'
86            '  --option                  Option\n'
87            '  --with-returned-default   Returned default [not-simple]\n'
88            '  --returned-choices        Choices\n'
89            '  --enable-imports-in-template\n'
90            '                            Imports in template\n'
91            '  --enable-include          Include\n'
92            '  --with-imports            Imports\n'
93            '\n'
94            'Environment variables:\n'
95            '  CC                        C Compiler\n',
96            help
97        )
98
99    def test_unknown(self):
100        with self.assertRaises(InvalidOptionError):
101            self.get_config(['--unknown'])
102
103    def test_simple(self):
104        for config in (
105                self.get_config(),
106                self.get_config(['--disable-simple']),
107                # Last option wins.
108                self.get_config(['--enable-simple', '--disable-simple']),
109        ):
110            self.assertNotIn('ENABLED_SIMPLE', config)
111            self.assertIn('SIMPLE', config)
112            self.assertEquals(NegativeOptionValue(), config['SIMPLE'])
113
114        for config in (
115                self.get_config(['--enable-simple']),
116                self.get_config(['--disable-simple', '--enable-simple']),
117        ):
118            self.assertIn('ENABLED_SIMPLE', config)
119            self.assertIn('SIMPLE', config)
120            self.assertEquals(PositiveOptionValue(), config['SIMPLE'])
121            self.assertIs(config['SIMPLE'], config['ENABLED_SIMPLE'])
122
123        # --enable-simple doesn't take values.
124        with self.assertRaises(InvalidOptionError):
125            self.get_config(['--enable-simple=value'])
126
127    def test_with_env(self):
128        for config in (
129                self.get_config(),
130                self.get_config(['--disable-with-env']),
131                self.get_config(['--enable-with-env', '--disable-with-env']),
132                self.get_config(env={'MOZ_WITH_ENV': ''}),
133                # Options win over environment
134                self.get_config(['--disable-with-env'],
135                                env={'MOZ_WITH_ENV': '1'}),
136        ):
137            self.assertIn('WITH_ENV', config)
138            self.assertEquals(NegativeOptionValue(), config['WITH_ENV'])
139
140        for config in (
141                self.get_config(['--enable-with-env']),
142                self.get_config(['--disable-with-env', '--enable-with-env']),
143                self.get_config(env={'MOZ_WITH_ENV': '1'}),
144                self.get_config(['--enable-with-env'],
145                                env={'MOZ_WITH_ENV': ''}),
146        ):
147            self.assertIn('WITH_ENV', config)
148            self.assertEquals(PositiveOptionValue(), config['WITH_ENV'])
149
150        with self.assertRaises(InvalidOptionError):
151            self.get_config(['--enable-with-env=value'])
152
153        with self.assertRaises(InvalidOptionError):
154            self.get_config(env={'MOZ_WITH_ENV': 'value'})
155
156    def test_values(self, name='VALUES'):
157        for config in (
158            self.get_config(),
159            self.get_config(['--disable-values']),
160            self.get_config(['--enable-values', '--disable-values']),
161        ):
162            self.assertIn(name, config)
163            self.assertEquals(NegativeOptionValue(), config[name])
164
165        for config in (
166            self.get_config(['--enable-values']),
167            self.get_config(['--disable-values', '--enable-values']),
168        ):
169            self.assertIn(name, config)
170            self.assertEquals(PositiveOptionValue(), config[name])
171
172        config = self.get_config(['--enable-values=foo'])
173        self.assertIn(name, config)
174        self.assertEquals(PositiveOptionValue(('foo',)), config[name])
175
176        config = self.get_config(['--enable-values=foo,bar'])
177        self.assertIn(name, config)
178        self.assertTrue(config[name])
179        self.assertEquals(PositiveOptionValue(('foo', 'bar')), config[name])
180
181    def test_values2(self):
182        self.test_values('VALUES2')
183
184    def test_values3(self):
185        self.test_values('VALUES3')
186
187    def test_returned_default(self):
188        config = self.get_config(['--enable-simple'])
189        self.assertIn('DEFAULTED', config)
190        self.assertEquals(
191            PositiveOptionValue(('simple',)), config['DEFAULTED'])
192
193        config = self.get_config(['--disable-simple'])
194        self.assertIn('DEFAULTED', config)
195        self.assertEquals(
196            PositiveOptionValue(('not-simple',)), config['DEFAULTED'])
197
198    def test_returned_choices(self):
199        for val in ('a', 'b', 'c'):
200            config = self.get_config(
201                ['--enable-values=alpha', '--returned-choices=%s' % val])
202            self.assertIn('CHOICES', config)
203            self.assertEquals(PositiveOptionValue((val,)), config['CHOICES'])
204
205        for val in ('0', '1', '2'):
206            config = self.get_config(
207                ['--enable-values=numeric', '--returned-choices=%s' % val])
208            self.assertIn('CHOICES', config)
209            self.assertEquals(PositiveOptionValue((val,)), config['CHOICES'])
210
211        with self.assertRaises(InvalidOptionError):
212            self.get_config(['--enable-values=numeric',
213                             '--returned-choices=a'])
214
215        with self.assertRaises(InvalidOptionError):
216            self.get_config(['--enable-values=alpha', '--returned-choices=0'])
217
218    def test_included(self):
219        config = self.get_config(env={'CC': 'gcc'})
220        self.assertIn('IS_GCC', config)
221        self.assertEquals(config['IS_GCC'], True)
222
223        config = self.get_config(
224            ['--enable-include=extra.configure', '--extra'])
225        self.assertIn('EXTRA', config)
226        self.assertEquals(PositiveOptionValue(), config['EXTRA'])
227
228        with self.assertRaises(InvalidOptionError):
229            self.get_config(['--extra'])
230
231    def test_template(self):
232        config = self.get_config(env={'CC': 'gcc'})
233        self.assertIn('CFLAGS', config)
234        self.assertEquals(config['CFLAGS'], ['-Werror=foobar'])
235
236        config = self.get_config(env={'CC': 'clang'})
237        self.assertNotIn('CFLAGS', config)
238
239    def test_imports(self):
240        config = {}
241        out = StringIO()
242        sandbox = ConfigureSandbox(config, {}, ['configure'], out, out)
243
244        with self.assertRaises(ImportError):
245            exec_(textwrap.dedent('''
246                @template
247                def foo():
248                    import sys
249                foo()'''),
250                sandbox
251            )
252
253        exec_(textwrap.dedent('''
254            @template
255            @imports('sys')
256            def foo():
257                return sys'''),
258            sandbox
259        )
260
261        self.assertIs(sandbox['foo'](), sys)
262
263        exec_(textwrap.dedent('''
264            @template
265            @imports(_from='os', _import='path')
266            def foo():
267                return path'''),
268            sandbox
269        )
270
271        self.assertIs(sandbox['foo'](), os.path)
272
273        exec_(textwrap.dedent('''
274            @template
275            @imports(_from='os', _import='path', _as='os_path')
276            def foo():
277                return os_path'''),
278            sandbox
279        )
280
281        self.assertIs(sandbox['foo'](), os.path)
282
283        exec_(textwrap.dedent('''
284            @template
285            @imports('__builtin__')
286            def foo():
287                return __builtin__'''),
288            sandbox
289        )
290
291        import __builtin__
292        self.assertIs(sandbox['foo'](), __builtin__)
293
294        exec_(textwrap.dedent('''
295            @template
296            @imports(_from='__builtin__', _import='open')
297            def foo():
298                return open('%s')''' % os.devnull),
299            sandbox
300        )
301
302        f = sandbox['foo']()
303        self.assertEquals(f.name, os.devnull)
304        f.close()
305
306        # This unlocks the sandbox
307        exec_(textwrap.dedent('''
308            @template
309            @imports(_import='__builtin__', _as='__builtins__')
310            def foo():
311                import sys
312                return sys'''),
313            sandbox
314        )
315
316        self.assertIs(sandbox['foo'](), sys)
317
318        exec_(textwrap.dedent('''
319            @template
320            @imports('__sandbox__')
321            def foo():
322                return __sandbox__'''),
323            sandbox
324        )
325
326        self.assertIs(sandbox['foo'](), sandbox)
327
328        exec_(textwrap.dedent('''
329            @template
330            @imports(_import='__sandbox__', _as='s')
331            def foo():
332                return s'''),
333            sandbox
334        )
335
336        self.assertIs(sandbox['foo'](), sandbox)
337
338        # Nothing leaked from the function being executed
339        self.assertEquals(sandbox.keys(), ['__builtins__', 'foo'])
340        self.assertEquals(sandbox['__builtins__'], ConfigureSandbox.BUILTINS)
341
342        exec_(textwrap.dedent('''
343            @template
344            @imports('sys')
345            def foo():
346                @depends(when=True)
347                def bar():
348                    return sys
349                return bar
350            bar = foo()'''),
351            sandbox
352        )
353
354        with self.assertRaises(NameError) as e:
355            sandbox._depends[sandbox['bar']].result()
356
357        self.assertEquals(e.exception.message,
358                          "global name 'sys' is not defined")
359
360    def test_apply_imports(self):
361        imports = []
362
363        class CountApplyImportsSandbox(ConfigureSandbox):
364            def _apply_imports(self, *args, **kwargs):
365                imports.append((args, kwargs))
366                super(CountApplyImportsSandbox, self)._apply_imports(
367                    *args, **kwargs)
368
369        config = {}
370        out = StringIO()
371        sandbox = CountApplyImportsSandbox(config, {}, ['configure'], out, out)
372
373        exec_(textwrap.dedent('''
374            @template
375            @imports('sys')
376            def foo():
377                return sys
378            foo()
379            foo()'''),
380            sandbox
381        )
382
383        self.assertEquals(len(imports), 1)
384
385    def test_os_path(self):
386        config = self.get_config(['--with-imports=%s' % __file__])
387        self.assertIn('HAS_ABSPATH', config)
388        self.assertEquals(config['HAS_ABSPATH'], True)
389        self.assertIn('HAS_GETATIME', config)
390        self.assertEquals(config['HAS_GETATIME'], True)
391        self.assertIn('HAS_GETATIME2', config)
392        self.assertEquals(config['HAS_GETATIME2'], False)
393
394    def test_template_call(self):
395        config = self.get_config(env={'CC': 'gcc'})
396        self.assertIn('TEMPLATE_VALUE', config)
397        self.assertEquals(config['TEMPLATE_VALUE'], 42)
398        self.assertIn('TEMPLATE_VALUE_2', config)
399        self.assertEquals(config['TEMPLATE_VALUE_2'], 21)
400
401    def test_template_imports(self):
402        config = self.get_config(['--enable-imports-in-template'])
403        self.assertIn('PLATFORM', config)
404        self.assertEquals(config['PLATFORM'], sys.platform)
405
406    def test_decorators(self):
407        config = {}
408        out = StringIO()
409        sandbox = ConfigureSandbox(config, {}, ['configure'], out, out)
410
411        sandbox.include_file(mozpath.join(test_data_path, 'decorators.configure'))
412
413        self.assertNotIn('FOO', sandbox)
414        self.assertNotIn('BAR', sandbox)
415        self.assertNotIn('QUX', sandbox)
416
417    def test_set_config(self):
418        def get_config(*args):
419            return self.get_config(*args, configure='set_config.configure')
420
421        help, config = get_config(['--help'])
422        self.assertEquals(config, {})
423
424        config = get_config(['--set-foo'])
425        self.assertIn('FOO', config)
426        self.assertEquals(config['FOO'], True)
427
428        config = get_config(['--set-bar'])
429        self.assertNotIn('FOO', config)
430        self.assertIn('BAR', config)
431        self.assertEquals(config['BAR'], True)
432
433        config = get_config(['--set-value=qux'])
434        self.assertIn('VALUE', config)
435        self.assertEquals(config['VALUE'], 'qux')
436
437        config = get_config(['--set-name=hoge'])
438        self.assertIn('hoge', config)
439        self.assertEquals(config['hoge'], True)
440
441        config = get_config([])
442        self.assertEquals(config, {'BAR': False})
443
444        with self.assertRaises(ConfigureError):
445            # Both --set-foo and --set-name=FOO are going to try to
446            # set_config('FOO'...)
447            get_config(['--set-foo', '--set-name=FOO'])
448
449    def test_set_config_when(self):
450        with self.moz_configure('''
451            option('--with-qux', help='qux')
452            set_config('FOO', 'foo', when=True)
453            set_config('BAR', 'bar', when=False)
454            set_config('QUX', 'qux', when='--with-qux')
455        '''):
456            config = self.get_config()
457            self.assertEquals(config, {
458                'FOO': 'foo',
459            })
460            config = self.get_config(['--with-qux'])
461            self.assertEquals(config, {
462                'FOO': 'foo',
463                'QUX': 'qux',
464            })
465
466    def test_set_define(self):
467        def get_config(*args):
468            return self.get_config(*args, configure='set_define.configure')
469
470        help, config = get_config(['--help'])
471        self.assertEquals(config, {'DEFINES': {}})
472
473        config = get_config(['--set-foo'])
474        self.assertIn('FOO', config['DEFINES'])
475        self.assertEquals(config['DEFINES']['FOO'], True)
476
477        config = get_config(['--set-bar'])
478        self.assertNotIn('FOO', config['DEFINES'])
479        self.assertIn('BAR', config['DEFINES'])
480        self.assertEquals(config['DEFINES']['BAR'], True)
481
482        config = get_config(['--set-value=qux'])
483        self.assertIn('VALUE', config['DEFINES'])
484        self.assertEquals(config['DEFINES']['VALUE'], 'qux')
485
486        config = get_config(['--set-name=hoge'])
487        self.assertIn('hoge', config['DEFINES'])
488        self.assertEquals(config['DEFINES']['hoge'], True)
489
490        config = get_config([])
491        self.assertEquals(config['DEFINES'], {'BAR': False})
492
493        with self.assertRaises(ConfigureError):
494            # Both --set-foo and --set-name=FOO are going to try to
495            # set_define('FOO'...)
496            get_config(['--set-foo', '--set-name=FOO'])
497
498    def test_set_define_when(self):
499        with self.moz_configure('''
500            option('--with-qux', help='qux')
501            set_define('FOO', 'foo', when=True)
502            set_define('BAR', 'bar', when=False)
503            set_define('QUX', 'qux', when='--with-qux')
504        '''):
505            config = self.get_config()
506            self.assertEquals(config['DEFINES'], {
507                'FOO': 'foo',
508            })
509            config = self.get_config(['--with-qux'])
510            self.assertEquals(config['DEFINES'], {
511                'FOO': 'foo',
512                'QUX': 'qux',
513            })
514
515    def test_imply_option_simple(self):
516        def get_config(*args):
517            return self.get_config(
518                *args, configure='imply_option/simple.configure')
519
520        help, config = get_config(['--help'])
521        self.assertEquals(config, {})
522
523        config = get_config([])
524        self.assertEquals(config, {})
525
526        config = get_config(['--enable-foo'])
527        self.assertIn('BAR', config)
528        self.assertEquals(config['BAR'], PositiveOptionValue())
529
530        with self.assertRaises(InvalidOptionError) as e:
531            get_config(['--enable-foo', '--disable-bar'])
532
533        self.assertEquals(
534            e.exception.message,
535            "'--enable-bar' implied by '--enable-foo' conflicts with "
536            "'--disable-bar' from the command-line")
537
538    def test_imply_option_negative(self):
539        def get_config(*args):
540            return self.get_config(
541                *args, configure='imply_option/negative.configure')
542
543        help, config = get_config(['--help'])
544        self.assertEquals(config, {})
545
546        config = get_config([])
547        self.assertEquals(config, {})
548
549        config = get_config(['--enable-foo'])
550        self.assertIn('BAR', config)
551        self.assertEquals(config['BAR'], NegativeOptionValue())
552
553        with self.assertRaises(InvalidOptionError) as e:
554            get_config(['--enable-foo', '--enable-bar'])
555
556        self.assertEquals(
557            e.exception.message,
558            "'--disable-bar' implied by '--enable-foo' conflicts with "
559            "'--enable-bar' from the command-line")
560
561        config = get_config(['--disable-hoge'])
562        self.assertIn('BAR', config)
563        self.assertEquals(config['BAR'], NegativeOptionValue())
564
565        with self.assertRaises(InvalidOptionError) as e:
566            get_config(['--disable-hoge', '--enable-bar'])
567
568        self.assertEquals(
569            e.exception.message,
570            "'--disable-bar' implied by '--disable-hoge' conflicts with "
571            "'--enable-bar' from the command-line")
572
573    def test_imply_option_values(self):
574        def get_config(*args):
575            return self.get_config(
576                *args, configure='imply_option/values.configure')
577
578        help, config = get_config(['--help'])
579        self.assertEquals(config, {})
580
581        config = get_config([])
582        self.assertEquals(config, {})
583
584        config = get_config(['--enable-foo=a'])
585        self.assertIn('BAR', config)
586        self.assertEquals(config['BAR'], PositiveOptionValue(('a',)))
587
588        config = get_config(['--enable-foo=a,b'])
589        self.assertIn('BAR', config)
590        self.assertEquals(config['BAR'], PositiveOptionValue(('a','b')))
591
592        with self.assertRaises(InvalidOptionError) as e:
593            get_config(['--enable-foo=a,b', '--disable-bar'])
594
595        self.assertEquals(
596            e.exception.message,
597            "'--enable-bar=a,b' implied by '--enable-foo' conflicts with "
598            "'--disable-bar' from the command-line")
599
600    def test_imply_option_infer(self):
601        def get_config(*args):
602            return self.get_config(
603                *args, configure='imply_option/infer.configure')
604
605        help, config = get_config(['--help'])
606        self.assertEquals(config, {})
607
608        config = get_config([])
609        self.assertEquals(config, {})
610
611        with self.assertRaises(InvalidOptionError) as e:
612            get_config(['--enable-foo', '--disable-bar'])
613
614        self.assertEquals(
615            e.exception.message,
616            "'--enable-bar' implied by '--enable-foo' conflicts with "
617            "'--disable-bar' from the command-line")
618
619        with self.assertRaises(ConfigureError) as e:
620            self.get_config([], configure='imply_option/infer_ko.configure')
621
622        self.assertEquals(
623            e.exception.message,
624            "Cannot infer what implies '--enable-bar'. Please add a `reason` "
625            "to the `imply_option` call.")
626
627    def test_imply_option_immediate_value(self):
628        def get_config(*args):
629            return self.get_config(
630                *args, configure='imply_option/imm.configure')
631
632        help, config = get_config(['--help'])
633        self.assertEquals(config, {})
634
635        config = get_config([])
636        self.assertEquals(config, {})
637
638        config_path = mozpath.abspath(
639            mozpath.join(test_data_path, 'imply_option', 'imm.configure'))
640
641        with self.assertRaisesRegexp(InvalidOptionError,
642            "--enable-foo' implied by 'imply_option at %s:7' conflicts with "
643            "'--disable-foo' from the command-line" % config_path):
644            get_config(['--disable-foo'])
645
646        with self.assertRaisesRegexp(InvalidOptionError,
647            "--enable-bar=foo,bar' implied by 'imply_option at %s:16' conflicts"
648            " with '--enable-bar=a,b,c' from the command-line" % config_path):
649            get_config(['--enable-bar=a,b,c'])
650
651        with self.assertRaisesRegexp(InvalidOptionError,
652            "--enable-baz=BAZ' implied by 'imply_option at %s:25' conflicts"
653            " with '--enable-baz=QUUX' from the command-line" % config_path):
654            get_config(['--enable-baz=QUUX'])
655
656    def test_imply_option_failures(self):
657        with self.assertRaises(ConfigureError) as e:
658            with self.moz_configure('''
659                imply_option('--with-foo', ('a',), 'bar')
660            '''):
661                self.get_config()
662
663        self.assertEquals(e.exception.message,
664                          "`--with-foo`, emitted from `%s` line 2, is unknown."
665                          % mozpath.join(test_data_path, 'moz.configure'))
666
667        with self.assertRaises(TypeError) as e:
668            with self.moz_configure('''
669                imply_option('--with-foo', 42, 'bar')
670
671                option('--with-foo', help='foo')
672                @depends('--with-foo')
673                def foo(value):
674                    return value
675            '''):
676                self.get_config()
677
678        self.assertEquals(e.exception.message,
679                          "Unexpected type: 'int'")
680
681    def test_imply_option_when(self):
682        with self.moz_configure('''
683            option('--with-foo', help='foo')
684            imply_option('--with-qux', True, when='--with-foo')
685            option('--with-qux', help='qux')
686            set_config('QUX', depends('--with-qux')(lambda x: x))
687        '''):
688            config = self.get_config()
689            self.assertEquals(config, {
690                'QUX': NegativeOptionValue(),
691            })
692
693            config = self.get_config(['--with-foo'])
694            self.assertEquals(config, {
695                'QUX': PositiveOptionValue(),
696            })
697
698    def test_option_failures(self):
699        with self.assertRaises(ConfigureError) as e:
700            with self.moz_configure('option("--with-foo", help="foo")'):
701                self.get_config()
702
703        self.assertEquals(
704            e.exception.message,
705            'Option `--with-foo` is not handled ; reference it with a @depends'
706        )
707
708        with self.assertRaises(ConfigureError) as e:
709            with self.moz_configure('''
710                option("--with-foo", help="foo")
711                option("--with-foo", help="foo")
712            '''):
713                self.get_config()
714
715        self.assertEquals(
716            e.exception.message,
717            'Option `--with-foo` already defined'
718        )
719
720        with self.assertRaises(ConfigureError) as e:
721            with self.moz_configure('''
722                option(env="MOZ_FOO", help="foo")
723                option(env="MOZ_FOO", help="foo")
724            '''):
725                self.get_config()
726
727        self.assertEquals(
728            e.exception.message,
729            'Option `MOZ_FOO` already defined'
730        )
731
732        with self.assertRaises(ConfigureError) as e:
733            with self.moz_configure('''
734                option('--with-foo', env="MOZ_FOO", help="foo")
735                option(env="MOZ_FOO", help="foo")
736            '''):
737                self.get_config()
738
739        self.assertEquals(
740            e.exception.message,
741            'Option `MOZ_FOO` already defined'
742        )
743
744        with self.assertRaises(ConfigureError) as e:
745            with self.moz_configure('''
746                option(env="MOZ_FOO", help="foo")
747                option('--with-foo', env="MOZ_FOO", help="foo")
748            '''):
749                self.get_config()
750
751        self.assertEquals(
752            e.exception.message,
753            'Option `MOZ_FOO` already defined'
754        )
755
756        with self.assertRaises(ConfigureError) as e:
757            with self.moz_configure('''
758                option('--with-foo', env="MOZ_FOO", help="foo")
759                option('--with-foo', help="foo")
760            '''):
761                self.get_config()
762
763        self.assertEquals(
764            e.exception.message,
765            'Option `--with-foo` already defined'
766        )
767
768    def test_option_when(self):
769        with self.moz_configure('''
770            option('--with-foo', help='foo', when=True)
771            option('--with-bar', help='bar', when=False)
772            option('--with-qux', env="QUX", help='qux', when='--with-foo')
773
774            set_config('FOO', depends('--with-foo', when=True)(lambda x: x))
775            set_config('BAR', depends('--with-bar', when=False)(lambda x: x))
776            set_config('QUX', depends('--with-qux', when='--with-foo')(lambda x: x))
777        '''):
778            config = self.get_config()
779            self.assertEquals(config, {
780                'FOO': NegativeOptionValue(),
781            })
782
783            config = self.get_config(['--with-foo'])
784            self.assertEquals(config, {
785                'FOO': PositiveOptionValue(),
786                'QUX': NegativeOptionValue(),
787            })
788
789            config = self.get_config(['--with-foo', '--with-qux'])
790            self.assertEquals(config, {
791                'FOO': PositiveOptionValue(),
792                'QUX': PositiveOptionValue(),
793            })
794
795            with self.assertRaises(InvalidOptionError) as e:
796                self.get_config(['--with-bar'])
797
798            self.assertEquals(
799                e.exception.message,
800                '--with-bar is not available in this configuration'
801            )
802
803            with self.assertRaises(InvalidOptionError) as e:
804                self.get_config(['--with-qux'])
805
806            self.assertEquals(
807                e.exception.message,
808                '--with-qux is not available in this configuration'
809            )
810
811            with self.assertRaises(InvalidOptionError) as e:
812                self.get_config(['QUX=1'])
813
814            self.assertEquals(
815                e.exception.message,
816                'QUX is not available in this configuration'
817            )
818
819            config = self.get_config(env={'QUX': '1'})
820            self.assertEquals(config, {
821                'FOO': NegativeOptionValue(),
822            })
823
824            help, config = self.get_config(['--help'])
825            self.assertEquals(help, textwrap.dedent('''\
826                Usage: configure [options]
827
828                Options: [defaults in brackets after descriptions]
829                  --help                    print this message
830                  --with-foo                foo
831
832                Environment variables:
833            '''))
834
835            help, config = self.get_config(['--help', '--with-foo'])
836            self.assertEquals(help, textwrap.dedent('''\
837                Usage: configure [options]
838
839                Options: [defaults in brackets after descriptions]
840                  --help                    print this message
841                  --with-foo                foo
842                  --with-qux                qux
843
844                Environment variables:
845            '''))
846
847        with self.moz_configure('''
848            option('--with-foo', help='foo', when=True)
849            set_config('FOO', depends('--with-foo')(lambda x: x))
850        '''):
851            with self.assertRaises(ConfigureError) as e:
852                self.get_config()
853
854            self.assertEquals(e.exception.message,
855                              '@depends function needs the same `when` as '
856                              'options it depends on')
857
858        with self.moz_configure('''
859            @depends(when=True)
860            def always():
861                return True
862            @depends(when=True)
863            def always2():
864                return True
865            option('--with-foo', help='foo', when=always)
866            set_config('FOO', depends('--with-foo', when=always2)(lambda x: x))
867        '''):
868            with self.assertRaises(ConfigureError) as e:
869                self.get_config()
870
871            self.assertEquals(e.exception.message,
872                              '@depends function needs the same `when` as '
873                              'options it depends on')
874
875        with self.moz_configure('''
876            @depends(when=True)
877            def always():
878                return True
879            @depends(when=True)
880            def always2():
881                return True
882            with only_when(always2):
883                option('--with-foo', help='foo', when=always)
884                # include() triggers resolution of its dependencies, and their
885                # side effects.
886                include(depends('--with-foo', when=always)(lambda x: x))
887                # The sandbox should figure that the `when` here is
888                # appropriate. Bad behavior in CombinedDependsFunction.__eq__
889                # made this fail in the past.
890                set_config('FOO', depends('--with-foo', when=always)(lambda x: x))
891        '''):
892            self.get_config()
893
894    def test_include_failures(self):
895        with self.assertRaises(ConfigureError) as e:
896            with self.moz_configure('include("../foo.configure")'):
897                self.get_config()
898
899        self.assertEquals(
900            e.exception.message,
901            'Cannot include `%s` because it is not in a subdirectory of `%s`'
902            % (mozpath.normpath(mozpath.join(test_data_path, '..',
903                                             'foo.configure')),
904               mozpath.normsep(test_data_path))
905        )
906
907        with self.assertRaises(ConfigureError) as e:
908            with self.moz_configure('''
909                include('extra.configure')
910                include('extra.configure')
911            '''):
912                self.get_config()
913
914        self.assertEquals(
915            e.exception.message,
916            'Cannot include `%s` because it was included already.'
917            % mozpath.normpath(mozpath.join(test_data_path,
918                                            'extra.configure'))
919        )
920
921        with self.assertRaises(TypeError) as e:
922            with self.moz_configure('''
923                include(42)
924            '''):
925                self.get_config()
926
927        self.assertEquals(e.exception.message, "Unexpected type: 'int'")
928
929    def test_include_when(self):
930        with MockedOpen({
931            os.path.join(test_data_path, 'moz.configure'): textwrap.dedent('''
932                option('--with-foo', help='foo')
933
934                include('always.configure', when=True)
935                include('never.configure', when=False)
936                include('foo.configure', when='--with-foo')
937
938                set_config('FOO', foo)
939                set_config('BAR', bar)
940                set_config('QUX', qux)
941            '''),
942            os.path.join(test_data_path, 'always.configure'): textwrap.dedent('''
943                option('--with-bar', help='bar')
944                @depends('--with-bar')
945                def bar(x):
946                    if x:
947                        return 'bar'
948            '''),
949            os.path.join(test_data_path, 'never.configure'): textwrap.dedent('''
950                option('--with-qux', help='qux')
951                @depends('--with-qux')
952                def qux(x):
953                    if x:
954                        return 'qux'
955            '''),
956            os.path.join(test_data_path, 'foo.configure'): textwrap.dedent('''
957                option('--with-foo-really', help='really foo')
958                @depends('--with-foo-really')
959                def foo(x):
960                    if x:
961                        return 'foo'
962
963                include('foo2.configure', when='--with-foo-really')
964            '''),
965            os.path.join(test_data_path, 'foo2.configure'): textwrap.dedent('''
966                set_config('FOO2', True)
967            '''),
968        }):
969            config = self.get_config()
970            self.assertEquals(config, {})
971
972            config = self.get_config(['--with-foo'])
973            self.assertEquals(config, {})
974
975            config = self.get_config(['--with-bar'])
976            self.assertEquals(config, {
977                'BAR': 'bar',
978            })
979
980            with self.assertRaises(InvalidOptionError) as e:
981                self.get_config(['--with-qux'])
982
983            self.assertEquals(
984                e.exception.message,
985                '--with-qux is not available in this configuration'
986            )
987
988            config = self.get_config(['--with-foo', '--with-foo-really'])
989            self.assertEquals(config, {
990                'FOO': 'foo',
991                'FOO2': True,
992            })
993
994    def test_sandbox_failures(self):
995        with self.assertRaises(KeyError) as e:
996            with self.moz_configure('''
997                include = 42
998            '''):
999                self.get_config()
1000
1001        self.assertEquals(e.exception.message, 'Cannot reassign builtins')
1002
1003        with self.assertRaises(KeyError) as e:
1004            with self.moz_configure('''
1005                foo = 42
1006            '''):
1007                self.get_config()
1008
1009        self.assertEquals(e.exception.message,
1010                          'Cannot assign `foo` because it is neither a '
1011                          '@depends nor a @template')
1012
1013    def test_depends_failures(self):
1014        with self.assertRaises(ConfigureError) as e:
1015            with self.moz_configure('''
1016                @depends()
1017                def foo():
1018                    return
1019            '''):
1020                self.get_config()
1021
1022        self.assertEquals(e.exception.message,
1023                          "@depends needs at least one argument")
1024
1025        with self.assertRaises(ConfigureError) as e:
1026            with self.moz_configure('''
1027                @depends('--with-foo')
1028                def foo(value):
1029                    return value
1030            '''):
1031                self.get_config()
1032
1033        self.assertEquals(e.exception.message,
1034                          "'--with-foo' is not a known option. Maybe it's "
1035                          "declared too late?")
1036
1037        with self.assertRaises(ConfigureError) as e:
1038            with self.moz_configure('''
1039                @depends('--with-foo=42')
1040                def foo(value):
1041                    return value
1042            '''):
1043                self.get_config()
1044
1045        self.assertEquals(e.exception.message,
1046                          "Option must not contain an '='")
1047
1048        with self.assertRaises(TypeError) as e:
1049            with self.moz_configure('''
1050                @depends(42)
1051                def foo(value):
1052                    return value
1053            '''):
1054                self.get_config()
1055
1056        self.assertEquals(e.exception.message,
1057                          "Cannot use object of type 'int' as argument "
1058                          "to @depends")
1059
1060        with self.assertRaises(ConfigureError) as e:
1061            with self.moz_configure('''
1062                @depends('--help')
1063                def foo(value):
1064                    yield
1065            '''):
1066                self.get_config()
1067
1068        self.assertEquals(e.exception.message,
1069                          "Cannot decorate generator functions with @depends")
1070
1071        with self.assertRaises(TypeError) as e:
1072            with self.moz_configure('''
1073                depends('--help')(42)
1074            '''):
1075                self.get_config()
1076
1077        self.assertEquals(e.exception.message,
1078                          "Unexpected type: 'int'")
1079
1080        with self.assertRaises(ConfigureError) as e:
1081            with self.moz_configure('''
1082                option('--foo', help='foo')
1083                @depends('--foo')
1084                def foo(value):
1085                    return value
1086
1087                foo()
1088            '''):
1089                self.get_config()
1090
1091        self.assertEquals(e.exception.message,
1092                          "The `foo` function may not be called")
1093
1094        with self.assertRaises(TypeError) as e:
1095            with self.moz_configure('''
1096                @depends('--help', foo=42)
1097                def foo(_):
1098                    return
1099            '''):
1100                self.get_config()
1101
1102        self.assertEquals(e.exception.message,
1103                          "depends_impl() got an unexpected keyword argument 'foo'")
1104
1105    def test_depends_when(self):
1106        with self.moz_configure('''
1107            @depends(when=True)
1108            def foo():
1109                return 'foo'
1110
1111            set_config('FOO', foo)
1112
1113            @depends(when=False)
1114            def bar():
1115                return 'bar'
1116
1117            set_config('BAR', bar)
1118
1119            option('--with-qux', help='qux')
1120            @depends(when='--with-qux')
1121            def qux():
1122                return 'qux'
1123
1124            set_config('QUX', qux)
1125        '''):
1126            config = self.get_config()
1127            self.assertEquals(config, {
1128                'FOO': 'foo',
1129            })
1130
1131            config = self.get_config(['--with-qux'])
1132            self.assertEquals(config, {
1133                'FOO': 'foo',
1134                'QUX': 'qux',
1135            })
1136
1137    def test_imports_failures(self):
1138        with self.assertRaises(ConfigureError) as e:
1139            with self.moz_configure('''
1140                @imports('os')
1141                @template
1142                def foo(value):
1143                    return value
1144            '''):
1145                self.get_config()
1146
1147        self.assertEquals(e.exception.message,
1148                          '@imports must appear after @template')
1149
1150        with self.assertRaises(ConfigureError) as e:
1151            with self.moz_configure('''
1152                option('--foo', help='foo')
1153                @imports('os')
1154                @depends('--foo')
1155                def foo(value):
1156                    return value
1157            '''):
1158                self.get_config()
1159
1160        self.assertEquals(e.exception.message,
1161                          '@imports must appear after @depends')
1162
1163        for import_ in (
1164            "42",
1165            "_from=42, _import='os'",
1166            "_from='os', _import='path', _as=42",
1167        ):
1168            with self.assertRaises(TypeError) as e:
1169                with self.moz_configure('''
1170                    @imports(%s)
1171                    @template
1172                    def foo(value):
1173                        return value
1174                ''' % import_):
1175                    self.get_config()
1176
1177            self.assertEquals(e.exception.message, "Unexpected type: 'int'")
1178
1179        with self.assertRaises(TypeError) as e:
1180            with self.moz_configure('''
1181                @imports('os', 42)
1182                @template
1183                def foo(value):
1184                    return value
1185            '''):
1186                self.get_config()
1187
1188        self.assertEquals(e.exception.message, "Unexpected type: 'int'")
1189
1190        with self.assertRaises(ValueError) as e:
1191            with self.moz_configure('''
1192                @imports('os*')
1193                def foo(value):
1194                    return value
1195            '''):
1196                self.get_config()
1197
1198        self.assertEquals(e.exception.message,
1199                          "Invalid argument to @imports: 'os*'")
1200
1201    def test_only_when(self):
1202        moz_configure = '''
1203            option('--enable-when', help='when')
1204            @depends('--enable-when', '--help')
1205            def when(value, _):
1206                return bool(value)
1207
1208            with only_when(when):
1209                option('--foo', nargs='*', help='foo')
1210                @depends('--foo')
1211                def foo(value):
1212                    return value
1213
1214                set_config('FOO', foo)
1215                set_define('FOO', foo)
1216
1217            # It is possible to depend on a function defined in a only_when
1218            # block. It then resolves to `None`.
1219            set_config('BAR', depends(foo)(lambda x: x))
1220            set_define('BAR', depends(foo)(lambda x: x))
1221        '''
1222
1223        with self.moz_configure(moz_configure):
1224            config = self.get_config()
1225            self.assertEqual(config, {
1226                'DEFINES': {},
1227            })
1228
1229            config = self.get_config(['--enable-when'])
1230            self.assertEqual(config, {
1231                'BAR': NegativeOptionValue(),
1232                'FOO': NegativeOptionValue(),
1233                'DEFINES': {
1234                    'BAR': NegativeOptionValue(),
1235                    'FOO': NegativeOptionValue(),
1236                },
1237            })
1238
1239            config = self.get_config(['--enable-when', '--foo=bar'])
1240            self.assertEqual(config, {
1241                'BAR': PositiveOptionValue(['bar']),
1242                'FOO': PositiveOptionValue(['bar']),
1243                'DEFINES': {
1244                    'BAR': PositiveOptionValue(['bar']),
1245                    'FOO': PositiveOptionValue(['bar']),
1246                },
1247            })
1248
1249            # The --foo option doesn't exist when --enable-when is not given.
1250            with self.assertRaises(InvalidOptionError) as e:
1251                self.get_config(['--foo'])
1252
1253            self.assertEquals(e.exception.message,
1254                              '--foo is not available in this configuration')
1255
1256        # Cannot depend on an option defined in a only_when block, because we
1257        # don't know what OptionValue would make sense.
1258        with self.moz_configure(moz_configure + '''
1259            set_config('QUX', depends('--foo')(lambda x: x))
1260        '''):
1261            with self.assertRaises(ConfigureError) as e:
1262                self.get_config()
1263
1264            self.assertEquals(e.exception.message,
1265                              '@depends function needs the same `when` as '
1266                              'options it depends on')
1267
1268        with self.moz_configure(moz_configure + '''
1269            set_config('QUX', depends('--foo', when=when)(lambda x: x))
1270        '''):
1271            self.get_config(['--enable-when'])
1272
1273        # Using imply_option for an option defined in a only_when block fails
1274        # similarly if the imply_option happens outside the block.
1275        with self.moz_configure('''
1276            imply_option('--foo', True)
1277        ''' + moz_configure):
1278            with self.assertRaises(InvalidOptionError) as e:
1279                self.get_config()
1280
1281            self.assertEquals(e.exception.message,
1282                              '--foo is not available in this configuration')
1283
1284        # And similarly doesn't fail when the condition is true.
1285        with self.moz_configure('''
1286            imply_option('--foo', True)
1287        ''' + moz_configure):
1288            self.get_config(['--enable-when'])
1289
1290    def test_depends_binary_ops(self):
1291        with self.moz_configure('''
1292            option('--foo', nargs=1, help='foo')
1293            @depends('--foo')
1294            def foo(value):
1295                return value or 0
1296
1297            option('--bar', nargs=1, help='bar')
1298            @depends('--bar')
1299            def bar(value):
1300                return value or ''
1301
1302            option('--baz', nargs=1, help='baz')
1303            @depends('--baz')
1304            def baz(value):
1305                return value
1306
1307            set_config('FOOorBAR', foo | bar)
1308            set_config('FOOorBARorBAZ', foo | bar | baz)
1309            set_config('FOOandBAR', foo & bar)
1310            set_config('FOOandBARandBAZ', foo & bar & baz)
1311        '''):
1312            for foo_opt, foo_value in (
1313                ('',  0),
1314                ('--foo=foo', PositiveOptionValue(('foo',)))
1315            ):
1316                for bar_opt, bar_value in (
1317                    ('', ''),
1318                    ('--bar=bar', PositiveOptionValue(('bar',)))
1319                ):
1320                    for baz_opt, baz_value in (
1321                        ('', NegativeOptionValue()),
1322                        ('--baz=baz', PositiveOptionValue(('baz',)))
1323                    ):
1324                        config = self.get_config(
1325                            [x for x in (foo_opt, bar_opt, baz_opt) if x])
1326                        self.assertEqual(config, {
1327                            'FOOorBAR': foo_value or bar_value,
1328                            'FOOorBARorBAZ': foo_value or bar_value or baz_value,
1329                            'FOOandBAR': foo_value and bar_value,
1330                            'FOOandBARandBAZ': foo_value and bar_value and baz_value,
1331                        })
1332
1333    def test_depends_getattr(self):
1334        with self.moz_configure('''
1335            @imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
1336            def namespace(**kwargs):
1337                return ReadOnlyNamespace(**kwargs)
1338
1339            option('--foo', nargs=1, help='foo')
1340            @depends('--foo')
1341            def foo(value):
1342                return value
1343
1344            option('--bar', nargs=1, help='bar')
1345            @depends('--bar')
1346            def bar(value):
1347                return value or None
1348
1349            @depends(foo, bar)
1350            def foobar(foo, bar):
1351                return namespace(foo=foo, bar=bar)
1352
1353            set_config('FOO', foobar.foo)
1354            set_config('BAR', foobar.bar)
1355            set_config('BAZ', foobar.baz)
1356        '''):
1357            config = self.get_config()
1358            self.assertEqual(config, {
1359                'FOO': NegativeOptionValue(),
1360            })
1361
1362            config = self.get_config(['--foo=foo'])
1363            self.assertEqual(config, {
1364                'FOO': PositiveOptionValue(('foo',)),
1365            })
1366
1367            config = self.get_config(['--bar=bar'])
1368            self.assertEqual(config, {
1369                'FOO': NegativeOptionValue(),
1370                'BAR': PositiveOptionValue(('bar',)),
1371            })
1372
1373            config = self.get_config(['--foo=foo', '--bar=bar'])
1374            self.assertEqual(config, {
1375                'FOO': PositiveOptionValue(('foo',)),
1376                'BAR': PositiveOptionValue(('bar',)),
1377            })
1378
1379
1380if __name__ == '__main__':
1381    main()
1382