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 import (
19    ConfigureError,
20    ConfigureSandbox,
21)
22from mozbuild.util import exec_
23from mozpack import path as mozpath
24
25from buildconfig import topsrcdir
26from common import (
27    ConfigureTestSandbox,
28    ensure_exe_extension,
29    fake_short_path,
30)
31
32
33class TestChecksConfigure(unittest.TestCase):
34    def test_checking(self):
35        out = StringIO()
36        sandbox = ConfigureSandbox({}, stdout=out, stderr=out)
37        base_dir = os.path.join(topsrcdir, 'build', 'moz.configure')
38        sandbox.include_file(os.path.join(base_dir, 'checks.configure'))
39
40        exec_(textwrap.dedent('''
41            @checking('for a thing')
42            def foo(value):
43                return value
44        '''), sandbox)
45
46        foo = sandbox['foo']
47
48        foo(True)
49        self.assertEqual(out.getvalue(), 'checking for a thing... yes\n')
50
51        out.truncate(0)
52        foo(False)
53        self.assertEqual(out.getvalue(), 'checking for a thing... no\n')
54
55        out.truncate(0)
56        foo(42)
57        self.assertEqual(out.getvalue(), 'checking for a thing... 42\n')
58
59        out.truncate(0)
60        foo('foo')
61        self.assertEqual(out.getvalue(), 'checking for a thing... foo\n')
62
63        out.truncate(0)
64        data = ['foo', 'bar']
65        foo(data)
66        self.assertEqual(out.getvalue(), 'checking for a thing... %r\n' % data)
67
68        # When the function given to checking does nothing interesting, the
69        # behavior is not altered
70        exec_(textwrap.dedent('''
71            @checking('for a thing', lambda x: x)
72            def foo(value):
73                return value
74        '''), sandbox)
75
76        foo = sandbox['foo']
77
78        out.truncate(0)
79        foo(True)
80        self.assertEqual(out.getvalue(), 'checking for a thing... yes\n')
81
82        out.truncate(0)
83        foo(False)
84        self.assertEqual(out.getvalue(), 'checking for a thing... no\n')
85
86        out.truncate(0)
87        foo(42)
88        self.assertEqual(out.getvalue(), 'checking for a thing... 42\n')
89
90        out.truncate(0)
91        foo('foo')
92        self.assertEqual(out.getvalue(), 'checking for a thing... foo\n')
93
94        out.truncate(0)
95        data = ['foo', 'bar']
96        foo(data)
97        self.assertEqual(out.getvalue(), 'checking for a thing... %r\n' % data)
98
99        exec_(textwrap.dedent('''
100            def munge(x):
101                if not x:
102                    return 'not found'
103                if isinstance(x, (str, bool, int)):
104                    return x
105                return ' '.join(x)
106
107            @checking('for a thing', munge)
108            def foo(value):
109                return value
110        '''), sandbox)
111
112        foo = sandbox['foo']
113
114        out.truncate(0)
115        foo(True)
116        self.assertEqual(out.getvalue(), 'checking for a thing... yes\n')
117
118        out.truncate(0)
119        foo(False)
120        self.assertEqual(out.getvalue(), 'checking for a thing... not found\n')
121
122        out.truncate(0)
123        foo(42)
124        self.assertEqual(out.getvalue(), 'checking for a thing... 42\n')
125
126        out.truncate(0)
127        foo('foo')
128        self.assertEqual(out.getvalue(), 'checking for a thing... foo\n')
129
130        out.truncate(0)
131        foo(['foo', 'bar'])
132        self.assertEqual(out.getvalue(), 'checking for a thing... foo bar\n')
133
134    KNOWN_A = ensure_exe_extension(mozpath.abspath('/usr/bin/known-a'))
135    KNOWN_B = ensure_exe_extension(mozpath.abspath('/usr/local/bin/known-b'))
136    KNOWN_C = ensure_exe_extension(mozpath.abspath('/home/user/bin/known c'))
137    OTHER_A = ensure_exe_extension(mozpath.abspath('/lib/other/known-a'))
138
139    def get_result(self, command='', args=[], environ={},
140                   prog='/bin/configure', extra_paths=None,
141                   includes=('util.configure', 'checks.configure')):
142        config = {}
143        out = StringIO()
144        paths = {
145            self.KNOWN_A: None,
146            self.KNOWN_B: None,
147            self.KNOWN_C: None,
148        }
149        if extra_paths:
150            paths.update(extra_paths)
151        environ = dict(environ)
152        if 'PATH' not in environ:
153            environ['PATH'] = os.pathsep.join(os.path.dirname(p) for p in paths)
154        paths[self.OTHER_A] = None
155        sandbox = ConfigureTestSandbox(paths, config, environ, [prog] + args,
156                                       out, out)
157        base_dir = os.path.join(topsrcdir, 'build', 'moz.configure')
158        for f in includes:
159            sandbox.include_file(os.path.join(base_dir, f))
160
161        status = 0
162        try:
163            exec_(command, sandbox)
164            sandbox.run()
165        except SystemExit as e:
166            status = e.code
167
168        return config, out.getvalue(), status
169
170    def test_check_prog(self):
171        config, out, status = self.get_result(
172            'check_prog("FOO", ("known-a",))')
173        self.assertEqual(status, 0)
174        self.assertEqual(config, {'FOO': self.KNOWN_A})
175        self.assertEqual(out, 'checking for foo... %s\n' % self.KNOWN_A)
176
177        config, out, status = self.get_result(
178            'check_prog("FOO", ("unknown", "known-b", "known c"))')
179        self.assertEqual(status, 0)
180        self.assertEqual(config, {'FOO': self.KNOWN_B})
181        self.assertEqual(out, 'checking for foo... %s\n' % self.KNOWN_B)
182
183        config, out, status = self.get_result(
184            'check_prog("FOO", ("unknown", "unknown-2", "known c"))')
185        self.assertEqual(status, 0)
186        self.assertEqual(config, {'FOO': fake_short_path(self.KNOWN_C)})
187        self.assertEqual(out, "checking for foo... '%s'\n"
188                              % fake_short_path(self.KNOWN_C))
189
190        config, out, status = self.get_result(
191            'check_prog("FOO", ("unknown",))')
192        self.assertEqual(status, 1)
193        self.assertEqual(config, {})
194        self.assertEqual(out, textwrap.dedent('''\
195            checking for foo... not found
196            DEBUG: foo: Trying unknown
197            ERROR: Cannot find foo
198        '''))
199
200        config, out, status = self.get_result(
201            'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"))')
202        self.assertEqual(status, 1)
203        self.assertEqual(config, {})
204        self.assertEqual(out, textwrap.dedent('''\
205            checking for foo... not found
206            DEBUG: foo: Trying unknown
207            DEBUG: foo: Trying unknown-2
208            DEBUG: foo: Trying 'unknown 3'
209            ERROR: Cannot find foo
210        '''))
211
212        config, out, status = self.get_result(
213            'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"), '
214            'allow_missing=True)')
215        self.assertEqual(status, 0)
216        self.assertEqual(config, {'FOO': ':'})
217        self.assertEqual(out, 'checking for foo... not found\n')
218
219    @unittest.skipIf(not sys.platform.startswith('win'), 'Windows-only test')
220    def test_check_prog_exe(self):
221        config, out, status = self.get_result(
222            'check_prog("FOO", ("unknown", "known-b", "known c"))',
223            ['FOO=known-a.exe'])
224        self.assertEqual(status, 0)
225        self.assertEqual(config, {'FOO': self.KNOWN_A})
226        self.assertEqual(out, 'checking for foo... %s\n' % self.KNOWN_A)
227
228        config, out, status = self.get_result(
229            'check_prog("FOO", ("unknown", "known-b", "known c"))',
230            ['FOO=%s' % os.path.splitext(self.KNOWN_A)[0]])
231        self.assertEqual(status, 0)
232        self.assertEqual(config, {'FOO': self.KNOWN_A})
233        self.assertEqual(out, 'checking for foo... %s\n' % self.KNOWN_A)
234
235
236    def test_check_prog_with_args(self):
237        config, out, status = self.get_result(
238            'check_prog("FOO", ("unknown", "known-b", "known c"))',
239            ['FOO=known-a'])
240        self.assertEqual(status, 0)
241        self.assertEqual(config, {'FOO': self.KNOWN_A})
242        self.assertEqual(out, 'checking for foo... %s\n' % self.KNOWN_A)
243
244        config, out, status = self.get_result(
245            'check_prog("FOO", ("unknown", "known-b", "known c"))',
246            ['FOO=%s' % self.KNOWN_A])
247        self.assertEqual(status, 0)
248        self.assertEqual(config, {'FOO': self.KNOWN_A})
249        self.assertEqual(out, 'checking for foo... %s\n' % self.KNOWN_A)
250
251        path = self.KNOWN_B.replace('known-b', 'known-a')
252        config, out, status = self.get_result(
253            'check_prog("FOO", ("unknown", "known-b", "known c"))',
254            ['FOO=%s' % path])
255        self.assertEqual(status, 1)
256        self.assertEqual(config, {})
257        self.assertEqual(out, textwrap.dedent('''\
258            checking for foo... not found
259            DEBUG: foo: Trying %s
260            ERROR: Cannot find foo
261        ''') % path)
262
263        config, out, status = self.get_result(
264            'check_prog("FOO", ("unknown",))',
265            ['FOO=known c'])
266        self.assertEqual(status, 0)
267        self.assertEqual(config, {'FOO': fake_short_path(self.KNOWN_C)})
268        self.assertEqual(out, "checking for foo... '%s'\n"
269                              % fake_short_path(self.KNOWN_C))
270
271        config, out, status = self.get_result(
272            'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"), '
273            'allow_missing=True)', ['FOO=unknown'])
274        self.assertEqual(status, 1)
275        self.assertEqual(config, {})
276        self.assertEqual(out, textwrap.dedent('''\
277            checking for foo... not found
278            DEBUG: foo: Trying unknown
279            ERROR: Cannot find foo
280        '''))
281
282    def test_check_prog_what(self):
283        config, out, status = self.get_result(
284            'check_prog("CC", ("known-a",), what="the target C compiler")')
285        self.assertEqual(status, 0)
286        self.assertEqual(config, {'CC': self.KNOWN_A})
287        self.assertEqual(
288            out, 'checking for the target C compiler... %s\n' % self.KNOWN_A)
289
290        config, out, status = self.get_result(
291            'check_prog("CC", ("unknown", "unknown-2", "unknown 3"),'
292            '           what="the target C compiler")')
293        self.assertEqual(status, 1)
294        self.assertEqual(config, {})
295        self.assertEqual(out, textwrap.dedent('''\
296            checking for the target C compiler... not found
297            DEBUG: cc: Trying unknown
298            DEBUG: cc: Trying unknown-2
299            DEBUG: cc: Trying 'unknown 3'
300            ERROR: Cannot find the target C compiler
301        '''))
302
303    def test_check_prog_input(self):
304        config, out, status = self.get_result(textwrap.dedent('''
305            option("--with-ccache", nargs=1, help="ccache")
306            check_prog("CCACHE", ("known-a",), input="--with-ccache")
307        '''), ['--with-ccache=known-b'])
308        self.assertEqual(status, 0)
309        self.assertEqual(config, {'CCACHE': self.KNOWN_B})
310        self.assertEqual(out, 'checking for ccache... %s\n' % self.KNOWN_B)
311
312        script = textwrap.dedent('''
313            option(env="CC", nargs=1, help="compiler")
314            @depends("CC")
315            def compiler(value):
316                return value[0].split()[0] if value else None
317            check_prog("CC", ("known-a",), input=compiler)
318        ''')
319        config, out, status = self.get_result(script)
320        self.assertEqual(status, 0)
321        self.assertEqual(config, {'CC': self.KNOWN_A})
322        self.assertEqual(out, 'checking for cc... %s\n' % self.KNOWN_A)
323
324        config, out, status = self.get_result(script, ['CC=known-b'])
325        self.assertEqual(status, 0)
326        self.assertEqual(config, {'CC': self.KNOWN_B})
327        self.assertEqual(out, 'checking for cc... %s\n' % self.KNOWN_B)
328
329        config, out, status = self.get_result(script, ['CC=known-b -m32'])
330        self.assertEqual(status, 0)
331        self.assertEqual(config, {'CC': self.KNOWN_B})
332        self.assertEqual(out, 'checking for cc... %s\n' % self.KNOWN_B)
333
334    def test_check_prog_progs(self):
335        config, out, status = self.get_result(
336            'check_prog("FOO", ())')
337        self.assertEqual(status, 0)
338        self.assertEqual(config, {})
339        self.assertEqual(out, '')
340
341        config, out, status = self.get_result(
342            'check_prog("FOO", ())', ['FOO=known-a'])
343        self.assertEqual(status, 0)
344        self.assertEqual(config, {'FOO': self.KNOWN_A})
345        self.assertEqual(out, 'checking for foo... %s\n' % self.KNOWN_A)
346
347        script = textwrap.dedent('''
348            option(env="TARGET", nargs=1, default="linux", help="target")
349            @depends("TARGET")
350            def compiler(value):
351                if value:
352                    if value[0] == "linux":
353                        return ("gcc", "clang")
354                    if value[0] == "winnt":
355                        return ("cl", "clang-cl")
356            check_prog("CC", compiler)
357        ''')
358        config, out, status = self.get_result(script)
359        self.assertEqual(status, 1)
360        self.assertEqual(config, {})
361        self.assertEqual(out, textwrap.dedent('''\
362            checking for cc... not found
363            DEBUG: cc: Trying gcc
364            DEBUG: cc: Trying clang
365            ERROR: Cannot find cc
366        '''))
367
368        config, out, status = self.get_result(script, ['TARGET=linux'])
369        self.assertEqual(status, 1)
370        self.assertEqual(config, {})
371        self.assertEqual(out, textwrap.dedent('''\
372            checking for cc... not found
373            DEBUG: cc: Trying gcc
374            DEBUG: cc: Trying clang
375            ERROR: Cannot find cc
376        '''))
377
378        config, out, status = self.get_result(script, ['TARGET=winnt'])
379        self.assertEqual(status, 1)
380        self.assertEqual(config, {})
381        self.assertEqual(out, textwrap.dedent('''\
382            checking for cc... not found
383            DEBUG: cc: Trying cl
384            DEBUG: cc: Trying clang-cl
385            ERROR: Cannot find cc
386        '''))
387
388        config, out, status = self.get_result(script, ['TARGET=none'])
389        self.assertEqual(status, 0)
390        self.assertEqual(config, {})
391        self.assertEqual(out, '')
392
393        config, out, status = self.get_result(script, ['TARGET=winnt',
394                                                       'CC=known-a'])
395        self.assertEqual(status, 0)
396        self.assertEqual(config, {'CC': self.KNOWN_A})
397        self.assertEqual(out, 'checking for cc... %s\n' % self.KNOWN_A)
398
399        config, out, status = self.get_result(script, ['TARGET=none',
400                                                       'CC=known-a'])
401        self.assertEqual(status, 0)
402        self.assertEqual(config, {'CC': self.KNOWN_A})
403        self.assertEqual(out, 'checking for cc... %s\n' % self.KNOWN_A)
404
405    def test_check_prog_configure_error(self):
406        with self.assertRaises(ConfigureError) as e:
407            self.get_result('check_prog("FOO", "foo")')
408
409        self.assertEqual(e.exception.message,
410                         'progs must resolve to a list or tuple!')
411
412        with self.assertRaises(ConfigureError) as e:
413            self.get_result(
414                'foo = depends(when=True)(lambda: ("a", "b"))\n'
415                'check_prog("FOO", ("known-a",), input=foo)'
416            )
417
418        self.assertEqual(e.exception.message,
419                         'input must resolve to a tuple or a list with a '
420                         'single element, or a string')
421
422        with self.assertRaises(ConfigureError) as e:
423            self.get_result(
424                'foo = depends(when=True)(lambda: {"a": "b"})\n'
425                'check_prog("FOO", ("known-a",), input=foo)'
426            )
427
428        self.assertEqual(e.exception.message,
429                         'input must resolve to a tuple or a list with a '
430                         'single element, or a string')
431
432    def test_check_prog_with_path(self):
433        config, out, status = self.get_result('check_prog("A", ("known-a",), paths=["/some/path"])')
434        self.assertEqual(status, 1)
435        self.assertEqual(config, {})
436        self.assertEqual(out, textwrap.dedent('''\
437            checking for a... not found
438            DEBUG: a: Trying known-a
439            ERROR: Cannot find a
440        '''))
441
442        config, out, status = self.get_result('check_prog("A", ("known-a",), paths=["%s"])' %
443                                              os.path.dirname(self.OTHER_A))
444        self.assertEqual(status, 0)
445        self.assertEqual(config, {'A': self.OTHER_A})
446        self.assertEqual(out, textwrap.dedent('''\
447            checking for a... %s
448        ''' % self.OTHER_A))
449
450        dirs = map(mozpath.dirname, (self.OTHER_A, self.KNOWN_A))
451        config, out, status = self.get_result(textwrap.dedent('''\
452            check_prog("A", ("known-a",), paths=["%s"])
453        ''' % os.pathsep.join(dirs)))
454        self.assertEqual(status, 0)
455        self.assertEqual(config, {'A': self.OTHER_A})
456        self.assertEqual(out, textwrap.dedent('''\
457            checking for a... %s
458        ''' % self.OTHER_A))
459
460        dirs = map(mozpath.dirname, (self.KNOWN_A, self.KNOWN_B))
461        config, out, status = self.get_result(textwrap.dedent('''\
462            check_prog("A", ("known-a",), paths=["%s", "%s"])
463        ''' % (os.pathsep.join(dirs), self.OTHER_A)))
464        self.assertEqual(status, 0)
465        self.assertEqual(config, {'A': self.KNOWN_A})
466        self.assertEqual(out, textwrap.dedent('''\
467            checking for a... %s
468        ''' % self.KNOWN_A))
469
470        config, out, status = self.get_result('check_prog("A", ("known-a",), paths="%s")' %
471                                              os.path.dirname(self.OTHER_A))
472
473        self.assertEqual(status, 1)
474        self.assertEqual(config, {})
475        self.assertEqual(out, textwrap.dedent('''\
476            checking for a...
477            DEBUG: a: Trying known-a
478            ERROR: Paths provided to find_program must be a list of strings, not %r
479        ''' % mozpath.dirname(self.OTHER_A)))
480
481    def test_java_tool_checks(self):
482        includes = ('util.configure', 'checks.configure', 'java.configure')
483
484        def mock_valid_javac(_, args):
485            if len(args) == 1 and args[0] == '-version':
486                return 0, '1.8', ''
487            self.fail("Unexpected arguments to mock_valid_javac: %s" % args)
488
489        # A valid set of tools in a standard location.
490        java = mozpath.abspath('/usr/bin/java')
491        javah = mozpath.abspath('/usr/bin/javah')
492        javac = mozpath.abspath('/usr/bin/javac')
493        jar = mozpath.abspath('/usr/bin/jar')
494        jarsigner = mozpath.abspath('/usr/bin/jarsigner')
495        keytool = mozpath.abspath('/usr/bin/keytool')
496
497        paths = {
498            java: None,
499            javah: None,
500            javac: mock_valid_javac,
501            jar: None,
502            jarsigner: None,
503            keytool: None,
504        }
505
506        config, out, status = self.get_result(includes=includes, extra_paths=paths)
507        self.assertEqual(status, 0)
508        self.assertEqual(config, {
509            'JAVA': java,
510            'JAVAH': javah,
511            'JAVAC': javac,
512            'JAR': jar,
513            'JARSIGNER': jarsigner,
514            'KEYTOOL': keytool,
515        })
516        self.assertEqual(out, textwrap.dedent('''\
517             checking for java... %s
518             checking for javah... %s
519             checking for jar... %s
520             checking for jarsigner... %s
521             checking for keytool... %s
522             checking for javac... %s
523             checking for javac version... 1.8
524        ''' % (java, javah, jar, jarsigner, keytool, javac)))
525
526        # An alternative valid set of tools referred to by JAVA_HOME.
527        alt_java = mozpath.abspath('/usr/local/bin/java')
528        alt_javah = mozpath.abspath('/usr/local/bin/javah')
529        alt_javac = mozpath.abspath('/usr/local/bin/javac')
530        alt_jar = mozpath.abspath('/usr/local/bin/jar')
531        alt_jarsigner = mozpath.abspath('/usr/local/bin/jarsigner')
532        alt_keytool = mozpath.abspath('/usr/local/bin/keytool')
533        alt_java_home = mozpath.dirname(mozpath.dirname(alt_java))
534
535        paths.update({
536            alt_java: None,
537            alt_javah: None,
538            alt_javac: mock_valid_javac,
539            alt_jar: None,
540            alt_jarsigner: None,
541            alt_keytool: None,
542        })
543
544        config, out, status = self.get_result(includes=includes,
545                                              extra_paths=paths,
546                                              environ={
547                                                  'JAVA_HOME': alt_java_home,
548                                                  'PATH': mozpath.dirname(java)
549                                              })
550        self.assertEqual(status, 0)
551        self.assertEqual(config, {
552            'JAVA': alt_java,
553            'JAVAH': alt_javah,
554            'JAVAC': alt_javac,
555            'JAR': alt_jar,
556            'JARSIGNER': alt_jarsigner,
557            'KEYTOOL': alt_keytool,
558        })
559        self.assertEqual(out, textwrap.dedent('''\
560             checking for java... %s
561             checking for javah... %s
562             checking for jar... %s
563             checking for jarsigner... %s
564             checking for keytool... %s
565             checking for javac... %s
566             checking for javac version... 1.8
567        ''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
568               alt_keytool, alt_javac)))
569
570        # We can use --with-java-bin-path instead of JAVA_HOME to similar
571        # effect.
572        config, out, status = self.get_result(
573            args=['--with-java-bin-path=%s' % mozpath.dirname(alt_java)],
574            includes=includes,
575            extra_paths=paths,
576            environ={
577                'PATH': mozpath.dirname(java)
578            })
579        self.assertEqual(status, 0)
580        self.assertEqual(config, {
581            'JAVA': alt_java,
582            'JAVAH': alt_javah,
583            'JAVAC': alt_javac,
584            'JAR': alt_jar,
585            'JARSIGNER': alt_jarsigner,
586            'KEYTOOL': alt_keytool,
587        })
588        self.assertEqual(out, textwrap.dedent('''\
589             checking for java... %s
590             checking for javah... %s
591             checking for jar... %s
592             checking for jarsigner... %s
593             checking for keytool... %s
594             checking for javac... %s
595             checking for javac version... 1.8
596        ''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
597               alt_keytool, alt_javac)))
598
599        # If --with-java-bin-path and JAVA_HOME are both set,
600        # --with-java-bin-path takes precedence.
601        config, out, status = self.get_result(
602            args=['--with-java-bin-path=%s' % mozpath.dirname(alt_java)],
603            includes=includes,
604            extra_paths=paths,
605            environ={
606                'PATH': mozpath.dirname(java),
607                'JAVA_HOME': mozpath.dirname(mozpath.dirname(java)),
608            })
609        self.assertEqual(status, 0)
610        self.assertEqual(config, {
611            'JAVA': alt_java,
612            'JAVAH': alt_javah,
613            'JAVAC': alt_javac,
614            'JAR': alt_jar,
615            'JARSIGNER': alt_jarsigner,
616            'KEYTOOL': alt_keytool,
617        })
618        self.assertEqual(out, textwrap.dedent('''\
619             checking for java... %s
620             checking for javah... %s
621             checking for jar... %s
622             checking for jarsigner... %s
623             checking for keytool... %s
624             checking for javac... %s
625             checking for javac version... 1.8
626        ''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
627               alt_keytool, alt_javac)))
628
629        def mock_old_javac(_, args):
630            if len(args) == 1 and args[0] == '-version':
631                return 0, '1.6.9', ''
632            self.fail("Unexpected arguments to mock_old_javac: %s" % args)
633
634        # An old javac is fatal.
635        paths[javac] = mock_old_javac
636        config, out, status = self.get_result(includes=includes,
637                                              extra_paths=paths,
638                                              environ={
639                                                  'PATH': mozpath.dirname(java)
640                                              })
641        self.assertEqual(status, 1)
642        self.assertEqual(config, {
643            'JAVA': java,
644            'JAVAH': javah,
645            'JAVAC': javac,
646            'JAR': jar,
647            'JARSIGNER': jarsigner,
648            'KEYTOOL': keytool,
649        })
650        self.assertEqual(out, textwrap.dedent('''\
651             checking for java... %s
652             checking for javah... %s
653             checking for jar... %s
654             checking for jarsigner... %s
655             checking for keytool... %s
656             checking for javac... %s
657             checking for javac version...
658             ERROR: javac 1.8 or higher is required (found 1.6.9). Check the JAVA_HOME environment variable.
659        ''' % (java, javah, jar, jarsigner, keytool, javac)))
660
661        # Any missing tool is fatal when these checks run.
662        del paths[jarsigner]
663        config, out, status = self.get_result(includes=includes,
664                                              extra_paths=paths,
665                                              environ={
666                                                  'PATH': mozpath.dirname(java)
667                                              })
668        self.assertEqual(status, 1)
669        self.assertEqual(config, {
670            'JAVA': java,
671            'JAVAH': javah,
672            'JAR': jar,
673            'JARSIGNER': ':',
674        })
675        self.assertEqual(out, textwrap.dedent('''\
676             checking for java... %s
677             checking for javah... %s
678             checking for jar... %s
679             checking for jarsigner... not found
680             ERROR: The program jarsigner was not found.  Set $JAVA_HOME to your Java SDK directory or use '--with-java-bin-path={java-bin-dir}'
681        ''' % (java, javah, jar)))
682
683    def test_pkg_check_modules(self):
684        mock_pkg_config_version = '0.10.0'
685        mock_pkg_config_path = mozpath.abspath('/usr/bin/pkg-config')
686
687        def mock_pkg_config(_, args):
688            if args[0:2] == ['--errors-to-stdout', '--print-errors']:
689                assert len(args) == 3
690                package = args[2]
691                if package == 'unknown':
692                    return (1, "Package unknown was not found in the pkg-config search path.\n"
693                            "Perhaps you should add the directory containing `unknown.pc'\n"
694                            "to the PKG_CONFIG_PATH environment variable\n"
695                            "No package 'unknown' found", '')
696                if package == 'valid':
697                    return 0, '', ''
698                if package == 'new > 1.1':
699                    return 1, "Requested 'new > 1.1' but version of new is 1.1", ''
700            if args[0] == '--cflags':
701                assert len(args) == 2
702                return 0, '-I/usr/include/%s' % args[1], ''
703            if args[0] == '--libs':
704                assert len(args) == 2
705                return 0, '-l%s' % args[1], ''
706            if args[0] == '--version':
707                return 0, mock_pkg_config_version, ''
708            self.fail("Unexpected arguments to mock_pkg_config: %s" % args)
709
710        def get_result(cmd, args=[], extra_paths=None):
711            return self.get_result(textwrap.dedent('''\
712                option('--disable-compile-environment', help='compile env')
713                include('%(topsrcdir)s/build/moz.configure/util.configure')
714                include('%(topsrcdir)s/build/moz.configure/checks.configure')
715                include('%(topsrcdir)s/build/moz.configure/pkg.configure')
716            ''' % {'topsrcdir': topsrcdir}) + cmd, args=args, extra_paths=extra_paths,
717                                                   includes=())
718
719        extra_paths = {
720            mock_pkg_config_path: mock_pkg_config,
721        }
722        includes = ('util.configure', 'checks.configure', 'pkg.configure')
723
724        config, output, status = get_result("pkg_check_modules('MOZ_VALID', 'valid')")
725        self.assertEqual(status, 1)
726        self.assertEqual(output, textwrap.dedent('''\
727            checking for pkg_config... not found
728            ERROR: *** The pkg-config script could not be found. Make sure it is
729            *** in your path, or set the PKG_CONFIG environment variable
730            *** to the full path to pkg-config.
731        '''))
732
733
734        config, output, status = get_result("pkg_check_modules('MOZ_VALID', 'valid')",
735                                            extra_paths=extra_paths)
736        self.assertEqual(status, 0)
737        self.assertEqual(output, textwrap.dedent('''\
738            checking for pkg_config... %s
739            checking for pkg-config version... %s
740            checking for valid... yes
741            checking MOZ_VALID_CFLAGS... -I/usr/include/valid
742            checking MOZ_VALID_LIBS... -lvalid
743        ''' % (mock_pkg_config_path, mock_pkg_config_version)))
744        self.assertEqual(config, {
745            'PKG_CONFIG': mock_pkg_config_path,
746            'MOZ_VALID_CFLAGS': ('-I/usr/include/valid',),
747            'MOZ_VALID_LIBS': ('-lvalid',),
748        })
749
750        config, output, status = get_result("pkg_check_modules('MOZ_UKNOWN', 'unknown')",
751                                            extra_paths=extra_paths)
752        self.assertEqual(status, 1)
753        self.assertEqual(output, textwrap.dedent('''\
754            checking for pkg_config... %s
755            checking for pkg-config version... %s
756            checking for unknown... no
757            ERROR: Package unknown was not found in the pkg-config search path.
758            ERROR: Perhaps you should add the directory containing `unknown.pc'
759            ERROR: to the PKG_CONFIG_PATH environment variable
760            ERROR: No package 'unknown' found
761        ''' % (mock_pkg_config_path, mock_pkg_config_version)))
762        self.assertEqual(config, {
763            'PKG_CONFIG': mock_pkg_config_path,
764        })
765
766        config, output, status = get_result("pkg_check_modules('MOZ_NEW', 'new > 1.1')",
767                                            extra_paths=extra_paths)
768        self.assertEqual(status, 1)
769        self.assertEqual(output, textwrap.dedent('''\
770            checking for pkg_config... %s
771            checking for pkg-config version... %s
772            checking for new > 1.1... no
773            ERROR: Requested 'new > 1.1' but version of new is 1.1
774        ''' % (mock_pkg_config_path, mock_pkg_config_version)))
775        self.assertEqual(config, {
776            'PKG_CONFIG': mock_pkg_config_path,
777        })
778
779        # allow_missing makes missing packages non-fatal.
780        cmd = textwrap.dedent('''\
781        have_new_module = pkg_check_modules('MOZ_NEW', 'new > 1.1', allow_missing=True)
782        @depends(have_new_module)
783        def log_new_module_error(mod):
784            if mod is not True:
785                log.info('Module not found.')
786        ''')
787
788        config, output, status = get_result(cmd, extra_paths=extra_paths)
789        self.assertEqual(status, 0)
790        self.assertEqual(output, textwrap.dedent('''\
791            checking for pkg_config... %s
792            checking for pkg-config version... %s
793            checking for new > 1.1... no
794            WARNING: Requested 'new > 1.1' but version of new is 1.1
795            Module not found.
796        ''' % (mock_pkg_config_path, mock_pkg_config_version)))
797        self.assertEqual(config, {
798            'PKG_CONFIG': mock_pkg_config_path,
799        })
800
801        config, output, status = get_result(cmd,
802                                            args=['--disable-compile-environment'],
803                                            extra_paths=extra_paths)
804        self.assertEqual(status, 0)
805        self.assertEqual(output, 'Module not found.\n')
806        self.assertEqual(config, {})
807
808        def mock_old_pkg_config(_, args):
809            if args[0] == '--version':
810                return 0, '0.8.10', ''
811            self.fail("Unexpected arguments to mock_old_pkg_config: %s" % args)
812
813        extra_paths = {
814            mock_pkg_config_path: mock_old_pkg_config,
815        }
816
817        config, output, status = get_result("pkg_check_modules('MOZ_VALID', 'valid')",
818                                            extra_paths=extra_paths)
819        self.assertEqual(status, 1)
820        self.assertEqual(output, textwrap.dedent('''\
821            checking for pkg_config... %s
822            checking for pkg-config version... 0.8.10
823            ERROR: *** Your version of pkg-config is too old. You need version 0.9.0 or newer.
824        ''' % mock_pkg_config_path))
825
826    def test_simple_keyfile(self):
827        includes = ('util.configure', 'checks.configure', 'keyfiles.configure')
828
829        config, output, status = self.get_result(
830            "simple_keyfile('Mozilla API')", includes=includes)
831        self.assertEqual(status, 0)
832        self.assertEqual(output, textwrap.dedent('''\
833            checking for the Mozilla API key... no
834        '''))
835        self.assertEqual(config, {
836            'MOZ_MOZILLA_API_KEY': 'no-mozilla-api-key',
837        })
838
839        config, output, status = self.get_result(
840            "simple_keyfile('Mozilla API')",
841            args=['--with-mozilla-api-keyfile=/foo/bar/does/not/exist'],
842            includes=includes)
843        self.assertEqual(status, 1)
844        self.assertEqual(output, textwrap.dedent('''\
845            checking for the Mozilla API key... no
846            ERROR: '/foo/bar/does/not/exist': No such file or directory.
847        '''))
848        self.assertEqual(config, {})
849
850        with MockedOpen({'key': ''}):
851            config, output, status = self.get_result(
852                "simple_keyfile('Mozilla API')",
853                args=['--with-mozilla-api-keyfile=key'],
854                includes=includes)
855            self.assertEqual(status, 1)
856            self.assertEqual(output, textwrap.dedent('''\
857                checking for the Mozilla API key... no
858                ERROR: 'key' is empty.
859            '''))
860            self.assertEqual(config, {})
861
862        with MockedOpen({'key': 'fake-key\n'}):
863            config, output, status = self.get_result(
864                "simple_keyfile('Mozilla API')",
865                args=['--with-mozilla-api-keyfile=key'],
866                includes=includes)
867            self.assertEqual(status, 0)
868            self.assertEqual(output, textwrap.dedent('''\
869                checking for the Mozilla API key... yes
870            '''))
871            self.assertEqual(config, {
872                'MOZ_MOZILLA_API_KEY': 'fake-key',
873            })
874
875        with MockedOpen({'default': 'default-key\n'}):
876            config, output, status = self.get_result(
877                "simple_keyfile('Mozilla API', default='default')",
878                includes=includes)
879            self.assertEqual(status, 0)
880            self.assertEqual(output, textwrap.dedent('''\
881                checking for the Mozilla API key... yes
882            '''))
883            self.assertEqual(config, {
884                'MOZ_MOZILLA_API_KEY': 'default-key',
885            })
886
887        with MockedOpen({'default': 'default-key\n',
888                         'key': 'fake-key\n'}):
889            config, output, status = self.get_result(
890                "simple_keyfile('Mozilla API', default='key')",
891                includes=includes)
892            self.assertEqual(status, 0)
893            self.assertEqual(output, textwrap.dedent('''\
894                checking for the Mozilla API key... yes
895            '''))
896            self.assertEqual(config, {
897                'MOZ_MOZILLA_API_KEY': 'fake-key',
898            })
899
900    def test_id_and_secret_keyfile(self):
901        includes = ('util.configure', 'checks.configure', 'keyfiles.configure')
902
903        config, output, status = self.get_result(
904            "id_and_secret_keyfile('Bing API')", includes=includes)
905        self.assertEqual(status, 0)
906        self.assertEqual(output, textwrap.dedent('''\
907            checking for the Bing API key... no
908        '''))
909        self.assertEqual(config, {
910            'MOZ_BING_API_CLIENTID': 'no-bing-api-clientid',
911            'MOZ_BING_API_KEY': 'no-bing-api-key',
912        })
913
914        config, output, status = self.get_result(
915            "id_and_secret_keyfile('Bing API')",
916            args=['--with-bing-api-keyfile=/foo/bar/does/not/exist'],
917            includes=includes)
918        self.assertEqual(status, 1)
919        self.assertEqual(output, textwrap.dedent('''\
920            checking for the Bing API key... no
921            ERROR: '/foo/bar/does/not/exist': No such file or directory.
922        '''))
923        self.assertEqual(config, {})
924
925        with MockedOpen({'key': ''}):
926            config, output, status = self.get_result(
927                "id_and_secret_keyfile('Bing API')",
928                args=['--with-bing-api-keyfile=key'],
929                includes=includes)
930            self.assertEqual(status, 1)
931            self.assertEqual(output, textwrap.dedent('''\
932                checking for the Bing API key... no
933                ERROR: 'key' is empty.
934            '''))
935            self.assertEqual(config, {})
936
937        with MockedOpen({'key': 'fake-id fake-key\n'}):
938            config, output, status = self.get_result(
939                "id_and_secret_keyfile('Bing API')",
940                args=['--with-bing-api-keyfile=key'],
941                includes=includes)
942            self.assertEqual(status, 0)
943            self.assertEqual(output, textwrap.dedent('''\
944                checking for the Bing API key... yes
945            '''))
946            self.assertEqual(config, {
947                'MOZ_BING_API_CLIENTID': 'fake-id',
948                'MOZ_BING_API_KEY': 'fake-key',
949            })
950
951        with MockedOpen({'key': 'fake-key\n'}):
952            config, output, status = self.get_result(
953                "id_and_secret_keyfile('Bing API')",
954                args=['--with-bing-api-keyfile=key'],
955                includes=includes)
956            self.assertEqual(status, 1)
957            self.assertEqual(output, textwrap.dedent('''\
958                checking for the Bing API key... no
959                ERROR: Bing API key file has an invalid format.
960            '''))
961            self.assertEqual(config, {})
962
963        with MockedOpen({'default-key': 'default-id default-key\n'}):
964            config, output, status = self.get_result(
965                "id_and_secret_keyfile('Bing API', default='default-key')",
966                includes=includes)
967            self.assertEqual(status, 0)
968            self.assertEqual(output, textwrap.dedent('''\
969                checking for the Bing API key... yes
970            '''))
971            self.assertEqual(config, {
972                'MOZ_BING_API_CLIENTID': 'default-id',
973                'MOZ_BING_API_KEY': 'default-key',
974            })
975
976        with MockedOpen({'default-key': 'default-id default-key\n',
977                         'key': 'fake-id fake-key\n'}):
978            config, output, status = self.get_result(
979                "id_and_secret_keyfile('Bing API', default='default-key')",
980                args=['--with-bing-api-keyfile=key'],
981                includes=includes)
982            self.assertEqual(status, 0)
983            self.assertEqual(output, textwrap.dedent('''\
984                checking for the Bing API key... yes
985            '''))
986            self.assertEqual(config, {
987                'MOZ_BING_API_CLIENTID': 'fake-id',
988                'MOZ_BING_API_KEY': 'fake-key',
989            })
990
991
992if __name__ == '__main__':
993    main()
994