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