1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import, print_function, unicode_literals
6
7import os
8import six
9import unittest
10
11from mozunit import main
12
13from mozbuild.frontend.context import (
14    ObjDirPath,
15    Path,
16)
17from mozbuild.frontend.data import (
18    ComputedFlags,
19    ConfigFileSubstitution,
20    Defines,
21    DirectoryTraversal,
22    Exports,
23    FinalTargetPreprocessedFiles,
24    GeneratedFile,
25    GeneratedSources,
26    HostProgram,
27    HostRustLibrary,
28    HostRustProgram,
29    HostSources,
30    IPDLCollection,
31    JARManifest,
32    LocalInclude,
33    LocalizedFiles,
34    LocalizedPreprocessedFiles,
35    Program,
36    RustLibrary,
37    RustProgram,
38    SharedLibrary,
39    SimpleProgram,
40    Sources,
41    StaticLibrary,
42    TestHarnessFiles,
43    TestManifest,
44    UnifiedSources,
45    VariablePassthru,
46    WasmSources,
47)
48from mozbuild.frontend.emitter import TreeMetadataEmitter
49from mozbuild.frontend.reader import (
50    BuildReader,
51    BuildReaderError,
52    SandboxValidationError,
53)
54
55from mozbuild.test.common import MockConfig
56
57import mozpack.path as mozpath
58
59
60data_path = mozpath.abspath(mozpath.dirname(__file__))
61data_path = mozpath.join(data_path, 'data')
62
63
64class TestEmitterBasic(unittest.TestCase):
65    def setUp(self):
66        self._old_env = dict(os.environ)
67        os.environ.pop('MOZ_OBJDIR', None)
68
69    def tearDown(self):
70        os.environ.clear()
71        os.environ.update(self._old_env)
72
73    def reader(self, name, enable_tests=False, extra_substs=None):
74        substs = dict(
75            ENABLE_TESTS='1' if enable_tests else '',
76            BIN_SUFFIX='.prog',
77            HOST_BIN_SUFFIX='.hostprog',
78            OS_TARGET='WINNT',
79            COMPILE_ENVIRONMENT='1',
80            STL_FLAGS=['-I/path/to/topobjdir/dist/stl_wrappers'],
81            VISIBILITY_FLAGS=['-include',
82                              '$(topsrcdir)/config/gcc_hidden.h'],
83            OBJ_SUFFIX='obj',
84            WASM_OBJ_SUFFIX='wasm',
85            WASM_CFLAGS=['-foo'],
86        )
87        if extra_substs:
88            substs.update(extra_substs)
89        config = MockConfig(mozpath.join(data_path, name), extra_substs=substs)
90
91        return BuildReader(config)
92
93    def read_topsrcdir(self, reader, filter_common=True):
94        emitter = TreeMetadataEmitter(reader.config)
95        objs = list(emitter.emit(reader.read_topsrcdir()))
96        self.assertGreater(len(objs), 0)
97
98        filtered = []
99        for obj in objs:
100            if filter_common and isinstance(obj, DirectoryTraversal):
101                continue
102
103            filtered.append(obj)
104
105        return filtered
106
107    def test_dirs_traversal_simple(self):
108        reader = self.reader('traversal-simple')
109        objs = self.read_topsrcdir(reader, filter_common=False)
110        self.assertEqual(len(objs), 4)
111
112        for o in objs:
113            self.assertIsInstance(o, DirectoryTraversal)
114            self.assertTrue(os.path.isabs(o.context_main_path))
115            self.assertEqual(len(o.context_all_paths), 1)
116
117        reldirs = [o.relsrcdir for o in objs]
118        self.assertEqual(reldirs, ['', 'foo', 'foo/biz', 'bar'])
119
120        dirs = [[d.full_path for d in o.dirs] for o in objs]
121        self.assertEqual(dirs, [
122            [
123                mozpath.join(reader.config.topsrcdir, 'foo'),
124                mozpath.join(reader.config.topsrcdir, 'bar')
125            ], [
126                mozpath.join(reader.config.topsrcdir, 'foo', 'biz')
127            ], [], []])
128
129    def test_traversal_all_vars(self):
130        reader = self.reader('traversal-all-vars')
131        objs = self.read_topsrcdir(reader, filter_common=False)
132        self.assertEqual(len(objs), 2)
133
134        for o in objs:
135            self.assertIsInstance(o, DirectoryTraversal)
136
137        reldirs = set([o.relsrcdir for o in objs])
138        self.assertEqual(reldirs, set(['', 'regular']))
139
140        for o in objs:
141            reldir = o.relsrcdir
142
143            if reldir == '':
144                self.assertEqual([d.full_path for d in o.dirs], [
145                    mozpath.join(reader.config.topsrcdir, 'regular')])
146
147    def test_traversal_all_vars_enable_tests(self):
148        reader = self.reader('traversal-all-vars', enable_tests=True)
149        objs = self.read_topsrcdir(reader, filter_common=False)
150        self.assertEqual(len(objs), 3)
151
152        for o in objs:
153            self.assertIsInstance(o, DirectoryTraversal)
154
155        reldirs = set([o.relsrcdir for o in objs])
156        self.assertEqual(reldirs, set(['', 'regular', 'test']))
157
158        for o in objs:
159            reldir = o.relsrcdir
160
161            if reldir == '':
162                self.assertEqual([d.full_path for d in o.dirs], [
163                    mozpath.join(reader.config.topsrcdir, 'regular'),
164                    mozpath.join(reader.config.topsrcdir, 'test')])
165
166    def test_config_file_substitution(self):
167        reader = self.reader('config-file-substitution')
168        objs = self.read_topsrcdir(reader)
169        self.assertEqual(len(objs), 2)
170
171        self.assertIsInstance(objs[0], ConfigFileSubstitution)
172        self.assertIsInstance(objs[1], ConfigFileSubstitution)
173
174        topobjdir = mozpath.abspath(reader.config.topobjdir)
175        self.assertEqual(objs[0].relpath, 'foo')
176        self.assertEqual(mozpath.normpath(objs[0].output_path),
177                         mozpath.normpath(mozpath.join(topobjdir, 'foo')))
178        self.assertEqual(mozpath.normpath(objs[1].output_path),
179                         mozpath.normpath(mozpath.join(topobjdir, 'bar')))
180
181    def test_variable_passthru(self):
182        reader = self.reader('variable-passthru')
183        objs = self.read_topsrcdir(reader)
184
185        self.assertEqual(len(objs), 1)
186        self.assertIsInstance(objs[0], VariablePassthru)
187
188        wanted = {
189            'NO_DIST_INSTALL': True,
190            'RCFILE': 'foo.rc',
191            'RESFILE': 'bar.res',
192            'RCINCLUDE': 'bar.rc',
193            'EXTRA_DEPS': [mozpath.join(mozpath.relpath(reader.config.topsrcdir,
194                                                        reader.config.topobjdir),
195                                        'baz.def')],
196            'WIN32_EXE_LDFLAGS': ['-subsystem:console'],
197        }
198
199        variables = objs[0].variables
200        maxDiff = self.maxDiff
201        self.maxDiff = None
202        self.assertEqual(wanted, variables)
203        self.maxDiff = maxDiff
204
205    def test_compile_flags(self):
206        reader = self.reader('compile-flags', extra_substs={
207            'WARNINGS_AS_ERRORS': '-Werror',
208        })
209        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
210        self.assertIsInstance(flags, ComputedFlags)
211        self.assertEqual(flags.flags['STL'], reader.config.substs['STL_FLAGS'])
212        self.assertEqual(flags.flags['VISIBILITY'], reader.config.substs['VISIBILITY_FLAGS'])
213        self.assertEqual(flags.flags['WARNINGS_AS_ERRORS'], ['-Werror'])
214        self.assertEqual(flags.flags['MOZBUILD_CFLAGS'], ['-Wall', '-funroll-loops'])
215        self.assertEqual(flags.flags['MOZBUILD_CXXFLAGS'], ['-funroll-loops', '-Wall'])
216
217    def test_asflags(self):
218        reader = self.reader('asflags', extra_substs={
219            'ASFLAGS': ['-safeseh'],
220        })
221        as_sources, sources, ldflags, lib, flags, asflags = self.read_topsrcdir(reader)
222        self.assertIsInstance(asflags, ComputedFlags)
223        self.assertEqual(asflags.flags['OS'], reader.config.substs['ASFLAGS'])
224        self.assertEqual(asflags.flags['MOZBUILD'], ['-no-integrated-as'])
225
226    def test_debug_flags(self):
227        reader = self.reader('compile-flags', extra_substs={
228            'MOZ_DEBUG_FLAGS': '-g',
229            'MOZ_DEBUG_SYMBOLS': '1',
230        })
231        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
232        self.assertIsInstance(flags, ComputedFlags)
233        self.assertEqual(flags.flags['DEBUG'], ['-g'])
234
235    def test_disable_debug_flags(self):
236        reader = self.reader('compile-flags', extra_substs={
237            'MOZ_DEBUG_FLAGS': '-g',
238            'MOZ_DEBUG_SYMBOLS': '',
239        })
240        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
241        self.assertIsInstance(flags, ComputedFlags)
242        self.assertEqual(flags.flags['DEBUG'], [])
243
244    def test_link_flags(self):
245        reader = self.reader('link-flags', extra_substs={
246            'OS_LDFLAGS': ['-Wl,rpath-link=/usr/lib'],
247            'MOZ_OPTIMIZE': '',
248            'MOZ_OPTIMIZE_LDFLAGS': ['-Wl,-dead_strip'],
249            'MOZ_DEBUG_LDFLAGS': ['-framework ExceptionHandling'],
250        })
251        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
252        self.assertIsInstance(ldflags, ComputedFlags)
253        self.assertEqual(ldflags.flags['OS'], reader.config.substs['OS_LDFLAGS'])
254        self.assertEqual(ldflags.flags['MOZBUILD'], ['-Wl,-U_foo', '-framework Foo', '-x'])
255        self.assertEqual(ldflags.flags['OPTIMIZE'], [])
256
257    def test_debug_ldflags(self):
258        reader = self.reader('link-flags', extra_substs={
259            'MOZ_DEBUG_SYMBOLS': '1',
260            'MOZ_DEBUG_LDFLAGS': ['-framework ExceptionHandling'],
261        })
262        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
263        self.assertIsInstance(ldflags, ComputedFlags)
264        self.assertEqual(ldflags.flags['OS'],
265                         reader.config.substs['MOZ_DEBUG_LDFLAGS'])
266
267    def test_windows_opt_link_flags(self):
268        reader = self.reader('link-flags', extra_substs={
269            'OS_ARCH': 'WINNT',
270            'GNU_CC': '',
271            'MOZ_OPTIMIZE': '1',
272            'MOZ_DEBUG_LDFLAGS': ['-DEBUG'],
273            'MOZ_DEBUG_SYMBOLS': '1',
274            'MOZ_OPTIMIZE_FLAGS': [],
275            'MOZ_OPTIMIZE_LDFLAGS': [],
276        })
277        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
278        self.assertIsInstance(ldflags, ComputedFlags)
279        self.assertIn('-DEBUG', ldflags.flags['OS'])
280        self.assertIn('-OPT:REF,ICF', ldflags.flags['OS'])
281
282    def test_windows_dmd_link_flags(self):
283        reader = self.reader('link-flags', extra_substs={
284            'OS_ARCH': 'WINNT',
285            'GNU_CC': '',
286            'MOZ_DMD': '1',
287            'MOZ_DEBUG_LDFLAGS': ['-DEBUG'],
288            'MOZ_DEBUG_SYMBOLS': '1',
289            'MOZ_OPTIMIZE': '1',
290            'MOZ_OPTIMIZE_FLAGS': [],
291        })
292        sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
293        self.assertIsInstance(ldflags, ComputedFlags)
294        self.assertEqual(ldflags.flags['OS'],
295                         ['-DEBUG', '-OPT:REF,ICF'])
296
297    def test_host_compile_flags(self):
298        reader = self.reader('host-compile-flags', extra_substs={
299            'HOST_CXXFLAGS': ['-Wall', '-Werror'],
300            'HOST_CFLAGS': ['-Werror', '-Wall'],
301        })
302        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
303        self.assertIsInstance(flags, ComputedFlags)
304        self.assertEqual(flags.flags['HOST_CXXFLAGS'], reader.config.substs['HOST_CXXFLAGS'])
305        self.assertEqual(flags.flags['HOST_CFLAGS'], reader.config.substs['HOST_CFLAGS'])
306        self.assertEqual(set(flags.flags['HOST_DEFINES']),
307                         set(['-DFOO', '-DBAZ="abcd"', '-UQUX', '-DBAR=7', '-DVALUE=xyz']))
308        self.assertEqual(flags.flags['MOZBUILD_HOST_CFLAGS'], ['-funroll-loops', '-host-arg'])
309        self.assertEqual(flags.flags['MOZBUILD_HOST_CXXFLAGS'], [])
310
311    def test_host_no_optimize_flags(self):
312        reader = self.reader('host-compile-flags', extra_substs={
313            'MOZ_OPTIMIZE': '',
314            'MOZ_OPTIMIZE_FLAGS': ['-O2'],
315        })
316        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
317        self.assertIsInstance(flags, ComputedFlags)
318        self.assertEqual(flags.flags['HOST_OPTIMIZE'], [])
319
320    def test_host_optimize_flags(self):
321        reader = self.reader('host-compile-flags', extra_substs={
322            'MOZ_OPTIMIZE': '1',
323            'MOZ_OPTIMIZE_FLAGS': ['-O2'],
324        })
325        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
326        self.assertIsInstance(flags, ComputedFlags)
327        self.assertEqual(flags.flags['HOST_OPTIMIZE'], ['-O2'])
328
329    def test_cross_optimize_flags(self):
330        reader = self.reader('host-compile-flags', extra_substs={
331            'MOZ_OPTIMIZE': '1',
332            'MOZ_OPTIMIZE_FLAGS': ['-O2'],
333            'HOST_OPTIMIZE_FLAGS': ['-O3'],
334            'CROSS_COMPILE': '1',
335        })
336        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
337        self.assertIsInstance(flags, ComputedFlags)
338        self.assertEqual(flags.flags['HOST_OPTIMIZE'], ['-O3'])
339
340    def test_host_rtl_flag(self):
341        reader = self.reader('host-compile-flags', extra_substs={
342            'OS_ARCH': 'WINNT',
343            'MOZ_DEBUG': '1',
344        })
345        sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
346        self.assertIsInstance(flags, ComputedFlags)
347        self.assertEqual(flags.flags['RTL'], ['-MDd'])
348
349    def test_compile_flags_validation(self):
350        reader = self.reader('compile-flags-field-validation')
351
352        with six.assertRaisesRegex(self, BuildReaderError, 'Invalid value.'):
353            self.read_topsrcdir(reader)
354
355        reader = self.reader('compile-flags-type-validation')
356        with six.assertRaisesRegex(self, BuildReaderError,
357                                   'A list of strings must be provided'):
358            self.read_topsrcdir(reader)
359
360    def test_compile_flags_templates(self):
361        reader = self.reader('compile-flags-templates', extra_substs={
362            'NSPR_CFLAGS': ['-I/nspr/path'],
363            'NSS_CFLAGS': ['-I/nss/path'],
364            'MOZ_JPEG_CFLAGS': ['-I/jpeg/path'],
365            'MOZ_PNG_CFLAGS': ['-I/png/path'],
366            'MOZ_ZLIB_CFLAGS': ['-I/zlib/path'],
367            'MOZ_PIXMAN_CFLAGS': ['-I/pixman/path'],
368        })
369        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
370        self.assertIsInstance(flags, ComputedFlags)
371        self.assertEqual(flags.flags['STL'], [])
372        self.assertEqual(flags.flags['VISIBILITY'], [])
373        self.assertEqual(flags.flags['OS_INCLUDES'], [
374            '-I/nspr/path',
375            '-I/nss/path',
376            '-I/jpeg/path',
377            '-I/png/path',
378            '-I/zlib/path',
379            '-I/pixman/path',
380        ])
381
382    def test_disable_stl_wrapping(self):
383        reader = self.reader('disable-stl-wrapping')
384        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
385        self.assertIsInstance(flags, ComputedFlags)
386        self.assertEqual(flags.flags['STL'], [])
387
388    def test_visibility_flags(self):
389        reader = self.reader('visibility-flags')
390        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
391        self.assertIsInstance(flags, ComputedFlags)
392        self.assertEqual(flags.flags['VISIBILITY'], [])
393
394    def test_defines_in_flags(self):
395        reader = self.reader('compile-defines')
396        defines, sources, ldflags, lib, flags = self.read_topsrcdir(reader)
397        self.assertIsInstance(flags, ComputedFlags)
398        self.assertEqual(flags.flags['LIBRARY_DEFINES'],
399                         ['-DMOZ_LIBRARY_DEFINE=MOZ_TEST'])
400        self.assertEqual(flags.flags['DEFINES'],
401                         ['-DMOZ_TEST_DEFINE'])
402
403    def test_resolved_flags_error(self):
404        reader = self.reader('resolved-flags-error')
405        with six.assertRaisesRegex(self, BuildReaderError,
406                                   "`DEFINES` may not be set in COMPILE_FLAGS from moz.build"):
407            self.read_topsrcdir(reader)
408
409    def test_includes_in_flags(self):
410        reader = self.reader('compile-includes')
411        defines, sources, ldflags, lib, flags = self.read_topsrcdir(reader)
412        self.assertIsInstance(flags, ComputedFlags)
413        self.assertEqual(flags.flags['BASE_INCLUDES'],
414                         ['-I%s' % reader.config.topsrcdir,
415                          '-I%s' % reader.config.topobjdir])
416        self.assertEqual(flags.flags['EXTRA_INCLUDES'],
417                         ['-I%s/dist/include' % reader.config.topobjdir])
418        self.assertEqual(flags.flags['LOCAL_INCLUDES'],
419                         ['-I%s/subdir' % reader.config.topsrcdir])
420
421    def test_allow_compiler_warnings(self):
422        reader = self.reader('allow-compiler-warnings', extra_substs={
423            'WARNINGS_AS_ERRORS': '-Werror',
424        })
425        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
426        self.assertEqual(flags.flags['WARNINGS_AS_ERRORS'], [])
427
428    def test_disable_compiler_warnings(self):
429        reader = self.reader('disable-compiler-warnings', extra_substs={
430            'WARNINGS_CFLAGS': '-Wall',
431        })
432        sources, ldflags, lib, flags = self.read_topsrcdir(reader)
433        self.assertEqual(flags.flags['WARNINGS_CFLAGS'], [])
434
435    def test_use_yasm(self):
436        # When yasm is not available, this should raise.
437        reader = self.reader('use-yasm')
438        with six.assertRaisesRegex(self, SandboxValidationError,
439                                   'yasm is not available'):
440            self.read_topsrcdir(reader)
441
442        # When yasm is available, this should work.
443        reader = self.reader('use-yasm',
444                             extra_substs=dict(
445                                 YASM='yasm',
446                                 YASM_ASFLAGS='-foo',
447                             ))
448
449        sources, passthru, ldflags, lib, flags, asflags = self.read_topsrcdir(reader)
450
451        self.assertIsInstance(passthru, VariablePassthru)
452        self.assertIsInstance(ldflags, ComputedFlags)
453        self.assertIsInstance(flags, ComputedFlags)
454        self.assertIsInstance(asflags, ComputedFlags)
455
456        self.assertEqual(asflags.flags['OS'], reader.config.substs['YASM_ASFLAGS'])
457
458        maxDiff = self.maxDiff
459        self.maxDiff = None
460        self.assertEqual(passthru.variables,
461                         {'AS': 'yasm',
462                          'AS_DASH_C_FLAG': '',
463                          'ASOUTOPTION': '-o '})
464        self.maxDiff = maxDiff
465
466    def test_generated_files(self):
467        reader = self.reader('generated-files')
468        objs = self.read_topsrcdir(reader)
469
470        self.assertEqual(len(objs), 3)
471        for o in objs:
472            self.assertIsInstance(o, GeneratedFile)
473            self.assertFalse(o.localized)
474            self.assertFalse(o.force)
475
476        expected = ['bar.c', 'foo.c', ('xpidllex.py', 'xpidlyacc.py'), ]
477        for o, f in zip(objs, expected):
478            expected_filename = f if isinstance(f, tuple) else (f,)
479            self.assertEqual(o.outputs, expected_filename)
480            self.assertEqual(o.script, None)
481            self.assertEqual(o.method, None)
482            self.assertEqual(o.inputs, [])
483
484    def test_generated_files_force(self):
485        reader = self.reader('generated-files-force')
486        objs = self.read_topsrcdir(reader)
487
488        self.assertEqual(len(objs), 3)
489        for o in objs:
490            self.assertIsInstance(o, GeneratedFile)
491            self.assertEqual(o.force, 'bar.c' in o.outputs)
492
493    def test_localized_generated_files(self):
494        reader = self.reader('localized-generated-files')
495        objs = self.read_topsrcdir(reader)
496
497        self.assertEqual(len(objs), 2)
498        for o in objs:
499            self.assertIsInstance(o, GeneratedFile)
500            self.assertTrue(o.localized)
501
502        expected = ['abc.ini', ('bar', 'baz'), ]
503        for o, f in zip(objs, expected):
504            expected_filename = f if isinstance(f, tuple) else (f,)
505            self.assertEqual(o.outputs, expected_filename)
506            self.assertEqual(o.script, None)
507            self.assertEqual(o.method, None)
508            self.assertEqual(o.inputs, [])
509
510    def test_localized_generated_files_force(self):
511        reader = self.reader('localized-generated-files-force')
512        objs = self.read_topsrcdir(reader)
513
514        self.assertEqual(len(objs), 2)
515        for o in objs:
516            self.assertIsInstance(o, GeneratedFile)
517            self.assertTrue(o.localized)
518            self.assertEqual(o.force, 'abc.ini' in o.outputs)
519
520    def test_localized_files_from_generated(self):
521        """Test that using LOCALIZED_GENERATED_FILES and then putting the output in
522        LOCALIZED_FILES as an objdir path works.
523        """
524        reader = self.reader('localized-files-from-generated')
525        objs = self.read_topsrcdir(reader)
526
527        self.assertEqual(len(objs), 2)
528        self.assertIsInstance(objs[0], GeneratedFile)
529        self.assertIsInstance(objs[1], LocalizedFiles)
530
531    def test_localized_files_not_localized_generated(self):
532        """Test that using GENERATED_FILES and then putting the output in
533        LOCALIZED_FILES as an objdir path produces an error.
534        """
535        reader = self.reader('localized-files-not-localized-generated')
536        with six.assertRaisesRegex(
537                self,
538                SandboxValidationError,
539                'Objdir file listed in LOCALIZED_FILES not in LOCALIZED_GENERATED_FILES:'
540        ):
541            self.read_topsrcdir(reader)
542
543    def test_localized_generated_files_final_target_files(self):
544        """Test that using LOCALIZED_GENERATED_FILES and then putting the output in
545        FINAL_TARGET_FILES as an objdir path produces an error.
546        """
547        reader = self.reader('localized-generated-files-final-target-files')
548        with six.assertRaisesRegex(
549                self,
550                SandboxValidationError,
551                'Outputs of LOCALIZED_GENERATED_FILES cannot be used in FINAL_TARGET_FILES:'
552        ):
553            self.read_topsrcdir(reader)
554
555    def test_generated_files_method_names(self):
556        reader = self.reader('generated-files-method-names')
557        objs = self.read_topsrcdir(reader)
558
559        self.assertEqual(len(objs), 2)
560        for o in objs:
561            self.assertIsInstance(o, GeneratedFile)
562
563        expected = ['bar.c', 'foo.c']
564        expected_method_names = ['make_bar', 'main']
565        for o, expected_filename, expected_method in zip(objs, expected, expected_method_names):
566            self.assertEqual(o.outputs, (expected_filename,))
567            self.assertEqual(o.method, expected_method)
568            self.assertEqual(o.inputs, [])
569
570    def test_generated_files_absolute_script(self):
571        reader = self.reader('generated-files-absolute-script')
572        objs = self.read_topsrcdir(reader)
573
574        self.assertEqual(len(objs), 1)
575
576        o = objs[0]
577        self.assertIsInstance(o, GeneratedFile)
578        self.assertEqual(o.outputs, ('bar.c',))
579        self.assertRegexpMatches(o.script, 'script.py$')
580        self.assertEqual(o.method, 'make_bar')
581        self.assertEqual(o.inputs, [])
582
583    def test_generated_files_no_script(self):
584        reader = self.reader('generated-files-no-script')
585        with six.assertRaisesRegex(self, SandboxValidationError,
586                                   'Script for generating bar.c does not exist'):
587            self.read_topsrcdir(reader)
588
589    def test_generated_files_no_inputs(self):
590        reader = self.reader('generated-files-no-inputs')
591        with six.assertRaisesRegex(self, SandboxValidationError,
592                                   'Input for generating foo.c does not exist'):
593            self.read_topsrcdir(reader)
594
595    def test_generated_files_no_python_script(self):
596        reader = self.reader('generated-files-no-python-script')
597        with six.assertRaisesRegex(self, SandboxValidationError,
598                                   'Script for generating bar.c does not end in .py'):
599            self.read_topsrcdir(reader)
600
601    def test_exports(self):
602        reader = self.reader('exports')
603        objs = self.read_topsrcdir(reader)
604
605        self.assertEqual(len(objs), 1)
606        self.assertIsInstance(objs[0], Exports)
607
608        expected = [
609            ('', ['foo.h', 'bar.h', 'baz.h']),
610            ('mozilla', ['mozilla1.h', 'mozilla2.h']),
611            ('mozilla/dom', ['dom1.h', 'dom2.h', 'dom3.h']),
612            ('mozilla/gfx', ['gfx.h']),
613            ('nspr/private', ['pprio.h', 'pprthred.h']),
614            ('vpx', ['mem.h', 'mem2.h']),
615        ]
616        for (expect_path, expect_headers), (actual_path, actual_headers) in \
617                zip(expected, [(path, list(seq)) for path, seq in objs[0].files.walk()]):
618            self.assertEqual(expect_path, actual_path)
619            self.assertEqual(expect_headers, actual_headers)
620
621    def test_exports_missing(self):
622        '''
623        Missing files in EXPORTS is an error.
624        '''
625        reader = self.reader('exports-missing')
626        with six.assertRaisesRegex(self, SandboxValidationError,
627                                   'File listed in EXPORTS does not exist:'):
628            self.read_topsrcdir(reader)
629
630    def test_exports_missing_generated(self):
631        '''
632        An objdir file in EXPORTS that is not in GENERATED_FILES is an error.
633        '''
634        reader = self.reader('exports-missing-generated')
635        with six.assertRaisesRegex(self, SandboxValidationError,
636                                   'Objdir file listed in EXPORTS not in GENERATED_FILES:'):
637            self.read_topsrcdir(reader)
638
639    def test_exports_generated(self):
640        reader = self.reader('exports-generated')
641        objs = self.read_topsrcdir(reader)
642
643        self.assertEqual(len(objs), 2)
644        self.assertIsInstance(objs[0], GeneratedFile)
645        self.assertIsInstance(objs[1], Exports)
646        exports = [(path, list(seq)) for path, seq in objs[1].files.walk()]
647        self.assertEqual(exports,
648                         [('', ['foo.h']),
649                          ('mozilla', ['mozilla1.h', '!mozilla2.h'])])
650        path, files = exports[1]
651        self.assertIsInstance(files[1], ObjDirPath)
652
653    def test_test_harness_files(self):
654        reader = self.reader('test-harness-files')
655        objs = self.read_topsrcdir(reader)
656
657        self.assertEqual(len(objs), 1)
658        self.assertIsInstance(objs[0], TestHarnessFiles)
659
660        expected = {
661            'mochitest': ['runtests.py', 'utils.py'],
662            'testing/mochitest': ['mochitest.py', 'mochitest.ini'],
663        }
664
665        for path, strings in objs[0].files.walk():
666            self.assertTrue(path in expected)
667            basenames = sorted(mozpath.basename(s) for s in strings)
668            self.assertEqual(sorted(expected[path]), basenames)
669
670    def test_test_harness_files_root(self):
671        reader = self.reader('test-harness-files-root')
672        with six.assertRaisesRegex(self, SandboxValidationError,
673                                   'Cannot install files to the root of TEST_HARNESS_FILES'):
674            self.read_topsrcdir(reader)
675
676    def test_program(self):
677        reader = self.reader('program')
678        objs = self.read_topsrcdir(reader)
679
680        self.assertEqual(len(objs), 6)
681        self.assertIsInstance(objs[0], Sources)
682        self.assertIsInstance(objs[1], ComputedFlags)
683        self.assertIsInstance(objs[2], ComputedFlags)
684        self.assertIsInstance(objs[3], Program)
685        self.assertIsInstance(objs[4], SimpleProgram)
686        self.assertIsInstance(objs[5], SimpleProgram)
687
688        self.assertEqual(objs[3].program, 'test_program.prog')
689        self.assertEqual(objs[4].program, 'test_program1.prog')
690        self.assertEqual(objs[5].program, 'test_program2.prog')
691
692        self.assertEqual(objs[3].name, 'test_program.prog')
693        self.assertEqual(objs[4].name, 'test_program1.prog')
694        self.assertEqual(objs[5].name, 'test_program2.prog')
695
696        self.assertEqual(objs[4].objs,
697                         [mozpath.join(reader.config.topobjdir,
698                                       'test_program1.%s' %
699                                       reader.config.substs['OBJ_SUFFIX'])])
700        self.assertEqual(objs[5].objs,
701                         [mozpath.join(reader.config.topobjdir,
702                                       'test_program2.%s' %
703                                       reader.config.substs['OBJ_SUFFIX'])])
704
705    def test_program_paths(self):
706        """Various moz.build settings that change the destination of PROGRAM should be
707        accurately reflected in Program.output_path."""
708        reader = self.reader('program-paths')
709        objs = self.read_topsrcdir(reader)
710        prog_paths = [o.output_path for o in objs if isinstance(o, Program)]
711        self.assertEqual(prog_paths, [
712            '!/dist/bin/dist-bin.prog',
713            '!/dist/bin/foo/dist-subdir.prog',
714            '!/final/target/final-target.prog',
715            '!not-installed.prog',
716        ])
717
718    def test_host_program_paths(self):
719        """The destination of a HOST_PROGRAM (almost always dist/host/bin)
720        should be accurately reflected in Program.output_path."""
721        reader = self.reader('host-program-paths')
722        objs = self.read_topsrcdir(reader)
723        prog_paths = [o.output_path for o in objs if isinstance(o, HostProgram)]
724        self.assertEqual(prog_paths, [
725            '!/dist/host/bin/final-target.hostprog',
726            '!/dist/host/bin/dist-host-bin.hostprog',
727            '!not-installed.hostprog',
728        ])
729
730    def test_test_manifest_missing_manifest(self):
731        """A missing manifest file should result in an error."""
732        reader = self.reader('test-manifest-missing-manifest')
733
734        with six.assertRaisesRegex(self, BuildReaderError, 'Missing files'):
735            self.read_topsrcdir(reader)
736
737    def test_empty_test_manifest_rejected(self):
738        """A test manifest without any entries is rejected."""
739        reader = self.reader('test-manifest-empty')
740
741        with six.assertRaisesRegex(self, SandboxValidationError, 'Empty test manifest'):
742            self.read_topsrcdir(reader)
743
744    def test_test_manifest_just_support_files(self):
745        """A test manifest with no tests but support-files is not supported."""
746        reader = self.reader('test-manifest-just-support')
747
748        with six.assertRaisesRegex(self, SandboxValidationError, 'Empty test manifest'):
749            self.read_topsrcdir(reader)
750
751    def test_test_manifest_dupe_support_files(self):
752        """A test manifest with dupe support-files in a single test is not
753        supported.
754        """
755        reader = self.reader('test-manifest-dupes')
756
757        with six.assertRaisesRegex(
758                self,
759                SandboxValidationError,
760                'bar.js appears multiple times '
761                'in a test manifest under a support-files field, please omit the duplicate entry.'
762        ):
763            self.read_topsrcdir(reader)
764
765    def test_test_manifest_absolute_support_files(self):
766        """Support files starting with '/' are placed relative to the install root"""
767        reader = self.reader('test-manifest-absolute-support')
768
769        objs = self.read_topsrcdir(reader)
770        self.assertEqual(len(objs), 1)
771        o = objs[0]
772        self.assertEqual(len(o.installs), 3)
773        expected = [
774            mozpath.normpath(mozpath.join(o.install_prefix, "../.well-known/foo.txt")),
775            mozpath.join(o.install_prefix, "absolute-support.ini"),
776            mozpath.join(o.install_prefix, "test_file.js"),
777        ]
778        paths = sorted([v[0] for v in o.installs.values()])
779        self.assertEqual(paths, expected)
780
781    @unittest.skip('Bug 1304316 - Items in the second set but not the first')
782    def test_test_manifest_shared_support_files(self):
783        """Support files starting with '!' are given separate treatment, so their
784        installation can be resolved when running tests.
785        """
786        reader = self.reader('test-manifest-shared-support')
787        supported, child = self.read_topsrcdir(reader)
788
789        expected_deferred_installs = {
790            '!/child/test_sub.js',
791            '!/child/another-file.sjs',
792            '!/child/data/**',
793        }
794
795        self.assertEqual(len(supported.installs), 3)
796        self.assertEqual(set(supported.deferred_installs),
797                         expected_deferred_installs)
798        self.assertEqual(len(child.installs), 3)
799        self.assertEqual(len(child.pattern_installs), 1)
800
801    def test_test_manifest_deffered_install_missing(self):
802        """A non-existent shared support file reference produces an error."""
803        reader = self.reader('test-manifest-shared-missing')
804
805        with six.assertRaisesRegex(self, SandboxValidationError,
806                                   'entry in support-files not present in the srcdir'):
807            self.read_topsrcdir(reader)
808
809    def test_test_manifest_install_includes(self):
810        """Ensure that any [include:foo.ini] are copied to the objdir."""
811        reader = self.reader('test-manifest-install-includes')
812
813        objs = self.read_topsrcdir(reader)
814        self.assertEqual(len(objs), 1)
815        o = objs[0]
816        self.assertEqual(len(o.installs), 3)
817        self.assertEqual(o.manifest_relpath, "mochitest.ini")
818        self.assertEqual(o.manifest_obj_relpath, "mochitest.ini")
819        expected = [
820            mozpath.normpath(mozpath.join(o.install_prefix, "common.ini")),
821            mozpath.normpath(mozpath.join(o.install_prefix, "mochitest.ini")),
822            mozpath.normpath(mozpath.join(o.install_prefix, "test_foo.html")),
823        ]
824        paths = sorted([v[0] for v in o.installs.values()])
825        self.assertEqual(paths, expected)
826
827    def test_test_manifest_includes(self):
828        """Ensure that manifest objects from the emitter list a correct manifest.
829        """
830        reader = self.reader('test-manifest-emitted-includes')
831        [obj] = self.read_topsrcdir(reader)
832
833        # Expected manifest leafs for our tests.
834        expected_manifests = {
835            'reftest1.html': 'reftest.list',
836            'reftest1-ref.html': 'reftest.list',
837            'reftest2.html': 'included-reftest.list',
838            'reftest2-ref.html': 'included-reftest.list',
839        }
840
841        for t in obj.tests:
842            self.assertTrue(t['manifest'].endswith(expected_manifests[t['name']]))
843
844    def test_test_manifest_keys_extracted(self):
845        """Ensure all metadata from test manifests is extracted."""
846        reader = self.reader('test-manifest-keys-extracted')
847
848        objs = [o for o in self.read_topsrcdir(reader)
849                if isinstance(o, TestManifest)]
850
851        self.assertEqual(len(objs), 8)
852
853        metadata = {
854            'a11y.ini': {
855                'flavor': 'a11y',
856                'installs': {
857                    'a11y.ini': False,
858                    'test_a11y.js': True,
859                },
860                'pattern-installs': 1,
861            },
862            'browser.ini': {
863                'flavor': 'browser-chrome',
864                'installs': {
865                    'browser.ini': False,
866                    'test_browser.js': True,
867                    'support1': False,
868                    'support2': False,
869                },
870            },
871            'mochitest.ini': {
872                'flavor': 'mochitest',
873                'installs': {
874                    'mochitest.ini': False,
875                    'test_mochitest.js': True,
876                },
877                'external': {
878                    'external1',
879                    'external2',
880                },
881            },
882            'chrome.ini': {
883                'flavor': 'chrome',
884                'installs': {
885                    'chrome.ini': False,
886                    'test_chrome.js': True,
887                },
888            },
889            'xpcshell.ini': {
890                'flavor': 'xpcshell',
891                'dupe': True,
892                'installs': {
893                    'xpcshell.ini': False,
894                    'test_xpcshell.js': True,
895                    'head1': False,
896                    'head2': False,
897                },
898            },
899            'reftest.list': {
900                'flavor': 'reftest',
901                'installs': {},
902            },
903            'crashtest.list': {
904                'flavor': 'crashtest',
905                'installs': {},
906            },
907            'python.ini': {
908                'flavor': 'python',
909                'installs': {
910                    'python.ini': False,
911                },
912            }
913        }
914
915        for o in objs:
916            m = metadata[mozpath.basename(o.manifest_relpath)]
917
918            self.assertTrue(o.path.startswith(o.directory))
919            self.assertEqual(o.flavor, m['flavor'])
920            self.assertEqual(o.dupe_manifest, m.get('dupe', False))
921
922            external_normalized = set(mozpath.basename(p) for p in
923                                      o.external_installs)
924            self.assertEqual(external_normalized, m.get('external', set()))
925
926            self.assertEqual(len(o.installs), len(m['installs']))
927            for path in o.installs.keys():
928                self.assertTrue(path.startswith(o.directory))
929                relpath = path[len(o.directory)+1:]
930
931                self.assertIn(relpath, m['installs'])
932                self.assertEqual(o.installs[path][1], m['installs'][relpath])
933
934            if 'pattern-installs' in m:
935                self.assertEqual(len(o.pattern_installs), m['pattern-installs'])
936
937    def test_test_manifest_unmatched_generated(self):
938        reader = self.reader('test-manifest-unmatched-generated')
939
940        with six.assertRaisesRegex(self, SandboxValidationError,
941                                   'entry in generated-files not present elsewhere'):
942            self.read_topsrcdir(reader),
943
944    def test_test_manifest_parent_support_files_dir(self):
945        """support-files referencing a file in a parent directory works."""
946        reader = self.reader('test-manifest-parent-support-files-dir')
947
948        objs = [o for o in self.read_topsrcdir(reader)
949                if isinstance(o, TestManifest)]
950
951        self.assertEqual(len(objs), 1)
952
953        o = objs[0]
954
955        expected = mozpath.join(o.srcdir, 'support-file.txt')
956        self.assertIn(expected, o.installs)
957        self.assertEqual(o.installs[expected],
958                         ('testing/mochitest/tests/child/support-file.txt', False))
959
960    def test_test_manifest_missing_test_error(self):
961        """Missing test files should result in error."""
962        reader = self.reader('test-manifest-missing-test-file')
963
964        with six.assertRaisesRegex(self, SandboxValidationError,
965                                   'lists test that does not exist: test_missing.html'):
966            self.read_topsrcdir(reader)
967
968    def test_test_manifest_missing_test_error_unfiltered(self):
969        """Missing test files should result in error, even when the test list is not filtered."""
970        reader = self.reader('test-manifest-missing-test-file-unfiltered')
971
972        with six.assertRaisesRegex(self, SandboxValidationError,
973                                   'lists test that does not exist: missing.js'):
974            self.read_topsrcdir(reader)
975
976    def test_ipdl_sources(self):
977        reader = self.reader('ipdl_sources',
978                             extra_substs={'IPDL_ROOT': mozpath.abspath('/path/to/topobjdir')})
979        objs = self.read_topsrcdir(reader)
980        ipdl_collection = objs[0]
981        self.assertIsInstance(ipdl_collection, IPDLCollection)
982
983        ipdls = set(mozpath.relpath(p, ipdl_collection.topsrcdir)
984                    for p in ipdl_collection.all_regular_sources())
985        expected = set([
986            'bar/bar.ipdl',
987            'bar/bar2.ipdlh',
988            'foo/foo.ipdl',
989            'foo/foo2.ipdlh',
990        ])
991
992        self.assertEqual(ipdls, expected)
993
994        pp_ipdls = set(mozpath.relpath(p, ipdl_collection.topsrcdir)
995                       for p in ipdl_collection.all_preprocessed_sources())
996        expected = set([
997            'bar/bar1.ipdl',
998            'foo/foo1.ipdl',
999        ])
1000        self.assertEqual(pp_ipdls, expected)
1001
1002        generated_sources = set(ipdl_collection.all_generated_sources())
1003        expected = set([
1004            'bar.cpp',
1005            'barChild.cpp',
1006            'barParent.cpp',
1007            'bar1.cpp',
1008            'bar1Child.cpp',
1009            'bar1Parent.cpp',
1010            'bar2.cpp',
1011            'foo.cpp',
1012            'fooChild.cpp',
1013            'fooParent.cpp',
1014            'foo1.cpp',
1015            'foo1Child.cpp',
1016            'foo1Parent.cpp',
1017            'foo2.cpp'
1018        ])
1019        self.assertEqual(generated_sources, expected)
1020
1021    def test_local_includes(self):
1022        """Test that LOCAL_INCLUDES is emitted correctly."""
1023        reader = self.reader('local_includes')
1024        objs = self.read_topsrcdir(reader)
1025
1026        local_includes = [o.path for o in objs if isinstance(o, LocalInclude)]
1027        expected = [
1028            '/bar/baz',
1029            'foo',
1030        ]
1031
1032        self.assertEqual(local_includes, expected)
1033
1034        local_includes = [o.path.full_path
1035                          for o in objs if isinstance(o, LocalInclude)]
1036        expected = [
1037            mozpath.join(reader.config.topsrcdir, 'bar/baz'),
1038            mozpath.join(reader.config.topsrcdir, 'foo'),
1039        ]
1040
1041        self.assertEqual(local_includes, expected)
1042
1043    def test_local_includes_invalid(self):
1044        """Test that invalid LOCAL_INCLUDES are properly detected."""
1045        reader = self.reader('local_includes-invalid/srcdir')
1046
1047        with six.assertRaisesRegex(
1048                self,
1049                SandboxValidationError,
1050                'Path specified in LOCAL_INCLUDES.*resolves to the '
1051                'topsrcdir or topobjdir'):
1052            self.read_topsrcdir(reader)
1053
1054        reader = self.reader('local_includes-invalid/objdir')
1055
1056        with six.assertRaisesRegex(
1057                self,
1058                SandboxValidationError,
1059                'Path specified in LOCAL_INCLUDES.*resolves to the '
1060                'topsrcdir or topobjdir'):
1061            self.read_topsrcdir(reader)
1062
1063    def test_local_includes_file(self):
1064        """Test that a filename can't be used in LOCAL_INCLUDES."""
1065        reader = self.reader('local_includes-filename')
1066
1067        with six.assertRaisesRegex(
1068                self,
1069                SandboxValidationError,
1070                'Path specified in LOCAL_INCLUDES is a filename'):
1071            self.read_topsrcdir(reader)
1072
1073    def test_generated_includes(self):
1074        """Test that GENERATED_INCLUDES is emitted correctly."""
1075        reader = self.reader('generated_includes')
1076        objs = self.read_topsrcdir(reader)
1077
1078        generated_includes = [o.path for o in objs if isinstance(o, LocalInclude)]
1079        expected = [
1080            '!/bar/baz',
1081            '!foo',
1082        ]
1083
1084        self.assertEqual(generated_includes, expected)
1085
1086        generated_includes = [o.path.full_path
1087                              for o in objs if isinstance(o, LocalInclude)]
1088        expected = [
1089            mozpath.join(reader.config.topobjdir, 'bar/baz'),
1090            mozpath.join(reader.config.topobjdir, 'foo'),
1091        ]
1092
1093        self.assertEqual(generated_includes, expected)
1094
1095    def test_defines(self):
1096        reader = self.reader('defines')
1097        objs = self.read_topsrcdir(reader)
1098
1099        defines = {}
1100        for o in objs:
1101            if isinstance(o, Defines):
1102                defines = o.defines
1103
1104        expected = {
1105            'BAR': 7,
1106            'BAZ': '"abcd"',
1107            'FOO': True,
1108            'VALUE': 'xyz',
1109            'QUX': False,
1110        }
1111
1112        self.assertEqual(defines, expected)
1113
1114    def test_jar_manifests(self):
1115        reader = self.reader('jar-manifests')
1116        objs = self.read_topsrcdir(reader)
1117
1118        self.assertEqual(len(objs), 1)
1119        for obj in objs:
1120            self.assertIsInstance(obj, JARManifest)
1121            self.assertIsInstance(obj.path, Path)
1122
1123    def test_jar_manifests_multiple_files(self):
1124        with six.assertRaisesRegex(self, SandboxValidationError, 'limited to one value'):
1125            reader = self.reader('jar-manifests-multiple-files')
1126            self.read_topsrcdir(reader)
1127
1128    def test_xpidl_module_no_sources(self):
1129        """XPIDL_MODULE without XPIDL_SOURCES should be rejected."""
1130        with six.assertRaisesRegex(self, SandboxValidationError, 'XPIDL_MODULE '
1131                                   'cannot be defined'):
1132            reader = self.reader('xpidl-module-no-sources')
1133            self.read_topsrcdir(reader)
1134
1135    def test_xpidl_module_missing_sources(self):
1136        """Missing XPIDL_SOURCES should be rejected."""
1137        with six.assertRaisesRegex(self, SandboxValidationError, 'File .* '
1138                                   'from XPIDL_SOURCES does not exist'):
1139            reader = self.reader('missing-xpidl')
1140            self.read_topsrcdir(reader)
1141
1142    def test_missing_local_includes(self):
1143        """LOCAL_INCLUDES containing non-existent directories should be rejected."""
1144        with six.assertRaisesRegex(self, SandboxValidationError, 'Path specified in '
1145                                   'LOCAL_INCLUDES does not exist'):
1146            reader = self.reader('missing-local-includes')
1147            self.read_topsrcdir(reader)
1148
1149    def test_library_defines(self):
1150        """Test that LIBRARY_DEFINES is propagated properly."""
1151        reader = self.reader('library-defines')
1152        objs = self.read_topsrcdir(reader)
1153
1154        libraries = [o for o in objs if isinstance(o, StaticLibrary)]
1155        library_flags = [o for o in objs if isinstance(o, ComputedFlags)
1156                         and 'LIBRARY_DEFINES' in o.flags]
1157        expected = {
1158            'liba': '-DIN_LIBA',
1159            'libb': '-DIN_LIBB -DIN_LIBA',
1160            'libc': '-DIN_LIBA -DIN_LIBB',
1161            'libd': ''
1162        }
1163        defines = {}
1164        for lib in libraries:
1165            defines[lib.basename] = ' '.join(lib.lib_defines.get_defines())
1166        self.assertEqual(expected, defines)
1167        defines_in_flags = {}
1168        for flags in library_flags:
1169            defines_in_flags[flags.relobjdir] = ' '.join(flags.flags['LIBRARY_DEFINES'] or [])
1170        self.assertEqual(expected, defines_in_flags)
1171
1172    def test_sources(self):
1173        """Test that SOURCES works properly."""
1174        reader = self.reader('sources')
1175        objs = self.read_topsrcdir(reader)
1176
1177        as_flags = objs.pop()
1178        self.assertIsInstance(as_flags, ComputedFlags)
1179        computed_flags = objs.pop()
1180        self.assertIsInstance(computed_flags, ComputedFlags)
1181        # The third to last object is a Linkable.
1182        linkable = objs.pop()
1183        self.assertTrue(linkable.cxx_link)
1184        ld_flags = objs.pop()
1185        self.assertIsInstance(ld_flags, ComputedFlags)
1186        self.assertEqual(len(objs), 6)
1187        for o in objs:
1188            self.assertIsInstance(o, Sources)
1189
1190        suffix_map = {obj.canonical_suffix: obj for obj in objs}
1191        self.assertEqual(len(suffix_map), 6)
1192
1193        expected = {
1194            '.cpp': ['a.cpp', 'b.cc', 'c.cxx'],
1195            '.c': ['d.c'],
1196            '.m': ['e.m'],
1197            '.mm': ['f.mm'],
1198            '.S': ['g.S'],
1199            '.s': ['h.s', 'i.asm'],
1200        }
1201        for suffix, files in expected.items():
1202            sources = suffix_map[suffix]
1203            self.assertEqual(
1204                sources.files,
1205                [mozpath.join(reader.config.topsrcdir, f) for f in files])
1206
1207            for f in files:
1208                self.assertIn(mozpath.join(reader.config.topobjdir,
1209                                           '%s.%s' % (mozpath.splitext(f)[0],
1210                                                      reader.config.substs['OBJ_SUFFIX'])),
1211                              linkable.objs)
1212
1213    def test_sources_just_c(self):
1214        """Test that a linkable with no C++ sources doesn't have cxx_link set."""
1215        reader = self.reader('sources-just-c')
1216        objs = self.read_topsrcdir(reader)
1217
1218        as_flags = objs.pop()
1219        self.assertIsInstance(as_flags, ComputedFlags)
1220        flags = objs.pop()
1221        self.assertIsInstance(flags, ComputedFlags)
1222        # The third to last object is a Linkable.
1223        linkable = objs.pop()
1224        self.assertFalse(linkable.cxx_link)
1225
1226    def test_linkables_cxx_link(self):
1227        """Test that linkables transitively set cxx_link properly."""
1228        reader = self.reader('test-linkables-cxx-link')
1229        got_results = 0
1230        for obj in self.read_topsrcdir(reader):
1231            if isinstance(obj, SharedLibrary):
1232                if obj.basename == 'cxx_shared':
1233                    self.assertEquals(obj.name, '%scxx_shared%s' %
1234                                      (reader.config.dll_prefix,
1235                                       reader.config.dll_suffix))
1236                    self.assertTrue(obj.cxx_link)
1237                    got_results += 1
1238                elif obj.basename == 'just_c_shared':
1239                    self.assertEquals(obj.name, '%sjust_c_shared%s' %
1240                                      (reader.config.dll_prefix,
1241                                       reader.config.dll_suffix))
1242                    self.assertFalse(obj.cxx_link)
1243                    got_results += 1
1244        self.assertEqual(got_results, 2)
1245
1246    def test_generated_sources(self):
1247        """Test that GENERATED_SOURCES works properly."""
1248        reader = self.reader('generated-sources')
1249        objs = self.read_topsrcdir(reader)
1250
1251        as_flags = objs.pop()
1252        self.assertIsInstance(as_flags, ComputedFlags)
1253        flags = objs.pop()
1254        self.assertIsInstance(flags, ComputedFlags)
1255        # The third to last object is a Linkable.
1256        linkable = objs.pop()
1257        self.assertTrue(linkable.cxx_link)
1258        flags = objs.pop()
1259        self.assertIsInstance(flags, ComputedFlags)
1260        self.assertEqual(len(objs), 6)
1261
1262        generated_sources = [o for o in objs if isinstance(o, GeneratedSources)]
1263        self.assertEqual(len(generated_sources), 6)
1264
1265        suffix_map = {obj.canonical_suffix: obj for obj in generated_sources}
1266        self.assertEqual(len(suffix_map), 6)
1267
1268        expected = {
1269            '.cpp': ['a.cpp', 'b.cc', 'c.cxx'],
1270            '.c': ['d.c'],
1271            '.m': ['e.m'],
1272            '.mm': ['f.mm'],
1273            '.S': ['g.S'],
1274            '.s': ['h.s', 'i.asm'],
1275        }
1276        for suffix, files in expected.items():
1277            sources = suffix_map[suffix]
1278            self.assertEqual(
1279                sources.files,
1280                [mozpath.join(reader.config.topobjdir, f) for f in files])
1281
1282            for f in files:
1283                self.assertIn(mozpath.join(reader.config.topobjdir,
1284                                           '%s.%s' % (mozpath.splitext(f)[0],
1285                                                      reader.config.substs['OBJ_SUFFIX'])),
1286                              linkable.objs)
1287
1288    def test_host_sources(self):
1289        """Test that HOST_SOURCES works properly."""
1290        reader = self.reader('host-sources')
1291        objs = self.read_topsrcdir(reader)
1292
1293        # This objdir will generate target flags.
1294        flags = objs.pop()
1295        self.assertIsInstance(flags, ComputedFlags)
1296        # The second to last object is a Linkable
1297        linkable = objs.pop()
1298        self.assertTrue(linkable.cxx_link)
1299        # This objdir will also generate host flags.
1300        host_flags = objs.pop()
1301        self.assertIsInstance(host_flags, ComputedFlags)
1302        # ...and ldflags.
1303        ldflags = objs.pop()
1304        self.assertIsInstance(ldflags, ComputedFlags)
1305        self.assertEqual(len(objs), 3)
1306        for o in objs:
1307            self.assertIsInstance(o, HostSources)
1308
1309        suffix_map = {obj.canonical_suffix: obj for obj in objs}
1310        self.assertEqual(len(suffix_map), 3)
1311
1312        expected = {
1313            '.cpp': ['a.cpp', 'b.cc', 'c.cxx'],
1314            '.c': ['d.c'],
1315            '.mm': ['e.mm', 'f.mm'],
1316        }
1317        for suffix, files in expected.items():
1318            sources = suffix_map[suffix]
1319            self.assertEqual(
1320                sources.files,
1321                [mozpath.join(reader.config.topsrcdir, f) for f in files])
1322
1323            for f in files:
1324                self.assertIn(mozpath.join(reader.config.topobjdir,
1325                                           'host_%s.%s' % (mozpath.splitext(f)[0],
1326                                                           reader.config.substs['OBJ_SUFFIX'])),
1327                              linkable.objs)
1328
1329    def test_wasm_sources(self):
1330        """Test that WASM_SOURCES works properly."""
1331        reader = self.reader('wasm-sources', extra_substs={'OS_TARGET': 'Linux'})
1332        objs = list(self.read_topsrcdir(reader))
1333
1334        # The second to last object is a linkable.
1335        linkable = objs[-2]
1336        # Other than that, we only care about the WasmSources objects.
1337        objs = objs[:2]
1338        for o in objs:
1339            self.assertIsInstance(o, WasmSources)
1340
1341        suffix_map = {obj.canonical_suffix: obj for obj in objs}
1342        self.assertEqual(len(suffix_map), 2)
1343
1344        expected = {
1345            '.cpp': ['a.cpp', 'b.cc', 'c.cxx'],
1346            '.c': ['d.c'],
1347        }
1348        for suffix, files in expected.items():
1349            sources = suffix_map[suffix]
1350            self.assertEqual(
1351                sources.files,
1352                [mozpath.join(reader.config.topsrcdir, f) for f in files] +
1353                ([mozpath.join(
1354                    reader.config.topsrcdir,
1355                    'third_party/rust/rlbox_lucet_sandbox/c_src/lucet_sandbox_wrapper.c')]
1356                 if suffix == '.c' else []))
1357            for f in files:
1358                self.assertIn(mozpath.join(
1359                    reader.config.topobjdir,
1360                    '%s.%s' % (mozpath.splitext(f)[0],
1361                               reader.config.substs['WASM_OBJ_SUFFIX'])),
1362                              linkable.objs)
1363
1364    def test_unified_sources(self):
1365        """Test that UNIFIED_SOURCES works properly."""
1366        reader = self.reader('unified-sources')
1367        objs = self.read_topsrcdir(reader)
1368
1369        # The last object is a ComputedFlags, the second to last a Linkable,
1370        # followed by ldflags, ignore them.
1371        linkable = objs[-2]
1372        objs = objs[:-3]
1373        self.assertEqual(len(objs), 3)
1374        for o in objs:
1375            self.assertIsInstance(o, UnifiedSources)
1376
1377        suffix_map = {obj.canonical_suffix: obj for obj in objs}
1378        self.assertEqual(len(suffix_map), 3)
1379
1380        expected = {
1381            '.cpp': ['bar.cxx', 'foo.cpp', 'quux.cc'],
1382            '.mm': ['objc1.mm', 'objc2.mm'],
1383            '.c': ['c1.c', 'c2.c'],
1384        }
1385        for suffix, files in expected.items():
1386            sources = suffix_map[suffix]
1387            self.assertEqual(
1388                sources.files,
1389                [mozpath.join(reader.config.topsrcdir, f) for f in files])
1390            self.assertTrue(sources.have_unified_mapping)
1391
1392            for f in dict(sources.unified_source_mapping).keys():
1393                self.assertIn(mozpath.join(reader.config.topobjdir,
1394                                           '%s.%s' % (mozpath.splitext(f)[0],
1395                                                      reader.config.substs['OBJ_SUFFIX'])),
1396                              linkable.objs)
1397
1398    def test_unified_sources_non_unified(self):
1399        """Test that UNIFIED_SOURCES with FILES_PER_UNIFIED_FILE=1 works properly."""
1400        reader = self.reader('unified-sources-non-unified')
1401        objs = self.read_topsrcdir(reader)
1402
1403        # The last object is a Linkable, the second to last ComputedFlags,
1404        # followed by ldflags, ignore them.
1405        objs = objs[:-3]
1406        self.assertEqual(len(objs), 3)
1407        for o in objs:
1408            self.assertIsInstance(o, UnifiedSources)
1409
1410        suffix_map = {obj.canonical_suffix: obj for obj in objs}
1411        self.assertEqual(len(suffix_map), 3)
1412
1413        expected = {
1414            '.cpp': ['bar.cxx', 'foo.cpp', 'quux.cc'],
1415            '.mm': ['objc1.mm', 'objc2.mm'],
1416            '.c': ['c1.c', 'c2.c'],
1417        }
1418        for suffix, files in expected.items():
1419            sources = suffix_map[suffix]
1420            self.assertEqual(
1421                sources.files,
1422                [mozpath.join(reader.config.topsrcdir, f) for f in files])
1423            self.assertFalse(sources.have_unified_mapping)
1424
1425    def test_final_target_pp_files(self):
1426        """Test that FINAL_TARGET_PP_FILES works properly."""
1427        reader = self.reader('dist-files')
1428        objs = self.read_topsrcdir(reader)
1429
1430        self.assertEqual(len(objs), 1)
1431        self.assertIsInstance(objs[0], FinalTargetPreprocessedFiles)
1432
1433        # Ideally we'd test hierarchies, but that would just be testing
1434        # the HierarchicalStringList class, which we test separately.
1435        for path, files in objs[0].files.walk():
1436            self.assertEqual(path, '')
1437            self.assertEqual(len(files), 2)
1438
1439            expected = {'install.rdf', 'main.js'}
1440            for f in files:
1441                self.assertTrue(six.text_type(f) in expected)
1442
1443    def test_missing_final_target_pp_files(self):
1444        """Test that FINAL_TARGET_PP_FILES with missing files throws errors."""
1445        with six.assertRaisesRegex(self, SandboxValidationError, 'File listed in '
1446                                   'FINAL_TARGET_PP_FILES does not exist'):
1447            reader = self.reader('dist-files-missing')
1448            self.read_topsrcdir(reader)
1449
1450    def test_final_target_pp_files_non_srcdir(self):
1451        '''Test that non-srcdir paths in FINAL_TARGET_PP_FILES throws errors.'''
1452        reader = self.reader('final-target-pp-files-non-srcdir')
1453        with six.assertRaisesRegex(
1454                self,
1455                SandboxValidationError,
1456                'Only source directory paths allowed in FINAL_TARGET_PP_FILES:'
1457        ):
1458            self.read_topsrcdir(reader)
1459
1460    def test_localized_files(self):
1461        """Test that LOCALIZED_FILES works properly."""
1462        reader = self.reader('localized-files')
1463        objs = self.read_topsrcdir(reader)
1464
1465        self.assertEqual(len(objs), 1)
1466        self.assertIsInstance(objs[0], LocalizedFiles)
1467
1468        for path, files in objs[0].files.walk():
1469            self.assertEqual(path, 'foo')
1470            self.assertEqual(len(files), 3)
1471
1472            expected = {'en-US/bar.ini', 'en-US/code/*.js', 'en-US/foo.js'}
1473            for f in files:
1474                self.assertTrue(six.text_type(f) in expected)
1475
1476    def test_localized_files_no_en_us(self):
1477        """Test that LOCALIZED_FILES errors if a path does not start with
1478        `en-US/` or contain `locales/en-US/`."""
1479        reader = self.reader('localized-files-no-en-us')
1480        with six.assertRaisesRegex(
1481                self,
1482                SandboxValidationError,
1483                'LOCALIZED_FILES paths must start with `en-US/` or contain `locales/en-US/`: '
1484                'foo.js'
1485        ):
1486            self.read_topsrcdir(reader)
1487
1488    def test_localized_pp_files(self):
1489        """Test that LOCALIZED_PP_FILES works properly."""
1490        reader = self.reader('localized-pp-files')
1491        objs = self.read_topsrcdir(reader)
1492
1493        self.assertEqual(len(objs), 1)
1494        self.assertIsInstance(objs[0], LocalizedPreprocessedFiles)
1495
1496        for path, files in objs[0].files.walk():
1497            self.assertEqual(path, 'foo')
1498            self.assertEqual(len(files), 2)
1499
1500            expected = {'en-US/bar.ini', 'en-US/foo.js'}
1501            for f in files:
1502                self.assertTrue(six.text_type(f) in expected)
1503
1504    def test_rust_library_no_cargo_toml(self):
1505        '''Test that defining a RustLibrary without a Cargo.toml fails.'''
1506        reader = self.reader('rust-library-no-cargo-toml')
1507        with six.assertRaisesRegex(self, SandboxValidationError,
1508                                   'No Cargo.toml file found'):
1509            self.read_topsrcdir(reader)
1510
1511    def test_rust_library_name_mismatch(self):
1512        '''Test that defining a RustLibrary that doesn't match Cargo.toml fails.'''
1513        reader = self.reader('rust-library-name-mismatch')
1514        with six.assertRaisesRegex(self, SandboxValidationError,
1515                                   'library.*does not match Cargo.toml-defined package'):
1516            self.read_topsrcdir(reader)
1517
1518    def test_rust_library_no_lib_section(self):
1519        '''Test that a RustLibrary Cargo.toml with no [lib] section fails.'''
1520        reader = self.reader('rust-library-no-lib-section')
1521        with six.assertRaisesRegex(self, SandboxValidationError,
1522                                   'Cargo.toml for.* has no \\[lib\\] section'):
1523            self.read_topsrcdir(reader)
1524
1525    def test_rust_library_invalid_crate_type(self):
1526        '''Test that a RustLibrary Cargo.toml has a permitted crate-type.'''
1527        reader = self.reader('rust-library-invalid-crate-type')
1528        with six.assertRaisesRegex(self, SandboxValidationError,
1529                                   'crate-type.* is not permitted'):
1530            self.read_topsrcdir(reader)
1531
1532    def test_rust_library_dash_folding(self):
1533        '''Test that on-disk names of RustLibrary objects convert dashes to underscores.'''
1534        reader = self.reader('rust-library-dash-folding',
1535                             extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
1536        objs = self.read_topsrcdir(reader)
1537
1538        self.assertEqual(len(objs), 3)
1539        ldflags, lib, cflags = objs
1540        self.assertIsInstance(ldflags, ComputedFlags)
1541        self.assertIsInstance(cflags, ComputedFlags)
1542        self.assertIsInstance(lib, RustLibrary)
1543        self.assertRegexpMatches(lib.lib_name, "random_crate")
1544        self.assertRegexpMatches(lib.import_name, "random_crate")
1545        self.assertRegexpMatches(lib.basename, "random-crate")
1546
1547    def test_multiple_rust_libraries(self):
1548        '''Test that linking multiple Rust libraries throws an error'''
1549        reader = self.reader('multiple-rust-libraries',
1550                             extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
1551        with six.assertRaisesRegex(
1552                self,
1553                SandboxValidationError,
1554                'Cannot link the following Rust libraries'):
1555            self.read_topsrcdir(reader)
1556
1557    def test_rust_library_features(self):
1558        '''Test that RustLibrary features are correctly emitted.'''
1559        reader = self.reader('rust-library-features',
1560                             extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
1561        objs = self.read_topsrcdir(reader)
1562
1563        self.assertEqual(len(objs), 3)
1564        ldflags, lib, cflags = objs
1565        self.assertIsInstance(ldflags, ComputedFlags)
1566        self.assertIsInstance(cflags, ComputedFlags)
1567        self.assertIsInstance(lib, RustLibrary)
1568        self.assertEqual(lib.features, ['musthave', 'cantlivewithout'])
1569
1570    def test_rust_library_duplicate_features(self):
1571        '''Test that duplicate RustLibrary features are rejected.'''
1572        reader = self.reader('rust-library-duplicate-features')
1573        with six.assertRaisesRegex(self, SandboxValidationError,
1574                                   'features for .* should not contain duplicates'):
1575            self.read_topsrcdir(reader)
1576
1577    def test_rust_program_no_cargo_toml(self):
1578        '''Test that specifying RUST_PROGRAMS without a Cargo.toml fails.'''
1579        reader = self.reader('rust-program-no-cargo-toml')
1580        with six.assertRaisesRegex(self, SandboxValidationError,
1581                                   'No Cargo.toml file found'):
1582            self.read_topsrcdir(reader)
1583
1584    def test_host_rust_program_no_cargo_toml(self):
1585        '''Test that specifying HOST_RUST_PROGRAMS without a Cargo.toml fails.'''
1586        reader = self.reader('host-rust-program-no-cargo-toml')
1587        with six.assertRaisesRegex(self, SandboxValidationError,
1588                                   'No Cargo.toml file found'):
1589            self.read_topsrcdir(reader)
1590
1591    def test_rust_program_nonexistent_name(self):
1592        '''Test that specifying RUST_PROGRAMS that don't exist in Cargo.toml
1593        correctly throws an error.'''
1594        reader = self.reader('rust-program-nonexistent-name')
1595        with six.assertRaisesRegex(self, SandboxValidationError,
1596                                   'Cannot find Cargo.toml definition for'):
1597            self.read_topsrcdir(reader)
1598
1599    def test_host_rust_program_nonexistent_name(self):
1600        '''Test that specifying HOST_RUST_PROGRAMS that don't exist in
1601        Cargo.toml correctly throws an error.'''
1602        reader = self.reader('host-rust-program-nonexistent-name')
1603        with six.assertRaisesRegex(self, SandboxValidationError,
1604                                   'Cannot find Cargo.toml definition for'):
1605            self.read_topsrcdir(reader)
1606
1607    def test_rust_programs(self):
1608        '''Test RUST_PROGRAMS emission.'''
1609        reader = self.reader('rust-programs',
1610                             extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc',
1611                                               BIN_SUFFIX='.exe'))
1612        objs = self.read_topsrcdir(reader)
1613
1614        self.assertEqual(len(objs), 3)
1615        ldflags, cflags, prog = objs
1616        self.assertIsInstance(ldflags, ComputedFlags)
1617        self.assertIsInstance(cflags, ComputedFlags)
1618        self.assertIsInstance(prog, RustProgram)
1619        self.assertEqual(prog.name, 'some')
1620
1621    def test_host_rust_programs(self):
1622        '''Test HOST_RUST_PROGRAMS emission.'''
1623        reader = self.reader('host-rust-programs',
1624                             extra_substs=dict(RUST_HOST_TARGET='i686-pc-windows-msvc',
1625                                               HOST_BIN_SUFFIX='.exe'))
1626        objs = self.read_topsrcdir(reader)
1627
1628        self.assertEqual(len(objs), 4)
1629        print(objs)
1630        ldflags, cflags, hostflags, prog = objs
1631        self.assertIsInstance(ldflags, ComputedFlags)
1632        self.assertIsInstance(cflags, ComputedFlags)
1633        self.assertIsInstance(hostflags, ComputedFlags)
1634        self.assertIsInstance(prog, HostRustProgram)
1635        self.assertEqual(prog.name, 'some')
1636
1637    def test_host_rust_libraries(self):
1638        '''Test HOST_RUST_LIBRARIES emission.'''
1639        reader = self.reader('host-rust-libraries',
1640                             extra_substs=dict(RUST_HOST_TARGET='i686-pc-windows-msvc',
1641                                               HOST_BIN_SUFFIX='.exe'))
1642        objs = self.read_topsrcdir(reader)
1643
1644        self.assertEqual(len(objs), 3)
1645        ldflags, lib, cflags = objs
1646        self.assertIsInstance(ldflags, ComputedFlags)
1647        self.assertIsInstance(cflags, ComputedFlags)
1648        self.assertIsInstance(lib, HostRustLibrary)
1649        self.assertRegexpMatches(lib.lib_name, 'host_lib')
1650        self.assertRegexpMatches(lib.import_name, 'host_lib')
1651
1652    def test_crate_dependency_path_resolution(self):
1653        '''Test recursive dependencies resolve with the correct paths.'''
1654        reader = self.reader('crate-dependency-path-resolution',
1655                             extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
1656        objs = self.read_topsrcdir(reader)
1657
1658        self.assertEqual(len(objs), 3)
1659        ldflags, lib, cflags = objs
1660        self.assertIsInstance(ldflags, ComputedFlags)
1661        self.assertIsInstance(cflags, ComputedFlags)
1662        self.assertIsInstance(lib, RustLibrary)
1663
1664    def test_install_shared_lib(self):
1665        """Test that we can install a shared library with TEST_HARNESS_FILES"""
1666        reader = self.reader('test-install-shared-lib')
1667        objs = self.read_topsrcdir(reader)
1668        self.assertIsInstance(objs[0], TestHarnessFiles)
1669        self.assertIsInstance(objs[1], VariablePassthru)
1670        self.assertIsInstance(objs[2], ComputedFlags)
1671        self.assertIsInstance(objs[3], SharedLibrary)
1672        self.assertIsInstance(objs[4], ComputedFlags)
1673        for path, files in objs[0].files.walk():
1674            for f in files:
1675                self.assertEqual(str(f), '!libfoo.so')
1676                self.assertEqual(path, 'foo/bar')
1677
1678    def test_symbols_file(self):
1679        """Test that SYMBOLS_FILE works"""
1680        reader = self.reader('test-symbols-file')
1681        genfile, ldflags, shlib, flags = self.read_topsrcdir(reader)
1682        self.assertIsInstance(genfile, GeneratedFile)
1683        self.assertIsInstance(flags, ComputedFlags)
1684        self.assertIsInstance(ldflags, ComputedFlags)
1685        self.assertIsInstance(shlib, SharedLibrary)
1686        # This looks weird but MockConfig sets DLL_{PREFIX,SUFFIX} and
1687        # the reader method in this class sets OS_TARGET=WINNT.
1688        self.assertEqual(shlib.symbols_file, 'libfoo.so.def')
1689
1690    def test_symbols_file_objdir(self):
1691        """Test that a SYMBOLS_FILE in the objdir works"""
1692        reader = self.reader('test-symbols-file-objdir')
1693        genfile, ldflags, shlib, flags = self.read_topsrcdir(reader)
1694        self.assertIsInstance(genfile, GeneratedFile)
1695        self.assertEqual(genfile.script,
1696                         mozpath.join(reader.config.topsrcdir, 'foo.py'))
1697        self.assertIsInstance(flags, ComputedFlags)
1698        self.assertIsInstance(ldflags, ComputedFlags)
1699        self.assertIsInstance(shlib, SharedLibrary)
1700        self.assertEqual(shlib.symbols_file, 'foo.symbols')
1701
1702    def test_symbols_file_objdir_missing_generated(self):
1703        """Test that a SYMBOLS_FILE in the objdir that's missing
1704        from GENERATED_FILES is an error.
1705        """
1706        reader = self.reader('test-symbols-file-objdir-missing-generated')
1707        with six.assertRaisesRegex(
1708                self,
1709                SandboxValidationError,
1710                'Objdir file specified in SYMBOLS_FILE not in GENERATED_FILES:'
1711        ):
1712            self.read_topsrcdir(reader)
1713
1714    def test_wasm_compile_flags(self):
1715        reader = self.reader('wasm-compile-flags', extra_substs={'OS_TARGET': 'Linux'})
1716        flags = list(self.read_topsrcdir(reader))[2]
1717        self.assertIsInstance(flags, ComputedFlags)
1718        self.assertEqual(flags.flags['WASM_CFLAGS'],
1719                         reader.config.substs['WASM_CFLAGS'])
1720        self.assertEqual(flags.flags['MOZBUILD_WASM_CFLAGS'],
1721                         ['-funroll-loops', '-wasm-arg'])
1722        self.assertEqual(set(flags.flags['WASM_DEFINES']),
1723                         set(['-DFOO', '-DBAZ="abcd"', '-UQUX', '-DBAR=7', '-DVALUE=xyz']))
1724
1725
1726if __name__ == '__main__':
1727    main()
1728