summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/test
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/test')
-rw-r--r--python/mozbuild/mozbuild/test/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_basic.xml10
-rw-r--r--python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_multiple_templates.xml30
-rw-r--r--python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_xul.xml14
-rw-r--r--python/mozbuild/mozbuild/test/action/data/invalid/region.properties12
-rw-r--r--python/mozbuild/mozbuild/test/action/data/node/node-test-script.js11
-rw-r--r--python/mozbuild/mozbuild/test/action/test_buildlist.py96
-rw-r--r--python/mozbuild/mozbuild/test/action/test_html_fragment_preprocessor.py196
-rw-r--r--python/mozbuild/mozbuild/test/action/test_langpack_manifest.py269
-rw-r--r--python/mozbuild/mozbuild/test/action/test_node.py80
-rw-r--r--python/mozbuild/mozbuild/test/action/test_process_install_manifest.py65
-rw-r--r--python/mozbuild/mozbuild/test/backend/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/common.py253
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/app/moz.build54
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/bar.ini1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/bar.js2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/bar.jsm1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/baz.ini2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/baz.jsm2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/components.manifest2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/foo.css2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/foo.ini1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/foo.js1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/foo.jsm1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/jar.mn11
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/moz.build68
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/prefs.js1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/qux.ini5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/qux.jsm5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/resource1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/resource21
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/build/subdir/bar.js1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/database/bar.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/database/baz.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/database/build/non-unified-compat0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/database/foo.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/database/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/database/qux.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/defines/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/dist-files/install.rdf0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/dist-files/main.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports-generated/dom1.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports-generated/foo.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports-generated/gfx.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports-generated/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports-generated/mozilla1.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/dom1.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/dom2.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/foo.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/gfx.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/mozilla1.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/mozilla2.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/exports/pprio.h0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/bar.xyz0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/foo.xyz0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final_target/both/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final_target/dist-subdir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final_target/final-target/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final_target/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/final_target/xpi-name/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files-force/foo-data0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-bar.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-foo.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files-force/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files/foo-data0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files/generate-bar.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files/generate-foo.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated-files/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/generated_includes/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/host-defines/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/Cargo.toml13
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/moz.build22
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/host-rust-library/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/host-rust-library/moz.build22
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/foo.h.in1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build16
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build16
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/ipdl_sources/ipdl/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/ipdl_sources/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/jar-manifests/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/prog/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/qux1.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo1.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo2.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/real/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/baz1.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/shared/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar1.cc0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar2.cc0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/bar_helper1.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/moz.build13
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/static/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/linkage/templates.mozbuild23
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/local_includes/foo/dummy_file_for_nonempty_directory0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/local_includes/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/bar.ini0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-files/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/en-US/localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/foo-data0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/generate-foo.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/inner/locales/en-US/localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/locales/en-US/localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/moz.build32
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/non-localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/en-US/localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/foo-data0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/generate-foo.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/moz.build22
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/non-localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files/en-US/localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files/foo-data0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files/generate-foo.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-generated-files/non-localized-input0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/bar.ini0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/localized-pp-files/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/c-library.c2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/c_test_program.c2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/c_simple_program.c2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/c-source.c2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/cxx-library.cpp2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/moz.build10
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/cxx_test_program.cpp2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/cxx_simple_program.cpp2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/moz.build35
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/simple-programs/moz.build3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/program-paths/dist-bin/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/program-paths/dist-subdir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/program-paths/final-target/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/program-paths/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/program-paths/not-installed/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/bar.res.in0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/cursor.cur0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/desktop1.ttf0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/desktop2.ttf0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/extra.manifest0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/font1.ttf0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/font2.ttf0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/foo.res0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/mobile.ttf0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/resources/test.manifest0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/rust-library-features/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/rust-library-features/moz.build20
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/rust-library/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/rust-library/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/rust-programs/code/Cargo.toml10
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/rust-programs/code/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/rust-programs/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/bar.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/bar.s0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/baz.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/foo.asm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/foo.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/fuga.mm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/hoge.mm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/moz.build26
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/qux.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/titi.S0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/toto.S0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/Makefile.in4
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/dir1/Makefile.in7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/dir1/moz.build3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/dir2/moz.build3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/dir3/Makefile.in7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/dir3/moz.build3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/substitute_config_files/Makefile.in0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/substitute_config_files/foo.in1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/substitute_config_files/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/another-file.sjs0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/browser.ini6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/one.txt0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/two.txt0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/test_sub.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/mochitest.ini8
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/support-file.txt0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/test_foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest-common.ini1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest.ini2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_bar.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest1.ini4
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest2.ini4
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_bar.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/instrumentation.ini1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.ini1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/moz.build10
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/not_packaged.java0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/test_bar.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/xpcshell.ini3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.ini3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-written/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.ini4
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/src/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/moz.build32
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-one.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-two.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test_config/file.in3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/test_config/moz.build3
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/Makefile.in0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/baz.def0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.mm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.mm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/bar.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/foo.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/visual-studio/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/xpidl/bar.idl0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/xpidl/config/makefiles/xpidl/Makefile.in0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/xpidl/foo.idl0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/xpidl/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_build.py265
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_configenvironment.py73
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_database.py91
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_fastermake.py42
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_partialconfigenvironment.py173
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_recursivemake.py1307
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_test_manifest.py94
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_visualstudio.py63
-rw-r--r--python/mozbuild/mozbuild/test/code_analysis/test_mach_commands.py90
-rw-r--r--python/mozbuild/mozbuild/test/codecoverage/sample_lcov.info1895
-rw-r--r--python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py444
-rw-r--r--python/mozbuild/mozbuild/test/common.py69
-rw-r--r--python/mozbuild/mozbuild/test/compilation/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/compilation/test_warnings.py240
-rw-r--r--python/mozbuild/mozbuild/test/configure/common.py307
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/decorators.configure53
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/empty_mozconfig0
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/extra.configure15
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure37
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure28
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure36
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure40
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure28
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure28
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/included.configure68
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/moz.configure205
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/set_config.configure51
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/set_define.configure51
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/subprocess.configure24
-rw-r--r--python/mozbuild/mozbuild/test/configure/lint.py62
-rw-r--r--python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist8
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_bootstrap.py43
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_checks_configure.py1169
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_compile_checks.py599
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_configure.py1986
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_lint.py487
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_moz_configure.py185
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_options.py905
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py2056
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py433
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py102
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_util.py539
-rw-r--r--python/mozbuild/mozbuild/test/controller/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/controller/test_ccachestats.py866
-rw-r--r--python/mozbuild/mozbuild/test/controller/test_clobber.py214
-rw-r--r--python/mozbuild/mozbuild/test/data/Makefile0
-rw-r--r--python/mozbuild/mozbuild/test/data/bad.properties12
-rw-r--r--python/mozbuild/mozbuild/test/data/test-dir/Makefile0
-rw-r--r--python/mozbuild/mozbuild/test/data/test-dir/with/Makefile0
-rw-r--r--python/mozbuild/mozbuild/test/data/test-dir/with/without/with/Makefile0
-rw-r--r--python/mozbuild/mozbuild/test/data/test-dir/without/with/Makefile0
-rw-r--r--python/mozbuild/mozbuild/test/data/valid.properties11
-rw-r--r--python/mozbuild/mozbuild/test/frontend/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/moz.build20
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/asflags/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/asflags/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/asflags/test2.S0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/branding-files/bar.ico0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/branding-files/baz.png0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/branding-files/foo.xpm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/branding-files/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/branding-files/quux.icns0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-defines/moz.build16
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-defines/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/moz.build27
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags/moz.build22
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-flags/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-includes/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-includes/subdir/header.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/compile-includes/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/config-file-substitution/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/Cargo.toml18
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/shallow/Cargo.toml6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/the/depths/Cargo.toml9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/defines/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/moz.build20
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/moz.build21
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/install.rdf0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/dist-files/install.rdf0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/dist-files/main.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-generated/foo.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-generated/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-generated/mozilla1.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/foo.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-missing/foo.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-missing/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports-missing/mozilla1.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/bar.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/baz.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/dom1.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/dom2.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/dom3.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/foo.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/gfx.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/mem.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/mem2.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/moz.build13
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/mozilla1.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/mozilla2.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/pprio.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/exports/pprthred.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/bad-assignment/moz.build2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/different-matchers/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/moz.build3
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/subcomponent/moz.build2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/moz.build2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/simple/moz.build2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/static/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-info/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/final-target-pp-files-non-srcdir/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/script.py0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-force/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/moz.build13
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/script.py0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/script.py0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/script.rb0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files-no-script/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-files/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/a.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/b.cc0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/c.cxx0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/d.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/e.m0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/f.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/g.S0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/h.s0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/i.asm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated-sources/moz.build39
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated_includes/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/moz.build22
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-program-paths/final-target/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-program-paths/installed/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-program-paths/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-program-paths/not-installed/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/moz.build22
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-rust-program-no-cargo-toml/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/Cargo.toml7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/Cargo.toml7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-sources/a.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-sources/b.cc0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-sources/c.cxx0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-sources/d.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-sources/e.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-sources/f.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-sources/moz.build27
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-basic/included.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-basic/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-1.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-2.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-file-stack/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-missing/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-outside-topsrcdir/relative.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child2.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/grandchild/grandchild.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/parent.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/sibling.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/bar/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/moz.build10
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/moz.build10
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/jar-manifests-multiple-files/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/jar-manifests/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/library-defines/liba/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/library-defines/libb/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/library-defines/libc/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/library-defines/libd/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/library-defines/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/link-flags/moz.build16
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/link-flags/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/foo.h0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/objdir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/srcdir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/local_includes/foo/dummy_file_for_nonempty_directory0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/local_includes/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files-from-generated/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/en-US/bar.ini0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/inner/locales/en-US/bar.ini0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files-not-localized-generated/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/bar.ini0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-files/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-final-target-files/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-force/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-generated-files/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/bar.ini0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/moz.build8
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/missing-local-includes/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/missing-xpidl/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/moz.build29
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/Test.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/subdir/Test.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-bin/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-subdir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program-paths/final-target/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program-paths/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program-paths/not-installed/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program/moz.build18
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program/test_program1.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/program/test_program2.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-bad-dir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-basic/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-empty-list/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-error-func/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/child.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-missing-include/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-outside-topsrcdir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-read-unknown-global/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-repeated-dir/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-script-error/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-syntax/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-write-bad-value/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-error-write-unknown-global/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/file0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/file0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file10
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file20
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/file0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/dir1/dir2/dir3/file0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/file0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/file0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/file0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/moz.build17
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/moz.build20
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-features/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-features/moz.build20
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/Cargo.toml12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-no-cargo-toml/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/Cargo.toml12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-program-no-cargo-toml/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/Cargo.toml7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-programs/Cargo.toml7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-programs/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/schedules/moz.build19
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/schedules/subd/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources-just-c/d.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources-just-c/e.m0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources-just-c/g.S0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources-just-c/h.s0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources-just-c/i.asm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources-just-c/moz.build29
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/a.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/b.cc0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/c.cxx0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/d.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/e.m0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/f.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/g.S0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/h.s0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/i.asm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sources/moz.build39
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/templates/templates.mozbuild21
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-harness-files-root/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.ini1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.py1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-harness-files/moz.build7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-harness-files/runtests.py1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-harness-files/utils.py1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-install-shared-lib/moz.build16
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/foo.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/three/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/foo.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/absolute-support.ini4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/foo.txt1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/test_file.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/bar.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/mochitest.ini7
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/test_baz.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/included-reftest.list1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/reftest.list2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/empty.ini2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-inactive-ignored/test_inactive.html0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/common.ini1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/mochitest.ini3
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/test_foo.html1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/foo.txt1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/just-support.ini2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/dir1/bar0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/foo0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y.ini4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/browser.ini4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/chrome.ini3
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/crashtest.list1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/metro.ini3
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/mochitest.ini5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/python.ini1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest.list1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_a11y.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_browser.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_chrome.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_foo.py0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_metro.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_mochitest.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_xpcshell.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/xpcshell.ini5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-manifest/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/mochitest.ini1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/mochitest.ini4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/test_foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/support-file.txt0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/another-file.sjs0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/browser.ini6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/one.txt0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/two.txt0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/test_sub.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/mochitest.ini9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/support-file.txt0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/test_foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/another-file.sjs0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/browser.ini6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/one.txt0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/two.txt0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/test_sub.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/mochitest.ini8
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/support-file.txt0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/test_foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test.ini4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test_foo0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir-missing-generated/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/foo.py0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/foo.symbols1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/parallel/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/regular/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/test/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-outside-topsrcdir/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/bar/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/foo/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/bar/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/foo/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-simple/bar/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/biz/moz.build0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/moz.build2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/traversal-simple/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/bar.cxx0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c2.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/foo.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/moz.build30
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc1.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc2.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/quux.cc0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/bar.cxx0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/c1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/c2.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/foo.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/moz.build30
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc1.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc2.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/unified-sources/quux.cc0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/use-nasm/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/use-nasm/test1.S0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/bans.S0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/baz.def0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build13
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.mm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/visibility-flags/moz.build21
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/visibility-flags/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/moz.build14
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/test1.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/wasm-sources/a.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/wasm-sources/b.cc0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/wasm-sources/c.cxx0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/wasm-sources/d.c0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/wasm-sources/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/xpidl-module-no-sources/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_context.py736
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_emitter.py1877
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_namespaces.py225
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_reader.py531
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_sandbox.py536
-rw-r--r--python/mozbuild/mozbuild/test/python.ini64
-rw-r--r--python/mozbuild/mozbuild/test/repackaging/test_deb.py551
-rw-r--r--python/mozbuild/mozbuild/test/test_android_version_code.py111
-rw-r--r--python/mozbuild/mozbuild/test/test_artifact_cache.py145
-rw-r--r--python/mozbuild/mozbuild/test/test_artifacts.py115
-rw-r--r--python/mozbuild/mozbuild/test/test_base.py446
-rw-r--r--python/mozbuild/mozbuild/test/test_containers.py224
-rw-r--r--python/mozbuild/mozbuild/test/test_dotproperties.py183
-rw-r--r--python/mozbuild/mozbuild/test/test_expression.py88
-rw-r--r--python/mozbuild/mozbuild/test/test_jarmaker.py493
-rw-r--r--python/mozbuild/mozbuild/test/test_licenses.py33
-rw-r--r--python/mozbuild/mozbuild/test/test_line_endings.py45
-rw-r--r--python/mozbuild/mozbuild/test/test_makeutil.py164
-rw-r--r--python/mozbuild/mozbuild/test/test_manifest.py2081
-rw-r--r--python/mozbuild/mozbuild/test/test_mozconfig.py275
-rwxr-xr-xpython/mozbuild/mozbuild/test/test_mozinfo.py318
-rw-r--r--python/mozbuild/mozbuild/test/test_preprocessor.py832
-rw-r--r--python/mozbuild/mozbuild/test/test_pythonutil.py24
-rw-r--r--python/mozbuild/mozbuild/test/test_rewrite_mozbuild.py515
-rw-r--r--python/mozbuild/mozbuild/test/test_telemetry.py102
-rw-r--r--python/mozbuild/mozbuild/test/test_telemetry_settings.py174
-rw-r--r--python/mozbuild/mozbuild/test/test_util.py889
-rw-r--r--python/mozbuild/mozbuild/test/test_util_fileavoidwrite.py110
-rw-r--r--python/mozbuild/mozbuild/test/test_vendor.py48
-rw-r--r--python/mozbuild/mozbuild/test/test_vendor_tools.py90
-rw-r--r--python/mozbuild/mozbuild/test/vendor_requirements.in5
-rw-r--r--python/mozbuild/mozbuild/test/vendor_requirements.txt416
730 files changed, 31970 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/__init__.py b/python/mozbuild/mozbuild/test/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/__init__.py
diff --git a/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_basic.xml b/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_basic.xml
new file mode 100644
index 0000000000..251b4a3069
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_basic.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<template xmlns="http://www.w3.org/1999/xhtml">
+ <div class="main">
+ <p>Hello World</p>
+ </div>
+</template>
diff --git a/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_multiple_templates.xml b/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_multiple_templates.xml
new file mode 100644
index 0000000000..2e249aec63
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_multiple_templates.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<template xmlns="http://www.w3.org/1999/xhtml">
+ <template doctype="true">
+ <![CDATA[
+ <!DOCTYPE bindings [
+ <!ENTITY % exampleDTD SYSTEM "chrome://global/locale/example.dtd">
+ %exampleDTD;
+ ]>
+ ]]>
+ </template>
+ <template name="alpha">
+ <div class="main">
+ <p>Hello World</p>
+ </div>
+ </template>
+ <template name="beta">
+ <div class="body">
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
+ </div>
+ </template>
+ <template name="charlie">
+ <div class="footer">
+ <p>Goodbye</p>
+ </div>
+ </template>
+</template>
diff --git a/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_xul.xml b/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_xul.xml
new file mode 100644
index 0000000000..5e0ea0b34a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/html_fragment_preprocesor/example_xul.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<template xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:link href="chrome://global/skin/example.css" rel="stylesheet"/>
+ <hbox id="label-box" part="label-box" flex="1" role="none">
+ <image part="icon" role="none"/>
+ <label id="label" part="label" crop="end" flex="1" role="none"/>
+ <label id="highlightable-label" part="label" crop="end" flex="1" role="none"/>
+ </hbox>
+ <html:slot/>
+</template>
diff --git a/python/mozbuild/mozbuild/test/action/data/invalid/region.properties b/python/mozbuild/mozbuild/test/action/data/invalid/region.properties
new file mode 100644
index 0000000000..d4d8109b69
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/invalid/region.properties
@@ -0,0 +1,12 @@
+# A region.properties file with invalid unicode byte sequences. The
+# sequences were cribbed from Markus Kuhn's "UTF-8 decoder capability
+# and stress test", available at
+# http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+
+# 3.5 Impossible bytes |
+# |
+# The following two bytes cannot appear in a correct UTF-8 string |
+# |
+# 3.5.1 fe = "þ" |
+# 3.5.2 ff = "ÿ" |
+# 3.5.3 fe fe ff ff = "þþÿÿ" |
diff --git a/python/mozbuild/mozbuild/test/action/data/node/node-test-script.js b/python/mozbuild/mozbuild/test/action/data/node/node-test-script.js
new file mode 100644
index 0000000000..f6dbfcc594
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/node/node-test-script.js
@@ -0,0 +1,11 @@
+#! /usr/bin/env node
+"use strict";
+
+/* eslint-disable no-console */
+
+let args = process.argv.slice(2);
+
+for (let arg of args) {
+ console.log(`dep:${arg}`);
+}
+
diff --git a/python/mozbuild/mozbuild/test/action/test_buildlist.py b/python/mozbuild/mozbuild/test/action/test_buildlist.py
new file mode 100644
index 0000000000..9a1d2738ed
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_buildlist.py
@@ -0,0 +1,96 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import os.path
+import unittest
+from shutil import rmtree
+from tempfile import mkdtemp
+
+import mozunit
+
+from mozbuild.action.buildlist import addEntriesToListFile
+
+
+class TestBuildList(unittest.TestCase):
+ """
+ Unit tests for buildlist.py
+ """
+
+ def setUp(self):
+ self.tmpdir = mkdtemp()
+
+ def tearDown(self):
+ rmtree(self.tmpdir)
+
+ # utility methods for tests
+ def touch(self, file, dir=None):
+ if dir is None:
+ dir = self.tmpdir
+ f = os.path.join(dir, file)
+ open(f, "w").close()
+ return f
+
+ def assertFileContains(self, filename, l):
+ """Assert that the lines in the file |filename| are equal
+ to the contents of the list |l|, in order."""
+ l = l[:]
+ f = open(filename, "r")
+ lines = [line.rstrip() for line in f.readlines()]
+ f.close()
+ for line in lines:
+ self.assertTrue(
+ len(l) > 0,
+ "ran out of expected lines! (expected '{0}', got '{1}')".format(
+ l, lines
+ ),
+ )
+ self.assertEqual(line, l.pop(0))
+ self.assertTrue(
+ len(l) == 0,
+ "not enough lines in file! (expected '{0}'," " got '{1}'".format(l, lines),
+ )
+
+ def test_basic(self):
+ "Test that addEntriesToListFile works when file doesn't exist."
+ testfile = os.path.join(self.tmpdir, "test.list")
+ l = ["a", "b", "c"]
+ addEntriesToListFile(testfile, l)
+ self.assertFileContains(testfile, l)
+ # ensure that attempting to add the same entries again doesn't change it
+ addEntriesToListFile(testfile, l)
+ self.assertFileContains(testfile, l)
+
+ def test_append(self):
+ "Test adding new entries."
+ testfile = os.path.join(self.tmpdir, "test.list")
+ l = ["a", "b", "c"]
+ addEntriesToListFile(testfile, l)
+ self.assertFileContains(testfile, l)
+ l2 = ["x", "y", "z"]
+ addEntriesToListFile(testfile, l2)
+ l.extend(l2)
+ self.assertFileContains(testfile, l)
+
+ def test_append_some(self):
+ "Test adding new entries mixed with existing entries."
+ testfile = os.path.join(self.tmpdir, "test.list")
+ l = ["a", "b", "c"]
+ addEntriesToListFile(testfile, l)
+ self.assertFileContains(testfile, l)
+ addEntriesToListFile(testfile, ["a", "x", "c", "z"])
+ self.assertFileContains(testfile, ["a", "b", "c", "x", "z"])
+
+ def test_add_multiple(self):
+ """Test that attempting to add the same entry multiple times results in
+ only one entry being added."""
+ testfile = os.path.join(self.tmpdir, "test.list")
+ addEntriesToListFile(testfile, ["a", "b", "a", "a", "b"])
+ self.assertFileContains(testfile, ["a", "b"])
+ addEntriesToListFile(testfile, ["c", "a", "c", "b", "c"])
+ self.assertFileContains(testfile, ["a", "b", "c"])
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/action/test_html_fragment_preprocessor.py b/python/mozbuild/mozbuild/test/action/test_html_fragment_preprocessor.py
new file mode 100644
index 0000000000..3cce1c5f94
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_html_fragment_preprocessor.py
@@ -0,0 +1,196 @@
+import os
+import unittest
+import xml.etree.ElementTree as ET
+
+import mozpack.path as mozpath
+import mozunit
+
+from mozbuild.action.html_fragment_preprocesor import (
+ fill_html_fragments_map,
+ generate,
+ get_fragment_key,
+ get_html_fragments_from_file,
+)
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data", "html_fragment_preprocesor")
+
+
+def data(name):
+ return os.path.join(test_data_path, name)
+
+
+TEST_PATH = "/some/path/somewhere/example.xml".replace("/", os.sep)
+EXAMPLE_BASIC = data("example_basic.xml")
+EXAMPLE_TEMPLATES = data("example_multiple_templates.xml")
+EXAMPLE_XUL = data("example_xul.xml")
+DUMMY_FILE = data("dummy.js")
+
+
+class TestNode(unittest.TestCase):
+ """
+ Tests for html_fragment_preprocesor.py.
+ """
+
+ maxDiff = None
+
+ def assertXMLEqual(self, a, b, message):
+ aRoot = ET.fromstring(a)
+ bRoot = ET.fromstring(b)
+ self.assertXMLNodesEqual(aRoot, bRoot, message)
+
+ def assertXMLNodesEqual(self, a, b, message, xpath=""):
+ xpath += "/" + a.tag
+ messageWithPath = message + " at " + xpath
+ self.assertEqual(a.tag, b.tag, messageWithPath + " tag name")
+ self.assertEqual(a.text, b.text, messageWithPath + " text")
+ self.assertEqual(
+ a.attrib.keys(), b.attrib.keys(), messageWithPath + " attribute names"
+ )
+ for aKey, aValue in a.attrib.items():
+ self.assertEqual(
+ aValue,
+ b.attrib[aKey],
+ messageWithPath + "[@" + aKey + "] attribute value",
+ )
+ for aChild, bChild in zip(a, b):
+ self.assertXMLNodesEqual(aChild, bChild, message, xpath)
+
+ def test_get_fragment_key_path(self):
+ key = get_fragment_key("/some/path/somewhere/example.xml")
+ self.assertEqual(key, "example")
+
+ def test_get_fragment_key_with_named_template(self):
+ key = get_fragment_key(TEST_PATH, "some-template")
+ self.assertEqual(key, "example/some-template")
+
+ def test_get_html_fragments_from_template_no_doctype_no_name(self):
+ key = "example"
+ fragment_map = {}
+ template = ET.Element("template")
+ p1 = ET.SubElement(template, "p")
+ p1.text = "Hello World"
+ p2 = ET.SubElement(template, "p")
+ p2.text = "Goodbye"
+ fill_html_fragments_map(fragment_map, TEST_PATH, template)
+ self.assertEqual(fragment_map[key], "<p>Hello World</p><p>Goodbye</p>")
+
+ def test_get_html_fragments_from_named_template_with_html_element(self):
+ key = "example/some-template"
+ fragment_map = {}
+ template = ET.Element("template")
+ template.attrib["name"] = "some-template"
+ p = ET.SubElement(template, "p")
+ p.text = "Hello World"
+ fill_html_fragments_map(fragment_map, TEST_PATH, template)
+ self.assertEqual(fragment_map[key], "<p>Hello World</p>")
+
+ def test_get_html_fragments_from_template_with_doctype(self):
+ key = "example"
+ doctype = "doctype definition goes here"
+ fragment_map = {}
+ template = ET.Element("template")
+ p = ET.SubElement(template, "p")
+ p.text = "Hello World"
+ fill_html_fragments_map(fragment_map, TEST_PATH, template, doctype)
+ self.assertEqual(
+ fragment_map[key], "doctype definition goes here\n<p>Hello World</p>"
+ )
+
+ def test_get_html_fragments_from_file_basic(self):
+ key = "example_basic"
+ fragment_map = {}
+ get_html_fragments_from_file(fragment_map, EXAMPLE_BASIC)
+ self.assertEqual(
+ fragment_map[key],
+ '<div xmlns="http://www.w3.org/1999/xhtml" class="main">'
+ + " <p>Hello World</p> </div>",
+ )
+
+ def test_get_html_fragments_from_file_multiple_templates(self):
+ key1 = "example_multiple_templates/alpha"
+ key2 = "example_multiple_templates/beta"
+ key3 = "example_multiple_templates/charlie"
+ fragment_map = {}
+ get_html_fragments_from_file(fragment_map, EXAMPLE_TEMPLATES)
+ self.assertIn("<p>Hello World</p>", fragment_map[key1], "Has HTML content")
+ self.assertIn(
+ '<!ENTITY % exampleDTD SYSTEM "chrome://global/locale/example.dtd">',
+ fragment_map[key1],
+ "Has doctype",
+ )
+ self.assertIn("<p>Lorem ipsum", fragment_map[key2], "Has HTML content")
+ self.assertIn(
+ '<!ENTITY % exampleDTD SYSTEM "chrome://global/locale/example.dtd">',
+ fragment_map[key2],
+ "Has doctype",
+ )
+ self.assertIn("<p>Goodbye</p>", fragment_map[key3], "Has HTML content")
+ self.assertIn(
+ '<!ENTITY % exampleDTD SYSTEM "chrome://global/locale/example.dtd">',
+ fragment_map[key3],
+ "Has doctype",
+ )
+
+ def test_get_html_fragments_from_file_with_xul(self):
+ key = "example_xul"
+ fragment_map = {}
+ get_html_fragments_from_file(fragment_map, EXAMPLE_XUL)
+ xml = "<root>" + fragment_map[key] + "</root>"
+ self.assertXMLEqual(
+ xml,
+ "<root>"
+ + '<html:link xmlns:html="http://www.w3.org/1999/xhtml" '
+ + 'href="chrome://global/skin/example.css" rel="stylesheet">'
+ + "</html:link> "
+ + '<hbox xmlns="http://www.mozilla.org/keymaster/'
+ + 'gatekeeper/there.is.only.xul" flex="1" id="label-box" '
+ + 'part="label-box" role="none"> '
+ + '<image part="icon" role="none"></image> '
+ + '<label crop="end" flex="1" id="label" part="label" '
+ + 'role="none"></label> '
+ + '<label crop="end" flex="1" id="highlightable-label" '
+ + 'part="label" role="none"></label> '
+ + "</hbox> "
+ + '<html:slot xmlns:html="http://www.w3.org/1999/xhtml">'
+ + "</html:slot></root>",
+ "XML values must match",
+ )
+
+ def test_generate(self):
+ with open(DUMMY_FILE, "w") as file:
+ deps = generate(
+ file,
+ EXAMPLE_BASIC,
+ EXAMPLE_TEMPLATES,
+ EXAMPLE_XUL,
+ )
+ with open(DUMMY_FILE, "r") as file:
+ contents = file.read()
+ self.assertIn(
+ "<!ENTITY % exampleDTD SYSTEM",
+ contents,
+ "Has doctype",
+ )
+ self.assertIn("<p>Lorem ipsum", contents, "Has HTML content")
+ self.assertIn('"example_basic"', contents, "Has basic fragment key")
+ self.assertIn(
+ '"example_multiple_templates/alpha"',
+ contents,
+ "Has multiple templates fragment key",
+ )
+ self.assertIn('"example_xul"', contents, "Has XUL fragment key")
+ self.assertIn(
+ "const getHTMLFragment =",
+ contents,
+ "Has fragment loader method declaration",
+ )
+ os.remove(DUMMY_FILE)
+ self.assertEqual(len(deps), 3, "deps are correct")
+ self.assertIn(EXAMPLE_BASIC, deps, "deps are correct")
+ self.assertIn(EXAMPLE_TEMPLATES, deps, "deps are correct")
+ self.assertIn(EXAMPLE_XUL, deps, "deps are correct")
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/action/test_langpack_manifest.py b/python/mozbuild/mozbuild/test/action/test_langpack_manifest.py
new file mode 100644
index 0000000000..29e8642fc7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_langpack_manifest.py
@@ -0,0 +1,269 @@
+# -*- coding: utf-8 -*-
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import json
+import os
+import shutil
+import tempfile
+import unittest
+
+import mozunit
+
+from mozbuild.action import langpack_manifest
+
+
+class TestGenerateManifest(unittest.TestCase):
+ """
+ Unit tests for langpack_manifest.py.
+ """
+
+ def test_parse_flat_ftl(self):
+ src = """
+langpack-creator = bar {"bar"}
+langpack-contributors = { "" }
+"""
+ tmp = tempfile.NamedTemporaryFile(mode="wt", suffix=".ftl", delete=False)
+ try:
+ tmp.write(src)
+ tmp.close()
+ ftl = langpack_manifest.parse_flat_ftl(tmp.name)
+ self.assertEqual(ftl["langpack-creator"], "bar bar")
+ self.assertEqual(ftl["langpack-contributors"], "")
+ finally:
+ os.remove(tmp.name)
+
+ def test_parse_flat_ftl_missing(self):
+ ftl = langpack_manifest.parse_flat_ftl("./does-not-exist.ftl")
+ self.assertEqual(len(ftl), 0)
+
+ def test_manifest(self):
+ ctx = {
+ "langpack-creator": "Suomennosprojekti",
+ "langpack-contributors": "Joe Smith, Mary White",
+ }
+ os.environ["MOZ_BUILD_DATE"] = "20210928100000"
+ manifest = langpack_manifest.create_webmanifest(
+ "fi",
+ "57.0.1",
+ "57.0",
+ "57.0.*",
+ "Firefox",
+ "/var/vcs/l10n-central",
+ "langpack-fi@firefox.mozilla.og",
+ ctx,
+ {},
+ )
+
+ data = json.loads(manifest)
+ self.assertEqual(data["name"], "Language: Suomi (Finnish)")
+ self.assertEqual(
+ data["description"], "Firefox Language Pack for Suomi (fi) – Finnish"
+ )
+ self.assertEqual(
+ data["author"], "Suomennosprojekti (contributors: Joe Smith, Mary White)"
+ )
+ self.assertEqual(data["version"], "57.0.20210928.100000")
+
+ def test_manifest_truncated_name(self):
+ ctx = {
+ "langpack-creator": "Mozilla.org / Softcatalà",
+ "langpack-contributors": "Joe Smith, Mary White",
+ }
+ os.environ["MOZ_BUILD_DATE"] = "20210928100000"
+ manifest = langpack_manifest.create_webmanifest(
+ "ca-valencia",
+ "57.0.1",
+ "57.0",
+ "57.0.*",
+ "Firefox",
+ "/var/vcs/l10n-central",
+ "langpack-ca-valencia@firefox.mozilla.og",
+ ctx,
+ {},
+ )
+
+ data = json.loads(manifest)
+ self.assertEqual(data["name"], "Language: Català (Valencià)")
+ self.assertEqual(
+ data["description"],
+ "Firefox Language Pack for Català (Valencià) (ca-valencia) – Catalan, Valencian",
+ )
+
+ def test_manifest_name_untranslated(self):
+ ctx = {
+ "langpack-creator": "Mozilla.org",
+ "langpack-contributors": "Joe Smith, Mary White",
+ }
+ os.environ["MOZ_BUILD_DATE"] = "20210928100000"
+ manifest = langpack_manifest.create_webmanifest(
+ "en-US",
+ "57.0.1",
+ "57.0",
+ "57.0.*",
+ "Firefox",
+ "/var/vcs/l10n-central",
+ "langpack-ca-valencia@firefox.mozilla.og",
+ ctx,
+ {},
+ )
+
+ data = json.loads(manifest)
+ self.assertEqual(data["name"], "Language: English (US)")
+ self.assertEqual(
+ data["description"],
+ "Firefox Language Pack for English (US) (en-US)",
+ )
+
+ def test_manifest_without_contributors(self):
+ ctx = {
+ "langpack-creator": "Suomennosprojekti",
+ "langpack-contributors": "",
+ }
+ manifest = langpack_manifest.create_webmanifest(
+ "fi",
+ "57.0.1",
+ "57.0",
+ "57.0.*",
+ "Firefox",
+ "/var/vcs/l10n-central",
+ "langpack-fi@firefox.mozilla.og",
+ ctx,
+ {},
+ )
+
+ data = json.loads(manifest)
+ self.assertEqual(data["name"], "Language: Suomi (Finnish)")
+ self.assertEqual(
+ data["description"], "Firefox Language Pack for Suomi (fi) – Finnish"
+ )
+ self.assertEqual(data["author"], "Suomennosprojekti")
+
+ def test_manifest_truncation(self):
+ locale = (
+ "Long locale code that will be truncated and will cause both "
+ "the name and the description to exceed the maximum number of "
+ "characters allowed in manifest.json"
+ )
+ title, description = langpack_manifest.get_title_and_description(
+ "Firefox", locale
+ )
+
+ self.assertEqual(len(title), 45)
+ self.assertEqual(len(description), 132)
+
+ def test_get_version_maybe_buildid(self):
+ for (app_version, buildid, expected_version) in [
+ ("109", "", "109"),
+ ("109.0", "", "109.0"),
+ ("109.0.0", "", "109.0.0"),
+ ("109", "20210928", "109"), # buildid should be 14 chars
+ ("109", "20210928123456", "109.20210928.123456"),
+ ("109.0", "20210928123456", "109.0.20210928.123456"),
+ ("109.0.0", "20210928123456", "109.0.20210928.123456"),
+ ("109", "20230215023456", "109.20230215.23456"),
+ ("109.0", "20230215023456", "109.0.20230215.23456"),
+ ("109.0.0", "20230215023456", "109.0.20230215.23456"),
+ ("109", "20230215003456", "109.20230215.3456"),
+ ("109", "20230215000456", "109.20230215.456"),
+ ("109", "20230215000056", "109.20230215.56"),
+ ("109", "20230215000006", "109.20230215.6"),
+ ("109", "20230215000000", "109.20230215.0"),
+ ("109.1.2.3", "20230201000000", "109.1.20230201.0"),
+ ("109.0a1", "", "109.0"),
+ ("109a0.0b0", "", "109.0"),
+ ("109.0.0b1", "", "109.0.0"),
+ ("109.0.b1", "", "109.0.0"),
+ ("109..1", "", "109.0.1"),
+ ]:
+ os.environ["MOZ_BUILD_DATE"] = buildid
+ version = langpack_manifest.get_version_maybe_buildid(app_version)
+ self.assertEqual(version, expected_version)
+
+ def test_main(self):
+ # We set this env variable so that the manifest.json version string
+ # uses a "buildid", see: `get_version_maybe_buildid()` for more
+ # information.
+ os.environ["MOZ_BUILD_DATE"] = "20210928100000"
+
+ TEST_CASES = [
+ {
+ "app_version": "112.0.1",
+ "max_app_version": "112.*",
+ "expected_version": "112.0.20210928.100000",
+ "expected_min_version": "112.0",
+ "expected_max_version": "112.*",
+ },
+ {
+ "app_version": "112.1.0",
+ "max_app_version": "112.*",
+ "expected_version": "112.1.20210928.100000",
+ # We expect the second part to be "0" even if the app version
+ # has a minor part equal to "1".
+ "expected_min_version": "112.0",
+ "expected_max_version": "112.*",
+ },
+ {
+ "app_version": "114.0a1",
+ "max_app_version": "114.*",
+ "expected_version": "114.0.20210928.100000",
+ # We expect the min version to be equal to the app version
+ # because we don't change alpha versions.
+ "expected_min_version": "114.0a1",
+ "expected_max_version": "114.*",
+ },
+ ]
+
+ tmpdir = tempfile.mkdtemp()
+ try:
+ # These files are required by the `main()` function.
+ for file in ["chrome.manifest", "empty-metadata.ftl"]:
+ langpack_manifest.write_file(os.path.join(tmpdir, file), "")
+
+ for tc in TEST_CASES:
+ extension_id = "some@extension-id"
+ locale = "fr"
+
+ args = [
+ "--input",
+ tmpdir,
+ # This file has been created right above.
+ "--metadata",
+ "empty-metadata.ftl",
+ "--app-name",
+ "Firefox",
+ "--l10n-basedir",
+ "/var/vcs/l10n-central",
+ "--locales",
+ locale,
+ "--langpack-eid",
+ extension_id,
+ "--app-version",
+ tc["app_version"],
+ "--max-app-ver",
+ tc["max_app_version"],
+ ]
+ langpack_manifest.main(args)
+
+ with open(os.path.join(tmpdir, "manifest.json")) as manifest_file:
+ manifest = json.load(manifest_file)
+ self.assertEqual(manifest["version"], tc["expected_version"])
+ self.assertEqual(manifest["langpack_id"], locale)
+ self.assertEqual(
+ manifest["browser_specific_settings"],
+ {
+ "gecko": {
+ "id": extension_id,
+ "strict_min_version": tc["expected_min_version"],
+ "strict_max_version": tc["expected_max_version"],
+ }
+ },
+ )
+ finally:
+ shutil.rmtree(tmpdir, ignore_errors=True)
+ del os.environ["MOZ_BUILD_DATE"]
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/action/test_node.py b/python/mozbuild/mozbuild/test/action/test_node.py
new file mode 100644
index 0000000000..f1ab5afd17
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_node.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import os
+import unittest
+
+import buildconfig
+import mozpack.path as mozpath
+import mozunit
+
+from mozbuild.action.node import SCRIPT_ALLOWLIST, generate
+from mozbuild.nodeutil import find_node_executable
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data", "node")
+
+
+def data(name):
+ return os.path.join(test_data_path, name)
+
+
+TEST_SCRIPT = data("node-test-script.js")
+NONEXISTENT_TEST_SCRIPT = data("non-existent-test-script.js")
+
+
+class TestNode(unittest.TestCase):
+ """
+ Tests for node.py.
+ """
+
+ def setUp(self):
+ if not buildconfig.substs.get("NODEJS"):
+ buildconfig.substs["NODEJS"] = find_node_executable()[0]
+ SCRIPT_ALLOWLIST.append(TEST_SCRIPT)
+
+ def tearDown(self):
+ try:
+ SCRIPT_ALLOWLIST.remove(TEST_SCRIPT)
+ except Exception:
+ pass
+
+ def test_generate_no_returned_deps(self):
+ deps = generate("dummy_argument", TEST_SCRIPT)
+
+ self.assertSetEqual(deps, set([]))
+
+ def test_generate_returns_passed_deps(self):
+ deps = generate("dummy_argument", TEST_SCRIPT, "a", "b")
+
+ self.assertSetEqual(deps, set(["a", "b"]))
+
+ def test_called_process_error_handled(self):
+ SCRIPT_ALLOWLIST.append(NONEXISTENT_TEST_SCRIPT)
+
+ with self.assertRaises(SystemExit) as cm:
+ generate("dummy_arg", NONEXISTENT_TEST_SCRIPT)
+
+ self.assertEqual(cm.exception.code, 1)
+ SCRIPT_ALLOWLIST.remove(NONEXISTENT_TEST_SCRIPT)
+
+ def test_nodejs_not_set(self):
+ buildconfig.substs["NODEJS"] = None
+
+ with self.assertRaises(SystemExit) as cm:
+ generate("dummy_arg", TEST_SCRIPT)
+
+ self.assertEqual(cm.exception.code, 1)
+
+ def test_generate_missing_allowlist_entry_exit_code(self):
+ SCRIPT_ALLOWLIST.remove(TEST_SCRIPT)
+ with self.assertRaises(SystemExit) as cm:
+ generate("dummy_arg", TEST_SCRIPT)
+
+ self.assertEqual(cm.exception.code, 1)
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/action/test_process_install_manifest.py b/python/mozbuild/mozbuild/test/action/test_process_install_manifest.py
new file mode 100644
index 0000000000..3aea4bca73
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_process_install_manifest.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import os
+
+import mozunit
+from mozpack.manifests import InstallManifest
+from mozpack.test.test_files import TestWithTmpDir
+
+import mozbuild.action.process_install_manifest as process_install_manifest
+
+
+class TestGenerateManifest(TestWithTmpDir):
+ """
+ Unit tests for process_install_manifest.py.
+ """
+
+ def test_process_manifest(self):
+ source = self.tmppath("source")
+ os.mkdir(source)
+ os.mkdir("%s/base" % source)
+ os.mkdir("%s/base/foo" % source)
+ os.mkdir("%s/base2" % source)
+
+ with open("%s/base/foo/file1" % source, "a"):
+ pass
+
+ with open("%s/base/foo/file2" % source, "a"):
+ pass
+
+ with open("%s/base2/file3" % source, "a"):
+ pass
+
+ m = InstallManifest()
+ m.add_pattern_link("%s/base" % source, "**", "")
+ m.add_link("%s/base2/file3" % source, "foo/file3")
+
+ p = self.tmppath("m")
+ m.write(path=p)
+
+ dest = self.tmppath("dest")
+ track = self.tmppath("track")
+
+ for i in range(2):
+ process_install_manifest.process_manifest(dest, [p], track)
+
+ self.assertTrue(os.path.exists(self.tmppath("dest/foo/file1")))
+ self.assertTrue(os.path.exists(self.tmppath("dest/foo/file2")))
+ self.assertTrue(os.path.exists(self.tmppath("dest/foo/file3")))
+
+ m = InstallManifest()
+ m.write(path=p)
+
+ for i in range(2):
+ process_install_manifest.process_manifest(dest, [p], track)
+
+ self.assertFalse(os.path.exists(self.tmppath("dest/foo/file1")))
+ self.assertFalse(os.path.exists(self.tmppath("dest/foo/file2")))
+ self.assertFalse(os.path.exists(self.tmppath("dest/foo/file3")))
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/backend/__init__.py b/python/mozbuild/mozbuild/test/backend/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/__init__.py
diff --git a/python/mozbuild/mozbuild/test/backend/common.py b/python/mozbuild/mozbuild/test/backend/common.py
new file mode 100644
index 0000000000..07cfa7540f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/common.py
@@ -0,0 +1,253 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+from collections import defaultdict
+from shutil import rmtree
+from tempfile import mkdtemp
+
+import mozpack.path as mozpath
+from mach.logging import LoggingManager
+
+from mozbuild.backend.configenvironment import ConfigEnvironment
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import BuildReader
+
+log_manager = LoggingManager()
+log_manager.add_terminal_logging()
+
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+
+CONFIGS = defaultdict(
+ lambda: {
+ "defines": {},
+ "substs": {"OS_TARGET": "WINNT"},
+ },
+ {
+ "binary-components": {
+ "defines": {},
+ "substs": {
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "COMPILE_ENVIRONMENT": "1",
+ },
+ },
+ "database": {
+ "defines": {},
+ "substs": {
+ "CC": "clang",
+ "CXX": "clang++",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ },
+ },
+ "rust-library": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "RUST_TARGET": "x86_64-unknown-linux-gnu",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ },
+ },
+ "host-rust-library": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "RUST_HOST_TARGET": "x86_64-unknown-linux-gnu",
+ "RUST_TARGET": "armv7-linux-androideabi",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ },
+ },
+ "host-rust-library-features": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "RUST_HOST_TARGET": "x86_64-unknown-linux-gnu",
+ "RUST_TARGET": "armv7-linux-androideabi",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ },
+ },
+ "rust-library-features": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "RUST_TARGET": "x86_64-unknown-linux-gnu",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ },
+ },
+ "rust-programs": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "RUST_TARGET": "i686-pc-windows-msvc",
+ "RUST_HOST_TARGET": "i686-pc-windows-msvc",
+ "BIN_SUFFIX": ".exe",
+ "HOST_BIN_SUFFIX": ".exe",
+ },
+ },
+ "test-support-binaries-tracked": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "LIB_SUFFIX": "dll",
+ "BIN_SUFFIX": ".exe",
+ },
+ },
+ "sources": {
+ "defines": {},
+ "substs": {
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ },
+ },
+ "stub0": {
+ "defines": {
+ "MOZ_TRUE_1": "1",
+ "MOZ_TRUE_2": "1",
+ },
+ "substs": {
+ "MOZ_FOO": "foo",
+ "MOZ_BAR": "bar",
+ },
+ },
+ "substitute_config_files": {
+ "defines": {},
+ "substs": {
+ "MOZ_FOO": "foo",
+ "MOZ_BAR": "bar",
+ },
+ },
+ "test_config": {
+ "defines": {
+ "foo": "baz qux",
+ "baz": 1,
+ },
+ "substs": {
+ "foo": "bar baz",
+ },
+ },
+ "visual-studio": {
+ "defines": {},
+ "substs": {
+ "MOZ_APP_NAME": "my_app",
+ },
+ },
+ "prog-lib-c-only": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "LIB_SUFFIX": ".a",
+ "BIN_SUFFIX": "",
+ },
+ },
+ "gn-processor": {
+ "defines": {},
+ "substs": {
+ "BUILD_BACKENDS": [
+ "GnMozbuildWriter",
+ "RecursiveMake",
+ ],
+ "COMPILE_ENVIRONMENT": "1",
+ "STL_FLAGS": [],
+ "RUST_TARGET": "x86_64-unknown-linux-gnu",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "OS_TARGET": "Darwin",
+ },
+ },
+ "ipdl_sources": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "LIB_SUFFIX": ".a",
+ "BIN_SUFFIX": "",
+ },
+ },
+ "program-paths": {
+ "defines": {},
+ "substs": {
+ "COMPILE_ENVIRONMENT": "1",
+ "BIN_SUFFIX": ".prog",
+ },
+ },
+ "linkage": {
+ "defines": {},
+ "substs": {
+ "CC_TYPE": "clang",
+ "COMPILE_ENVIRONMENT": "1",
+ "LIB_SUFFIX": "a",
+ "BIN_SUFFIX": ".exe",
+ "DLL_SUFFIX": ".so",
+ "OBJ_SUFFIX": "o",
+ "EXPAND_LIBS_LIST_STYLE": "list",
+ },
+ },
+ },
+)
+
+
+class BackendTester(unittest.TestCase):
+ def setUp(self):
+ self._old_env = dict(os.environ)
+ os.environ.pop("MOZ_OBJDIR", None)
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ def _get_environment(self, name):
+ """Obtain a new instance of a ConfigEnvironment for a known profile.
+
+ A new temporary object directory is created for the environment. The
+ environment is cleaned up automatically when the test finishes.
+ """
+ config = CONFIGS[name]
+ config["substs"]["MOZ_UI_LOCALE"] = "en-US"
+
+ srcdir = mozpath.join(test_data_path, name)
+ config["substs"]["top_srcdir"] = srcdir
+
+ # Create the objdir in the srcdir to ensure that they share the
+ # same drive on Windows.
+ objdir = mkdtemp(dir=srcdir)
+ self.addCleanup(rmtree, objdir)
+
+ return ConfigEnvironment(srcdir, objdir, **config)
+
+ def _emit(self, name, env=None):
+ env = env or self._get_environment(name)
+ reader = BuildReader(env)
+ emitter = TreeMetadataEmitter(env)
+
+ return env, emitter.emit(reader.read_topsrcdir())
+
+ def _consume(self, name, cls, env=None):
+ env, objs = self._emit(name, env=env)
+ backend = cls(env)
+ backend.consume(objs)
+
+ return env
+
+ def _tree_paths(self, topdir, filename):
+ for dirpath, dirnames, filenames in os.walk(topdir):
+ for f in filenames:
+ if f == filename:
+ yield mozpath.relpath(mozpath.join(dirpath, f), topdir)
+
+ def _mozbuild_paths(self, env):
+ return self._tree_paths(env.topsrcdir, "moz.build")
+
+ def _makefile_in_paths(self, env):
+ return self._tree_paths(env.topsrcdir, "Makefile.in")
+
+
+__all__ = ["BackendTester"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/app/moz.build b/python/mozbuild/mozbuild/test/backend/data/build/app/moz.build
new file mode 100644
index 0000000000..27641b2080
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/app/moz.build
@@ -0,0 +1,54 @@
+DIST_SUBDIR = "app"
+
+EXTRA_JS_MODULES += [
+ "../foo.jsm",
+]
+
+EXTRA_JS_MODULES.child += [
+ "../bar.jsm",
+]
+
+EXTRA_PP_JS_MODULES += [
+ "../baz.jsm",
+]
+
+EXTRA_PP_JS_MODULES.child2 += [
+ "../qux.jsm",
+]
+
+FINAL_TARGET_FILES += [
+ "../foo.ini",
+]
+
+FINAL_TARGET_FILES.child += [
+ "../bar.ini",
+]
+
+FINAL_TARGET_PP_FILES += [
+ "../baz.ini",
+ "../foo.css",
+]
+
+FINAL_TARGET_PP_FILES.child2 += [
+ "../qux.ini",
+]
+
+EXTRA_COMPONENTS += [
+ "../components.manifest",
+ "../foo.js",
+]
+
+EXTRA_PP_COMPONENTS += [
+ "../bar.js",
+]
+
+JS_PREFERENCE_FILES += [
+ "../prefs.js",
+]
+
+JAR_MANIFESTS += [
+ "../jar.mn",
+]
+
+DEFINES["FOO"] = "bar"
+DEFINES["BAR"] = True
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/bar.ini b/python/mozbuild/mozbuild/test/backend/data/build/bar.ini
new file mode 100644
index 0000000000..91dcbe1536
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/bar.ini
@@ -0,0 +1 @@
+bar.ini
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/bar.js b/python/mozbuild/mozbuild/test/backend/data/build/bar.js
new file mode 100644
index 0000000000..1a608e8a56
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/bar.js
@@ -0,0 +1,2 @@
+#filter substitution
+bar.js: FOO is @FOO@
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/bar.jsm b/python/mozbuild/mozbuild/test/backend/data/build/bar.jsm
new file mode 100644
index 0000000000..05db2e2f6a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/bar.jsm
@@ -0,0 +1 @@
+bar.jsm
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/baz.ini b/python/mozbuild/mozbuild/test/backend/data/build/baz.ini
new file mode 100644
index 0000000000..975a1e437d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/baz.ini
@@ -0,0 +1,2 @@
+#filter substitution
+baz.ini: FOO is @FOO@
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/baz.jsm b/python/mozbuild/mozbuild/test/backend/data/build/baz.jsm
new file mode 100644
index 0000000000..f39ed02082
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/baz.jsm
@@ -0,0 +1,2 @@
+#filter substitution
+baz.jsm: FOO is @FOO@
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/components.manifest b/python/mozbuild/mozbuild/test/backend/data/build/components.manifest
new file mode 100644
index 0000000000..b5bb87254c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/components.manifest
@@ -0,0 +1,2 @@
+component {foo} foo.js
+component {bar} bar.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/foo.css b/python/mozbuild/mozbuild/test/backend/data/build/foo.css
new file mode 100644
index 0000000000..1803d6c572
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/foo.css
@@ -0,0 +1,2 @@
+%filter substitution
+foo.css: FOO is @FOO@
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/foo.ini b/python/mozbuild/mozbuild/test/backend/data/build/foo.ini
new file mode 100644
index 0000000000..c93c9d7658
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/foo.ini
@@ -0,0 +1 @@
+foo.ini
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/foo.js b/python/mozbuild/mozbuild/test/backend/data/build/foo.js
new file mode 100644
index 0000000000..4fa71e2d27
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/foo.js
@@ -0,0 +1 @@
+foo.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/foo.jsm b/python/mozbuild/mozbuild/test/backend/data/build/foo.jsm
new file mode 100644
index 0000000000..d58fd61c16
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/foo.jsm
@@ -0,0 +1 @@
+foo.jsm
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/jar.mn b/python/mozbuild/mozbuild/test/backend/data/build/jar.mn
new file mode 100644
index 0000000000..393055c4ea
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/jar.mn
@@ -0,0 +1,11 @@
+foo.jar:
+% content bar %child/
+% content foo %
+ foo.js
+* foo.css
+ bar.js (subdir/bar.js)
+ qux.js (subdir/bar.js)
+* child/hoge.js (bar.js)
+* child/baz.jsm
+
+% override chrome://foo/bar.svg#hello chrome://bar/bar.svg#hello
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/moz.build b/python/mozbuild/mozbuild/test/backend/data/build/moz.build
new file mode 100644
index 0000000000..700516754d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/moz.build
@@ -0,0 +1,68 @@
+CONFIGURE_SUBST_FILES += [
+ "/config/autoconf.mk",
+ "/config/emptyvars.mk",
+]
+
+EXTRA_JS_MODULES += [
+ "foo.jsm",
+]
+
+EXTRA_JS_MODULES.child += [
+ "bar.jsm",
+]
+
+EXTRA_PP_JS_MODULES += [
+ "baz.jsm",
+]
+
+EXTRA_PP_JS_MODULES.child2 += [
+ "qux.jsm",
+]
+
+FINAL_TARGET_FILES += [
+ "foo.ini",
+]
+
+FINAL_TARGET_FILES.child += [
+ "bar.ini",
+]
+
+FINAL_TARGET_PP_FILES += [
+ "baz.ini",
+]
+
+FINAL_TARGET_PP_FILES.child2 += [
+ "foo.css",
+ "qux.ini",
+]
+
+EXTRA_COMPONENTS += [
+ "components.manifest",
+ "foo.js",
+]
+
+EXTRA_PP_COMPONENTS += [
+ "bar.js",
+]
+
+JS_PREFERENCE_FILES += [
+ "prefs.js",
+]
+
+RESOURCE_FILES += [
+ "resource",
+]
+
+RESOURCE_FILES.child += [
+ "resource2",
+]
+
+DEFINES["FOO"] = "foo"
+
+JAR_MANIFESTS += [
+ "jar.mn",
+]
+
+DIRS += [
+ "app",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/prefs.js b/python/mozbuild/mozbuild/test/backend/data/build/prefs.js
new file mode 100644
index 0000000000..a030da9fd7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/prefs.js
@@ -0,0 +1 @@
+prefs.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/qux.ini b/python/mozbuild/mozbuild/test/backend/data/build/qux.ini
new file mode 100644
index 0000000000..3ce157eb6d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/qux.ini
@@ -0,0 +1,5 @@
+#ifdef BAR
+qux.ini: BAR is defined
+#else
+qux.ini: BAR is not defined
+#endif
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/qux.jsm b/python/mozbuild/mozbuild/test/backend/data/build/qux.jsm
new file mode 100644
index 0000000000..9c5fe28d58
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/qux.jsm
@@ -0,0 +1,5 @@
+#ifdef BAR
+qux.jsm: BAR is defined
+#else
+qux.jsm: BAR is not defined
+#endif
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/resource b/python/mozbuild/mozbuild/test/backend/data/build/resource
new file mode 100644
index 0000000000..91e75c679e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/resource
@@ -0,0 +1 @@
+resource
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/resource2 b/python/mozbuild/mozbuild/test/backend/data/build/resource2
new file mode 100644
index 0000000000..b7c2700964
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/resource2
@@ -0,0 +1 @@
+resource2
diff --git a/python/mozbuild/mozbuild/test/backend/data/build/subdir/bar.js b/python/mozbuild/mozbuild/test/backend/data/build/subdir/bar.js
new file mode 100644
index 0000000000..80c887a84a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/build/subdir/bar.js
@@ -0,0 +1 @@
+bar.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/database/bar.c b/python/mozbuild/mozbuild/test/backend/data/database/bar.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/database/bar.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/database/baz.cpp b/python/mozbuild/mozbuild/test/backend/data/database/baz.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/database/baz.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/database/build/non-unified-compat b/python/mozbuild/mozbuild/test/backend/data/database/build/non-unified-compat
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/database/build/non-unified-compat
diff --git a/python/mozbuild/mozbuild/test/backend/data/database/foo.c b/python/mozbuild/mozbuild/test/backend/data/database/foo.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/database/foo.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/database/moz.build b/python/mozbuild/mozbuild/test/backend/data/database/moz.build
new file mode 100644
index 0000000000..ebc5d05b5c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/database/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+SOURCES = ["bar.c", "baz.cpp", "foo.c", "qux.cpp"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/database/qux.cpp b/python/mozbuild/mozbuild/test/backend/data/database/qux.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/database/qux.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/defines/moz.build b/python/mozbuild/mozbuild/test/backend/data/defines/moz.build
new file mode 100644
index 0000000000..b603cac3ff
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/defines/moz.build
@@ -0,0 +1,9 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+value = "xyz"
+DEFINES["FOO"] = True
+DEFINES["BAZ"] = '"ab\'cd"'
+DEFINES["QUX"] = False
+DEFINES["BAR"] = 7
+DEFINES["VALUE"] = value
diff --git a/python/mozbuild/mozbuild/test/backend/data/dist-files/install.rdf b/python/mozbuild/mozbuild/test/backend/data/dist-files/install.rdf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/dist-files/install.rdf
diff --git a/python/mozbuild/mozbuild/test/backend/data/dist-files/main.js b/python/mozbuild/mozbuild/test/backend/data/dist-files/main.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/dist-files/main.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build
new file mode 100644
index 0000000000..25961f149f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET_PP_FILES += [
+ "install.rdf",
+ "main.js",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports-generated/dom1.h b/python/mozbuild/mozbuild/test/backend/data/exports-generated/dom1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports-generated/dom1.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports-generated/foo.h b/python/mozbuild/mozbuild/test/backend/data/exports-generated/foo.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports-generated/foo.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports-generated/gfx.h b/python/mozbuild/mozbuild/test/backend/data/exports-generated/gfx.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports-generated/gfx.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports-generated/moz.build b/python/mozbuild/mozbuild/test/backend/data/exports-generated/moz.build
new file mode 100644
index 0000000000..44c31a3d9c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports-generated/moz.build
@@ -0,0 +1,12 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+EXPORTS += ["!bar.h", "foo.h"]
+EXPORTS.mozilla += ["!mozilla2.h", "mozilla1.h"]
+EXPORTS.mozilla.dom += ["!dom2.h", "!dom3.h", "dom1.h"]
+EXPORTS.gfx += ["gfx.h"]
+
+GENERATED_FILES += ["bar.h"]
+GENERATED_FILES += ["mozilla2.h"]
+GENERATED_FILES += ["dom2.h"]
+GENERATED_FILES += ["dom3.h"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports-generated/mozilla1.h b/python/mozbuild/mozbuild/test/backend/data/exports-generated/mozilla1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports-generated/mozilla1.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/dom1.h b/python/mozbuild/mozbuild/test/backend/data/exports/dom1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/dom1.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/dom2.h b/python/mozbuild/mozbuild/test/backend/data/exports/dom2.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/dom2.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/foo.h b/python/mozbuild/mozbuild/test/backend/data/exports/foo.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/foo.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/gfx.h b/python/mozbuild/mozbuild/test/backend/data/exports/gfx.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/gfx.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/moz.build b/python/mozbuild/mozbuild/test/backend/data/exports/moz.build
new file mode 100644
index 0000000000..371f26f572
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/moz.build
@@ -0,0 +1,8 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+EXPORTS += ["foo.h"]
+EXPORTS.mozilla += ["mozilla1.h", "mozilla2.h"]
+EXPORTS.mozilla.dom += ["dom1.h", "dom2.h"]
+EXPORTS.mozilla.gfx += ["gfx.h"]
+EXPORTS.nspr.private += ["pprio.h"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/mozilla1.h b/python/mozbuild/mozbuild/test/backend/data/exports/mozilla1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/mozilla1.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/mozilla2.h b/python/mozbuild/mozbuild/test/backend/data/exports/mozilla2.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/mozilla2.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/exports/pprio.h b/python/mozbuild/mozbuild/test/backend/data/exports/pprio.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/pprio.h
diff --git a/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/bar.xyz b/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/bar.xyz
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/bar.xyz
diff --git a/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/foo.xyz b/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/foo.xyz
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/foo.xyz
diff --git a/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/moz.build b/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/moz.build
new file mode 100644
index 0000000000..d665855234
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final-target-files-wildcard/moz.build
@@ -0,0 +1,5 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_TARGET_FILES.foo += ["*.xyz"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/final_target/both/moz.build b/python/mozbuild/mozbuild/test/backend/data/final_target/both/moz.build
new file mode 100644
index 0000000000..dfbda9183b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/both/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPI_NAME = "mycrazyxpi"
+DIST_SUBDIR = "asubdir"
diff --git a/python/mozbuild/mozbuild/test/backend/data/final_target/dist-subdir/moz.build b/python/mozbuild/mozbuild/test/backend/data/final_target/dist-subdir/moz.build
new file mode 100644
index 0000000000..e44dd197ad
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/dist-subdir/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_SUBDIR = "asubdir"
diff --git a/python/mozbuild/mozbuild/test/backend/data/final_target/final-target/moz.build b/python/mozbuild/mozbuild/test/backend/data/final_target/final-target/moz.build
new file mode 100644
index 0000000000..e008f94478
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/final-target/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET = "random-final-target"
diff --git a/python/mozbuild/mozbuild/test/backend/data/final_target/moz.build b/python/mozbuild/mozbuild/test/backend/data/final_target/moz.build
new file mode 100644
index 0000000000..319062b78f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["xpi-name", "dist-subdir", "both", "final-target"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/final_target/xpi-name/moz.build b/python/mozbuild/mozbuild/test/backend/data/final_target/xpi-name/moz.build
new file mode 100644
index 0000000000..980810caa3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/xpi-name/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPI_NAME = "mycrazyxpi"
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files-force/foo-data b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/foo-data
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/foo-data
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-bar.py b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-bar.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-bar.py
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-foo.py b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-foo.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/generate-foo.py
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files-force/moz.build b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/moz.build
new file mode 100644
index 0000000000..d86b7b09ea
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files-force/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["bar.c", "foo.c", "quux.c"]
+
+bar = GENERATED_FILES["bar.c"]
+bar.script = "generate-bar.py:baz"
+bar.force = True
+
+foo = GENERATED_FILES["foo.c"]
+foo.script = "generate-foo.py"
+foo.inputs = ["foo-data"]
+foo.force = False
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files/foo-data b/python/mozbuild/mozbuild/test/backend/data/generated-files/foo-data
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files/foo-data
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files/generate-bar.py b/python/mozbuild/mozbuild/test/backend/data/generated-files/generate-bar.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files/generate-bar.py
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files/generate-foo.py b/python/mozbuild/mozbuild/test/backend/data/generated-files/generate-foo.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files/generate-foo.py
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/generated-files/moz.build
new file mode 100644
index 0000000000..01b444238e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated-files/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["bar.c", "foo.h", "quux.c"]
+
+bar = GENERATED_FILES["bar.c"]
+bar.script = "generate-bar.py:baz"
+
+foo = GENERATED_FILES["foo.h"]
+foo.script = "generate-foo.py"
+foo.inputs = ["foo-data"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/generated_includes/moz.build b/python/mozbuild/mozbuild/test/backend/data/generated_includes/moz.build
new file mode 100644
index 0000000000..31f9042c0a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/generated_includes/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["!/bar/baz", "!foo"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/host-defines/moz.build b/python/mozbuild/mozbuild/test/backend/data/host-defines/moz.build
new file mode 100644
index 0000000000..f1a632c841
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/host-defines/moz.build
@@ -0,0 +1,9 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+value = "xyz"
+HOST_DEFINES["FOO"] = True
+HOST_DEFINES["BAZ"] = '"ab\'cd"'
+HOST_DEFINES["BAR"] = 7
+HOST_DEFINES["VALUE"] = value
+HOST_DEFINES["QUX"] = False
diff --git a/python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/Cargo.toml b/python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/Cargo.toml
new file mode 100644
index 0000000000..147cb3acb3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "hostrusttool"
+version = "0.1.0"
+authors = ["The Mozilla Project Developers"]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/moz.build b/python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/moz.build
new file mode 100644
index 0000000000..96fccf2063
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/host-rust-library-features/moz.build
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def HostLibrary(name):
+ """Template for libraries."""
+ HOST_LIBRARY_NAME = name
+
+
+@template
+def HostRustLibrary(name, features=None):
+ """Template for Rust libraries."""
+ HostLibrary(name)
+
+ IS_RUST_LIBRARY = True
+
+ if features:
+ HOST_RUST_LIBRARY_FEATURES = features
+
+
+HostRustLibrary("hostrusttool", ["musthave", "cantlivewithout"])
diff --git a/python/mozbuild/mozbuild/test/backend/data/host-rust-library/Cargo.toml b/python/mozbuild/mozbuild/test/backend/data/host-rust-library/Cargo.toml
new file mode 100644
index 0000000000..349664c621
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/host-rust-library/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "hostrusttool"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/backend/data/host-rust-library/moz.build b/python/mozbuild/mozbuild/test/backend/data/host-rust-library/moz.build
new file mode 100644
index 0000000000..515f5d1a9f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/host-rust-library/moz.build
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def HostLibrary(name):
+ """Template for libraries."""
+ HOST_LIBRARY_NAME = name
+
+
+@template
+def HostRustLibrary(name, features=None):
+ """Template for Rust libraries."""
+ HostLibrary(name)
+
+ IS_RUST_LIBRARY = True
+
+ if features:
+ HOST_RUST_LIBRARY_FEATURES = features
+
+
+HostRustLibrary("hostrusttool")
diff --git a/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/moz.build b/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/moz.build
new file mode 100644
index 0000000000..c38b472911
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# We want to test recursion into the subdir, so do the real work in 'sub'
+DIRS += ["sub"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/foo.h.in b/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/foo.h.in
new file mode 100644
index 0000000000..da287dfcaa
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/foo.h.in
@@ -0,0 +1 @@
+#define MOZ_FOO @MOZ_FOO@
diff --git a/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/moz.build b/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/moz.build
new file mode 100644
index 0000000000..1420a99a8f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/install_substitute_config_files/sub/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+CONFIGURE_SUBST_FILES = ["foo.h"]
+
+EXPORTS.out += ["!foo.h"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build
new file mode 100644
index 0000000000..f7d1560af3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+PREPROCESSED_IPDL_SOURCES += [
+ "bar1.ipdl",
+]
+
+IPDL_SOURCES += [
+ "bar.ipdl",
+ "bar2.ipdlh",
+]
+
+FINAL_LIBRARY = "dummy"
diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build
new file mode 100644
index 0000000000..02e9f78154
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+PREPROCESSED_IPDL_SOURCES += [
+ "foo1.ipdl",
+]
+
+IPDL_SOURCES += [
+ "foo.ipdl",
+ "foo2.ipdlh",
+]
+
+FINAL_LIBRARY = "dummy"
diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/ipdl/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/ipdl/moz.build
new file mode 100644
index 0000000000..066397cb84
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/ipdl/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file just exists to establish a directory as the IPDL root directory.
+
+FINAL_LIBRARY = "dummy"
diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/moz.build
new file mode 100644
index 0000000000..4f0ddaa10e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@template
+def Library(name):
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+DIRS += [
+ "bar",
+ "foo",
+ "ipdl",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/jar-manifests/moz.build b/python/mozbuild/mozbuild/test/backend/data/jar-manifests/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/jar-manifests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/moz.build
new file mode 100644
index 0000000000..f01a012760
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/moz.build
@@ -0,0 +1,11 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("templates.mozbuild")
+
+DIRS += [
+ "real",
+ "shared",
+ "prog",
+ "static",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/prog/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/prog/moz.build
new file mode 100644
index 0000000000..3741f4be09
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/prog/moz.build
@@ -0,0 +1,11 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["qux"]
+
+Program("MyProgram")
+
+USE_LIBS += [
+ "bar",
+ "baz",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/moz.build
new file mode 100644
index 0000000000..3152de6211
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/moz.build
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SOURCES += ["qux1.c"]
+
+SharedLibrary("qux")
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/qux1.c b/python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/qux1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/prog/qux/qux1.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo1.c b/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo1.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo2.c b/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo2.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/foo2.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/moz.build
new file mode 100644
index 0000000000..a0bd7526e6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/real/foo/moz.build
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SOURCES += ["foo1.c", "foo2.c"]
+
+FINAL_LIBRARY = "foo"
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/real/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/real/moz.build
new file mode 100644
index 0000000000..32f9c1d656
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/real/moz.build
@@ -0,0 +1,14 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += [
+ "foo",
+]
+
+NO_EXPAND_LIBS = True
+
+OS_LIBS += ["-lbaz"]
+
+USE_LIBS += ["static:baz"]
+
+Library("foo")
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/baz1.c b/python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/baz1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/baz1.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/moz.build
new file mode 100644
index 0000000000..3299fa28f4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/shared/baz/moz.build
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SOURCES += ["baz1.c"]
+
+FINAL_LIBRARY = "baz"
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/shared/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/shared/moz.build
new file mode 100644
index 0000000000..42d79fe1fd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/shared/moz.build
@@ -0,0 +1,14 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += [
+ "baz",
+]
+
+STATIC_LIBRARY_NAME = "baz_s"
+FORCE_STATIC_LIB = True
+
+OS_LIBS += ["-lfoo"]
+USE_LIBS += ["qux"]
+
+SharedLibrary("baz")
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar1.cc b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar1.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar1.cc
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar2.cc b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar2.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar2.cc
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/bar_helper1.cpp b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/bar_helper1.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/bar_helper1.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/moz.build
new file mode 100644
index 0000000000..12d0fc83fb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/bar_helper/moz.build
@@ -0,0 +1,8 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SOURCES += [
+ "bar_helper1.cpp",
+]
+
+FINAL_LIBRARY = "bar"
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/moz.build
new file mode 100644
index 0000000000..d9d75803ed
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/bar/moz.build
@@ -0,0 +1,13 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SOURCES += [
+ "bar1.cc",
+ "bar2.cc",
+]
+
+DIRS += [
+ "bar_helper",
+]
+
+FINAL_LIBRARY = "bar"
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/static/moz.build b/python/mozbuild/mozbuild/test/backend/data/linkage/static/moz.build
new file mode 100644
index 0000000000..37b3d96cc7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/static/moz.build
@@ -0,0 +1,12 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += [
+ "bar",
+]
+
+USE_LIBS += ["foo"]
+
+OS_LIBS += ["-lbar"]
+
+Library("bar")
diff --git a/python/mozbuild/mozbuild/test/backend/data/linkage/templates.mozbuild b/python/mozbuild/mozbuild/test/backend/data/linkage/templates.mozbuild
new file mode 100644
index 0000000000..1f874060df
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/linkage/templates.mozbuild
@@ -0,0 +1,23 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+ LIBRARY_NAME = name
+
+@template
+def SharedLibrary(name):
+ FORCE_SHARED_LIB = True
+ LIBRARY_NAME = name
+
+@template
+def Binary():
+ # Add -lfoo for testing purposes.
+ OS_LIBS += ['foo']
+
+
+@template
+def Program(name):
+ PROGRAM = name
+
+ Binary() \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/backend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory b/python/mozbuild/mozbuild/test/backend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory
diff --git a/python/mozbuild/mozbuild/test/backend/data/local_includes/foo/dummy_file_for_nonempty_directory b/python/mozbuild/mozbuild/test/backend/data/local_includes/foo/dummy_file_for_nonempty_directory
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/local_includes/foo/dummy_file_for_nonempty_directory
diff --git a/python/mozbuild/mozbuild/test/backend/data/local_includes/moz.build b/python/mozbuild/mozbuild/test/backend/data/local_includes/moz.build
new file mode 100644
index 0000000000..1c29ac2ea2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/local_includes/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["/bar/baz", "foo"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/bar.ini b/python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/bar.ini
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/bar.ini
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/foo.js b/python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-files/en-US/foo.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/localized-files/moz.build
new file mode 100644
index 0000000000..93a97c7b84
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-files/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_FILES += [
+ "en-US/abc/*.abc",
+ "en-US/bar.ini",
+ "en-US/foo.js",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/en-US/localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/en-US/localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/en-US/localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/foo-data b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/foo-data
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/foo-data
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/generate-foo.py b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/generate-foo.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/generate-foo.py
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/inner/locales/en-US/localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/inner/locales/en-US/localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/inner/locales/en-US/localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/locales/en-US/localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/locales/en-US/localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/locales/en-US/localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/moz.build b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/moz.build
new file mode 100644
index 0000000000..2b0cf472c9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_GENERATED_FILES += ["foo{AB_CD}.xyz"]
+
+foo = LOCALIZED_GENERATED_FILES["foo{AB_CD}.xyz"]
+foo.script = "generate-foo.py"
+foo.inputs = [
+ "en-US/localized-input",
+ "non-localized-input",
+]
+
+LOCALIZED_GENERATED_FILES += ["bar{AB_rCD}.xyz"]
+
+bar = LOCALIZED_GENERATED_FILES["bar{AB_rCD}.xyz"]
+bar.script = "generate-foo.py"
+bar.inputs = [
+ # Absolute source path.
+ "/inner/locales/en-US/localized-input",
+ "non-localized-input",
+]
+
+LOCALIZED_GENERATED_FILES += ["zot{AB_rCD}.xyz"]
+
+bar = LOCALIZED_GENERATED_FILES["zot{AB_rCD}.xyz"]
+bar.script = "generate-foo.py"
+bar.inputs = [
+ # Relative source path.
+ "locales/en-US/localized-input",
+ "non-localized-input",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/non-localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/non-localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-AB_CD/non-localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/en-US/localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/en-US/localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/en-US/localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/foo-data b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/foo-data
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/foo-data
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/generate-foo.py b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/generate-foo.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/generate-foo.py
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/moz.build b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/moz.build
new file mode 100644
index 0000000000..26fb165e06
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_GENERATED_FILES += ["foo.xyz"]
+
+foo = LOCALIZED_GENERATED_FILES["foo.xyz"]
+foo.script = "generate-foo.py"
+foo.inputs = [
+ "en-US/localized-input",
+ "non-localized-input",
+]
+
+LOCALIZED_GENERATED_FILES += ["abc.xyz"]
+
+abc = LOCALIZED_GENERATED_FILES["abc.xyz"]
+abc.script = "generate-foo.py"
+abc.inputs = [
+ "en-US/localized-input",
+ "non-localized-input",
+]
+abc.force = True
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/non-localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/non-localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files-force/non-localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/en-US/localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/en-US/localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/en-US/localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/foo-data b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/foo-data
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/foo-data
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/generate-foo.py b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/generate-foo.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/generate-foo.py
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/moz.build
new file mode 100644
index 0000000000..f44325dfb1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_GENERATED_FILES += ["foo.xyz"]
+
+foo = LOCALIZED_GENERATED_FILES["foo.xyz"]
+foo.script = "generate-foo.py"
+foo.inputs = [
+ "en-US/localized-input",
+ "non-localized-input",
+]
+
+# Also check that using it in LOCALIZED_FILES does the right thing.
+LOCALIZED_FILES += ["!foo.xyz"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/non-localized-input b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/non-localized-input
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-generated-files/non-localized-input
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/bar.ini b/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/bar.ini
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/bar.ini
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/foo.js b/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/en-US/foo.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/moz.build
new file mode 100644
index 0000000000..8cec207128
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/localized-pp-files/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_PP_FILES += [
+ "en-US/bar.ini",
+ "en-US/foo.js",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/c-library.c b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/c-library.c
new file mode 100644
index 0000000000..3b09e769db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/c-library.c
@@ -0,0 +1,2 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/moz.build
new file mode 100644
index 0000000000..8e15d10c43
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-library/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SharedLibrary("c_library")
+
+SOURCES = ["c-library.c"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/c_test_program.c b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/c_test_program.c
new file mode 100644
index 0000000000..3b09e769db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/c_test_program.c
@@ -0,0 +1,2 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/moz.build
new file mode 100644
index 0000000000..27f2cd3d5d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-program/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Program("c_test_program")
+
+SOURCES = ["c_test_program.c"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/c_simple_program.c b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/c_simple_program.c
new file mode 100644
index 0000000000..3b09e769db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/c_simple_program.c
@@ -0,0 +1,2 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/moz.build
new file mode 100644
index 0000000000..db958d1d1f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/c-simple-programs/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SimplePrograms(["c_simple_program"], ext=".c")
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/c-source.c b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/c-source.c
new file mode 100644
index 0000000000..3b09e769db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/c-source.c
@@ -0,0 +1,2 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/cxx-library.cpp b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/cxx-library.cpp
new file mode 100644
index 0000000000..3b09e769db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/cxx-library.cpp
@@ -0,0 +1,2 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/moz.build
new file mode 100644
index 0000000000..ee75ad0cb9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-library/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SharedLibrary("cxx-library")
+
+SOURCES = [
+ "c-source.c",
+ "cxx-library.cpp",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/cxx_test_program.cpp b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/cxx_test_program.cpp
new file mode 100644
index 0000000000..3b09e769db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/cxx_test_program.cpp
@@ -0,0 +1,2 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/moz.build
new file mode 100644
index 0000000000..175f18c88a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-program/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Program("cxx_test_program")
+
+SOURCES = ["cxx_test_program.cpp"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/cxx_simple_program.cpp b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/cxx_simple_program.cpp
new file mode 100644
index 0000000000..3b09e769db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/cxx_simple_program.cpp
@@ -0,0 +1,2 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/moz.build
new file mode 100644
index 0000000000..e055370900
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/cxx-simple-programs/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SimplePrograms(["cxx_simple_program"])
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/moz.build
new file mode 100644
index 0000000000..7f0a6b430b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += [
+ "c-program",
+ "cxx-program",
+ "c-simple-programs",
+ "cxx-simple-programs",
+ "c-library",
+ "cxx-library",
+]
+
+
+@template
+def Program(name):
+ PROGRAM = name
+
+
+@template
+def SimplePrograms(names, ext=".cpp"):
+ SIMPLE_PROGRAMS += names
+ SOURCES += ["%s%s" % (name, ext) for name in names]
+
+
+@template
+def Library(name):
+ LIBRARY_NAME = name
+
+
+@template
+def SharedLibrary(name):
+ Library(name)
+
+ FORCE_SHARED_LIB = True
diff --git a/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/simple-programs/moz.build b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/simple-programs/moz.build
new file mode 100644
index 0000000000..62966a58e1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/prog-lib-c-only/simple-programs/moz.build
@@ -0,0 +1,3 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/program-paths/dist-bin/moz.build b/python/mozbuild/mozbuild/test/backend/data/program-paths/dist-bin/moz.build
new file mode 100644
index 0000000000..d8b952c014
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/program-paths/dist-bin/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Program("dist-bin")
diff --git a/python/mozbuild/mozbuild/test/backend/data/program-paths/dist-subdir/moz.build b/python/mozbuild/mozbuild/test/backend/data/program-paths/dist-subdir/moz.build
new file mode 100644
index 0000000000..fc2f664c01
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/program-paths/dist-subdir/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_SUBDIR = "foo"
+Program("dist-subdir")
diff --git a/python/mozbuild/mozbuild/test/backend/data/program-paths/final-target/moz.build b/python/mozbuild/mozbuild/test/backend/data/program-paths/final-target/moz.build
new file mode 100644
index 0000000000..a0d5805262
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/program-paths/final-target/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET = "final/target"
+Program("final-target")
diff --git a/python/mozbuild/mozbuild/test/backend/data/program-paths/moz.build b/python/mozbuild/mozbuild/test/backend/data/program-paths/moz.build
new file mode 100644
index 0000000000..d1d087fd45
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/program-paths/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Program(name):
+ PROGRAM = name
+
+
+DIRS += [
+ "dist-bin",
+ "dist-subdir",
+ "final-target",
+ "not-installed",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/program-paths/not-installed/moz.build b/python/mozbuild/mozbuild/test/backend/data/program-paths/not-installed/moz.build
new file mode 100644
index 0000000000..c725ab7326
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/program-paths/not-installed/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_INSTALL = False
+Program("not-installed")
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/bar.res.in b/python/mozbuild/mozbuild/test/backend/data/resources/bar.res.in
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/bar.res.in
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/cursor.cur b/python/mozbuild/mozbuild/test/backend/data/resources/cursor.cur
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/cursor.cur
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/desktop1.ttf b/python/mozbuild/mozbuild/test/backend/data/resources/desktop1.ttf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/desktop1.ttf
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/desktop2.ttf b/python/mozbuild/mozbuild/test/backend/data/resources/desktop2.ttf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/desktop2.ttf
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/extra.manifest b/python/mozbuild/mozbuild/test/backend/data/resources/extra.manifest
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/extra.manifest
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/font1.ttf b/python/mozbuild/mozbuild/test/backend/data/resources/font1.ttf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/font1.ttf
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/font2.ttf b/python/mozbuild/mozbuild/test/backend/data/resources/font2.ttf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/font2.ttf
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/foo.res b/python/mozbuild/mozbuild/test/backend/data/resources/foo.res
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/foo.res
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/mobile.ttf b/python/mozbuild/mozbuild/test/backend/data/resources/mobile.ttf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/mobile.ttf
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/moz.build b/python/mozbuild/mozbuild/test/backend/data/resources/moz.build
new file mode 100644
index 0000000000..619af26e64
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/moz.build
@@ -0,0 +1,9 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+RESOURCE_FILES += ["bar.res.in", "foo.res"]
+RESOURCE_FILES.cursors += ["cursor.cur"]
+RESOURCE_FILES.fonts += ["font1.ttf", "font2.ttf"]
+RESOURCE_FILES.fonts.desktop += ["desktop1.ttf", "desktop2.ttf"]
+RESOURCE_FILES.fonts.mobile += ["mobile.ttf"]
+RESOURCE_FILES.tests += ["extra.manifest", "test.manifest"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/resources/test.manifest b/python/mozbuild/mozbuild/test/backend/data/resources/test.manifest
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/test.manifest
diff --git a/python/mozbuild/mozbuild/test/backend/data/rust-library-features/Cargo.toml b/python/mozbuild/mozbuild/test/backend/data/rust-library-features/Cargo.toml
new file mode 100644
index 0000000000..0d778b2b0e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/rust-library-features/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "feature-library"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/backend/data/rust-library-features/moz.build b/python/mozbuild/mozbuild/test/backend/data/rust-library-features/moz.build
new file mode 100644
index 0000000000..f17f29b0e7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/rust-library-features/moz.build
@@ -0,0 +1,20 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name, features):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+ RUST_LIBRARY_FEATURES = features
+
+
+RustLibrary("feature-library", ["musthave", "cantlivewithout"])
diff --git a/python/mozbuild/mozbuild/test/backend/data/rust-library/Cargo.toml b/python/mozbuild/mozbuild/test/backend/data/rust-library/Cargo.toml
new file mode 100644
index 0000000000..5e9e44632f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/rust-library/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "test-library"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/backend/data/rust-library/moz.build b/python/mozbuild/mozbuild/test/backend/data/rust-library/moz.build
new file mode 100644
index 0000000000..b0f29a1ef5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/rust-library/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+RustLibrary("test-library")
diff --git a/python/mozbuild/mozbuild/test/backend/data/rust-programs/code/Cargo.toml b/python/mozbuild/mozbuild/test/backend/data/rust-programs/code/Cargo.toml
new file mode 100644
index 0000000000..e0d400e070
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/rust-programs/code/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+authors = ["The Mozilla Project Developers"]
+name = "testing"
+version = "0.0.1"
+
+[[bin]]
+name = "target"
+
+[[bin]]
+name = "host"
diff --git a/python/mozbuild/mozbuild/test/backend/data/rust-programs/code/moz.build b/python/mozbuild/mozbuild/test/backend/data/rust-programs/code/moz.build
new file mode 100644
index 0000000000..f0efdb3799
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/rust-programs/code/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+RUST_PROGRAMS += ["target"]
+HOST_RUST_PROGRAMS += ["host"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/rust-programs/moz.build b/python/mozbuild/mozbuild/test/backend/data/rust-programs/moz.build
new file mode 100644
index 0000000000..cb635f6adb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/rust-programs/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["code"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/bar.cpp b/python/mozbuild/mozbuild/test/backend/data/sources/bar.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/bar.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/bar.s b/python/mozbuild/mozbuild/test/backend/data/sources/bar.s
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/bar.s
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/baz.c b/python/mozbuild/mozbuild/test/backend/data/sources/baz.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/baz.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/foo.asm b/python/mozbuild/mozbuild/test/backend/data/sources/foo.asm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/foo.asm
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/foo.cpp b/python/mozbuild/mozbuild/test/backend/data/sources/foo.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/foo.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/fuga.mm b/python/mozbuild/mozbuild/test/backend/data/sources/fuga.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/fuga.mm
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/hoge.mm b/python/mozbuild/mozbuild/test/backend/data/sources/hoge.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/hoge.mm
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/moz.build b/python/mozbuild/mozbuild/test/backend/data/sources/moz.build
new file mode 100644
index 0000000000..40d5a8d38d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+SOURCES += ["bar.s", "foo.asm"]
+
+HOST_SOURCES += ["bar.cpp", "foo.cpp"]
+HOST_SOURCES += ["baz.c", "qux.c"]
+
+SOURCES += ["baz.c", "qux.c"]
+
+SOURCES += ["fuga.mm", "hoge.mm"]
+
+SOURCES += ["titi.S", "toto.S"]
+
+WASM_SOURCES += ["bar.cpp"]
+WASM_SOURCES += ["baz.c"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/qux.c b/python/mozbuild/mozbuild/test/backend/data/sources/qux.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/qux.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/titi.S b/python/mozbuild/mozbuild/test/backend/data/sources/titi.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/titi.S
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/toto.S b/python/mozbuild/mozbuild/test/backend/data/sources/toto.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/toto.S
diff --git a/python/mozbuild/mozbuild/test/backend/data/stub0/Makefile.in b/python/mozbuild/mozbuild/test/backend/data/stub0/Makefile.in
new file mode 100644
index 0000000000..02ff0a3f90
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/Makefile.in
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FOO := foo
diff --git a/python/mozbuild/mozbuild/test/backend/data/stub0/dir1/Makefile.in b/python/mozbuild/mozbuild/test/backend/data/stub0/dir1/Makefile.in
new file mode 100644
index 0000000000..17c147d97a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir1/Makefile.in
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
+
diff --git a/python/mozbuild/mozbuild/test/backend/data/stub0/dir1/moz.build b/python/mozbuild/mozbuild/test/backend/data/stub0/dir1/moz.build
new file mode 100644
index 0000000000..62966a58e1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir1/moz.build
@@ -0,0 +1,3 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/stub0/dir2/moz.build b/python/mozbuild/mozbuild/test/backend/data/stub0/dir2/moz.build
new file mode 100644
index 0000000000..62966a58e1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir2/moz.build
@@ -0,0 +1,3 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/stub0/dir3/Makefile.in b/python/mozbuild/mozbuild/test/backend/data/stub0/dir3/Makefile.in
new file mode 100644
index 0000000000..17c147d97a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir3/Makefile.in
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
+
diff --git a/python/mozbuild/mozbuild/test/backend/data/stub0/dir3/moz.build b/python/mozbuild/mozbuild/test/backend/data/stub0/dir3/moz.build
new file mode 100644
index 0000000000..62966a58e1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir3/moz.build
@@ -0,0 +1,3 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/python/mozbuild/mozbuild/test/backend/data/stub0/moz.build b/python/mozbuild/mozbuild/test/backend/data/stub0/moz.build
new file mode 100644
index 0000000000..4f6e7cb318
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["dir1"]
+DIRS += ["dir2"]
+TEST_DIRS += ["dir3"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/Makefile.in b/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/Makefile.in
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/Makefile.in
diff --git a/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/foo.in b/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/foo.in
new file mode 100644
index 0000000000..5331f1f051
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/foo.in
@@ -0,0 +1 @@
+TEST = @MOZ_FOO@
diff --git a/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/moz.build b/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/moz.build
new file mode 100644
index 0000000000..bded13e07d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/substitute_config_files/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+CONFIGURE_SUBST_FILES = ["foo"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/another-file.sjs b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/another-file.sjs
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/another-file.sjs
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/browser.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/browser.ini
new file mode 100644
index 0000000000..4f1335d6b1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ another-file.sjs
+ data/**
+
+[test_sub.js] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/one.txt b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/one.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/one.txt
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/two.txt b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/two.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/data/two.txt
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/test_sub.js b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/test_sub.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/child/test_sub.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/mochitest.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/mochitest.ini
new file mode 100644
index 0000000000..a9860f3de8
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/mochitest.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ support-file.txt
+ !/child/test_sub.js
+ !/child/another-file.sjs
+ !/child/data/**
+
+[test_foo.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/moz.build
new file mode 100644
index 0000000000..9df54dbc99
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+BROWSER_CHROME_MANIFESTS += ["child/browser.ini"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/support-file.txt b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/support-file.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/support-file.txt
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/test_foo.js b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/test_foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifest-shared-support/test_foo.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest-common.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest-common.ini
new file mode 100644
index 0000000000..31d07b5af3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest-common.ini
@@ -0,0 +1 @@
+[test_bar.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest.ini
new file mode 100644
index 0000000000..cf7a3c44bd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/mochitest.ini
@@ -0,0 +1,2 @@
+[test_foo.js]
+[include:mochitest-common.ini]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/moz.build
new file mode 100644
index 0000000000..8058c0b836
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/moz.build
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_bar.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_bar.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_bar.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_foo.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-backend-sources/test_foo.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest1.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest1.ini
new file mode 100644
index 0000000000..1f9816a899
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest1.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = support-file.txt
+
+[test_foo.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest2.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest2.ini
new file mode 100644
index 0000000000..e2a2fc96a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest2.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = support-file.txt
+
+[test_bar.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/moz.build
new file mode 100644
index 0000000000..a86b934fa1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/moz.build
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += [
+ "mochitest1.ini",
+ "mochitest2.ini",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_bar.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_bar.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_bar.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_foo.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/test_foo.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/instrumentation.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/instrumentation.ini
new file mode 100644
index 0000000000..03d4f794e2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/instrumentation.ini
@@ -0,0 +1 @@
+[not_packaged.java]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.ini
new file mode 100644
index 0000000000..009b2b2239
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.ini
@@ -0,0 +1 @@
+[mochitest.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/mochitest.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/moz.build
new file mode 100644
index 0000000000..f0496e09d9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/moz.build
@@ -0,0 +1,10 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
+
+ANDROID_INSTRUMENTATION_MANIFESTS += [
+ "instrumentation.ini",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/not_packaged.java b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/not_packaged.java
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-package-tests/not_packaged.java
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/test_bar.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/test_bar.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/test_bar.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/xpcshell.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/xpcshell.ini
new file mode 100644
index 0000000000..0cddad8ba9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/xpcshell.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_bar.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.ini
new file mode 100644
index 0000000000..81869e1fa0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[mochitest.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/mochitest.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/moz.build
new file mode 100644
index 0000000000..42462a3059
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/moz.build
@@ -0,0 +1,9 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "dir1/xpcshell.ini",
+ "xpcshell.ini",
+]
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.ini
new file mode 100644
index 0000000000..f6a5351e94
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = support/**
+
+[xpcshell.js]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/moz.build
new file mode 100644
index 0000000000..eb83fd1826
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["test", "src"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/src/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/src/moz.build
new file mode 100644
index 0000000000..69cde19c29
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/src/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries"""
+ LIBRARY_NAME = name
+
+
+Library("foo")
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/moz.build b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/moz.build
new file mode 100644
index 0000000000..a43f4083b3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET = "_tests/xpcshell/tests/mozbuildtest"
+
+
+@template
+def Library(name):
+ """Template for libraries"""
+ LIBRARY_NAME = name
+
+
+@template
+def SimplePrograms(names, ext=".cpp"):
+ """Template for simple program executables.
+
+ Those have a single source with the same base name as the executable.
+ """
+ SIMPLE_PROGRAMS += names
+ SOURCES += ["%s%s" % (name, ext) for name in names]
+
+
+@template
+def HostLibrary(name):
+ """Template for build tools libraries."""
+ HOST_LIBRARY_NAME = name
+
+
+Library("test-library")
+HostLibrary("host-test-library")
+SimplePrograms(["test-one", "test-two"])
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-one.cpp b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-one.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-one.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-two.cpp b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-two.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-support-binaries-tracked/test/test-two.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/test_config/file.in b/python/mozbuild/mozbuild/test/backend/data/test_config/file.in
new file mode 100644
index 0000000000..07aa30deb6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test_config/file.in
@@ -0,0 +1,3 @@
+#ifdef foo
+@foo@
+@bar@
diff --git a/python/mozbuild/mozbuild/test/backend/data/test_config/moz.build b/python/mozbuild/mozbuild/test/backend/data/test_config/moz.build
new file mode 100644
index 0000000000..5cf4c78f90
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test_config/moz.build
@@ -0,0 +1,3 @@
+CONFIGURE_SUBST_FILES = [
+ "file",
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/Makefile.in b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/Makefile.in
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/Makefile.in
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/baz.def b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/baz.def
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/baz.def
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
new file mode 100644
index 0000000000..81595d2db3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DELAYLOAD_DLLS = ["foo.dll", "bar.dll"]
+
+RCFILE = "foo.rc"
+RCINCLUDE = "bar.rc"
+DEFFILE = "baz.def"
+
+WIN32_EXE_LDFLAGS += ["-subsystem:console"]
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.c b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.cpp b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.mm b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test1.mm
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.c b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.c
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.cpp b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.mm b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/test2.mm
diff --git a/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/bar.cpp b/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/bar.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/bar.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/foo.cpp b/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/foo.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/foo.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/moz.build b/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/moz.build
new file mode 100644
index 0000000000..ae1fc0c370
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/visual-studio/dir1/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_LIBRARY = "test"
+SOURCES += ["bar.cpp", "foo.cpp"]
+LOCAL_INCLUDES += ["/includeA/foo"]
+DEFINES["DEFINEFOO"] = True
+DEFINES["DEFINEBAR"] = "bar"
diff --git a/python/mozbuild/mozbuild/test/backend/data/visual-studio/moz.build b/python/mozbuild/mozbuild/test/backend/data/visual-studio/moz.build
new file mode 100644
index 0000000000..a0a888fa01
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/visual-studio/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["dir1"]
+
+Library("test")
diff --git a/python/mozbuild/mozbuild/test/backend/data/xpidl/bar.idl b/python/mozbuild/mozbuild/test/backend/data/xpidl/bar.idl
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/xpidl/bar.idl
diff --git a/python/mozbuild/mozbuild/test/backend/data/xpidl/config/makefiles/xpidl/Makefile.in b/python/mozbuild/mozbuild/test/backend/data/xpidl/config/makefiles/xpidl/Makefile.in
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/xpidl/config/makefiles/xpidl/Makefile.in
diff --git a/python/mozbuild/mozbuild/test/backend/data/xpidl/foo.idl b/python/mozbuild/mozbuild/test/backend/data/xpidl/foo.idl
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/xpidl/foo.idl
diff --git a/python/mozbuild/mozbuild/test/backend/data/xpidl/moz.build b/python/mozbuild/mozbuild/test/backend/data/xpidl/moz.build
new file mode 100644
index 0000000000..df521ac7c5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/xpidl/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPIDL_MODULE = "my_module"
+XPIDL_SOURCES = ["bar.idl", "foo.idl"]
diff --git a/python/mozbuild/mozbuild/test/backend/test_build.py b/python/mozbuild/mozbuild/test/backend/test_build.py
new file mode 100644
index 0000000000..3287ba5e57
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_build.py
@@ -0,0 +1,265 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import shutil
+import sys
+import unittest
+from contextlib import contextmanager
+from tempfile import mkdtemp
+
+import buildconfig
+import mozpack.path as mozpath
+import six
+from mozfile import which
+from mozpack.files import FileFinder
+from mozunit import main
+
+from mozbuild.backend import get_backend_class
+from mozbuild.backend.configenvironment import ConfigEnvironment
+from mozbuild.backend.fastermake import FasterMakeBackend
+from mozbuild.backend.recursivemake import RecursiveMakeBackend
+from mozbuild.base import MozbuildObject
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import BuildReader
+from mozbuild.util import ensureParentDir
+
+
+def make_path():
+ try:
+ return buildconfig.substs["GMAKE"]
+ except KeyError:
+ fetches_dir = os.environ.get("MOZ_FETCHES_DIR")
+ extra_search_dirs = ()
+ if fetches_dir:
+ extra_search_dirs = (os.path.join(fetches_dir, "mozmake"),)
+ # Fallback for when running the test without an objdir.
+ for name in ("gmake", "make", "mozmake", "gnumake", "mingw32-make"):
+ path = which(name, extra_search_dirs=extra_search_dirs)
+ if path:
+ return path
+
+
+BASE_SUBSTS = [
+ ("PYTHON", mozpath.normsep(sys.executable)),
+ ("PYTHON3", mozpath.normsep(sys.executable)),
+ ("MOZ_UI_LOCALE", "en-US"),
+ ("GMAKE", make_path()),
+]
+
+
+class TestBuild(unittest.TestCase):
+ def setUp(self):
+ self._old_env = dict(os.environ)
+ os.environ.pop("MOZCONFIG", None)
+ os.environ.pop("MOZ_OBJDIR", None)
+ os.environ.pop("MOZ_PGO", None)
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ @contextmanager
+ def do_test_backend(self, *backends, **kwargs):
+ # Create the objdir in the srcdir to ensure that they share
+ # the same drive on Windows.
+ topobjdir = mkdtemp(dir=buildconfig.topsrcdir)
+ try:
+ config = ConfigEnvironment(buildconfig.topsrcdir, topobjdir, **kwargs)
+ reader = BuildReader(config)
+ emitter = TreeMetadataEmitter(config)
+ moz_build = mozpath.join(config.topsrcdir, "test.mozbuild")
+ definitions = list(emitter.emit(reader.read_mozbuild(moz_build, config)))
+ for backend in backends:
+ backend(config).consume(definitions)
+
+ yield config
+ except Exception:
+ raise
+ finally:
+ if not os.environ.get("MOZ_NO_CLEANUP"):
+ shutil.rmtree(topobjdir)
+
+ @contextmanager
+ def line_handler(self):
+ lines = []
+
+ def handle_make_line(line):
+ lines.append(line)
+
+ try:
+ yield handle_make_line
+ except Exception:
+ print("\n".join(lines))
+ raise
+
+ if os.environ.get("MOZ_VERBOSE_MAKE"):
+ print("\n".join(lines))
+
+ def test_recursive_make(self):
+ substs = list(BASE_SUBSTS)
+ with self.do_test_backend(RecursiveMakeBackend, substs=substs) as config:
+ build = MozbuildObject(config.topsrcdir, None, None, config.topobjdir)
+ build._config_environment = config
+ overrides = [
+ "install_manifest_depends=",
+ "MOZ_JAR_MAKER_FILE_FORMAT=flat",
+ "TEST_MOZBUILD=1",
+ ]
+ with self.line_handler() as handle_make_line:
+ build._run_make(
+ directory=config.topobjdir,
+ target=overrides,
+ silent=False,
+ line_handler=handle_make_line,
+ )
+
+ self.validate(config)
+
+ def test_faster_recursive_make(self):
+ substs = list(BASE_SUBSTS) + [
+ ("BUILD_BACKENDS", "FasterMake+RecursiveMake"),
+ ]
+ with self.do_test_backend(
+ get_backend_class("FasterMake+RecursiveMake"), substs=substs
+ ) as config:
+ buildid = mozpath.join(config.topobjdir, "config", "buildid")
+ ensureParentDir(buildid)
+ with open(buildid, "w") as fh:
+ fh.write("20100101012345\n")
+
+ build = MozbuildObject(config.topsrcdir, None, None, config.topobjdir)
+ build._config_environment = config
+ overrides = [
+ "install_manifest_depends=",
+ "MOZ_JAR_MAKER_FILE_FORMAT=flat",
+ "TEST_MOZBUILD=1",
+ ]
+ with self.line_handler() as handle_make_line:
+ build._run_make(
+ directory=config.topobjdir,
+ target=overrides,
+ silent=False,
+ line_handler=handle_make_line,
+ )
+
+ self.validate(config)
+
+ def test_faster_make(self):
+ substs = list(BASE_SUBSTS) + [
+ ("MOZ_BUILD_APP", "dummy_app"),
+ ("MOZ_WIDGET_TOOLKIT", "dummy_widget"),
+ ]
+ with self.do_test_backend(
+ RecursiveMakeBackend, FasterMakeBackend, substs=substs
+ ) as config:
+ buildid = mozpath.join(config.topobjdir, "config", "buildid")
+ ensureParentDir(buildid)
+ with open(buildid, "w") as fh:
+ fh.write("20100101012345\n")
+
+ build = MozbuildObject(config.topsrcdir, None, None, config.topobjdir)
+ build._config_environment = config
+ overrides = [
+ "TEST_MOZBUILD=1",
+ ]
+ with self.line_handler() as handle_make_line:
+ build._run_make(
+ directory=mozpath.join(config.topobjdir, "faster"),
+ target=overrides,
+ silent=False,
+ line_handler=handle_make_line,
+ )
+
+ self.validate(config)
+
+ def validate(self, config):
+ self.maxDiff = None
+ test_path = mozpath.join(
+ "$SRCDIR",
+ "python",
+ "mozbuild",
+ "mozbuild",
+ "test",
+ "backend",
+ "data",
+ "build",
+ )
+
+ result = {
+ p: six.ensure_text(f.open().read())
+ for p, f in FileFinder(mozpath.join(config.topobjdir, "dist"))
+ }
+ self.assertTrue(len(result))
+ self.assertEqual(
+ result,
+ {
+ "bin/baz.ini": "baz.ini: FOO is foo\n",
+ "bin/child/bar.ini": "bar.ini\n",
+ "bin/child2/foo.css": "foo.css: FOO is foo\n",
+ "bin/child2/qux.ini": "qux.ini: BAR is not defined\n",
+ "bin/chrome.manifest": "manifest chrome/foo.manifest\n"
+ "manifest components/components.manifest\n",
+ "bin/chrome/foo.manifest": "content bar foo/child/\n"
+ "content foo foo/\n"
+ "override chrome://foo/bar.svg#hello "
+ "chrome://bar/bar.svg#hello\n",
+ "bin/chrome/foo/bar.js": "bar.js\n",
+ "bin/chrome/foo/child/baz.jsm": '//@line 2 "%s/baz.jsm"\nbaz.jsm: FOO is foo\n'
+ % (test_path),
+ "bin/chrome/foo/child/hoge.js": '//@line 2 "%s/bar.js"\nbar.js: FOO is foo\n'
+ % (test_path),
+ "bin/chrome/foo/foo.css": "foo.css: FOO is foo\n",
+ "bin/chrome/foo/foo.js": "foo.js\n",
+ "bin/chrome/foo/qux.js": "bar.js\n",
+ "bin/components/bar.js": '//@line 2 "%s/bar.js"\nbar.js: FOO is foo\n'
+ % (test_path),
+ "bin/components/components.manifest": "component {foo} foo.js\ncomponent {bar} bar.js\n", # NOQA: E501
+ "bin/components/foo.js": "foo.js\n",
+ "bin/defaults/pref/prefs.js": "prefs.js\n",
+ "bin/foo.ini": "foo.ini\n",
+ "bin/modules/baz.jsm": '//@line 2 "%s/baz.jsm"\nbaz.jsm: FOO is foo\n'
+ % (test_path),
+ "bin/modules/child/bar.jsm": "bar.jsm\n",
+ "bin/modules/child2/qux.jsm": '//@line 4 "%s/qux.jsm"\nqux.jsm: BAR is not defined\n' # NOQA: E501
+ % (test_path),
+ "bin/modules/foo.jsm": "foo.jsm\n",
+ "bin/res/resource": "resource\n",
+ "bin/res/child/resource2": "resource2\n",
+ "bin/app/baz.ini": "baz.ini: FOO is bar\n",
+ "bin/app/child/bar.ini": "bar.ini\n",
+ "bin/app/child2/qux.ini": "qux.ini: BAR is defined\n",
+ "bin/app/chrome.manifest": "manifest chrome/foo.manifest\n"
+ "manifest components/components.manifest\n",
+ "bin/app/chrome/foo.manifest": "content bar foo/child/\n"
+ "content foo foo/\n"
+ "override chrome://foo/bar.svg#hello "
+ "chrome://bar/bar.svg#hello\n",
+ "bin/app/chrome/foo/bar.js": "bar.js\n",
+ "bin/app/chrome/foo/child/baz.jsm": '//@line 2 "%s/baz.jsm"\nbaz.jsm: FOO is bar\n'
+ % (test_path),
+ "bin/app/chrome/foo/child/hoge.js": '//@line 2 "%s/bar.js"\nbar.js: FOO is bar\n'
+ % (test_path),
+ "bin/app/chrome/foo/foo.css": "foo.css: FOO is bar\n",
+ "bin/app/chrome/foo/foo.js": "foo.js\n",
+ "bin/app/chrome/foo/qux.js": "bar.js\n",
+ "bin/app/components/bar.js": '//@line 2 "%s/bar.js"\nbar.js: FOO is bar\n'
+ % (test_path),
+ "bin/app/components/components.manifest": "component {foo} foo.js\ncomponent {bar} bar.js\n", # NOQA: E501
+ "bin/app/components/foo.js": "foo.js\n",
+ "bin/app/defaults/preferences/prefs.js": "prefs.js\n",
+ "bin/app/foo.css": "foo.css: FOO is bar\n",
+ "bin/app/foo.ini": "foo.ini\n",
+ "bin/app/modules/baz.jsm": '//@line 2 "%s/baz.jsm"\nbaz.jsm: FOO is bar\n'
+ % (test_path),
+ "bin/app/modules/child/bar.jsm": "bar.jsm\n",
+ "bin/app/modules/child2/qux.jsm": '//@line 2 "%s/qux.jsm"\nqux.jsm: BAR is defined\n' # NOQA: E501
+ % (test_path),
+ "bin/app/modules/foo.jsm": "foo.jsm\n",
+ },
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/backend/test_configenvironment.py b/python/mozbuild/mozbuild/test/backend/test_configenvironment.py
new file mode 100644
index 0000000000..7900cdd737
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_configenvironment.py
@@ -0,0 +1,73 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+
+import mozpack.path as mozpath
+from mozunit import main
+
+import mozbuild.backend.configenvironment as ConfigStatus
+from mozbuild.util import ReadOnlyDict
+
+
+class ConfigEnvironment(ConfigStatus.ConfigEnvironment):
+ def __init__(self, *args, **kwargs):
+ ConfigStatus.ConfigEnvironment.__init__(self, *args, **kwargs)
+ # Be helpful to unit tests
+ if "top_srcdir" not in self.substs:
+ if os.path.isabs(self.topsrcdir):
+ top_srcdir = self.topsrcdir.replace(os.sep, "/")
+ else:
+ top_srcdir = mozpath.relpath(self.topsrcdir, self.topobjdir).replace(
+ os.sep, "/"
+ )
+
+ d = dict(self.substs)
+ d["top_srcdir"] = top_srcdir
+ self.substs = ReadOnlyDict(d)
+
+
+class TestEnvironment(unittest.TestCase):
+ def test_auto_substs(self):
+ """Test the automatically set values of ACDEFINES, ALLSUBSTS
+ and ALLEMPTYSUBSTS.
+ """
+ env = ConfigEnvironment(
+ ".",
+ ".",
+ defines={"foo": "bar", "baz": "qux 42", "abc": "d'e'f"},
+ substs={
+ "FOO": "bar",
+ "FOOBAR": "",
+ "ABC": "def",
+ "bar": "baz qux",
+ "zzz": '"abc def"',
+ "qux": "",
+ },
+ )
+ # Original order of the defines need to be respected in ACDEFINES
+ self.assertEqual(
+ env.substs["ACDEFINES"],
+ """-Dabc='d'\\''e'\\''f' -Dbaz='qux 42' -Dfoo=bar""",
+ )
+ # Likewise for ALLSUBSTS, which also must contain ACDEFINES
+ self.assertEqual(
+ env.substs["ALLSUBSTS"],
+ '''ABC = def
+ACDEFINES = -Dabc='d'\\''e'\\''f' -Dbaz='qux 42' -Dfoo=bar
+FOO = bar
+bar = baz qux
+zzz = "abc def"''',
+ )
+ # ALLEMPTYSUBSTS contains all substs with no value.
+ self.assertEqual(
+ env.substs["ALLEMPTYSUBSTS"],
+ """FOOBAR =
+qux =""",
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/backend/test_database.py b/python/mozbuild/mozbuild/test/backend/test_database.py
new file mode 100644
index 0000000000..3bc0dfefb1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_database.py
@@ -0,0 +1,91 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import os
+
+import six
+from mozunit import main
+
+from mozbuild.backend.clangd import ClangdBackend
+from mozbuild.backend.static_analysis import StaticAnalysisBackend
+from mozbuild.compilation.database import CompileDBBackend
+from mozbuild.test.backend.common import BackendTester
+
+
+class TestCompileDBBackends(BackendTester):
+ def perform_check(self, compile_commands_path, topsrcdir, topobjdir):
+ self.assertTrue(os.path.exists(compile_commands_path))
+ compile_db = json.loads(open(compile_commands_path, "r").read())
+
+ # Verify that we have the same number of items
+ self.assertEqual(len(compile_db), 4)
+
+ expected_db = [
+ {
+ "directory": topobjdir,
+ "command": "clang -o /dev/null -c -ferror-limit=0 {}/bar.c".format(
+ topsrcdir
+ ),
+ "file": "{}/bar.c".format(topsrcdir),
+ },
+ {
+ "directory": topobjdir,
+ "command": "clang -o /dev/null -c -ferror-limit=0 {}/foo.c".format(
+ topsrcdir
+ ),
+ "file": "{}/foo.c".format(topsrcdir),
+ },
+ {
+ "directory": topobjdir,
+ "command": "clang++ -o /dev/null -c -ferror-limit=0 {}/baz.cpp".format(
+ topsrcdir
+ ),
+ "file": "{}/baz.cpp".format(topsrcdir),
+ },
+ {
+ "directory": topobjdir,
+ "command": "clang++ -o /dev/null -c -ferror-limit=0 {}/qux.cpp".format(
+ topsrcdir
+ ),
+ "file": "{}/qux.cpp".format(topsrcdir),
+ },
+ ]
+
+ # Verify item consistency against `expected_db`
+ six.assertCountEqual(self, compile_db, expected_db)
+
+ def test_database(self):
+ """Ensure we can generate a `compile_commands.json` and that is correct."""
+
+ env = self._consume("database", CompileDBBackend)
+ compile_commands_path = os.path.join(env.topobjdir, "compile_commands.json")
+
+ self.perform_check(compile_commands_path, env.topsrcdir, env.topobjdir)
+
+ def test_clangd(self):
+ """Ensure we can generate a `compile_commands.json` and that is correct.
+ in order to be used by ClandBackend"""
+
+ env = self._consume("database", ClangdBackend)
+ compile_commands_path = os.path.join(
+ env.topobjdir, "clangd", "compile_commands.json"
+ )
+
+ self.perform_check(compile_commands_path, env.topsrcdir, env.topobjdir)
+
+ def test_static_analysis(self):
+ """Ensure we can generate a `compile_commands.json` and that is correct.
+ in order to be used by StaticAnalysisBackend"""
+
+ env = self._consume("database", StaticAnalysisBackend)
+ compile_commands_path = os.path.join(
+ env.topobjdir, "static-analysis", "compile_commands.json"
+ )
+
+ self.perform_check(compile_commands_path, env.topsrcdir, env.topobjdir)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/backend/test_fastermake.py b/python/mozbuild/mozbuild/test/backend/test_fastermake.py
new file mode 100644
index 0000000000..1c9670b091
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_fastermake.py
@@ -0,0 +1,42 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+import mozpack.path as mozpath
+from mozpack.copier import FileRegistry
+from mozpack.manifests import InstallManifest
+from mozunit import main
+
+from mozbuild.backend.fastermake import FasterMakeBackend
+from mozbuild.test.backend.common import BackendTester
+
+
+class TestFasterMakeBackend(BackendTester):
+ def test_basic(self):
+ """Ensure the FasterMakeBackend works without error."""
+ env = self._consume("stub0", FasterMakeBackend)
+ self.assertTrue(
+ os.path.exists(mozpath.join(env.topobjdir, "backend.FasterMakeBackend"))
+ )
+ self.assertTrue(
+ os.path.exists(mozpath.join(env.topobjdir, "backend.FasterMakeBackend.in"))
+ )
+
+ def test_final_target_files_wildcard(self):
+ """Ensure that wildcards in FINAL_TARGET_FILES work properly."""
+ env = self._consume("final-target-files-wildcard", FasterMakeBackend)
+ m = InstallManifest(
+ path=mozpath.join(env.topobjdir, "faster", "install_dist_bin")
+ )
+ self.assertEqual(len(m), 1)
+ reg = FileRegistry()
+ m.populate_registry(reg)
+ expected = [("foo/bar.xyz", "bar.xyz"), ("foo/foo.xyz", "foo.xyz")]
+ actual = [(path, mozpath.relpath(f.path, env.topsrcdir)) for (path, f) in reg]
+ self.assertEqual(expected, actual)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/backend/test_partialconfigenvironment.py b/python/mozbuild/mozbuild/test/backend/test_partialconfigenvironment.py
new file mode 100644
index 0000000000..13b1656981
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_partialconfigenvironment.py
@@ -0,0 +1,173 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+from shutil import rmtree
+from tempfile import mkdtemp
+
+import buildconfig
+import mozpack.path as mozpath
+from mozunit import main
+
+from mozbuild.backend.configenvironment import PartialConfigEnvironment
+
+config = {
+ "defines": {
+ "MOZ_FOO": "1",
+ "MOZ_BAR": "2",
+ },
+ "substs": {
+ "MOZ_SUBST_1": "1",
+ "MOZ_SUBST_2": "2",
+ "CPP": "cpp",
+ },
+}
+
+
+class TestPartial(unittest.TestCase):
+ def setUp(self):
+ self._old_env = dict(os.environ)
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ def _objdir(self):
+ objdir = mkdtemp(dir=buildconfig.topsrcdir)
+ self.addCleanup(rmtree, objdir)
+ return objdir
+
+ def test_auto_substs(self):
+ """Test the automatically set values of ACDEFINES, and ALLDEFINES"""
+ env = PartialConfigEnvironment(self._objdir())
+ env.write_vars(config)
+ self.assertEqual(env.substs["ACDEFINES"], "-DMOZ_BAR=2 -DMOZ_FOO=1")
+ self.assertEqual(
+ env.defines["ALLDEFINES"],
+ {
+ "MOZ_BAR": "2",
+ "MOZ_FOO": "1",
+ },
+ )
+
+ def test_remove_subst(self):
+ """Test removing a subst from the config. The file should be overwritten with 'None'"""
+ env = PartialConfigEnvironment(self._objdir())
+ path = mozpath.join(env.topobjdir, "config.statusd", "substs", "MYSUBST")
+ myconfig = config.copy()
+ env.write_vars(myconfig)
+ with self.assertRaises(KeyError):
+ _ = env.substs["MYSUBST"]
+ self.assertFalse(os.path.exists(path))
+
+ myconfig["substs"]["MYSUBST"] = "new"
+ env.write_vars(myconfig)
+
+ self.assertEqual(env.substs["MYSUBST"], "new")
+ self.assertTrue(os.path.exists(path))
+
+ del myconfig["substs"]["MYSUBST"]
+ env.write_vars(myconfig)
+ with self.assertRaises(KeyError):
+ _ = env.substs["MYSUBST"]
+ # Now that the subst is gone, the file still needs to be present so that
+ # make can update dependencies correctly. Overwriting the file with
+ # 'None' is the same as deleting it as far as the
+ # PartialConfigEnvironment is concerned, but make can't track a
+ # dependency on a file that doesn't exist.
+ self.assertTrue(os.path.exists(path))
+
+ def _assert_deps(self, env, deps):
+ deps = sorted(
+ [
+ "$(wildcard %s)" % (mozpath.join(env.topobjdir, "config.statusd", d))
+ for d in deps
+ ]
+ )
+ self.assertEqual(sorted(env.get_dependencies()), deps)
+
+ def test_dependencies(self):
+ """Test getting dependencies on defines and substs."""
+ env = PartialConfigEnvironment(self._objdir())
+ env.write_vars(config)
+ self._assert_deps(env, [])
+
+ self.assertEqual(env.defines["MOZ_FOO"], "1")
+ self._assert_deps(env, ["defines/MOZ_FOO"])
+
+ self.assertEqual(env.defines["MOZ_BAR"], "2")
+ self._assert_deps(env, ["defines/MOZ_FOO", "defines/MOZ_BAR"])
+
+ # Getting a define again shouldn't add a redundant dependency
+ self.assertEqual(env.defines["MOZ_FOO"], "1")
+ self._assert_deps(env, ["defines/MOZ_FOO", "defines/MOZ_BAR"])
+
+ self.assertEqual(env.substs["MOZ_SUBST_1"], "1")
+ self._assert_deps(
+ env, ["defines/MOZ_FOO", "defines/MOZ_BAR", "substs/MOZ_SUBST_1"]
+ )
+
+ with self.assertRaises(KeyError):
+ _ = env.substs["NON_EXISTENT"]
+ self._assert_deps(
+ env,
+ [
+ "defines/MOZ_FOO",
+ "defines/MOZ_BAR",
+ "substs/MOZ_SUBST_1",
+ "substs/NON_EXISTENT",
+ ],
+ )
+ self.assertEqual(env.substs.get("NON_EXISTENT"), None)
+
+ def test_set_subst(self):
+ """Test setting a subst"""
+ env = PartialConfigEnvironment(self._objdir())
+ env.write_vars(config)
+
+ self.assertEqual(env.substs["MOZ_SUBST_1"], "1")
+ env.substs["MOZ_SUBST_1"] = "updated"
+ self.assertEqual(env.substs["MOZ_SUBST_1"], "updated")
+
+ # A new environment should pull the result from the file again.
+ newenv = PartialConfigEnvironment(env.topobjdir)
+ self.assertEqual(newenv.substs["MOZ_SUBST_1"], "1")
+
+ def test_env_override(self):
+ """Test overriding a subst with an environment variable"""
+ env = PartialConfigEnvironment(self._objdir())
+ env.write_vars(config)
+
+ self.assertEqual(env.substs["MOZ_SUBST_1"], "1")
+ self.assertEqual(env.substs["CPP"], "cpp")
+
+ # Reset the environment and set some environment variables.
+ env = PartialConfigEnvironment(env.topobjdir)
+ os.environ["MOZ_SUBST_1"] = "subst 1 environ"
+ os.environ["CPP"] = "cpp environ"
+
+ # The MOZ_SUBST_1 should be overridden by the environment, while CPP is
+ # a special variable and should not.
+ self.assertEqual(env.substs["MOZ_SUBST_1"], "subst 1 environ")
+ self.assertEqual(env.substs["CPP"], "cpp")
+
+ def test_update(self):
+ """Test calling update on the substs or defines pseudo dicts"""
+ env = PartialConfigEnvironment(self._objdir())
+ env.write_vars(config)
+
+ mysubsts = {"NEW": "new"}
+ mysubsts.update(env.substs.iteritems())
+ self.assertEqual(mysubsts["NEW"], "new")
+ self.assertEqual(mysubsts["CPP"], "cpp")
+
+ mydefines = {"DEBUG": "1"}
+ mydefines.update(env.defines.iteritems())
+ self.assertEqual(mydefines["DEBUG"], "1")
+ self.assertEqual(mydefines["MOZ_FOO"], "1")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
new file mode 100644
index 0000000000..acbada060b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -0,0 +1,1307 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import io
+import os
+import unittest
+
+import mozpack.path as mozpath
+import six
+import six.moves.cPickle as pickle
+from mozpack.manifests import InstallManifest
+from mozunit import main
+
+from mozbuild.backend.recursivemake import RecursiveMakeBackend, RecursiveMakeTraversal
+from mozbuild.backend.test_manifest import TestManifestBackend
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import BuildReader
+from mozbuild.test.backend.common import BackendTester
+
+
+class TestRecursiveMakeTraversal(unittest.TestCase):
+ def test_traversal(self):
+ traversal = RecursiveMakeTraversal()
+ traversal.add("", dirs=["A", "B", "C"])
+ traversal.add("", dirs=["D"])
+ traversal.add("A")
+ traversal.add("B", dirs=["E", "F"])
+ traversal.add("C", dirs=["G", "H"])
+ traversal.add("D", dirs=["I", "K"])
+ traversal.add("D", dirs=["J", "L"])
+ traversal.add("E")
+ traversal.add("F")
+ traversal.add("G")
+ traversal.add("H")
+ traversal.add("I", dirs=["M", "N"])
+ traversal.add("J", dirs=["O", "P"])
+ traversal.add("K", dirs=["Q", "R"])
+ traversal.add("L", dirs=["S"])
+ traversal.add("M")
+ traversal.add("N", dirs=["T"])
+ traversal.add("O")
+ traversal.add("P", dirs=["U"])
+ traversal.add("Q")
+ traversal.add("R", dirs=["V"])
+ traversal.add("S", dirs=["W"])
+ traversal.add("T")
+ traversal.add("U")
+ traversal.add("V")
+ traversal.add("W", dirs=["X"])
+ traversal.add("X")
+
+ parallels = set(("G", "H", "I", "J", "O", "P", "Q", "R", "U"))
+
+ def filter(current, subdirs):
+ return (
+ current,
+ [d for d in subdirs.dirs if d in parallels],
+ [d for d in subdirs.dirs if d not in parallels],
+ )
+
+ start, deps = traversal.compute_dependencies(filter)
+ self.assertEqual(start, ("X",))
+ self.maxDiff = None
+ self.assertEqual(
+ deps,
+ {
+ "A": ("",),
+ "B": ("A",),
+ "C": ("F",),
+ "D": ("G", "H"),
+ "E": ("B",),
+ "F": ("E",),
+ "G": ("C",),
+ "H": ("C",),
+ "I": ("D",),
+ "J": ("D",),
+ "K": ("T", "O", "U"),
+ "L": ("Q", "V"),
+ "M": ("I",),
+ "N": ("M",),
+ "O": ("J",),
+ "P": ("J",),
+ "Q": ("K",),
+ "R": ("K",),
+ "S": ("L",),
+ "T": ("N",),
+ "U": ("P",),
+ "V": ("R",),
+ "W": ("S",),
+ "X": ("W",),
+ },
+ )
+
+ self.assertEqual(
+ list(traversal.traverse("", filter)),
+ [
+ "",
+ "A",
+ "B",
+ "E",
+ "F",
+ "C",
+ "G",
+ "H",
+ "D",
+ "I",
+ "M",
+ "N",
+ "T",
+ "J",
+ "O",
+ "P",
+ "U",
+ "K",
+ "Q",
+ "R",
+ "V",
+ "L",
+ "S",
+ "W",
+ "X",
+ ],
+ )
+
+ self.assertEqual(list(traversal.traverse("C", filter)), ["C", "G", "H"])
+
+ def test_traversal_2(self):
+ traversal = RecursiveMakeTraversal()
+ traversal.add("", dirs=["A", "B", "C"])
+ traversal.add("A")
+ traversal.add("B", dirs=["D", "E", "F"])
+ traversal.add("C", dirs=["G", "H", "I"])
+ traversal.add("D")
+ traversal.add("E")
+ traversal.add("F")
+ traversal.add("G")
+ traversal.add("H")
+ traversal.add("I")
+
+ start, deps = traversal.compute_dependencies()
+ self.assertEqual(start, ("I",))
+ self.assertEqual(
+ deps,
+ {
+ "A": ("",),
+ "B": ("A",),
+ "C": ("F",),
+ "D": ("B",),
+ "E": ("D",),
+ "F": ("E",),
+ "G": ("C",),
+ "H": ("G",),
+ "I": ("H",),
+ },
+ )
+
+ def test_traversal_filter(self):
+ traversal = RecursiveMakeTraversal()
+ traversal.add("", dirs=["A", "B", "C"])
+ traversal.add("A")
+ traversal.add("B", dirs=["D", "E", "F"])
+ traversal.add("C", dirs=["G", "H", "I"])
+ traversal.add("D")
+ traversal.add("E")
+ traversal.add("F")
+ traversal.add("G")
+ traversal.add("H")
+ traversal.add("I")
+
+ def filter(current, subdirs):
+ if current == "B":
+ current = None
+ return current, [], subdirs.dirs
+
+ start, deps = traversal.compute_dependencies(filter)
+ self.assertEqual(start, ("I",))
+ self.assertEqual(
+ deps,
+ {
+ "A": ("",),
+ "C": ("F",),
+ "D": ("A",),
+ "E": ("D",),
+ "F": ("E",),
+ "G": ("C",),
+ "H": ("G",),
+ "I": ("H",),
+ },
+ )
+
+ def test_traversal_parallel(self):
+ traversal = RecursiveMakeTraversal()
+ traversal.add("", dirs=["A", "B", "C"])
+ traversal.add("A")
+ traversal.add("B", dirs=["D", "E", "F"])
+ traversal.add("C", dirs=["G", "H", "I"])
+ traversal.add("D")
+ traversal.add("E")
+ traversal.add("F")
+ traversal.add("G")
+ traversal.add("H")
+ traversal.add("I")
+ traversal.add("J")
+
+ def filter(current, subdirs):
+ return current, subdirs.dirs, []
+
+ start, deps = traversal.compute_dependencies(filter)
+ self.assertEqual(start, ("A", "D", "E", "F", "G", "H", "I", "J"))
+ self.assertEqual(
+ deps,
+ {
+ "A": ("",),
+ "B": ("",),
+ "C": ("",),
+ "D": ("B",),
+ "E": ("B",),
+ "F": ("B",),
+ "G": ("C",),
+ "H": ("C",),
+ "I": ("C",),
+ "J": ("",),
+ },
+ )
+
+
+class TestRecursiveMakeBackend(BackendTester):
+ def test_basic(self):
+ """Ensure the RecursiveMakeBackend works without error."""
+ env = self._consume("stub0", RecursiveMakeBackend)
+ self.assertTrue(
+ os.path.exists(mozpath.join(env.topobjdir, "backend.RecursiveMakeBackend"))
+ )
+ self.assertTrue(
+ os.path.exists(
+ mozpath.join(env.topobjdir, "backend.RecursiveMakeBackend.in")
+ )
+ )
+
+ def test_output_files(self):
+ """Ensure proper files are generated."""
+ env = self._consume("stub0", RecursiveMakeBackend)
+
+ expected = ["", "dir1", "dir2"]
+
+ for d in expected:
+ out_makefile = mozpath.join(env.topobjdir, d, "Makefile")
+ out_backend = mozpath.join(env.topobjdir, d, "backend.mk")
+
+ self.assertTrue(os.path.exists(out_makefile))
+ self.assertTrue(os.path.exists(out_backend))
+
+ def test_makefile_conversion(self):
+ """Ensure Makefile.in is converted properly."""
+ env = self._consume("stub0", RecursiveMakeBackend)
+
+ p = mozpath.join(env.topobjdir, "Makefile")
+
+ lines = [
+ l.strip() for l in open(p, "rt").readlines()[1:] if not l.startswith("#")
+ ]
+ self.assertEqual(
+ lines,
+ [
+ "DEPTH := .",
+ "topobjdir := %s" % env.topobjdir,
+ "topsrcdir := %s" % env.topsrcdir,
+ "srcdir := %s" % env.topsrcdir,
+ "srcdir_rel := %s" % mozpath.relpath(env.topsrcdir, env.topobjdir),
+ "relativesrcdir := .",
+ "include $(DEPTH)/config/autoconf.mk",
+ "",
+ "FOO := foo",
+ "",
+ "include $(topsrcdir)/config/recurse.mk",
+ ],
+ )
+
+ def test_missing_makefile_in(self):
+ """Ensure missing Makefile.in results in Makefile creation."""
+ env = self._consume("stub0", RecursiveMakeBackend)
+
+ p = mozpath.join(env.topobjdir, "dir2", "Makefile")
+ self.assertTrue(os.path.exists(p))
+
+ lines = [l.strip() for l in open(p, "rt").readlines()]
+ self.assertEqual(len(lines), 10)
+
+ self.assertTrue(lines[0].startswith("# THIS FILE WAS AUTOMATICALLY"))
+
+ def test_backend_mk(self):
+ """Ensure backend.mk file is written out properly."""
+ env = self._consume("stub0", RecursiveMakeBackend)
+
+ p = mozpath.join(env.topobjdir, "backend.mk")
+
+ lines = [l.strip() for l in open(p, "rt").readlines()[2:]]
+ self.assertEqual(lines, ["DIRS := dir1 dir2"])
+
+ # Make env.substs writable to add ENABLE_TESTS
+ env.substs = dict(env.substs)
+ env.substs["ENABLE_TESTS"] = "1"
+ self._consume("stub0", RecursiveMakeBackend, env=env)
+ p = mozpath.join(env.topobjdir, "backend.mk")
+
+ lines = [l.strip() for l in open(p, "rt").readlines()[2:]]
+ self.assertEqual(lines, ["DIRS := dir1 dir2 dir3"])
+
+ def test_mtime_no_change(self):
+ """Ensure mtime is not updated if file content does not change."""
+
+ env = self._consume("stub0", RecursiveMakeBackend)
+
+ makefile_path = mozpath.join(env.topobjdir, "Makefile")
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ makefile_mtime = os.path.getmtime(makefile_path)
+ backend_mtime = os.path.getmtime(backend_path)
+
+ reader = BuildReader(env)
+ emitter = TreeMetadataEmitter(env)
+ backend = RecursiveMakeBackend(env)
+ backend.consume(emitter.emit(reader.read_topsrcdir()))
+
+ self.assertEqual(os.path.getmtime(makefile_path), makefile_mtime)
+ self.assertEqual(os.path.getmtime(backend_path), backend_mtime)
+
+ def test_substitute_config_files(self):
+ """Ensure substituted config files are produced."""
+ env = self._consume("substitute_config_files", RecursiveMakeBackend)
+
+ p = mozpath.join(env.topobjdir, "foo")
+ self.assertTrue(os.path.exists(p))
+ lines = [l.strip() for l in open(p, "rt").readlines()]
+ self.assertEqual(lines, ["TEST = foo"])
+
+ def test_install_substitute_config_files(self):
+ """Ensure we recurse into the dirs that install substituted config files."""
+ env = self._consume("install_substitute_config_files", RecursiveMakeBackend)
+
+ root_deps_path = mozpath.join(env.topobjdir, "root-deps.mk")
+ lines = [l.strip() for l in open(root_deps_path, "rt").readlines()]
+
+ # Make sure we actually recurse into the sub directory during export to
+ # install the subst file.
+ self.assertTrue(any(l == "recurse_export: sub/export" for l in lines))
+
+ def test_variable_passthru(self):
+ """Ensure variable passthru is written out correctly."""
+ env = self._consume("variable_passthru", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = {
+ "RCFILE": ["RCFILE := $(srcdir)/foo.rc"],
+ "RCINCLUDE": ["RCINCLUDE := $(srcdir)/bar.rc"],
+ "WIN32_EXE_LDFLAGS": ["WIN32_EXE_LDFLAGS += -subsystem:console"],
+ }
+
+ for var, val in expected.items():
+ # print("test_variable_passthru[%s]" % (var))
+ found = [str for str in lines if str.startswith(var)]
+ self.assertEqual(found, val)
+
+ def test_sources(self):
+ """Ensure SOURCES, HOST_SOURCES and WASM_SOURCES are handled properly."""
+ env = self._consume("sources", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = {
+ "ASFILES": ["ASFILES += $(srcdir)/bar.s", "ASFILES += $(srcdir)/foo.asm"],
+ "CMMSRCS": ["CMMSRCS += $(srcdir)/fuga.mm", "CMMSRCS += $(srcdir)/hoge.mm"],
+ "CSRCS": ["CSRCS += $(srcdir)/baz.c", "CSRCS += $(srcdir)/qux.c"],
+ "HOST_CPPSRCS": [
+ "HOST_CPPSRCS += $(srcdir)/bar.cpp",
+ "HOST_CPPSRCS += $(srcdir)/foo.cpp",
+ ],
+ "HOST_CSRCS": [
+ "HOST_CSRCS += $(srcdir)/baz.c",
+ "HOST_CSRCS += $(srcdir)/qux.c",
+ ],
+ "SSRCS": ["SSRCS += $(srcdir)/titi.S", "SSRCS += $(srcdir)/toto.S"],
+ "WASM_CSRCS": ["WASM_CSRCS += $(srcdir)/baz.c"],
+ "WASM_CPPSRCS": ["WASM_CPPSRCS += $(srcdir)/bar.cpp"],
+ }
+
+ for var, val in expected.items():
+ found = [str for str in lines if str.startswith(var)]
+ self.assertEqual(found, val)
+
+ def test_exports(self):
+ """Ensure EXPORTS is handled properly."""
+ env = self._consume("exports", RecursiveMakeBackend)
+
+ # EXPORTS files should appear in the dist_include install manifest.
+ m = InstallManifest(
+ path=mozpath.join(
+ env.topobjdir, "_build_manifests", "install", "dist_include"
+ )
+ )
+ self.assertEqual(len(m), 7)
+ self.assertIn("foo.h", m)
+ self.assertIn("mozilla/mozilla1.h", m)
+ self.assertIn("mozilla/dom/dom2.h", m)
+
+ def test_generated_files(self):
+ """Ensure GENERATED_FILES is handled properly."""
+ env = self._consume("generated-files", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "include $(topsrcdir)/config/AB_rCD.mk",
+ "PRE_COMPILE_TARGETS += $(MDDEPDIR)/bar.c.stub",
+ "bar.c: $(MDDEPDIR)/bar.c.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/bar.c.pp",
+ "$(MDDEPDIR)/bar.c.stub: %s/generate-bar.py" % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp $(MDDEPDIR)/bar.c.stub)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ "EXPORT_TARGETS += $(MDDEPDIR)/foo.h.stub",
+ "foo.h: $(MDDEPDIR)/foo.h.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.h.pp",
+ "$(MDDEPDIR)/foo.h.stub: %s/generate-foo.py $(srcdir)/foo-data"
+ % (env.topsrcdir),
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,%s/generate-foo.py main foo.h $(MDDEPDIR)/foo.h.pp $(MDDEPDIR)/foo.h.stub $(srcdir)/foo-data)" # noqa
+ % (env.topsrcdir),
+ "@$(TOUCH) $@",
+ "",
+ ]
+
+ self.maxDiff = None
+ self.assertEqual(lines, expected)
+
+ def test_generated_files_force(self):
+ """Ensure GENERATED_FILES with .force is handled properly."""
+ env = self._consume("generated-files-force", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "include $(topsrcdir)/config/AB_rCD.mk",
+ "PRE_COMPILE_TARGETS += $(MDDEPDIR)/bar.c.stub",
+ "bar.c: $(MDDEPDIR)/bar.c.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/bar.c.pp",
+ "$(MDDEPDIR)/bar.c.stub: %s/generate-bar.py FORCE" % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp $(MDDEPDIR)/bar.c.stub)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ "PRE_COMPILE_TARGETS += $(MDDEPDIR)/foo.c.stub",
+ "foo.c: $(MDDEPDIR)/foo.c.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.c.pp",
+ "$(MDDEPDIR)/foo.c.stub: %s/generate-foo.py $(srcdir)/foo-data"
+ % (env.topsrcdir),
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,%s/generate-foo.py main foo.c $(MDDEPDIR)/foo.c.pp $(MDDEPDIR)/foo.c.stub $(srcdir)/foo-data)" # noqa
+ % (env.topsrcdir),
+ "@$(TOUCH) $@",
+ "",
+ ]
+
+ self.maxDiff = None
+ self.assertEqual(lines, expected)
+
+ def test_localized_generated_files(self):
+ """Ensure LOCALIZED_GENERATED_FILES is handled properly."""
+ env = self._consume("localized-generated-files", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "include $(topsrcdir)/config/AB_rCD.mk",
+ "MISC_TARGETS += $(MDDEPDIR)/foo.xyz.stub",
+ "foo.xyz: $(MDDEPDIR)/foo.xyz.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.xyz.pp",
+ "$(MDDEPDIR)/foo.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)" # noqa
+ % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(MDDEPDIR)/foo.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ "LOCALIZED_FILES_0_FILES += foo.xyz",
+ "LOCALIZED_FILES_0_DEST = $(FINAL_TARGET)/",
+ "LOCALIZED_FILES_0_TARGET := misc",
+ "INSTALL_TARGETS += LOCALIZED_FILES_0",
+ ]
+
+ self.maxDiff = None
+ self.assertEqual(lines, expected)
+
+ def test_localized_generated_files_force(self):
+ """Ensure LOCALIZED_GENERATED_FILES with .force is handled properly."""
+ env = self._consume("localized-generated-files-force", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "include $(topsrcdir)/config/AB_rCD.mk",
+ "MISC_TARGETS += $(MDDEPDIR)/foo.xyz.stub",
+ "foo.xyz: $(MDDEPDIR)/foo.xyz.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.xyz.pp",
+ "$(MDDEPDIR)/foo.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)" # noqa
+ % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(MDDEPDIR)/foo.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ "MISC_TARGETS += $(MDDEPDIR)/abc.xyz.stub",
+ "abc.xyz: $(MDDEPDIR)/abc.xyz.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/abc.xyz.pp",
+ "$(MDDEPDIR)/abc.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input FORCE" # noqa
+ % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main abc.xyz $(MDDEPDIR)/abc.xyz.pp $(MDDEPDIR)/abc.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ ]
+
+ self.maxDiff = None
+ self.assertEqual(lines, expected)
+
+ def test_localized_generated_files_AB_CD(self):
+ """Ensure LOCALIZED_GENERATED_FILES is handled properly
+ when {AB_CD} and {AB_rCD} are used."""
+ env = self._consume("localized-generated-files-AB_CD", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "include $(topsrcdir)/config/AB_rCD.mk",
+ "MISC_TARGETS += $(MDDEPDIR)/foo$(AB_CD).xyz.stub",
+ "foo$(AB_CD).xyz: $(MDDEPDIR)/foo$(AB_CD).xyz.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo$(AB_CD).xyz.pp",
+ "$(MDDEPDIR)/foo$(AB_CD).xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)" # noqa
+ % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo$(AB_CD).xyz $(MDDEPDIR)/foo$(AB_CD).xyz.pp $(MDDEPDIR)/foo$(AB_CD).xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ "bar$(AB_rCD).xyz: $(MDDEPDIR)/bar$(AB_rCD).xyz.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/bar$(AB_rCD).xyz.pp",
+ "$(MDDEPDIR)/bar$(AB_rCD).xyz.stub: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)" # noqa
+ % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main bar$(AB_rCD).xyz $(MDDEPDIR)/bar$(AB_rCD).xyz.pp $(MDDEPDIR)/bar$(AB_rCD).xyz.stub $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ "zot$(AB_rCD).xyz: $(MDDEPDIR)/zot$(AB_rCD).xyz.stub ;",
+ "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/zot$(AB_rCD).xyz.pp",
+ "$(MDDEPDIR)/zot$(AB_rCD).xyz.stub: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)" # noqa
+ % env.topsrcdir,
+ "$(REPORT_BUILD)",
+ "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main zot$(AB_rCD).xyz $(MDDEPDIR)/zot$(AB_rCD).xyz.pp $(MDDEPDIR)/zot$(AB_rCD).xyz.stub $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input)" # noqa
+ % env.topsrcdir,
+ "@$(TOUCH) $@",
+ "",
+ ]
+
+ self.maxDiff = None
+ self.assertEqual(lines, expected)
+
+ def test_exports_generated(self):
+ """Ensure EXPORTS that are listed in GENERATED_FILES
+ are handled properly."""
+ env = self._consume("exports-generated", RecursiveMakeBackend)
+
+ # EXPORTS files should appear in the dist_include install manifest.
+ m = InstallManifest(
+ path=mozpath.join(
+ env.topobjdir, "_build_manifests", "install", "dist_include"
+ )
+ )
+ self.assertEqual(len(m), 8)
+ self.assertIn("foo.h", m)
+ self.assertIn("mozilla/mozilla1.h", m)
+ self.assertIn("mozilla/dom/dom1.h", m)
+ self.assertIn("gfx/gfx.h", m)
+ self.assertIn("bar.h", m)
+ self.assertIn("mozilla/mozilla2.h", m)
+ self.assertIn("mozilla/dom/dom2.h", m)
+ self.assertIn("mozilla/dom/dom3.h", m)
+ # EXPORTS files that are also GENERATED_FILES should be handled as
+ # INSTALL_TARGETS.
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+ expected = [
+ "include $(topsrcdir)/config/AB_rCD.mk",
+ "dist_include_FILES += bar.h",
+ "dist_include_DEST := $(DEPTH)/dist/include/",
+ "dist_include_TARGET := export",
+ "INSTALL_TARGETS += dist_include",
+ "dist_include_mozilla_FILES += mozilla2.h",
+ "dist_include_mozilla_DEST := $(DEPTH)/dist/include/mozilla",
+ "dist_include_mozilla_TARGET := export",
+ "INSTALL_TARGETS += dist_include_mozilla",
+ "dist_include_mozilla_dom_FILES += dom2.h",
+ "dist_include_mozilla_dom_FILES += dom3.h",
+ "dist_include_mozilla_dom_DEST := $(DEPTH)/dist/include/mozilla/dom",
+ "dist_include_mozilla_dom_TARGET := export",
+ "INSTALL_TARGETS += dist_include_mozilla_dom",
+ ]
+ self.maxDiff = None
+ self.assertEqual(lines, expected)
+
+ def test_resources(self):
+ """Ensure RESOURCE_FILES is handled properly."""
+ env = self._consume("resources", RecursiveMakeBackend)
+
+ # RESOURCE_FILES should appear in the dist_bin install manifest.
+ m = InstallManifest(
+ path=os.path.join(env.topobjdir, "_build_manifests", "install", "dist_bin")
+ )
+ self.assertEqual(len(m), 10)
+ self.assertIn("res/foo.res", m)
+ self.assertIn("res/fonts/font1.ttf", m)
+ self.assertIn("res/fonts/desktop/desktop2.ttf", m)
+
+ self.assertIn("res/bar.res.in", m)
+ self.assertIn("res/tests/test.manifest", m)
+ self.assertIn("res/tests/extra.manifest", m)
+
+ def test_test_manifests_files_written(self):
+ """Ensure test manifests get turned into files."""
+ env = self._consume("test-manifests-written", RecursiveMakeBackend)
+
+ tests_dir = mozpath.join(env.topobjdir, "_tests")
+ m_master = mozpath.join(
+ tests_dir, "testing", "mochitest", "tests", "mochitest.ini"
+ )
+ x_master = mozpath.join(tests_dir, "xpcshell", "xpcshell.ini")
+ self.assertTrue(os.path.exists(m_master))
+ self.assertTrue(os.path.exists(x_master))
+
+ lines = [l.strip() for l in open(x_master, "rt").readlines()]
+ self.assertEqual(
+ lines,
+ [
+ "# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.",
+ "",
+ "[include:dir1/xpcshell.ini]",
+ "[include:xpcshell.ini]",
+ ],
+ )
+
+ def test_test_manifest_pattern_matches_recorded(self):
+ """Pattern matches in test manifests' support-files should be recorded."""
+ env = self._consume("test-manifests-written", RecursiveMakeBackend)
+ m = InstallManifest(
+ path=mozpath.join(
+ env.topobjdir, "_build_manifests", "install", "_test_files"
+ )
+ )
+
+ # This is not the most robust test in the world, but it gets the job
+ # done.
+ entries = [e for e in m._dests.keys() if "**" in e]
+ self.assertEqual(len(entries), 1)
+ self.assertIn("support/**", entries[0])
+
+ def test_test_manifest_deffered_installs_written(self):
+ """Shared support files are written to their own data file by the backend."""
+ env = self._consume("test-manifest-shared-support", RecursiveMakeBackend)
+
+ # First, read the generated for ini manifest contents.
+ test_files_manifest = mozpath.join(
+ env.topobjdir, "_build_manifests", "install", "_test_files"
+ )
+ m = InstallManifest(path=test_files_manifest)
+
+ # Then, synthesize one from the test-installs.pkl file. This should
+ # allow us to re-create a subset of the above.
+ env = self._consume("test-manifest-shared-support", TestManifestBackend)
+ test_installs_path = mozpath.join(env.topobjdir, "test-installs.pkl")
+
+ with open(test_installs_path, "rb") as fh:
+ test_installs = pickle.load(fh)
+
+ self.assertEqual(
+ set(test_installs.keys()),
+ set(["child/test_sub.js", "child/data/**", "child/another-file.sjs"]),
+ )
+ for key in test_installs.keys():
+ self.assertIn(key, test_installs)
+
+ synthesized_manifest = InstallManifest()
+ for item, installs in test_installs.items():
+ for install_info in installs:
+ if len(install_info) == 3:
+ synthesized_manifest.add_pattern_link(*install_info)
+ if len(install_info) == 2:
+ synthesized_manifest.add_link(*install_info)
+
+ self.assertEqual(len(synthesized_manifest), 3)
+ for item, info in synthesized_manifest._dests.items():
+ self.assertIn(item, m)
+ self.assertEqual(info, m._dests[item])
+
+ def test_xpidl_generation(self):
+ """Ensure xpidl files and directories are written out."""
+ env = self._consume("xpidl", RecursiveMakeBackend)
+
+ # Install manifests should contain entries.
+ install_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
+ self.assertTrue(os.path.isfile(mozpath.join(install_dir, "xpidl")))
+
+ m = InstallManifest(path=mozpath.join(install_dir, "xpidl"))
+ self.assertIn(".deps/my_module.pp", m)
+
+ m = InstallManifest(path=mozpath.join(install_dir, "xpidl"))
+ self.assertIn("my_module.xpt", m)
+
+ m = InstallManifest(path=mozpath.join(install_dir, "dist_include"))
+ self.assertIn("foo.h", m)
+
+ p = mozpath.join(env.topobjdir, "config/makefiles/xpidl")
+ self.assertTrue(os.path.isdir(p))
+
+ self.assertTrue(os.path.isfile(mozpath.join(p, "Makefile")))
+
+ def test_test_support_files_tracked(self):
+ env = self._consume("test-support-binaries-tracked", RecursiveMakeBackend)
+ m = InstallManifest(
+ path=mozpath.join(env.topobjdir, "_build_manifests", "install", "_tests")
+ )
+ self.assertEqual(len(m), 4)
+ self.assertIn("xpcshell/tests/mozbuildtest/test-library.dll", m)
+ self.assertIn("xpcshell/tests/mozbuildtest/test-one.exe", m)
+ self.assertIn("xpcshell/tests/mozbuildtest/test-two.exe", m)
+ self.assertIn("xpcshell/tests/mozbuildtest/host-test-library.dll", m)
+
+ def test_old_install_manifest_deleted(self):
+ # Simulate an install manifest from a previous backend version. Ensure
+ # it is deleted.
+ env = self._get_environment("stub0")
+ purge_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
+ manifest_path = mozpath.join(purge_dir, "old_manifest")
+ os.makedirs(purge_dir)
+ m = InstallManifest()
+ m.write(path=manifest_path)
+ with open(
+ mozpath.join(env.topobjdir, "backend.RecursiveMakeBackend"), "w"
+ ) as f:
+ f.write("%s\n" % manifest_path)
+
+ self.assertTrue(os.path.exists(manifest_path))
+ self._consume("stub0", RecursiveMakeBackend, env)
+ self.assertFalse(os.path.exists(manifest_path))
+
+ def test_install_manifests_written(self):
+ env, objs = self._emit("stub0")
+ backend = RecursiveMakeBackend(env)
+
+ m = InstallManifest()
+ backend._install_manifests["testing"] = m
+ m.add_link(__file__, "self")
+ backend.consume(objs)
+
+ man_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
+ self.assertTrue(os.path.isdir(man_dir))
+
+ expected = ["testing"]
+ for e in expected:
+ full = mozpath.join(man_dir, e)
+ self.assertTrue(os.path.exists(full))
+
+ m2 = InstallManifest(path=full)
+ self.assertEqual(m, m2)
+
+ def test_ipdl_sources(self):
+ """Test that PREPROCESSED_IPDL_SOURCES and IPDL_SOURCES are written to
+ ipdlsrcs.mk correctly."""
+ env = self._get_environment("ipdl_sources")
+
+ # Use the ipdl directory as the IPDL root for testing.
+ ipdl_root = mozpath.join(env.topobjdir, "ipdl")
+
+ # Make substs writable so we can set the value of IPDL_ROOT to reflect
+ # the correct objdir.
+ env.substs = dict(env.substs)
+ env.substs["IPDL_ROOT"] = ipdl_root
+
+ self._consume("ipdl_sources", RecursiveMakeBackend, env)
+
+ manifest_path = mozpath.join(ipdl_root, "ipdlsrcs.mk")
+ lines = [l.strip() for l in open(manifest_path, "rt").readlines()]
+
+ # Handle Windows paths correctly
+ topsrcdir = mozpath.normsep(env.topsrcdir)
+
+ expected = [
+ "ALL_IPDLSRCS := bar1.ipdl foo1.ipdl %s/bar/bar.ipdl %s/bar/bar2.ipdlh %s/foo/foo.ipdl %s/foo/foo2.ipdlh" # noqa
+ % tuple([topsrcdir] * 4),
+ "IPDLDIRS := %s %s/bar %s/foo" % (ipdl_root, topsrcdir, topsrcdir),
+ ]
+
+ found = [str for str in lines if str.startswith(("ALL_IPDLSRCS", "IPDLDIRS"))]
+ self.assertEqual(found, expected)
+
+ # Check that each directory declares the generated relevant .cpp files
+ # to be built in CPPSRCS.
+ # ENABLE_UNIFIED_BUILD defaults to False without mozilla-central's
+ # moz.configure so we don't see unified sources here.
+ for dir, expected in (
+ (".", []),
+ ("ipdl", []),
+ (
+ "bar",
+ [
+ "CPPSRCS += "
+ + " ".join(
+ f"{ipdl_root}/{f}"
+ for f in [
+ "bar.cpp",
+ "bar1.cpp",
+ "bar1Child.cpp",
+ "bar1Parent.cpp",
+ "bar2.cpp",
+ "barChild.cpp",
+ "barParent.cpp",
+ ]
+ )
+ ],
+ ),
+ (
+ "foo",
+ [
+ "CPPSRCS += "
+ + " ".join(
+ f"{ipdl_root}/{f}"
+ for f in [
+ "foo.cpp",
+ "foo1.cpp",
+ "foo1Child.cpp",
+ "foo1Parent.cpp",
+ "foo2.cpp",
+ "fooChild.cpp",
+ "fooParent.cpp",
+ ]
+ )
+ ],
+ ),
+ ):
+ backend_path = mozpath.join(env.topobjdir, dir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()]
+
+ found = [str for str in lines if str.startswith("CPPSRCS")]
+ self.assertEqual(found, expected)
+
+ def test_defines(self):
+ """Test that DEFINES are written to backend.mk correctly."""
+ env = self._consume("defines", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ var = "DEFINES"
+ defines = [val for val in lines if val.startswith(var)]
+
+ expected = ["DEFINES += -DFOO '-DBAZ=\"ab'\\''cd\"' -UQUX -DBAR=7 -DVALUE=xyz"]
+ self.assertEqual(defines, expected)
+
+ def test_local_includes(self):
+ """Test that LOCAL_INCLUDES are written to backend.mk correctly."""
+ env = self._consume("local_includes", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "LOCAL_INCLUDES += -I$(srcdir)/bar/baz",
+ "LOCAL_INCLUDES += -I$(srcdir)/foo",
+ ]
+
+ found = [str for str in lines if str.startswith("LOCAL_INCLUDES")]
+ self.assertEqual(found, expected)
+
+ def test_generated_includes(self):
+ """Test that GENERATED_INCLUDES are written to backend.mk correctly."""
+ env = self._consume("generated_includes", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "LOCAL_INCLUDES += -I$(CURDIR)/bar/baz",
+ "LOCAL_INCLUDES += -I$(CURDIR)/foo",
+ ]
+
+ found = [str for str in lines if str.startswith("LOCAL_INCLUDES")]
+ self.assertEqual(found, expected)
+
+ def test_rust_library(self):
+ """Test that a Rust library is written to backend.mk correctly."""
+ env = self._consume("rust-library", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [
+ l.strip()
+ for l in open(backend_path, "rt").readlines()[2:]
+ # Strip out computed flags, they're a PITA to test.
+ if not l.startswith("COMPUTED_")
+ ]
+
+ expected = [
+ "RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libtest_library.a"
+ % env.topobjdir, # noqa
+ "CARGO_FILE := $(srcdir)/Cargo.toml",
+ "CARGO_TARGET_DIR := %s" % env.topobjdir,
+ ]
+
+ self.assertEqual(lines, expected)
+
+ def test_host_rust_library(self):
+ """Test that a Rust library is written to backend.mk correctly."""
+ env = self._consume("host-rust-library", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [
+ l.strip()
+ for l in open(backend_path, "rt").readlines()[2:]
+ # Strip out computed flags, they're a PITA to test.
+ if not l.startswith("COMPUTED_")
+ ]
+
+ expected = [
+ "HOST_RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libhostrusttool.a"
+ % env.topobjdir, # noqa
+ "CARGO_FILE := $(srcdir)/Cargo.toml",
+ "CARGO_TARGET_DIR := %s" % env.topobjdir,
+ ]
+
+ self.assertEqual(lines, expected)
+
+ def test_host_rust_library_with_features(self):
+ """Test that a host Rust library with features is written to backend.mk correctly."""
+ env = self._consume("host-rust-library-features", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [
+ l.strip()
+ for l in open(backend_path, "rt").readlines()[2:]
+ # Strip out computed flags, they're a PITA to test.
+ if not l.startswith("COMPUTED_")
+ ]
+
+ expected = [
+ "HOST_RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libhostrusttool.a"
+ % env.topobjdir, # noqa
+ "CARGO_FILE := $(srcdir)/Cargo.toml",
+ "CARGO_TARGET_DIR := %s" % env.topobjdir,
+ "HOST_RUST_LIBRARY_FEATURES := musthave cantlivewithout",
+ ]
+
+ self.assertEqual(lines, expected)
+
+ def test_rust_library_with_features(self):
+ """Test that a Rust library with features is written to backend.mk correctly."""
+ env = self._consume("rust-library-features", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [
+ l.strip()
+ for l in open(backend_path, "rt").readlines()[2:]
+ # Strip out computed flags, they're a PITA to test.
+ if not l.startswith("COMPUTED_")
+ ]
+
+ expected = [
+ "RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libfeature_library.a"
+ % env.topobjdir, # noqa
+ "CARGO_FILE := $(srcdir)/Cargo.toml",
+ "CARGO_TARGET_DIR := %s" % env.topobjdir,
+ "RUST_LIBRARY_FEATURES := musthave cantlivewithout",
+ ]
+
+ self.assertEqual(lines, expected)
+
+ def test_rust_programs(self):
+ """Test that `{HOST_,}RUST_PROGRAMS` are written to backend.mk correctly."""
+ env = self._consume("rust-programs", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "code/backend.mk")
+ lines = [
+ l.strip()
+ for l in open(backend_path, "rt").readlines()[2:]
+ # Strip out computed flags, they're a PITA to test.
+ if not l.startswith("COMPUTED_")
+ ]
+
+ expected = [
+ "CARGO_FILE := %s/code/Cargo.toml" % env.topsrcdir,
+ "CARGO_TARGET_DIR := %s" % env.topobjdir,
+ "RUST_PROGRAMS += $(DEPTH)/i686-pc-windows-msvc/release/target.exe",
+ "RUST_CARGO_PROGRAMS += target",
+ "HOST_RUST_PROGRAMS += $(DEPTH)/i686-pc-windows-msvc/release/host.exe",
+ "HOST_RUST_CARGO_PROGRAMS += host",
+ ]
+
+ self.assertEqual(lines, expected)
+
+ root_deps_path = mozpath.join(env.topobjdir, "root-deps.mk")
+ lines = [l.strip() for l in open(root_deps_path, "rt").readlines()]
+
+ self.assertTrue(
+ any(l == "recurse_compile: code/host code/target" for l in lines)
+ )
+
+ def test_final_target(self):
+ """Test that FINAL_TARGET is written to backend.mk correctly."""
+ env = self._consume("final_target", RecursiveMakeBackend)
+
+ final_target_rule = "FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)" # noqa
+ expected = dict()
+ expected[env.topobjdir] = []
+ expected[mozpath.join(env.topobjdir, "both")] = [
+ "XPI_NAME = mycrazyxpi",
+ "DIST_SUBDIR = asubdir",
+ final_target_rule,
+ ]
+ expected[mozpath.join(env.topobjdir, "dist-subdir")] = [
+ "DIST_SUBDIR = asubdir",
+ final_target_rule,
+ ]
+ expected[mozpath.join(env.topobjdir, "xpi-name")] = [
+ "XPI_NAME = mycrazyxpi",
+ final_target_rule,
+ ]
+ expected[mozpath.join(env.topobjdir, "final-target")] = [
+ "FINAL_TARGET = $(DEPTH)/random-final-target"
+ ]
+ for key, expected_rules in six.iteritems(expected):
+ backend_path = mozpath.join(key, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+ found = [
+ str
+ for str in lines
+ if str.startswith("FINAL_TARGET")
+ or str.startswith("XPI_NAME")
+ or str.startswith("DIST_SUBDIR")
+ ]
+ self.assertEqual(found, expected_rules)
+
+ def test_final_target_pp_files(self):
+ """Test that FINAL_TARGET_PP_FILES is written to backend.mk correctly."""
+ env = self._consume("dist-files", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "DIST_FILES_0 += $(srcdir)/install.rdf",
+ "DIST_FILES_0 += $(srcdir)/main.js",
+ "DIST_FILES_0_PATH := $(DEPTH)/dist/bin/",
+ "DIST_FILES_0_TARGET := misc",
+ "PP_TARGETS += DIST_FILES_0",
+ ]
+
+ found = [str for str in lines if "DIST_FILES" in str]
+ self.assertEqual(found, expected)
+
+ def test_localized_files(self):
+ """Test that LOCALIZED_FILES is written to backend.mk correctly."""
+ env = self._consume("localized-files", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "LOCALIZED_FILES_0_FILES += $(wildcard $(LOCALE_SRCDIR)/abc/*.abc)",
+ "LOCALIZED_FILES_0_FILES += $(call MERGE_FILE,bar.ini)",
+ "LOCALIZED_FILES_0_FILES += $(call MERGE_FILE,foo.js)",
+ "LOCALIZED_FILES_0_DEST = $(FINAL_TARGET)/",
+ "LOCALIZED_FILES_0_TARGET := misc",
+ "INSTALL_TARGETS += LOCALIZED_FILES_0",
+ ]
+
+ found = [str for str in lines if "LOCALIZED_FILES" in str]
+ self.assertEqual(found, expected)
+
+ def test_localized_pp_files(self):
+ """Test that LOCALIZED_PP_FILES is written to backend.mk correctly."""
+ env = self._consume("localized-pp-files", RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.mk")
+ lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
+
+ expected = [
+ "LOCALIZED_PP_FILES_0 += $(call MERGE_FILE,bar.ini)",
+ "LOCALIZED_PP_FILES_0 += $(call MERGE_FILE,foo.js)",
+ "LOCALIZED_PP_FILES_0_PATH = $(FINAL_TARGET)/",
+ "LOCALIZED_PP_FILES_0_TARGET := misc",
+ "LOCALIZED_PP_FILES_0_FLAGS := --silence-missing-directive-warnings",
+ "PP_TARGETS += LOCALIZED_PP_FILES_0",
+ ]
+
+ found = [str for str in lines if "LOCALIZED_PP_FILES" in str]
+ self.assertEqual(found, expected)
+
+ def test_config(self):
+ """Test that CONFIGURE_SUBST_FILES are properly handled."""
+ env = self._consume("test_config", RecursiveMakeBackend)
+
+ self.assertEqual(
+ open(os.path.join(env.topobjdir, "file"), "r").readlines(),
+ ["#ifdef foo\n", "bar baz\n", "@bar@\n"],
+ )
+
+ def test_prog_lib_c_only(self):
+ """Test that C-only binary artifacts are marked as such."""
+ env = self._consume("prog-lib-c-only", RecursiveMakeBackend)
+
+ # PROGRAM C-onlyness.
+ with open(os.path.join(env.topobjdir, "c-program", "backend.mk"), "r") as fh:
+ lines = fh.readlines()
+ lines = [line.rstrip() for line in lines]
+
+ self.assertIn("PROG_IS_C_ONLY_c_test_program := 1", lines)
+
+ with open(os.path.join(env.topobjdir, "cxx-program", "backend.mk"), "r") as fh:
+ lines = fh.readlines()
+ lines = [line.rstrip() for line in lines]
+
+ # Test for only the absence of the variable, not the precise
+ # form of the variable assignment.
+ for line in lines:
+ self.assertNotIn("PROG_IS_C_ONLY_cxx_test_program", line)
+
+ # SIMPLE_PROGRAMS C-onlyness.
+ with open(
+ os.path.join(env.topobjdir, "c-simple-programs", "backend.mk"), "r"
+ ) as fh:
+ lines = fh.readlines()
+ lines = [line.rstrip() for line in lines]
+
+ self.assertIn("PROG_IS_C_ONLY_c_simple_program := 1", lines)
+
+ with open(
+ os.path.join(env.topobjdir, "cxx-simple-programs", "backend.mk"), "r"
+ ) as fh:
+ lines = fh.readlines()
+ lines = [line.rstrip() for line in lines]
+
+ for line in lines:
+ self.assertNotIn("PROG_IS_C_ONLY_cxx_simple_program", line)
+
+ # Libraries C-onlyness.
+ with open(os.path.join(env.topobjdir, "c-library", "backend.mk"), "r") as fh:
+ lines = fh.readlines()
+ lines = [line.rstrip() for line in lines]
+
+ self.assertIn("LIB_IS_C_ONLY := 1", lines)
+
+ with open(os.path.join(env.topobjdir, "cxx-library", "backend.mk"), "r") as fh:
+ lines = fh.readlines()
+ lines = [line.rstrip() for line in lines]
+
+ for line in lines:
+ self.assertNotIn("LIB_IS_C_ONLY", line)
+
+ def test_linkage(self):
+ env = self._consume("linkage", RecursiveMakeBackend)
+ expected_linkage = {
+ "prog": {
+ "SHARED_LIBS": ["qux/qux.so", "../shared/baz.so"],
+ "STATIC_LIBS": ["../real/foo.a"],
+ "OS_LIBS": ["-lfoo", "-lbaz", "-lbar"],
+ },
+ "shared": {
+ "OS_LIBS": ["-lfoo"],
+ "SHARED_LIBS": ["../prog/qux/qux.so"],
+ "STATIC_LIBS": [],
+ },
+ "static": {
+ "STATIC_LIBS": ["../real/foo.a"],
+ "OS_LIBS": ["-lbar"],
+ "SHARED_LIBS": ["../prog/qux/qux.so"],
+ },
+ "real": {
+ "STATIC_LIBS": [],
+ "SHARED_LIBS": ["../prog/qux/qux.so"],
+ "OS_LIBS": ["-lbaz"],
+ },
+ }
+ actual_linkage = {}
+ for name in expected_linkage.keys():
+ with open(os.path.join(env.topobjdir, name, "backend.mk"), "r") as fh:
+ actual_linkage[name] = [line.rstrip() for line in fh.readlines()]
+ for name in expected_linkage:
+ for var in expected_linkage[name]:
+ for val in expected_linkage[name][var]:
+ val = os.path.normpath(val)
+ line = "%s += %s" % (var, val)
+ self.assertIn(line, actual_linkage[name])
+ actual_linkage[name].remove(line)
+ for line in actual_linkage[name]:
+ self.assertNotIn("%s +=" % var, line)
+
+ def test_list_files(self):
+ env = self._consume("linkage", RecursiveMakeBackend)
+ expected_list_files = {
+ "prog/MyProgram_exe.list": [
+ "../static/bar/bar1.o",
+ "../static/bar/bar2.o",
+ "../static/bar/bar_helper/bar_helper1.o",
+ ],
+ "shared/baz_so.list": ["baz/baz1.o"],
+ }
+ actual_list_files = {}
+ for name in expected_list_files.keys():
+ with open(os.path.join(env.topobjdir, name), "r") as fh:
+ actual_list_files[name] = [line.rstrip() for line in fh.readlines()]
+ for name in expected_list_files:
+ self.assertEqual(
+ actual_list_files[name],
+ [os.path.normpath(f) for f in expected_list_files[name]],
+ )
+
+ # We don't produce a list file for a shared library composed only of
+ # object files in its directory, but instead list them in a variable.
+ with open(os.path.join(env.topobjdir, "prog", "qux", "backend.mk"), "r") as fh:
+ lines = [line.rstrip() for line in fh.readlines()]
+
+ self.assertIn("qux.so_OBJS := qux1.o", lines)
+
+ def test_jar_manifests(self):
+ env = self._consume("jar-manifests", RecursiveMakeBackend)
+
+ with open(os.path.join(env.topobjdir, "backend.mk"), "r") as fh:
+ lines = fh.readlines()
+
+ lines = [line.rstrip() for line in lines]
+
+ self.assertIn("JAR_MANIFEST := %s/jar.mn" % env.topsrcdir, lines)
+
+ def test_test_manifests_duplicate_support_files(self):
+ """Ensure duplicate support-files in test manifests work."""
+ env = self._consume(
+ "test-manifests-duplicate-support-files", RecursiveMakeBackend
+ )
+
+ p = os.path.join(env.topobjdir, "_build_manifests", "install", "_test_files")
+ m = InstallManifest(p)
+ self.assertIn("testing/mochitest/tests/support-file.txt", m)
+
+ def test_install_manifests_package_tests(self):
+ """Ensure test suites honor package_tests=False."""
+ env = self._consume("test-manifests-package-tests", RecursiveMakeBackend)
+
+ man_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
+ self.assertTrue(os.path.isdir(man_dir))
+
+ full = mozpath.join(man_dir, "_test_files")
+ self.assertTrue(os.path.exists(full))
+
+ m = InstallManifest(path=full)
+
+ # Only mochitest.js should be in the install manifest.
+ self.assertTrue("testing/mochitest/tests/mochitest.js" in m)
+
+ # The path is odd here because we do not normalize at test manifest
+ # processing time. This is a fragile test because there's currently no
+ # way to iterate the manifest.
+ self.assertFalse("instrumentation/./not_packaged.java" in m)
+
+ def test_program_paths(self):
+ """PROGRAMs with various moz.build settings that change the destination should produce
+ the expected paths in backend.mk."""
+ env = self._consume("program-paths", RecursiveMakeBackend)
+
+ expected = [
+ ("dist-bin", "$(DEPTH)/dist/bin/dist-bin.prog"),
+ ("dist-subdir", "$(DEPTH)/dist/bin/foo/dist-subdir.prog"),
+ ("final-target", "$(DEPTH)/final/target/final-target.prog"),
+ ("not-installed", "not-installed.prog"),
+ ]
+ prefix = "PROGRAM = "
+ for (subdir, expected_program) in expected:
+ with io.open(os.path.join(env.topobjdir, subdir, "backend.mk"), "r") as fh:
+ lines = fh.readlines()
+ program = [
+ line.rstrip().split(prefix, 1)[1]
+ for line in lines
+ if line.startswith(prefix)
+ ][0]
+ self.assertEqual(program, expected_program)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/backend/test_test_manifest.py b/python/mozbuild/mozbuild/test/backend/test_test_manifest.py
new file mode 100644
index 0000000000..fadf65e447
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_test_manifest.py
@@ -0,0 +1,94 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+import mozpack.path as mozpath
+import six.moves.cPickle as pickle
+from mozunit import main
+
+from mozbuild.backend.test_manifest import TestManifestBackend
+from mozbuild.test.backend.common import BackendTester
+
+
+class TestTestManifestBackend(BackendTester):
+ def test_all_tests_metadata_file_written(self):
+ """Ensure all-tests.pkl is generated."""
+ env = self._consume("test-manifests-written", TestManifestBackend)
+
+ all_tests_path = mozpath.join(env.topobjdir, "all-tests.pkl")
+ self.assertTrue(os.path.exists(all_tests_path))
+
+ with open(all_tests_path, "rb") as fh:
+ o = pickle.load(fh)
+
+ self.assertIn("xpcshell.js", o)
+ self.assertIn("dir1/test_bar.js", o)
+
+ self.assertEqual(len(o["xpcshell.js"]), 1)
+
+ def test_test_installs_metadata_file_written(self):
+ """Ensure test-installs.pkl is generated."""
+ env = self._consume("test-manifest-shared-support", TestManifestBackend)
+ all_tests_path = mozpath.join(env.topobjdir, "all-tests.pkl")
+ self.assertTrue(os.path.exists(all_tests_path))
+ test_installs_path = mozpath.join(env.topobjdir, "test-installs.pkl")
+
+ with open(test_installs_path, "rb") as fh:
+ test_installs = pickle.load(fh)
+
+ self.assertEqual(
+ set(test_installs.keys()),
+ set(["child/test_sub.js", "child/data/**", "child/another-file.sjs"]),
+ )
+
+ for key in test_installs.keys():
+ self.assertIn(key, test_installs)
+
+ def test_test_defaults_metadata_file_written(self):
+ """Ensure test-defaults.pkl is generated."""
+ env = self._consume("test-manifests-written", TestManifestBackend)
+
+ test_defaults_path = mozpath.join(env.topobjdir, "test-defaults.pkl")
+ self.assertTrue(os.path.exists(test_defaults_path))
+
+ with open(test_defaults_path, "rb") as fh:
+ o = {mozpath.normpath(k): v for k, v in pickle.load(fh).items()}
+
+ self.assertEqual(
+ set(mozpath.relpath(k, env.topsrcdir) for k in o.keys()),
+ set(["dir1/xpcshell.ini", "xpcshell.ini", "mochitest.ini"]),
+ )
+
+ manifest_path = mozpath.join(env.topsrcdir, "xpcshell.ini")
+ self.assertIn("here", o[manifest_path])
+ self.assertIn("support-files", o[manifest_path])
+
+ def test_test_manifest_sources(self):
+ """Ensure that backend sources are generated correctly."""
+ env = self._consume("test-manifests-backend-sources", TestManifestBackend)
+
+ backend_path = mozpath.join(env.topobjdir, "backend.TestManifestBackend.in")
+ self.assertTrue(os.path.exists(backend_path))
+
+ status_path = mozpath.join(env.topobjdir, "config.status")
+
+ with open(backend_path, "r") as fh:
+ sources = set(source.strip() for source in fh)
+
+ self.assertEqual(
+ sources,
+ set(
+ [
+ mozpath.join(env.topsrcdir, "mochitest.ini"),
+ mozpath.join(env.topsrcdir, "mochitest-common.ini"),
+ mozpath.join(env.topsrcdir, "moz.build"),
+ status_path,
+ ]
+ ),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/backend/test_visualstudio.py b/python/mozbuild/mozbuild/test/backend/test_visualstudio.py
new file mode 100644
index 0000000000..14cccb484b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_visualstudio.py
@@ -0,0 +1,63 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+from xml.dom.minidom import parse
+
+from mozunit import main
+
+from mozbuild.backend.visualstudio import VisualStudioBackend
+from mozbuild.test.backend.common import BackendTester
+
+
+class TestVisualStudioBackend(BackendTester):
+ @unittest.skip("Failing inconsistently in automation.")
+ def test_basic(self):
+ """Ensure we can consume our stub project."""
+
+ env = self._consume("visual-studio", VisualStudioBackend)
+
+ msvc = os.path.join(env.topobjdir, "msvc")
+ self.assertTrue(os.path.isdir(msvc))
+
+ self.assertTrue(os.path.isfile(os.path.join(msvc, "mozilla.sln")))
+ self.assertTrue(os.path.isfile(os.path.join(msvc, "mozilla.props")))
+ self.assertTrue(os.path.isfile(os.path.join(msvc, "mach.bat")))
+ self.assertTrue(os.path.isfile(os.path.join(msvc, "binary_my_app.vcxproj")))
+ self.assertTrue(os.path.isfile(os.path.join(msvc, "target_full.vcxproj")))
+ self.assertTrue(os.path.isfile(os.path.join(msvc, "library_dir1.vcxproj")))
+ self.assertTrue(os.path.isfile(os.path.join(msvc, "library_dir1.vcxproj.user")))
+
+ d = parse(os.path.join(msvc, "library_dir1.vcxproj"))
+ self.assertEqual(d.documentElement.tagName, "Project")
+ els = d.getElementsByTagName("ClCompile")
+ self.assertEqual(len(els), 2)
+
+ # mozilla-config.h should be explicitly listed as an include.
+ els = d.getElementsByTagName("NMakeForcedIncludes")
+ self.assertEqual(len(els), 1)
+ self.assertEqual(
+ els[0].firstChild.nodeValue, "$(TopObjDir)\\dist\\include\\mozilla-config.h"
+ )
+
+ # LOCAL_INCLUDES get added to the include search path.
+ els = d.getElementsByTagName("NMakeIncludeSearchPath")
+ self.assertEqual(len(els), 1)
+ includes = els[0].firstChild.nodeValue.split(";")
+ self.assertIn(os.path.normpath("$(TopSrcDir)/includeA/foo"), includes)
+ self.assertIn(os.path.normpath("$(TopSrcDir)/dir1"), includes)
+ self.assertIn(os.path.normpath("$(TopObjDir)/dir1"), includes)
+ self.assertIn(os.path.normpath("$(TopObjDir)\\dist\\include"), includes)
+
+ # DEFINES get added to the project.
+ els = d.getElementsByTagName("NMakePreprocessorDefinitions")
+ self.assertEqual(len(els), 1)
+ defines = els[0].firstChild.nodeValue.split(";")
+ self.assertIn("DEFINEFOO", defines)
+ self.assertIn("DEFINEBAR=bar", defines)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/code_analysis/test_mach_commands.py b/python/mozbuild/mozbuild/test/code_analysis/test_mach_commands.py
new file mode 100644
index 0000000000..774688c62f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/code_analysis/test_mach_commands.py
@@ -0,0 +1,90 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import os
+import unittest
+from unittest import mock
+
+import mozpack.path as mozpath
+from mach.registrar import Registrar
+from mozunit import main
+
+from mozbuild.base import MozbuildObject
+
+
+class TestStaticAnalysis(unittest.TestCase):
+ def setUp(self):
+ self.remove_cats = []
+ for cat in ("build", "post-build", "misc", "testing", "devenv"):
+ if cat in Registrar.categories:
+ continue
+ Registrar.register_category(cat, cat, cat)
+ self.remove_cats.append(cat)
+
+ def tearDown(self):
+ for cat in self.remove_cats:
+ del Registrar.categories[cat]
+ del Registrar.commands_by_category[cat]
+
+ def test_bug_1615884(self):
+ # TODO: cleaner test
+ # we're testing the `_is_ignored_path` but in an ideal
+ # world we should test the clang_analysis mach command
+ # since that small function is an internal detail.
+ # But there is zero test infra for that mach command
+ from mozbuild.code_analysis.mach_commands import _is_ignored_path
+
+ config = MozbuildObject.from_environment()
+ context = mock.MagicMock()
+ context.cwd = config.topsrcdir
+
+ command_context = mock.MagicMock()
+ command_context.topsrcdir = os.path.join("/root", "dir")
+ path = os.path.join("/root", "dir", "path1")
+
+ ignored_dirs_re = r"path1|path2/here|path3\there"
+ self.assertTrue(
+ _is_ignored_path(command_context, ignored_dirs_re, path) is not None
+ )
+
+ # simulating a win32 env
+ win32_path = "\\root\\dir\\path1"
+ command_context.topsrcdir = "\\root\\dir"
+ old_sep = os.sep
+ os.sep = "\\"
+ try:
+ self.assertTrue(
+ _is_ignored_path(command_context, ignored_dirs_re, win32_path)
+ is not None
+ )
+ finally:
+ os.sep = old_sep
+
+ self.assertTrue(
+ _is_ignored_path(command_context, ignored_dirs_re, "path2") is None
+ )
+
+ def test_get_files(self):
+ from mozbuild.code_analysis.mach_commands import get_abspath_files
+
+ config = MozbuildObject.from_environment()
+ context = mock.MagicMock()
+ context.cwd = config.topsrcdir
+
+ command_context = mock.MagicMock()
+ command_context.topsrcdir = mozpath.join("/root", "dir")
+ source = get_abspath_files(
+ command_context, ["file1", mozpath.join("directory", "file2")]
+ )
+
+ self.assertTrue(
+ source
+ == [
+ mozpath.join(command_context.topsrcdir, "file1"),
+ mozpath.join(command_context.topsrcdir, "directory", "file2"),
+ ]
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/codecoverage/sample_lcov.info b/python/mozbuild/mozbuild/test/codecoverage/sample_lcov.info
new file mode 100644
index 0000000000..996ccac215
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/codecoverage/sample_lcov.info
@@ -0,0 +1,1895 @@
+SF:lcov_test_newTab.js
+FN:1,top-level
+FN:31,top-level
+FN:232,Transformation_rearrangeSites/</<
+FN:259,Transformation_whenTransitionEnded
+FN:262,onEnd
+FN:275,Transformation_getNodeOpacity
+FN:287,Transformation_setNodeOpacity
+FN:307,Transformation_moveSite
+FN:317,Transformation_isFrozen
+FN:334,Page_init
+FN:363,Page_observe
+FN:399,update
+FN:417,update/this._scheduleUpdateTimeout<
+FN:431,Page_init
+FN:443,Page_init/<
+FN:459,Page_updateAttributes
+FN:482,Page_handleUnloadEvent
+FN:499,Page_handleEvent
+FN:538,Page_handleEvent/<
+FN:544,gPage.onPageFirstVisible
+FN:567,onPageVisibleAndLoaded
+FN:575,reportLastVisibleTileIndex
+FN:619,gGrid.node
+FN:630,gGrid.cells
+FN:635,gGrid.sites
+FN:638,gGrid.ready
+FN:641,gGrid.isDocumentLoaded
+FN:647,Grid_init
+FN:651,Grid_init/<
+FN:676,Grid_createSite
+FN:685,Grid_handleEvent
+FN:697,Grid_lock
+FN:704,Grid_unlock
+FN:714,refresh
+FN:722,_refreshGrid
+FN:755,Grid_computeHeight
+FN:764,Grid_createSiteFragment
+FN:791,Grid_isHistoricalTile
+FN:799,Grid_resizeGrid
+FN:870,Cell
+FN:876,Cell/<
+FN:890,Cell.prototype.node
+FN:895,Cell.prototype.index
+FN:907,Cell.prototype.previousSibling
+FN:920,Cell.prototype.nextSibling
+FN:933,Cell.prototype.site
+FN:942,Cell_containsPinnedSite
+FN:951,Cell_isEmpty
+FN:958,Cell_handleEvent
+FN:991,Site
+FN:1005,Site.prototype.node
+FN:1010,Site.prototype.link
+FN:1015,Site.prototype.url
+FN:1020,Site.prototype.title
+FN:1025,Site.prototype.cell
+FN:1035,Site_pin
+FN:1051,Site_unpin
+FN:1063,Site_isPinned
+FN:1071,Site_block
+FN:1084,Site_querySelector
+FN:1093,Site.prototype._updateAttributes
+FN:1105,Site.prototype._newTabString
+FN:1116,Site.prototype._getSuggestedTileExplanation
+FN:1128,Site_checkLinkEndTime
+FN:1147,Site_render
+FN:1199,Site_onFirstVisible
+FN:1213,Site_captureIfMissing
+FN:1222,Site_refreshThumbnail
+FN:1246,Site.prototype._ignoreHoverEvents
+FN:1247,Site.prototype._ignoreHoverEvents/<
+FN:1250,Site.prototype._ignoreHoverEvents/<
+FN:1258,Site_addEventHandlers
+FN:1275,Site_speculativeConnect
+FN:1284,Site_recordSiteClicked
+FN:1296,Site.prototype._toggleLegalText
+FN:1324,Site_onClick
+FN:1386,Site_handleEvent
+FN:1417,gDrag.draggedSite
+FN:1424,gDrag.cellWidth
+FN:1425,gDrag.cellHeight
+FN:1432,Drag_start
+FN:1465,Drag_drag
+FN:1489,Drag_end
+FN:1505,Drag_isValid
+FN:1524,Drag_setDragData
+FN:1545,Drag_setDragData/<
+FN:1551,gDragDataHelper.mimeType
+FN:1555,DragDataHelper_getLinkFromDragEvent
+FN:1585,Drop_enter
+FN:1594,Drop_exit
+FN:1609,Drop_drop
+FN:1628,Drop_repinSitesAfterDrop
+FN:1632,Drop_repinSitesAfterDrop/pinnedSites<
+FN:1637,Drop_repinSitesAfterDrop/<
+FN:1645,Drop_pinDraggedSite
+FN:1669,Drop_delayedRearrange
+FN:1676,callback
+FN:1691,Drop_cancelDelayedArrange
+FN:1702,Drop_rearrange
+FN:1733,gDropTargetShim.init
+FN:1740,gDropTargetShim._addEventListeners
+FN:1752,gDropTargetShim._removeEventListeners
+FN:1764,gDropTargetShim.handleEvent
+FN:1788,gDropTargetShim._dragstart
+FN:1799,gDropTargetShim._dragover
+FN:1819,gDropTargetShim._drop
+FN:1839,gDropTargetShim._dragend
+FN:1862,gDropTargetShim._updateDropTarget
+FN:1888,gDropTargetShim._findDropTarget
+FN:1914,DropTargetShim_getCellPositions
+FN:1918,DropTargetShim_getCellPositions/this._cellPositions<
+FN:1929,gDropTargetShim._dispatchEvent
+FN:1954,DropPreview_rearrange
+FN:1972,DropPreview_insertDraggedSite
+FN:1999,DropPreview_repositionPinnedSites
+FN:2005,DropPreview_repositionPinnedSites/<
+FN:2023,DropPreview_filterPinnedSites
+FN:2030,DropPreview_filterPinnedSites/<
+FN:2047,DropPreview_getPinnedRange
+FN:2077,DropPreview_hasOverflowedPinnedSite
+FN:2104,DropPreview_repositionOverflowedPinnedSite
+FN:2135,DropPreview_indexOfLowerPrioritySite
+FN:2170,Updater_updateGrid
+FN:2177,Updater_updateGrid/<
+FN:2189,Updater_updateGrid/</<
+FN:2206,Updater_findRemainingSites
+FN:2210,Updater_findRemainingSites/<
+FN:2216,Updater_findRemainingSites/<
+FN:2225,Updater_freezeSitePositions
+FN:2226,Updater_freezeSitePositions/<
+FN:2236,Updater_moveSiteNodes
+FN:2244,Updater_moveSiteNodes/<
+FN:2268,Updater_rearrangeSites
+FN:2279,Updater_removeLegacySites
+FN:2283,Updater_removeLegacySites/<
+FN:2288,Updater_removeLegacySites/</<
+FN:2290,Updater_removeLegacySites/</</<
+FN:2308,Updater_fillEmptyCells
+FN:2312,Updater_fillEmptyCells/<
+FN:2316,Updater_fillEmptyCells/</<
+FN:2351,UndoDialog_init
+FN:2363,UndoDialog_show
+FN:2383,UndoDialog_hide
+FN:2399,UndoDialog_handleEvent
+FN:2416,UndoDialog_undo
+FN:2434,UndoDialog_undoAll
+FN:2435,UndoDialog_undoAll/<
+FN:2446,gSearch.init
+FN:2448,gSearch.init/<
+FN:2469,gCustomize.init
+FN:2474,gCustomize.init/<
+FN:2483,gCustomize.hidePanel
+FN:2484,onTransitionEnd
+FN:2495,gCustomize.showPanel
+FN:2504,gCustomize.showPanel/<
+FN:2517,gCustomize.handleEvent
+FN:2528,gCustomize.onClick
+FN:2554,gCustomize.onKeyDown
+FN:2560,gCustomize.showLearn
+FN:2565,gCustomize.updateSelected
+FN:2568,gCustomize.updateSelected/<
+FN:2602,gIntro.init
+FN:2608,gIntro._showMessage
+FN:2612,gIntro._showMessage/<
+FN:2621,gIntro._bold
+FN:2625,gIntro._link
+FN:2629,gIntro._exitIntro
+FN:2631,gIntro._exitIntro/<
+FN:2636,gIntro._generateParagraphs
+FN:2646,gIntro.showIfNecessary
+FN:2656,gIntro.showPanel
+FN:36,newTabString
+FN:44,inPrivateBrowsingMode
+FN:67,gTransformation._cellBorderWidths
+FN:86,Transformation_getNodePosition
+FN:96,Transformation_fadeNodeIn
+FN:97,Transformation_fadeNodeIn/<
+FN:111,Transformation_fadeNodeOut
+FN:120,Transformation_showSite
+FN:129,Transformation_hideSite
+FN:138,Transformation_setSitePosition
+FN:150,Transformation_freezeSitePosition
+FN:167,Transformation_unfreezeSitePosition
+FN:184,Transformation_slideSiteTo
+FN:191,finish
+FN:221,Transformation_rearrangeSites
+FN:227,Transformation_rearrangeSites/<
+FNDA:1,top-level
+FNDA:1,top-level
+FNDA:1,Page_init
+FNDA:2,update
+FNDA:1,Page_init
+FNDA:1,Page_init/<
+FNDA:1,Page_updateAttributes
+FNDA:1,Page_handleUnloadEvent
+FNDA:2,Page_handleEvent
+FNDA:1,gPage.onPageFirstVisible
+FNDA:1,onPageVisibleAndLoaded
+FNDA:1,reportLastVisibleTileIndex
+FNDA:2,gGrid.node
+FNDA:13,gGrid.cells
+FNDA:12,gGrid.sites
+FNDA:1,gGrid.ready
+FNDA:4,gGrid.isDocumentLoaded
+FNDA:1,Grid_init
+FNDA:1,Grid_init/<
+FNDA:2,Grid_createSite
+FNDA:1,Grid_handleEvent
+FNDA:1,refresh
+FNDA:2,_refreshGrid
+FNDA:4,Grid_computeHeight
+FNDA:1,Grid_createSiteFragment
+FNDA:8,Grid_isHistoricalTile
+FNDA:3,Grid_resizeGrid
+FNDA:30,Cell
+FNDA:120,Cell/<
+FNDA:183,Cell.prototype.node
+FNDA:180,Cell.prototype.site
+FNDA:2,Site
+FNDA:16,Site.prototype.node
+FNDA:33,Site.prototype.link
+FNDA:7,Site.prototype.url
+FNDA:4,Site.prototype.title
+FNDA:2,Site_isPinned
+FNDA:12,Site_querySelector
+FNDA:2,Site_checkLinkEndTime
+FNDA:2,Site_render
+FNDA:1,Site_onFirstVisible
+FNDA:3,Site_captureIfMissing
+FNDA:2,Site_refreshThumbnail
+FNDA:4,Site.prototype._ignoreHoverEvents
+FNDA:2,Site_addEventHandlers
+FNDA:1,gDropTargetShim.init
+FNDA:1,UndoDialog_init
+FNDA:1,gSearch.init
+FNDA:1,gCustomize.init
+FNDA:1,gCustomize.updateSelected
+FNDA:3,gCustomize.updateSelected/<
+FNDA:1,gIntro.init
+FNDA:1,gIntro.showIfNecessary
+FNDA:3,newTabString
+FNF:187
+FNH:54
+BRDA:233,0,0,-
+BRDA:233,0,1,-
+BRDA:236,1,0,-
+BRDA:236,1,1,-
+BRDA:263,0,0,-
+BRDA:263,0,1,-
+BRDA:289,0,0,-
+BRDA:289,0,1,-
+BRDA:290,1,0,-
+BRDA:290,1,1,-
+BRDA:293,2,0,-
+BRDA:293,2,1,-
+BRDA:348,0,0,-
+BRDA:348,0,1,1
+BRDA:364,0,0,-
+BRDA:364,0,1,-
+BRDA:371,1,0,-
+BRDA:371,1,1,-
+BRDA:377,2,0,-
+BRDA:377,2,1,-
+BRDA:382,3,0,-
+BRDA:382,3,1,-
+BRDA:382,4,0,-
+BRDA:382,4,1,-
+BRDA:384,5,0,-
+BRDA:384,5,1,-
+BRDA:384,6,0,-
+BRDA:384,6,1,-
+BRDA:383,7,0,-
+BRDA:383,7,1,-
+BRDA:399,0,0,2
+BRDA:399,0,1,-
+BRDA:401,1,0,-
+BRDA:401,1,1,2
+BRDA:405,2,0,1
+BRDA:405,2,1,1
+BRDA:405,3,0,1
+BRDA:405,3,1,1
+BRDA:413,4,0,-
+BRDA:413,4,1,-
+BRDA:419,0,0,-
+BRDA:419,0,1,-
+BRDA:432,0,0,1
+BRDA:432,0,1,-
+BRDA:440,1,0,1
+BRDA:440,1,1,-
+BRDA:463,0,0,-
+BRDA:463,0,1,2
+BRDA:462,1,0,2
+BRDA:462,1,1,1
+BRDA:472,2,0,-
+BRDA:472,2,1,-
+BRDA:471,3,0,-
+BRDA:471,3,1,1
+BRDA:488,0,0,1
+BRDA:488,0,1,-
+BRDA:500,0,0,1
+BRDA:500,0,1,1
+BRDA:500,1,0,1
+BRDA:500,1,1,-
+BRDA:500,2,0,-
+BRDA:500,2,1,-
+BRDA:500,3,0,-
+BRDA:500,3,1,-
+BRDA:500,4,0,-
+BRDA:500,4,1,-
+BRDA:500,5,0,-
+BRDA:500,5,1,-
+BRDA:511,6,0,-
+BRDA:511,6,1,-
+BRDA:510,7,0,-
+BRDA:510,7,1,-
+BRDA:519,8,0,-
+BRDA:519,8,1,-
+BRDA:519,9,0,-
+BRDA:519,9,1,-
+BRDA:523,10,0,-
+BRDA:523,10,1,-
+BRDA:523,11,0,-
+BRDA:523,11,1,-
+BRDA:530,12,0,-
+BRDA:530,12,1,-
+BRDA:549,0,0,14
+BRDA:549,0,1,1
+BRDA:548,1,0,16
+BRDA:548,1,1,1
+BRDA:560,2,0,1
+BRDA:560,2,1,-
+BRDA:588,0,0,1
+BRDA:588,0,1,22
+BRDA:588,1,0,15
+BRDA:588,1,1,8
+BRDA:589,2,0,7
+BRDA:589,2,1,1
+BRDA:591,3,0,1
+BRDA:591,3,1,-
+BRDA:587,4,0,24
+BRDA:587,4,1,1
+BRDA:635,0,0,181
+BRDA:635,0,1,12
+BRDA:665,0,0,-
+BRDA:665,0,1,1
+BRDA:686,0,0,1
+BRDA:686,0,1,-
+BRDA:686,1,0,-
+BRDA:686,1,1,-
+BRDA:728,0,0,31
+BRDA:728,0,1,2
+BRDA:733,1,0,30
+BRDA:733,1,1,2
+BRDA:741,2,0,-
+BRDA:741,2,1,2
+BRDA:740,3,0,2
+BRDA:740,3,1,2
+BRDA:757,0,0,2
+BRDA:757,0,1,2
+BRDA:793,0,0,8
+BRDA:793,0,1,-
+BRDA:793,1,0,-
+BRDA:793,1,1,-
+BRDA:793,2,0,-
+BRDA:793,2,1,-
+BRDA:804,0,0,1
+BRDA:804,0,1,2
+BRDA:804,1,0,2
+BRDA:804,1,1,1
+BRDA:809,2,0,1
+BRDA:809,2,1,1
+BRDA:819,3,0,1
+BRDA:819,3,1,1
+BRDA:839,4,0,8
+BRDA:839,4,1,-
+BRDA:838,5,0,9
+BRDA:838,5,1,2
+BRDA:909,0,0,-
+BRDA:909,0,1,-
+BRDA:922,0,0,-
+BRDA:922,0,1,-
+BRDA:935,0,0,168
+BRDA:935,0,1,12
+BRDA:944,0,0,-
+BRDA:944,0,1,-
+BRDA:961,0,0,-
+BRDA:961,0,1,-
+BRDA:961,1,0,-
+BRDA:961,1,1,-
+BRDA:964,2,0,-
+BRDA:964,2,1,-
+BRDA:964,3,0,-
+BRDA:964,3,1,-
+BRDA:967,4,0,-
+BRDA:967,4,1,-
+BRDA:967,5,0,-
+BRDA:967,5,1,-
+BRDA:967,6,0,-
+BRDA:967,6,1,-
+BRDA:967,7,0,-
+BRDA:967,7,1,-
+BRDA:1020,0,0,4
+BRDA:1020,0,1,-
+BRDA:1027,0,0,-
+BRDA:1027,0,1,-
+BRDA:1036,0,0,-
+BRDA:1036,0,1,-
+BRDA:1041,1,0,-
+BRDA:1041,1,1,-
+BRDA:1052,0,0,-
+BRDA:1052,0,1,-
+BRDA:1072,0,0,-
+BRDA:1072,0,1,-
+BRDA:1096,0,0,-
+BRDA:1096,0,1,-
+BRDA:1108,0,0,-
+BRDA:1108,0,1,-
+BRDA:1119,0,0,-
+BRDA:1119,0,1,-
+BRDA:1129,0,0,2
+BRDA:1129,0,1,-
+BRDA:1129,1,0,2
+BRDA:1129,1,1,-
+BRDA:1151,0,0,-
+BRDA:1151,0,1,2
+BRDA:1153,1,0,-
+BRDA:1153,1,1,2
+BRDA:1153,2,0,-
+BRDA:1153,2,1,2
+BRDA:1154,3,0,-
+BRDA:1154,3,1,-
+BRDA:1156,4,0,2
+BRDA:1156,4,1,-
+BRDA:1165,5,0,-
+BRDA:1165,5,1,2
+BRDA:1173,6,0,2
+BRDA:1173,6,1,-
+BRDA:1174,7,0,-
+BRDA:1174,7,1,-
+BRDA:1185,8,0,2
+BRDA:1185,8,1,-
+BRDA:1200,0,0,1
+BRDA:1200,0,1,-
+BRDA:1200,1,0,1
+BRDA:1200,1,1,-
+BRDA:1214,0,0,-
+BRDA:1214,0,1,3
+BRDA:1214,1,0,-
+BRDA:1214,1,1,3
+BRDA:1224,0,0,-
+BRDA:1224,0,1,2
+BRDA:1224,1,0,2
+BRDA:1224,1,1,-
+BRDA:1228,2,0,2
+BRDA:1228,2,1,-
+BRDA:1232,3,0,-
+BRDA:1232,3,1,2
+BRDA:1235,4,0,-
+BRDA:1235,4,1,2
+BRDA:1239,5,0,2
+BRDA:1239,5,1,-
+BRDA:1285,0,0,-
+BRDA:1285,0,1,-
+BRDA:1286,1,0,-
+BRDA:1286,1,1,-
+BRDA:1287,2,0,-
+BRDA:1287,2,1,-
+BRDA:1298,0,0,-
+BRDA:1298,0,1,-
+BRDA:1311,1,0,-
+BRDA:1311,1,1,-
+BRDA:1311,2,0,-
+BRDA:1311,2,1,-
+BRDA:1314,3,0,-
+BRDA:1314,3,1,-
+BRDA:1315,4,0,-
+BRDA:1315,4,1,-
+BRDA:1331,0,0,-
+BRDA:1331,0,1,-
+BRDA:1332,1,0,-
+BRDA:1332,1,1,-
+BRDA:1334,2,0,-
+BRDA:1334,2,1,-
+BRDA:1334,3,0,-
+BRDA:1334,3,1,-
+BRDA:1340,4,0,-
+BRDA:1340,4,1,-
+BRDA:1343,5,0,-
+BRDA:1343,5,1,-
+BRDA:1347,6,0,-
+BRDA:1347,6,1,-
+BRDA:1349,7,0,-
+BRDA:1349,7,1,-
+BRDA:1353,8,0,-
+BRDA:1353,8,1,-
+BRDA:1359,9,0,-
+BRDA:1359,9,1,-
+BRDA:1360,10,0,-
+BRDA:1360,10,1,-
+BRDA:1364,11,0,-
+BRDA:1364,11,1,-
+BRDA:1364,12,0,-
+BRDA:1364,12,1,-
+BRDA:1368,13,0,-
+BRDA:1368,13,1,-
+BRDA:1368,14,0,-
+BRDA:1368,14,1,-
+BRDA:1369,15,0,-
+BRDA:1369,15,1,-
+BRDA:1378,16,0,-
+BRDA:1378,16,1,-
+BRDA:1387,0,0,-
+BRDA:1387,0,1,-
+BRDA:1387,1,0,-
+BRDA:1387,1,1,-
+BRDA:1387,2,0,-
+BRDA:1387,2,1,-
+BRDA:1439,0,0,-
+BRDA:1439,0,1,-
+BRDA:1491,0,0,-
+BRDA:1491,0,1,-
+BRDA:1510,0,0,-
+BRDA:1510,0,1,-
+BRDA:1510,1,0,-
+BRDA:1510,1,1,-
+BRDA:1557,0,0,-
+BRDA:1557,0,1,-
+BRDA:1557,1,0,-
+BRDA:1557,1,1,-
+BRDA:1561,2,0,-
+BRDA:1561,2,1,-
+BRDA:1562,3,0,-
+BRDA:1562,3,1,-
+BRDA:1562,4,0,-
+BRDA:1562,4,1,-
+BRDA:1595,0,0,-
+BRDA:1595,0,1,-
+BRDA:1595,1,0,-
+BRDA:1595,1,1,-
+BRDA:1612,0,0,-
+BRDA:1612,0,1,-
+BRDA:1633,0,0,-
+BRDA:1633,0,1,-
+BRDA:1649,0,0,-
+BRDA:1649,0,1,-
+BRDA:1651,1,0,-
+BRDA:1651,1,1,-
+BRDA:1655,2,0,-
+BRDA:1655,2,1,-
+BRDA:1671,0,0,-
+BRDA:1671,0,1,-
+BRDA:1692,0,0,-
+BRDA:1692,0,1,-
+BRDA:1706,0,0,-
+BRDA:1706,0,1,-
+BRDA:1765,0,0,-
+BRDA:1765,0,1,-
+BRDA:1765,1,0,-
+BRDA:1765,1,1,-
+BRDA:1765,2,0,-
+BRDA:1765,2,1,-
+BRDA:1765,3,0,-
+BRDA:1765,3,1,-
+BRDA:1765,4,0,-
+BRDA:1765,4,1,-
+BRDA:1789,0,0,-
+BRDA:1789,0,1,-
+BRDA:1810,0,0,-
+BRDA:1810,0,1,-
+BRDA:1840,0,0,-
+BRDA:1840,0,1,-
+BRDA:1841,1,0,-
+BRDA:1841,1,1,-
+BRDA:1841,2,0,-
+BRDA:1841,2,1,-
+BRDA:1866,0,0,-
+BRDA:1866,0,1,-
+BRDA:1867,1,0,-
+BRDA:1867,1,1,-
+BRDA:1871,2,0,-
+BRDA:1871,2,1,-
+BRDA:1875,3,0,-
+BRDA:1875,3,1,-
+BRDA:1902,0,0,-
+BRDA:1902,0,1,-
+BRDA:1902,1,0,-
+BRDA:1902,1,1,-
+BRDA:1898,2,0,-
+BRDA:1898,2,1,-
+BRDA:1915,0,0,-
+BRDA:1915,0,1,-
+BRDA:1977,0,0,-
+BRDA:1977,0,1,-
+BRDA:1982,1,0,-
+BRDA:1982,1,1,-
+BRDA:2012,0,0,-
+BRDA:2012,0,1,-
+BRDA:2032,0,0,-
+BRDA:2032,0,1,-
+BRDA:2032,1,0,-
+BRDA:2032,1,1,-
+BRDA:2032,2,0,-
+BRDA:2032,2,1,-
+BRDA:2038,3,0,-
+BRDA:2038,3,1,-
+BRDA:2052,0,0,-
+BRDA:2052,0,1,-
+BRDA:2056,1,0,-
+BRDA:2056,1,1,-
+BRDA:2056,2,0,-
+BRDA:2056,2,1,-
+BRDA:2062,3,0,-
+BRDA:2062,3,1,-
+BRDA:2062,4,0,-
+BRDA:2062,4,1,-
+BRDA:2081,0,0,-
+BRDA:2081,0,1,-
+BRDA:2087,1,0,-
+BRDA:2087,1,1,-
+BRDA:2093,2,0,-
+BRDA:2093,2,1,-
+BRDA:2109,0,0,-
+BRDA:2109,0,1,-
+BRDA:2116,1,0,-
+BRDA:2116,1,1,-
+BRDA:2115,2,0,-
+BRDA:2115,2,1,-
+BRDA:2145,0,0,-
+BRDA:2145,0,1,-
+BRDA:2151,1,0,-
+BRDA:2151,1,1,-
+BRDA:2151,2,0,-
+BRDA:2151,2,1,-
+BRDA:2143,3,0,-
+BRDA:2143,3,1,-
+BRDA:2211,0,0,-
+BRDA:2211,0,1,-
+BRDA:2217,0,0,-
+BRDA:2217,0,1,-
+BRDA:2217,1,0,-
+BRDA:2217,1,1,-
+BRDA:2227,0,0,-
+BRDA:2227,0,1,-
+BRDA:2249,0,0,-
+BRDA:2249,0,1,-
+BRDA:2249,1,0,-
+BRDA:2249,1,1,-
+BRDA:2253,2,0,-
+BRDA:2253,2,1,-
+BRDA:2257,3,0,-
+BRDA:2257,3,1,-
+BRDA:2285,0,0,-
+BRDA:2285,0,1,-
+BRDA:2285,1,0,-
+BRDA:2285,1,1,-
+BRDA:2313,0,0,-
+BRDA:2313,0,1,-
+BRDA:2313,1,0,-
+BRDA:2313,1,1,-
+BRDA:2364,0,0,-
+BRDA:2364,0,1,-
+BRDA:2384,0,0,-
+BRDA:2384,0,1,-
+BRDA:2400,0,0,-
+BRDA:2400,0,1,-
+BRDA:2400,1,0,-
+BRDA:2400,1,1,-
+BRDA:2400,2,0,-
+BRDA:2400,2,1,-
+BRDA:2417,0,0,-
+BRDA:2417,0,1,-
+BRDA:2423,1,0,-
+BRDA:2423,1,1,-
+BRDA:2470,0,0,7
+BRDA:2470,0,1,1
+BRDA:2496,0,0,-
+BRDA:2496,0,1,-
+BRDA:2518,0,0,-
+BRDA:2518,0,1,-
+BRDA:2518,1,0,-
+BRDA:2518,1,1,-
+BRDA:2529,0,0,-
+BRDA:2529,0,1,-
+BRDA:2530,1,0,-
+BRDA:2530,1,1,-
+BRDA:2534,2,0,-
+BRDA:2534,2,1,-
+BRDA:2534,3,0,-
+BRDA:2534,3,1,-
+BRDA:2534,4,0,-
+BRDA:2534,4,1,-
+BRDA:2534,5,0,-
+BRDA:2534,5,1,-
+BRDA:2539,6,0,-
+BRDA:2539,6,1,-
+BRDA:2555,0,0,-
+BRDA:2555,0,1,-
+BRDA:2567,0,0,-
+BRDA:2567,0,1,1
+BRDA:2567,1,0,-
+BRDA:2567,1,1,1
+BRDA:2577,2,0,-
+BRDA:2577,2,1,1
+BRDA:2570,0,0,2
+BRDA:2570,0,1,1
+BRDA:2603,0,0,6
+BRDA:2603,0,1,1
+BRDA:2647,0,0,1
+BRDA:2647,0,1,-
+BRDA:2650,1,0,1
+BRDA:2650,1,1,-
+BRDA:2660,0,0,-
+BRDA:2660,0,1,-
+BRDA:38,0,0,-
+BRDA:38,0,1,3
+BRDA:101,0,0,-
+BRDA:101,0,1,-
+BRDA:151,0,0,-
+BRDA:151,0,1,-
+BRDA:168,0,0,-
+BRDA:168,0,1,-
+BRDA:187,0,0,-
+BRDA:187,0,1,-
+BRDA:204,1,0,-
+BRDA:204,1,1,-
+BRDA:205,2,0,-
+BRDA:205,2,1,-
+BRDA:192,0,0,-
+BRDA:192,0,1,-
+BRDA:192,1,0,-
+BRDA:192,1,1,-
+BRDA:195,2,0,-
+BRDA:195,2,1,-
+BRDA:224,0,0,-
+BRDA:224,0,1,-
+BRDA:225,1,0,-
+BRDA:225,1,1,-
+BRDA:246,2,0,-
+BRDA:246,2,1,-
+BRDA:229,0,0,-
+BRDA:229,0,1,-
+BRDA:229,1,0,-
+BRDA:229,1,1,-
+BRF:500
+BRH:90
+DA:7,1
+DA:8,1
+DA:23,1
+DA:24,1
+DA:25,1
+DA:26,1
+DA:27,1
+DA:28,1
+DA:36,1
+DA:48,1
+DA:49,1
+DA:51,1
+DA:52,1
+DA:53,1
+DA:62,1
+DA:324,1
+DA:330,1
+DA:607,1
+DA:608,1
+DA:609,1
+DA:614,1
+DA:870,1
+DA:1406,1
+DA:1550,1
+DA:1570,1
+DA:1575,1
+DA:1719,1
+DA:1947,1
+DA:2164,1
+DA:2337,1
+DA:2445,1
+DA:2456,1
+DA:2585,1
+DA:2586,1
+DA:2588,1
+DA:7,1
+DA:8,1
+DA:10,1
+DA:11,1
+DA:12,1
+DA:13,1
+DA:14,1
+DA:15,1
+DA:17,1
+DA:18,1
+DA:17,1
+DA:19,1
+DA:20,1
+DA:19,1
+DA:29,1
+DA:31,1
+DA:48,1
+DA:49,1
+DA:51,1
+DA:52,1
+DA:53,1
+DA:62,1
+DA:67,1
+DA:86,1
+DA:96,1
+DA:111,1
+DA:120,1
+DA:129,1
+DA:138,1
+DA:150,1
+DA:167,1
+DA:184,1
+DA:221,1
+DA:259,1
+DA:275,1
+DA:287,1
+DA:307,1
+DA:317,1
+DA:324,1
+DA:330,1
+DA:334,1
+DA:363,1
+DA:399,1
+DA:431,1
+DA:459,1
+DA:482,1
+DA:499,1
+DA:544,1
+DA:567,1
+DA:575,1
+DA:607,1
+DA:608,1
+DA:609,1
+DA:614,1
+DA:618,1
+DA:619,1
+DA:624,1
+DA:629,1
+DA:630,1
+DA:635,1
+DA:638,1
+DA:641,1
+DA:647,1
+DA:676,1
+DA:685,1
+DA:697,1
+DA:704,1
+DA:714,1
+DA:722,1
+DA:755,1
+DA:764,1
+DA:791,1
+DA:799,1
+DA:881,1
+DA:885,1
+DA:890,1
+DA:895,1
+DA:907,1
+DA:920,1
+DA:933,1
+DA:942,1
+DA:951,1
+DA:958,1
+DA:1001,1
+DA:1005,1
+DA:1010,1
+DA:1015,1
+DA:1020,1
+DA:1025,1
+DA:1035,1
+DA:1051,1
+DA:1063,1
+DA:1071,1
+DA:1084,1
+DA:1093,1
+DA:1105,1
+DA:1116,1
+DA:1128,1
+DA:1147,1
+DA:1199,1
+DA:1213,1
+DA:1222,1
+DA:1246,1
+DA:1258,1
+DA:1275,1
+DA:1284,1
+DA:1296,1
+DA:1324,1
+DA:1386,1
+DA:1406,1
+DA:1410,1
+DA:1411,1
+DA:1416,1
+DA:1417,1
+DA:1422,1
+DA:1423,1
+DA:1424,1
+DA:1425,1
+DA:1432,1
+DA:1465,1
+DA:1489,1
+DA:1505,1
+DA:1524,1
+DA:1550,1
+DA:1551,1
+DA:1555,1
+DA:1570,1
+DA:1575,1
+DA:1579,1
+DA:1585,1
+DA:1594,1
+DA:1609,1
+DA:1628,1
+DA:1645,1
+DA:1669,1
+DA:1691,1
+DA:1702,1
+DA:1719,1
+DA:1723,1
+DA:1728,1
+DA:1733,1
+DA:1740,1
+DA:1752,1
+DA:1764,1
+DA:1788,1
+DA:1799,1
+DA:1819,1
+DA:1839,1
+DA:1862,1
+DA:1888,1
+DA:1914,1
+DA:1929,1
+DA:1947,1
+DA:1954,1
+DA:1972,1
+DA:1999,1
+DA:2023,1
+DA:2047,1
+DA:2077,1
+DA:2104,1
+DA:2135,1
+DA:2164,1
+DA:2170,1
+DA:2206,1
+DA:2225,1
+DA:2236,1
+DA:2268,1
+DA:2279,1
+DA:2308,1
+DA:2337,1
+DA:2341,1
+DA:2346,1
+DA:2351,1
+DA:2363,1
+DA:2383,1
+DA:2399,1
+DA:2416,1
+DA:2434,1
+DA:2442,1
+DA:2445,1
+DA:2446,1
+DA:2456,1
+DA:2457,1
+DA:2467,1
+DA:2469,1
+DA:2483,1
+DA:2495,1
+DA:2517,1
+DA:2528,1
+DA:2554,1
+DA:2560,1
+DA:2565,1
+DA:2585,1
+DA:2586,1
+DA:2588,1
+DA:2589,1
+DA:2598,1
+DA:2600,1
+DA:2602,1
+DA:2608,1
+DA:2621,1
+DA:2625,1
+DA:2629,1
+DA:2636,1
+DA:2646,1
+DA:2656,1
+DA:2677,1
+DA:32,1
+DA:33,1
+DA:32,1
+DA:233,0
+DA:235,0
+DA:236,0
+DA:238,0
+DA:241,0
+DA:261,0
+DA:262,0
+DA:263,0
+DA:264,0
+DA:265,0
+DA:276,0
+DA:277,0
+DA:289,0
+DA:290,0
+DA:291,0
+DA:293,0
+DA:294,0
+DA:297,0
+DA:308,0
+DA:309,0
+DA:318,0
+DA:336,1
+DA:339,1
+DA:344,1
+DA:347,1
+DA:348,1
+DA:349,1
+DA:351,1
+DA:354,1
+DA:357,1
+DA:364,0
+DA:365,0
+DA:367,0
+DA:368,0
+DA:371,0
+DA:372,0
+DA:373,0
+DA:377,0
+DA:378,0
+DA:380,0
+DA:382,0
+DA:383,0
+DA:384,0
+DA:385,0
+DA:383,0
+DA:401,2
+DA:405,2
+DA:406,1
+DA:409,2
+DA:413,0
+DA:414,0
+DA:417,0
+DA:424,0
+DA:417,0
+DA:419,0
+DA:420,0
+DA:423,0
+DA:432,1
+DA:433,0
+DA:435,1
+DA:438,1
+DA:440,1
+DA:441,0
+DA:443,1
+DA:447,1
+DA:450,1
+DA:461,1
+DA:462,1
+DA:463,2
+DA:464,2
+DA:466,0
+DA:462,3
+DA:470,1
+DA:471,1
+DA:472,0
+DA:473,0
+DA:475,0
+DA:471,1
+DA:483,1
+DA:487,1
+DA:488,1
+DA:489,0
+DA:492,1
+DA:500,2
+DA:502,1
+DA:505,1
+DA:508,0
+DA:511,0
+DA:512,0
+DA:515,0
+DA:510,0
+DA:519,0
+DA:520,0
+DA:523,0
+DA:524,0
+DA:525,0
+DA:530,0
+DA:531,0
+DA:532,0
+DA:535,0
+DA:538,0
+DA:539,0
+DA:546,1
+DA:548,1
+DA:549,15
+DA:553,1
+DA:548,17
+DA:558,1
+DA:560,1
+DA:561,0
+DA:563,1
+DA:569,1
+DA:572,1
+DA:576,1
+DA:577,1
+DA:576,1
+DA:579,1
+DA:580,1
+DA:581,1
+DA:580,1
+DA:583,1
+DA:584,1
+DA:585,1
+DA:587,1
+DA:588,23
+DA:589,8
+DA:590,1
+DA:591,1
+DA:593,0
+DA:587,25
+DA:599,1
+DA:648,1
+DA:649,1
+DA:651,1
+DA:665,1
+DA:666,1
+DA:652,1
+DA:653,1
+DA:659,1
+DA:661,1
+DA:677,2
+DA:678,2
+DA:679,2
+DA:686,1
+DA:689,1
+DA:698,0
+DA:705,0
+DA:715,1
+DA:716,1
+DA:723,2
+DA:724,2
+DA:727,2
+DA:728,2
+DA:729,30
+DA:728,30
+DA:733,2
+DA:736,2
+DA:739,2
+DA:740,2
+DA:741,2
+DA:742,2
+DA:740,2
+DA:746,2
+DA:747,2
+DA:748,2
+DA:756,4
+DA:757,4
+DA:758,4
+DA:765,1
+DA:766,1
+DA:767,1
+DA:770,1
+DA:771,1
+DA:777,1
+DA:779,1
+DA:783,1
+DA:784,1
+DA:792,8
+DA:793,8
+DA:804,3
+DA:805,1
+DA:809,2
+DA:810,1
+DA:811,1
+DA:812,1
+DA:813,1
+DA:814,1
+DA:817,2
+DA:819,2
+DA:820,1
+DA:821,1
+DA:825,2
+DA:826,2
+DA:827,2
+DA:830,2
+DA:832,2
+DA:833,2
+DA:832,2
+DA:835,2
+DA:837,2
+DA:839,8
+DA:842,8
+DA:838,11
+DA:849,2
+DA:856,2
+DA:857,2
+DA:858,2
+DA:859,2
+DA:860,2
+DA:871,30
+DA:872,30
+DA:873,30
+DA:876,30
+DA:878,30
+DA:876,30
+DA:877,120
+DA:896,0
+DA:899,0
+DA:901,0
+DA:908,0
+DA:909,0
+DA:912,0
+DA:914,0
+DA:921,0
+DA:922,0
+DA:925,0
+DA:927,0
+DA:934,180
+DA:935,180
+DA:943,0
+DA:944,0
+DA:952,0
+DA:961,0
+DA:962,0
+DA:964,0
+DA:965,0
+DA:967,0
+DA:969,0
+DA:970,0
+DA:973,0
+DA:976,0
+DA:979,0
+DA:980,0
+DA:992,2
+DA:993,2
+DA:995,2
+DA:997,2
+DA:998,2
+DA:1026,0
+DA:1027,0
+DA:1036,0
+DA:1037,0
+DA:1039,0
+DA:1040,0
+DA:1041,0
+DA:1043,0
+DA:1045,0
+DA:1052,0
+DA:1053,0
+DA:1054,0
+DA:1055,0
+DA:1064,2
+DA:1072,0
+DA:1073,0
+DA:1074,0
+DA:1075,0
+DA:1085,12
+DA:1094,0
+DA:1096,0
+DA:1097,0
+DA:1098,0
+DA:1100,0
+DA:1101,0
+DA:1106,0
+DA:1107,0
+DA:1109,0
+DA:1110,0
+DA:1111,0
+DA:1108,0
+DA:1113,0
+DA:1117,0
+DA:1118,0
+DA:1119,0
+DA:1120,0
+DA:1122,0
+DA:1129,2
+DA:1130,0
+DA:1132,0
+DA:1134,0
+DA:1135,0
+DA:1137,0
+DA:1139,0
+DA:1140,0
+DA:1149,2
+DA:1151,2
+DA:1152,2
+DA:1153,2
+DA:1154,0
+DA:1155,0
+DA:1156,2
+DA:1158,2
+DA:1159,2
+DA:1160,2
+DA:1161,2
+DA:1163,2
+DA:1164,2
+DA:1165,2
+DA:1166,2
+DA:1171,2
+DA:1173,2
+DA:1174,0
+DA:1175,0
+DA:1176,0
+DA:1179,0
+DA:1180,0
+DA:1181,0
+DA:1182,0
+DA:1185,2
+DA:1186,0
+DA:1189,2
+DA:1191,2
+DA:1200,1
+DA:1202,0
+DA:1205,1
+DA:1214,3
+DA:1215,3
+DA:1224,2
+DA:1225,0
+DA:1227,2
+DA:1228,2
+DA:1229,0
+DA:1232,2
+DA:1233,2
+DA:1235,2
+DA:1236,2
+DA:1237,2
+DA:1239,2
+DA:1240,0
+DA:1241,0
+DA:1247,4
+DA:1250,4
+DA:1248,0
+DA:1251,0
+DA:1260,2
+DA:1261,2
+DA:1262,2
+DA:1266,2
+DA:1267,2
+DA:1268,2
+DA:1269,2
+DA:1276,0
+DA:1277,0
+DA:1278,0
+DA:1285,0
+DA:1286,0
+DA:1287,0
+DA:1290,0
+DA:1292,0
+DA:1293,0
+DA:1292,0
+DA:1297,0
+DA:1298,0
+DA:1299,0
+DA:1300,0
+DA:1302,0
+DA:1304,0
+DA:1305,0
+DA:1306,0
+DA:1307,0
+DA:1309,0
+DA:1310,0
+DA:1311,0
+DA:1312,0
+DA:1313,0
+DA:1314,0
+DA:1315,0
+DA:1317,0
+DA:1325,0
+DA:1326,0
+DA:1327,0
+DA:1328,0
+DA:1331,0
+DA:1332,0
+DA:1334,0
+DA:1335,0
+DA:1336,0
+DA:1340,0
+DA:1341,0
+DA:1343,0
+DA:1344,0
+DA:1347,0
+DA:1348,0
+DA:1349,0
+DA:1353,0
+DA:1354,0
+DA:1356,0
+DA:1357,0
+DA:1359,0
+DA:1360,0
+DA:1361,0
+DA:1362,0
+DA:1364,0
+DA:1365,0
+DA:1366,0
+DA:1368,0
+DA:1369,0
+DA:1371,0
+DA:1373,0
+DA:1378,0
+DA:1379,0
+DA:1387,0
+DA:1389,0
+DA:1390,0
+DA:1393,0
+DA:1396,0
+DA:1433,0
+DA:1436,0
+DA:1437,0
+DA:1438,0
+DA:1439,0
+DA:1440,0
+DA:1439,0
+DA:1442,0
+DA:1444,0
+DA:1447,0
+DA:1448,0
+DA:1449,0
+DA:1450,0
+DA:1453,0
+DA:1454,0
+DA:1455,0
+DA:1457,0
+DA:1467,0
+DA:1470,0
+DA:1473,0
+DA:1474,0
+DA:1477,0
+DA:1478,0
+DA:1481,0
+DA:1490,0
+DA:1491,0
+DA:1492,0
+DA:1491,0
+DA:1495,0
+DA:1497,0
+DA:1506,0
+DA:1510,0
+DA:1511,0
+DA:1516,0
+DA:1525,0
+DA:1527,0
+DA:1528,0
+DA:1529,0
+DA:1530,0
+DA:1531,0
+DA:1532,0
+DA:1533,0
+DA:1537,0
+DA:1538,0
+DA:1539,0
+DA:1540,0
+DA:1541,0
+DA:1545,0
+DA:1552,0
+DA:1556,0
+DA:1557,0
+DA:1558,0
+DA:1561,0
+DA:1562,0
+DA:1563,0
+DA:1586,0
+DA:1595,0
+DA:1596,0
+DA:1599,0
+DA:1600,0
+DA:1612,0
+DA:1613,0
+DA:1616,0
+DA:1618,0
+DA:1621,0
+DA:1629,0
+DA:1632,0
+DA:1637,0
+DA:1633,0
+DA:1646,0
+DA:1647,0
+DA:1649,0
+DA:1651,0
+DA:1652,0
+DA:1653,0
+DA:1654,0
+DA:1655,0
+DA:1657,0
+DA:1660,0
+DA:1676,0
+DA:1671,0
+DA:1672,0
+DA:1674,0
+DA:1681,0
+DA:1682,0
+DA:1685,0
+DA:1677,0
+DA:1678,0
+DA:1692,0
+DA:1693,0
+DA:1694,0
+DA:1703,0
+DA:1706,0
+DA:1707,0
+DA:1709,0
+DA:1734,1
+DA:1741,0
+DA:1743,0
+DA:1744,0
+DA:1745,0
+DA:1746,0
+DA:1753,0
+DA:1755,0
+DA:1756,0
+DA:1757,0
+DA:1758,0
+DA:1765,0
+DA:1767,0
+DA:1770,0
+DA:1773,0
+DA:1776,0
+DA:1779,0
+DA:1789,0
+DA:1790,0
+DA:1791,0
+DA:1802,0
+DA:1803,0
+DA:1806,0
+DA:1810,0
+DA:1811,0
+DA:1821,0
+DA:1825,0
+DA:1829,0
+DA:1832,0
+DA:1840,0
+DA:1841,0
+DA:1843,0
+DA:1844,0
+DA:1848,0
+DA:1849,0
+DA:1852,0
+DA:1853,0
+DA:1854,0
+DA:1864,0
+DA:1866,0
+DA:1867,0
+DA:1869,0
+DA:1871,0
+DA:1873,0
+DA:1875,0
+DA:1877,0
+DA:1879,0
+DA:1891,0
+DA:1892,0
+DA:1894,0
+DA:1895,0
+DA:1898,0
+DA:1899,0
+DA:1902,0
+DA:1903,0
+DA:1898,0
+DA:1907,0
+DA:1915,0
+DA:1916,0
+DA:1918,0
+DA:1919,0
+DA:1930,0
+DA:1931,0
+DA:1934,0
+DA:1935,0
+DA:1934,0
+DA:1937,0
+DA:1955,0
+DA:1958,0
+DA:1962,0
+DA:1964,0
+DA:1973,0
+DA:1974,0
+DA:1977,0
+DA:1978,0
+DA:1979,0
+DA:1982,0
+DA:1983,0
+DA:1984,0
+DA:1988,0
+DA:2002,0
+DA:2005,0
+DA:2008,0
+DA:2005,0
+DA:2012,0
+DA:2013,0
+DA:2006,0
+DA:2007,0
+DA:2024,0
+DA:2028,0
+DA:2030,0
+DA:2032,0
+DA:2033,0
+DA:2035,0
+DA:2038,0
+DA:2048,0
+DA:2049,0
+DA:2052,0
+DA:2053,0
+DA:2057,0
+DA:2056,0
+DA:2059,0
+DA:2063,0
+DA:2062,0
+DA:2066,0
+DA:2081,0
+DA:2082,0
+DA:2084,0
+DA:2087,0
+DA:2088,0
+DA:2090,0
+DA:2093,0
+DA:2107,0
+DA:2109,0
+DA:2110,0
+DA:2111,0
+DA:2115,0
+DA:2116,0
+DA:2117,0
+DA:2118,0
+DA:2115,0
+DA:2123,0
+DA:2137,0
+DA:2138,0
+DA:2143,0
+DA:2145,0
+DA:2146,0
+DA:2148,0
+DA:2151,0
+DA:2152,0
+DA:2143,0
+DA:2155,0
+DA:2171,0
+DA:2174,0
+DA:2177,0
+DA:2180,0
+DA:2185,0
+DA:2189,0
+DA:2191,0
+DA:2194,0
+DA:2207,0
+DA:2210,0
+DA:2216,0
+DA:2211,0
+DA:2212,0
+DA:2217,0
+DA:2226,0
+DA:2227,0
+DA:2228,0
+DA:2237,0
+DA:2242,0
+DA:2244,0
+DA:2260,0
+DA:2244,0
+DA:2245,0
+DA:2246,0
+DA:2249,0
+DA:2250,0
+DA:2253,0
+DA:2254,0
+DA:2257,0
+DA:2258,0
+DA:2269,0
+DA:2270,0
+DA:2280,0
+DA:2283,0
+DA:2300,0
+DA:2285,0
+DA:2286,0
+DA:2288,0
+DA:2290,0
+DA:2291,0
+DA:2294,0
+DA:2295,0
+DA:2309,0
+DA:2312,0
+DA:2328,0
+DA:2312,0
+DA:2328,0
+DA:2312,0
+DA:2313,0
+DA:2314,0
+DA:2316,0
+DA:2318,0
+DA:2321,0
+DA:2325,0
+DA:2326,0
+DA:2352,1
+DA:2353,1
+DA:2354,1
+DA:2355,1
+DA:2356,1
+DA:2364,0
+DA:2365,0
+DA:2367,0
+DA:2368,0
+DA:2369,0
+DA:2370,0
+DA:2371,0
+DA:2374,0
+DA:2375,0
+DA:2376,0
+DA:2377,0
+DA:2384,0
+DA:2385,0
+DA:2387,0
+DA:2388,0
+DA:2389,0
+DA:2390,0
+DA:2391,0
+DA:2392,0
+DA:2400,0
+DA:2402,0
+DA:2405,0
+DA:2408,0
+DA:2417,0
+DA:2418,0
+DA:2420,0
+DA:2421,0
+DA:2423,0
+DA:2424,0
+DA:2427,0
+DA:2428,0
+DA:2435,0
+DA:2438,0
+DA:2435,0
+DA:2436,0
+DA:2437,0
+DA:2447,1
+DA:2448,1
+DA:2447,1
+DA:2449,1
+DA:2450,1
+DA:2451,1
+DA:2470,1
+DA:2471,7
+DA:2470,8
+DA:2474,1
+DA:2475,1
+DA:2476,1
+DA:2477,1
+DA:2478,1
+DA:2480,1
+DA:2484,0
+DA:2488,0
+DA:2489,0
+DA:2490,0
+DA:2491,0
+DA:2492,0
+DA:2485,0
+DA:2486,0
+DA:2496,0
+DA:2497,0
+DA:2500,0
+DA:2501,0
+DA:2502,0
+DA:2503,0
+DA:2504,0
+DA:2507,0
+DA:2504,0
+DA:2509,0
+DA:2510,0
+DA:2514,0
+DA:2506,0
+DA:2518,0
+DA:2520,0
+DA:2523,0
+DA:2529,0
+DA:2530,0
+DA:2531,0
+DA:2534,0
+DA:2536,0
+DA:2539,0
+DA:2540,0
+DA:2542,0
+DA:2546,0
+DA:2549,0
+DA:2555,0
+DA:2556,0
+DA:2561,0
+DA:2562,0
+DA:2566,1
+DA:2567,1
+DA:2568,1
+DA:2577,1
+DA:2579,1
+DA:2569,3
+DA:2570,3
+DA:2571,1
+DA:2574,2
+DA:2603,1
+DA:2604,6
+DA:2603,7
+DA:2610,0
+DA:2612,0
+DA:2617,0
+DA:2618,0
+DA:2617,0
+DA:2613,0
+DA:2622,0
+DA:2626,0
+DA:2630,0
+DA:2631,0
+DA:2632,0
+DA:2637,0
+DA:2638,0
+DA:2639,0
+DA:2640,0
+DA:2641,0
+DA:2642,0
+DA:2639,0
+DA:2647,1
+DA:2648,0
+DA:2650,1
+DA:2651,0
+DA:2652,0
+DA:2657,0
+DA:2658,0
+DA:2660,0
+DA:2662,0
+DA:2664,0
+DA:2667,0
+DA:2670,0
+DA:2671,0
+DA:37,3
+DA:38,3
+DA:39,3
+DA:41,0
+DA:45,0
+DA:68,0
+DA:69,0
+DA:70,0
+DA:71,0
+DA:75,0
+DA:76,0
+DA:75,0
+DA:78,0
+DA:87,0
+DA:88,0
+DA:97,0
+DA:99,0
+DA:101,0
+DA:102,0
+DA:112,0
+DA:121,0
+DA:130,0
+DA:139,0
+DA:140,0
+DA:142,0
+DA:143,0
+DA:151,0
+DA:152,0
+DA:154,0
+DA:155,0
+DA:156,0
+DA:157,0
+DA:159,0
+DA:160,0
+DA:168,0
+DA:169,0
+DA:171,0
+DA:172,0
+DA:173,0
+DA:191,0
+DA:185,0
+DA:186,0
+DA:187,0
+DA:189,0
+DA:200,0
+DA:201,0
+DA:204,0
+DA:205,0
+DA:206,0
+DA:208,0
+DA:209,0
+DA:192,0
+DA:193,0
+DA:195,0
+DA:196,0
+DA:222,0
+DA:223,0
+DA:224,0
+DA:225,0
+DA:227,0
+DA:244,0
+DA:227,0
+DA:246,0
+DA:247,0
+DA:229,0
+DA:230,0
+DA:232,0
+LF:1146
+LH:478
+end_of_record
diff --git a/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py b/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py
new file mode 100644
index 0000000000..9009300951
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py
@@ -0,0 +1,444 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import os
+import shutil
+import unittest
+from io import StringIO
+from tempfile import NamedTemporaryFile
+
+import buildconfig
+import mozunit
+
+from mozbuild.codecoverage import chrome_map, lcov_rewriter
+
+here = os.path.dirname(__file__)
+
+BUILDCONFIG = {
+ "topobjdir": buildconfig.topobjdir,
+ "MOZ_APP_NAME": buildconfig.substs.get("MOZ_APP_NAME", "nightly"),
+ "OMNIJAR_NAME": buildconfig.substs.get("OMNIJAR_NAME", "omni.ja"),
+ "MOZ_MACBUNDLE_NAME": buildconfig.substs.get("MOZ_MACBUNDLE_NAME", "Nightly.app"),
+}
+
+basic_file = """TN:Compartment_5f7f5c30251800
+SF:resource://gre/modules/osfile.jsm
+FN:1,top-level
+FNDA:1,top-level
+FNF:1
+FNH:1
+BRDA:9,0,61,1
+BRF:1
+BRH:1
+DA:9,1
+DA:24,1
+LF:2
+LH:2
+end_of_record
+"""
+
+# These line numbers are (synthetically) sorted.
+multiple_records = """SF:resource://gre/modules/workers/require.js
+FN:1,top-level
+FN:80,.get
+FN:95,require
+FNDA:1,top-level
+FNF:3
+FNH:1
+BRDA:46,0,16,-
+BRDA:135,225,446,-
+BRF:2
+BRH:0
+DA:43,1
+DA:46,1
+DA:152,0
+DA:163,1
+LF:4
+LH:3
+end_of_record
+SF:resource://gre/modules/osfile/osfile_async_worker.js
+FN:12,top-level
+FN:30,worker.dispatch
+FN:34,worker.postMessage
+FN:387,do_close
+FN:392,exists
+FN:394,do_exists
+FN:400,unixSymLink
+FNDA:1,do_exists
+FNDA:1,exists
+FNDA:1,top-level
+FNDA:594,worker.dispatch
+FNF:7
+FNH:4
+BRDA:6,0,30,1
+BRDA:365,0,103,-
+BRF:2
+BRH:1
+DA:6,1
+DA:7,0
+DA:12,1
+DA:18,1
+DA:19,1
+DA:20,1
+DA:23,1
+DA:25,1
+DA:401,0
+DA:407,1
+LF:10
+LH:8
+end_of_record
+"""
+
+fn_with_multiple_commas = """TN:Compartment_5f7f5c30251800
+SF:resource://gre/modules/osfile.jsm
+FN:1,function,name,with,commas
+FNDA:1,function,name,with,commas
+FNF:1
+FNH:1
+BRDA:9,0,61,1
+BRF:1
+BRH:1
+DA:9,1
+DA:24,1
+LF:2
+LH:2
+end_of_record
+"""
+
+
+class TempFile:
+ def __init__(self, content):
+ self.file = NamedTemporaryFile(mode="w", delete=False, encoding="utf-8")
+ self.file.write(content)
+ self.file.close()
+
+ def __enter__(self):
+ return self.file.name
+
+ def __exit__(self, *args):
+ os.remove(self.file.name)
+
+
+class TestLcovParser(unittest.TestCase):
+ def parser_roundtrip(self, lcov_string):
+ with TempFile(lcov_string) as fname:
+ file_obj = lcov_rewriter.LcovFile([fname])
+ out = StringIO()
+ file_obj.print_file(out, lambda s: (s, None), lambda x, pp: x)
+
+ return out.getvalue()
+
+ def test_basic_parse(self):
+ output = self.parser_roundtrip(basic_file)
+ self.assertEqual(basic_file, output)
+
+ output = self.parser_roundtrip(multiple_records)
+ self.assertEqual(multiple_records, output)
+
+ def test_multiple_commas(self):
+ output = self.parser_roundtrip(fn_with_multiple_commas)
+ self.assertEqual(fn_with_multiple_commas, output)
+
+
+multiple_included_files = """//@line 1 "/src/dir/foo.js"
+bazfoobar
+//@line 2 "/src/dir/path/bar.js"
+@foo@
+//@line 3 "/src/dir/foo.js"
+bazbarfoo
+//@line 2 "/src/dir/path/bar.js"
+foobarbaz
+//@line 3 "/src/dir/path2/test.js"
+barfoobaz
+//@line 1 "/src/dir/path/baz.js"
+baz
+//@line 6 "/src/dir/f.js"
+fin
+"""
+
+srcdir_prefix_files = """//@line 1 "/src/dir/foo.js"
+bazfoobar
+//@line 2 "$SRCDIR/path/file.js"
+@foo@
+//@line 3 "/src/dir/foo.js"
+bazbarfoo
+"""
+
+
+class TestLineRemapping(unittest.TestCase):
+ def setUp(self):
+ chrome_map_file = os.path.join(buildconfig.topobjdir, "chrome-map.json")
+ self._old_chrome_info_file = None
+ if os.path.isfile(chrome_map_file):
+ backup_file = os.path.join(buildconfig.topobjdir, "chrome-map-backup.json")
+ self._old_chrome_info_file = backup_file
+ self._chrome_map_file = chrome_map_file
+ shutil.move(chrome_map_file, backup_file)
+
+ empty_chrome_info = [
+ {},
+ {},
+ {},
+ BUILDCONFIG,
+ ]
+ with open(chrome_map_file, "w") as fh:
+ json.dump(empty_chrome_info, fh)
+
+ self.lcov_rewriter = lcov_rewriter.LcovFileRewriter(chrome_map_file, "", "", [])
+ self.pp_rewriter = self.lcov_rewriter.pp_rewriter
+
+ def tearDown(self):
+ if self._old_chrome_info_file:
+ shutil.move(self._old_chrome_info_file, self._chrome_map_file)
+
+ def test_map_multiple_included(self):
+ with TempFile(multiple_included_files) as fname:
+ actual = chrome_map.generate_pp_info(fname, "/src/dir")
+ expected = {
+ "2,3": ("foo.js", 1),
+ "4,5": ("path/bar.js", 2),
+ "6,7": ("foo.js", 3),
+ "8,9": ("path/bar.js", 2),
+ "10,11": ("path2/test.js", 3),
+ "12,13": ("path/baz.js", 1),
+ "14,15": ("f.js", 6),
+ }
+
+ self.assertEqual(actual, expected)
+
+ def test_map_srcdir_prefix(self):
+ with TempFile(srcdir_prefix_files) as fname:
+ actual = chrome_map.generate_pp_info(fname, "/src/dir")
+ expected = {
+ "2,3": ("foo.js", 1),
+ "4,5": ("path/file.js", 2),
+ "6,7": ("foo.js", 3),
+ }
+
+ self.assertEqual(actual, expected)
+
+ def test_remap_lcov(self):
+ pp_remap = {
+ "1941,2158": ("dropPreview.js", 6),
+ "2159,2331": ("updater.js", 6),
+ "2584,2674": ("intro.js", 6),
+ "2332,2443": ("undo.js", 6),
+ "864,985": ("cells.js", 6),
+ "2444,2454": ("search.js", 6),
+ "1567,1712": ("drop.js", 6),
+ "2455,2583": ("customize.js", 6),
+ "1713,1940": ("dropTargetShim.js", 6),
+ "1402,1548": ("drag.js", 6),
+ "1549,1566": ("dragDataHelper.js", 6),
+ "453,602": ("page.js", 141),
+ "2675,2678": ("newTab.js", 70),
+ "56,321": ("transformations.js", 6),
+ "603,863": ("grid.js", 6),
+ "322,452": ("page.js", 6),
+ "986,1401": ("sites.js", 6),
+ }
+
+ fpath = os.path.join(here, "sample_lcov.info")
+
+ # Read original records
+ lcov_file = lcov_rewriter.LcovFile([fpath])
+ records = [lcov_file.parse_record(r) for _, _, r in lcov_file.iterate_records()]
+
+ # This summarization changes values due multiple reports per line coming
+ # from the JS engine (bug 1198356).
+ for r in records:
+ r.resummarize()
+ original_line_count = r.line_count
+ original_covered_line_count = r.covered_line_count
+ original_function_count = r.function_count
+ original_covered_function_count = r.covered_function_count
+
+ self.assertEqual(len(records), 1)
+
+ # Rewrite preprocessed entries.
+ lcov_file = lcov_rewriter.LcovFile([fpath])
+ r_num = []
+
+ def rewrite_source(s):
+ r_num.append(1)
+ return s, pp_remap
+
+ out = StringIO()
+ lcov_file.print_file(out, rewrite_source, self.pp_rewriter.rewrite_record)
+ self.assertEqual(len(r_num), 1)
+
+ # Read rewritten lcov.
+ with TempFile(out.getvalue()) as fname:
+ lcov_file = lcov_rewriter.LcovFile([fname])
+ records = [
+ lcov_file.parse_record(r) for _, _, r in lcov_file.iterate_records()
+ ]
+
+ self.assertEqual(len(records), 17)
+
+ # Lines/functions are only "moved" between records, not duplicated or omited.
+ self.assertEqual(original_line_count, sum(r.line_count for r in records))
+ self.assertEqual(
+ original_covered_line_count, sum(r.covered_line_count for r in records)
+ )
+ self.assertEqual(
+ original_function_count, sum(r.function_count for r in records)
+ )
+ self.assertEqual(
+ original_covered_function_count,
+ sum(r.covered_function_count for r in records),
+ )
+
+
+class TestUrlFinder(unittest.TestCase):
+ def setUp(self):
+ chrome_map_file = os.path.join(buildconfig.topobjdir, "chrome-map.json")
+ self._old_chrome_info_file = None
+ if os.path.isfile(chrome_map_file):
+ backup_file = os.path.join(buildconfig.topobjdir, "chrome-map-backup.json")
+ self._old_chrome_info_file = backup_file
+ self._chrome_map_file = chrome_map_file
+ shutil.move(chrome_map_file, backup_file)
+
+ dummy_chrome_info = [
+ {
+ "resource://activity-stream/": [
+ "dist/bin/browser/chrome/browser/res/activity-stream",
+ ],
+ "chrome://browser/content/": [
+ "dist/bin/browser/chrome/browser/content/browser",
+ ],
+ },
+ {
+ "chrome://global/content/license.html": "chrome://browser/content/license.html",
+ },
+ {
+ "dist/bin/components/MainProcessSingleton.js": ["path1", None],
+ "dist/bin/browser/features/firefox@getpocket.com/bootstrap.js": [
+ "path4",
+ None,
+ ],
+ "dist/bin/modules/osfile/osfile_async_worker.js": [
+ "toolkit/components/osfile/modules/osfile_async_worker.js",
+ None,
+ ],
+ "dist/bin/browser/chrome/browser/res/activity-stream/lib/": [
+ "browser/components/newtab/lib/*",
+ None,
+ ],
+ "dist/bin/browser/chrome/browser/content/browser/license.html": [
+ "browser/base/content/license.html",
+ None,
+ ],
+ "dist/bin/modules/AppConstants.sys.mjs": [
+ "toolkit/modules/AppConstants.sys.mjs",
+ {
+ "101,102": ["toolkit/modules/AppConstants.sys.mjs", 135],
+ },
+ ],
+ },
+ BUILDCONFIG,
+ ]
+ with open(chrome_map_file, "w") as fh:
+ json.dump(dummy_chrome_info, fh)
+
+ def tearDown(self):
+ if self._old_chrome_info_file:
+ shutil.move(self._old_chrome_info_file, self._chrome_map_file)
+
+ def test_jar_paths(self):
+ app_name = BUILDCONFIG["MOZ_APP_NAME"]
+ omnijar_name = BUILDCONFIG["OMNIJAR_NAME"]
+
+ paths = [
+ (
+ "jar:file:///home/worker/workspace/build/application/"
+ + app_name
+ + "/"
+ + omnijar_name
+ + "!/components/MainProcessSingleton.js",
+ "path1",
+ ),
+ (
+ "jar:file:///home/worker/workspace/build/application/"
+ + app_name
+ + "/browser/features/firefox@getpocket.com.xpi!/bootstrap.js",
+ "path4",
+ ),
+ ]
+
+ url_finder = lcov_rewriter.UrlFinder(self._chrome_map_file, "", "", [])
+ for path, expected in paths:
+ self.assertEqual(url_finder.rewrite_url(path)[0], expected)
+
+ def test_wrong_scheme_paths(self):
+ paths = [
+ "http://www.mozilla.org/aFile.js",
+ "https://www.mozilla.org/aFile.js",
+ "data:something",
+ "about:newtab",
+ "javascript:something",
+ ]
+
+ url_finder = lcov_rewriter.UrlFinder(self._chrome_map_file, "", "", [])
+ for path in paths:
+ self.assertIsNone(url_finder.rewrite_url(path))
+
+ def test_chrome_resource_paths(self):
+ paths = [
+ # Path with default url prefix
+ (
+ "resource://gre/modules/osfile/osfile_async_worker.js",
+ ("toolkit/components/osfile/modules/osfile_async_worker.js", None),
+ ),
+ # Path with url prefix that is in chrome map
+ (
+ "resource://activity-stream/lib/PrefsFeed.jsm",
+ ("browser/components/newtab/lib/PrefsFeed.jsm", None),
+ ),
+ # Path which is in url overrides
+ (
+ "chrome://global/content/license.html",
+ ("browser/base/content/license.html", None),
+ ),
+ # Path which ends with > eval
+ (
+ "resource://gre/modules/osfile/osfile_async_worker.js line 3 > eval",
+ None,
+ ),
+ # Path which ends with > Function
+ (
+ "resource://gre/modules/osfile/osfile_async_worker.js line 3 > Function",
+ None,
+ ),
+ # Path which contains "->"
+ (
+ "resource://gre/modules/addons/XPIProvider.jsm -> resource://gre/modules/osfile/osfile_async_worker.js", # noqa
+ ("toolkit/components/osfile/modules/osfile_async_worker.js", None),
+ ),
+ # Path with pp_info
+ (
+ "resource://gre/modules/AppConstants.sys.mjs",
+ (
+ "toolkit/modules/AppConstants.sys.mjs",
+ {
+ "101,102": ["toolkit/modules/AppConstants.sys.mjs", 135],
+ },
+ ),
+ ),
+ # Path with query
+ (
+ "resource://activity-stream/lib/PrefsFeed.jsm?q=0.9098419174803978",
+ ("browser/components/newtab/lib/PrefsFeed.jsm", None),
+ ),
+ ]
+
+ url_finder = lcov_rewriter.UrlFinder(self._chrome_map_file, "", "dist/bin/", [])
+ for path, expected in paths:
+ self.assertEqual(url_finder.rewrite_url(path), expected)
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/common.py b/python/mozbuild/mozbuild/test/common.py
new file mode 100644
index 0000000000..47f04a8dd3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/common.py
@@ -0,0 +1,69 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import errno
+import os
+import shutil
+
+import mozpack.path as mozpath
+from buildconfig import topsrcdir
+from mach.logging import LoggingManager
+
+from mozbuild.util import ReadOnlyDict
+
+# By including this module, tests get structured logging.
+log_manager = LoggingManager()
+log_manager.add_terminal_logging()
+
+
+def prepare_tmp_topsrcdir(path):
+ for p in (
+ "build/autoconf/config.guess",
+ "build/autoconf/config.sub",
+ "build/moz.configure/checks.configure",
+ "build/moz.configure/init.configure",
+ "build/moz.configure/util.configure",
+ ):
+ file_path = os.path.join(path, p)
+ try:
+ os.makedirs(os.path.dirname(file_path))
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+ shutil.copy(os.path.join(topsrcdir, p), file_path)
+
+
+# mozconfig is not a reusable type (it's actually a module) so, we
+# have to mock it.
+class MockConfig(object):
+ def __init__(
+ self,
+ topsrcdir="/path/to/topsrcdir",
+ extra_substs={},
+ error_is_fatal=True,
+ ):
+ self.topsrcdir = mozpath.abspath(topsrcdir)
+ self.topobjdir = mozpath.abspath("/path/to/topobjdir")
+
+ self.substs = ReadOnlyDict(
+ {
+ "MOZ_FOO": "foo",
+ "MOZ_BAR": "bar",
+ "MOZ_TRUE": "1",
+ "MOZ_FALSE": "",
+ "DLL_PREFIX": "lib",
+ "DLL_SUFFIX": ".so",
+ },
+ **extra_substs
+ )
+
+ self.defines = self.substs
+
+ self.lib_prefix = "lib"
+ self.lib_suffix = ".a"
+ self.import_prefix = "lib"
+ self.import_suffix = ".so"
+ self.dll_prefix = "lib"
+ self.dll_suffix = ".so"
+ self.error_is_fatal = error_is_fatal
diff --git a/python/mozbuild/mozbuild/test/compilation/__init__.py b/python/mozbuild/mozbuild/test/compilation/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/compilation/__init__.py
diff --git a/python/mozbuild/mozbuild/test/compilation/test_warnings.py b/python/mozbuild/mozbuild/test/compilation/test_warnings.py
new file mode 100644
index 0000000000..1769e2e333
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/compilation/test_warnings.py
@@ -0,0 +1,240 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+
+from mozfile.mozfile import NamedTemporaryFile
+from mozunit import main
+
+from mozbuild.compilation.warnings import (
+ CompilerWarning,
+ WarningsCollector,
+ WarningsDatabase,
+)
+
+CLANG_TESTS = [
+ (
+ "foobar.cpp:123:10: warning: you messed up [-Wfoo]",
+ "foobar.cpp",
+ 123,
+ 10,
+ "warning",
+ "you messed up",
+ "-Wfoo",
+ ),
+ (
+ "c_locale_dummy.c:457:1: error: (near initialization for "
+ "'full_wmonthname[0]') [clang-diagnostic-error]",
+ "c_locale_dummy.c",
+ 457,
+ 1,
+ "error",
+ "(near initialization for 'full_wmonthname[0]')",
+ "clang-diagnostic-error",
+ ),
+]
+
+CURRENT_LINE = 1
+
+
+def get_warning():
+ global CURRENT_LINE
+
+ w = CompilerWarning()
+ w["filename"] = "/foo/bar/baz.cpp"
+ w["line"] = CURRENT_LINE
+ w["column"] = 12
+ w["message"] = "This is irrelevant"
+
+ CURRENT_LINE += 1
+
+ return w
+
+
+class TestCompilerWarning(unittest.TestCase):
+ def test_equivalence(self):
+ w1 = CompilerWarning()
+ w2 = CompilerWarning()
+
+ s = set()
+
+ # Empty warnings should be equal.
+ self.assertEqual(w1, w2)
+
+ s.add(w1)
+ s.add(w2)
+
+ self.assertEqual(len(s), 1)
+
+ w1["filename"] = "/foo.c"
+ w2["filename"] = "/bar.c"
+
+ self.assertNotEqual(w1, w2)
+
+ s = set()
+ s.add(w1)
+ s.add(w2)
+
+ self.assertEqual(len(s), 2)
+
+ w1["filename"] = "/foo.c"
+ w1["line"] = 5
+ w2["line"] = 5
+
+ w2["filename"] = "/foo.c"
+ w1["column"] = 3
+ w2["column"] = 3
+
+ self.assertEqual(w1, w2)
+
+ def test_comparison(self):
+ w1 = CompilerWarning()
+ w2 = CompilerWarning()
+
+ w1["filename"] = "/aaa.c"
+ w1["line"] = 5
+ w1["column"] = 5
+
+ w2["filename"] = "/bbb.c"
+ w2["line"] = 5
+ w2["column"] = 5
+
+ self.assertLess(w1, w2)
+ self.assertGreater(w2, w1)
+ self.assertGreaterEqual(w2, w1)
+
+ w2["filename"] = "/aaa.c"
+ w2["line"] = 4
+ w2["column"] = 6
+
+ self.assertLess(w2, w1)
+ self.assertGreater(w1, w2)
+ self.assertGreaterEqual(w1, w2)
+
+ w2["filename"] = "/aaa.c"
+ w2["line"] = 5
+ w2["column"] = 10
+
+ self.assertLess(w1, w2)
+ self.assertGreater(w2, w1)
+ self.assertGreaterEqual(w2, w1)
+
+ w2["filename"] = "/aaa.c"
+ w2["line"] = 5
+ w2["column"] = 5
+
+ self.assertLessEqual(w1, w2)
+ self.assertLessEqual(w2, w1)
+ self.assertGreaterEqual(w2, w1)
+ self.assertGreaterEqual(w1, w2)
+
+
+class TestWarningsAndErrorsParsing(unittest.TestCase):
+ def test_clang_parsing(self):
+ for source, filename, line, column, diag_type, message, flag in CLANG_TESTS:
+ collector = WarningsCollector(lambda w: None)
+ warning = collector.process_line(source)
+
+ self.assertIsNotNone(warning)
+
+ self.assertEqual(warning["filename"], filename)
+ self.assertEqual(warning["line"], line)
+ self.assertEqual(warning["column"], column)
+ self.assertEqual(warning["type"], diag_type)
+ self.assertEqual(warning["message"], message)
+ self.assertEqual(warning["flag"], flag)
+
+
+class TestWarningsDatabase(unittest.TestCase):
+ def test_basic(self):
+ db = WarningsDatabase()
+
+ self.assertEqual(len(db), 0)
+
+ for i in range(10):
+ db.insert(get_warning(), compute_hash=False)
+
+ self.assertEqual(len(db), 10)
+
+ warnings = list(db)
+ self.assertEqual(len(warnings), 10)
+
+ def test_hashing(self):
+ """Ensure that hashing files on insert works."""
+ db = WarningsDatabase()
+
+ temp = NamedTemporaryFile(mode="wt")
+ temp.write("x" * 100)
+ temp.flush()
+
+ w = CompilerWarning()
+ w["filename"] = temp.name
+ w["line"] = 1
+ w["column"] = 4
+ w["message"] = "foo bar"
+
+ # Should not throw.
+ db.insert(w)
+
+ w["filename"] = "DOES_NOT_EXIST"
+
+ with self.assertRaises(Exception):
+ db.insert(w)
+
+ def test_pruning(self):
+ """Ensure old warnings are removed from database appropriately."""
+ db = WarningsDatabase()
+
+ source_files = []
+ for i in range(1, 21):
+ temp = NamedTemporaryFile(mode="wt")
+ temp.write("x" * (100 * i))
+ temp.flush()
+
+ # Keep reference so it doesn't get GC'd and deleted.
+ source_files.append(temp)
+
+ w = CompilerWarning()
+ w["filename"] = temp.name
+ w["line"] = 1
+ w["column"] = i * 10
+ w["message"] = "irrelevant"
+
+ db.insert(w)
+
+ self.assertEqual(len(db), 20)
+
+ # If we change a source file, inserting a new warning should nuke the
+ # old one.
+ source_files[0].write("extra")
+ source_files[0].flush()
+
+ w = CompilerWarning()
+ w["filename"] = source_files[0].name
+ w["line"] = 1
+ w["column"] = 50
+ w["message"] = "replaced"
+
+ db.insert(w)
+
+ self.assertEqual(len(db), 20)
+
+ warnings = list(db.warnings_for_file(source_files[0].name))
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(warnings[0]["column"], w["column"])
+
+ # If we delete the source file, calling prune should cause the warnings
+ # to go away.
+ old_filename = source_files[0].name
+ del source_files[0]
+
+ self.assertFalse(os.path.exists(old_filename))
+
+ db.prune()
+ self.assertEqual(len(db), 19)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/common.py b/python/mozbuild/mozbuild/test/configure/common.py
new file mode 100644
index 0000000000..7dc1b85b22
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/common.py
@@ -0,0 +1,307 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import copy
+import errno
+import os
+import subprocess
+import sys
+import tempfile
+import unittest
+
+import six
+from buildconfig import topobjdir, topsrcdir
+from mozpack import path as mozpath
+from six import StringIO, string_types
+
+from mozbuild.configure import ConfigureSandbox
+from mozbuild.util import ReadOnlyNamespace, memoized_property
+
+
+def fake_short_path(path):
+ if sys.platform.startswith("win"):
+ return "/".join(
+ p.split(" ", 1)[0] + "~1" if " " in p else p for p in mozpath.split(path)
+ )
+ return path
+
+
+def ensure_exe_extension(path):
+ if sys.platform.startswith("win"):
+ return path + ".exe"
+ return path
+
+
+class ConfigureTestVFS(object):
+ def __init__(self, paths):
+ self._paths = set(mozpath.abspath(p) for p in paths)
+
+ def _real_file(self, path):
+ return mozpath.basedir(path, [topsrcdir, topobjdir, tempfile.gettempdir()])
+
+ def exists(self, path):
+ if path in self._paths:
+ return True
+ if self._real_file(path):
+ return os.path.exists(path)
+ return False
+
+ def isfile(self, path):
+ path = mozpath.abspath(path)
+ if path in self._paths:
+ return True
+ if self._real_file(path):
+ return os.path.isfile(path)
+ return False
+
+ def expanduser(self, path):
+ return os.path.expanduser(path)
+
+ def isdir(self, path):
+ path = mozpath.abspath(path)
+ if any(mozpath.basedir(mozpath.dirname(p), [path]) for p in self._paths):
+ return True
+ if self._real_file(path):
+ return os.path.isdir(path)
+ return False
+
+ def getsize(self, path):
+ if not self._real_file(path):
+ raise FileNotFoundError(path)
+ return os.path.getsize(path)
+
+
+class ConfigureTestSandbox(ConfigureSandbox):
+ """Wrapper around the ConfigureSandbox for testing purposes.
+
+ Its arguments are the same as ConfigureSandbox, except for the additional
+ `paths` argument, which is a dict where the keys are file paths and the
+ values are either None or a function that will be called when the sandbox
+ calls an implemented function from subprocess with the key as command.
+ When the command is CONFIG_SHELL, the function for the path of the script
+ that follows will be called.
+
+ The API for those functions is:
+ retcode, stdout, stderr = func(stdin, args)
+
+ This class is only meant to implement the minimal things to make
+ moz.configure testing possible. As such, it takes shortcuts.
+ """
+
+ def __init__(self, paths, config, environ, *args, **kwargs):
+ self._search_path = environ.get("PATH", "").split(os.pathsep)
+
+ self._subprocess_paths = {
+ mozpath.abspath(k): v for k, v in six.iteritems(paths) if v
+ }
+
+ paths = list(paths)
+
+ environ = copy.copy(environ)
+ if "CONFIG_SHELL" not in environ:
+ environ["CONFIG_SHELL"] = mozpath.abspath("/bin/sh")
+ self._subprocess_paths[environ["CONFIG_SHELL"]] = self.shell
+ paths.append(environ["CONFIG_SHELL"])
+ self._subprocess_paths[
+ mozpath.join(topsrcdir, "build/win32/vswhere.exe")
+ ] = self.vswhere
+
+ vfs = ConfigureTestVFS(paths)
+
+ os_path = {k: getattr(vfs, k) for k in dir(vfs) if not k.startswith("_")}
+
+ os_path.update(self.OS.path.__dict__)
+
+ os_contents = {}
+ exec("from os import *", {}, os_contents)
+ os_contents["path"] = ReadOnlyNamespace(**os_path)
+ os_contents["environ"] = dict(environ)
+ self.imported_os = ReadOnlyNamespace(**os_contents)
+
+ super(ConfigureTestSandbox, self).__init__(config, environ, *args, **kwargs)
+
+ @memoized_property
+ def _wrapped_mozfile(self):
+ return ReadOnlyNamespace(which=self.which)
+
+ @memoized_property
+ def _wrapped_os(self):
+ return self.imported_os
+
+ @memoized_property
+ def _wrapped_subprocess(self):
+ return ReadOnlyNamespace(
+ CalledProcessError=subprocess.CalledProcessError,
+ check_output=self.check_output,
+ PIPE=subprocess.PIPE,
+ STDOUT=subprocess.STDOUT,
+ Popen=self.Popen,
+ )
+
+ @memoized_property
+ def _wrapped_ctypes(self):
+ class CTypesFunc(object):
+ def __init__(self, func):
+ self._func = func
+
+ def __call__(self, *args, **kwargs):
+ return self._func(*args, **kwargs)
+
+ return ReadOnlyNamespace(
+ create_unicode_buffer=self.create_unicode_buffer,
+ windll=ReadOnlyNamespace(
+ kernel32=ReadOnlyNamespace(
+ GetShortPathNameW=CTypesFunc(self.GetShortPathNameW)
+ )
+ ),
+ wintypes=ReadOnlyNamespace(LPCWSTR=0, LPWSTR=1, DWORD=2),
+ )
+
+ @memoized_property
+ def _wrapped__winreg(self):
+ def OpenKey(*args, **kwargs):
+ raise WindowsError()
+
+ return ReadOnlyNamespace(HKEY_LOCAL_MACHINE=0, OpenKey=OpenKey)
+
+ def create_unicode_buffer(self, *args, **kwargs):
+ class Buffer(object):
+ def __init__(self):
+ self.value = ""
+
+ return Buffer()
+
+ def GetShortPathNameW(self, path_in, path_out, length):
+ path_out.value = fake_short_path(path_in)
+ return length
+
+ def which(self, command, mode=None, path=None, exts=None):
+ if isinstance(path, string_types):
+ path = path.split(os.pathsep)
+
+ for parent in path or self._search_path:
+ c = mozpath.abspath(mozpath.join(parent, command))
+ for candidate in (c, ensure_exe_extension(c)):
+ if self.imported_os.path.exists(candidate):
+ return candidate
+ return None
+
+ def Popen(self, args, stdin=None, stdout=None, stderr=None, **kargs):
+ program = self.which(args[0])
+ if not program:
+ raise OSError(errno.ENOENT, "File not found")
+
+ func = self._subprocess_paths.get(program)
+ retcode, stdout, stderr = func(stdin, args[1:])
+
+ class Process(object):
+ def communicate(self, stdin=None):
+ return stdout, stderr
+
+ def wait(self):
+ return retcode
+
+ return Process()
+
+ def check_output(self, args, **kwargs):
+ proc = self.Popen(args, **kwargs)
+ stdout, stderr = proc.communicate()
+ retcode = proc.wait()
+ if retcode:
+ raise subprocess.CalledProcessError(retcode, args, stdout)
+ return stdout
+
+ def shell(self, stdin, args):
+ script = mozpath.abspath(args[0])
+ if script in self._subprocess_paths:
+ return self._subprocess_paths[script](stdin, args[1:])
+ return 127, "", "File not found"
+
+ def vswhere(self, stdin, args):
+ return 0, "[]", ""
+
+ def get_config(self, name):
+ # Like the loop in ConfigureSandbox.run, but only execute the code
+ # associated with the given config item.
+ for func, args in self._execution_queue:
+ if (
+ func == self._resolve_and_set
+ and args[0] is self._config
+ and args[1] == name
+ ):
+ func(*args)
+ return self._config.get(name)
+
+
+class BaseConfigureTest(unittest.TestCase):
+ HOST = "x86_64-pc-linux-gnu"
+
+ def setUp(self):
+ self._cwd = os.getcwd()
+ os.chdir(topobjdir)
+
+ def tearDown(self):
+ os.chdir(self._cwd)
+
+ def config_guess(self, stdin, args):
+ return 0, self.HOST, ""
+
+ def config_sub(self, stdin, args):
+ return 0, args[0], ""
+
+ def get_sandbox(
+ self,
+ paths,
+ config,
+ args=[],
+ environ={},
+ mozconfig="",
+ out=None,
+ logger=None,
+ cls=ConfigureTestSandbox,
+ ):
+ kwargs = {}
+ if logger:
+ kwargs["logger"] = logger
+ else:
+ if not out:
+ out = StringIO()
+ kwargs["stdout"] = out
+ kwargs["stderr"] = out
+
+ if hasattr(self, "TARGET"):
+ target = ["--target=%s" % self.TARGET]
+ else:
+ target = []
+
+ if mozconfig:
+ fh, mozconfig_path = tempfile.mkstemp(text=True)
+ os.write(fh, six.ensure_binary(mozconfig))
+ os.close(fh)
+ else:
+ mozconfig_path = os.path.join(
+ os.path.dirname(__file__), "data", "empty_mozconfig"
+ )
+
+ try:
+ environ = dict(
+ environ,
+ OLD_CONFIGURE=os.path.join(topsrcdir, "old-configure"),
+ MOZCONFIG=mozconfig_path,
+ )
+
+ paths = dict(paths)
+ autoconf_dir = mozpath.join(topsrcdir, "build", "autoconf")
+ paths[mozpath.join(autoconf_dir, "config.guess")] = self.config_guess
+ paths[mozpath.join(autoconf_dir, "config.sub")] = self.config_sub
+
+ sandbox = cls(
+ paths, config, environ, ["configure"] + target + args, **kwargs
+ )
+ sandbox.include_file(os.path.join(topsrcdir, "moz.configure"))
+
+ return sandbox
+ finally:
+ if mozconfig:
+ os.remove(mozconfig_path)
diff --git a/python/mozbuild/mozbuild/test/configure/data/decorators.configure b/python/mozbuild/mozbuild/test/configure/data/decorators.configure
new file mode 100644
index 0000000000..b98eb26f3f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/decorators.configure
@@ -0,0 +1,53 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@template
+def simple_decorator(func):
+ return func
+
+
+@template
+def wrapper_decorator(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+@template
+def function_decorator(*args, **kwargs):
+ # We could return wrapper_decorator from above here, but then we wouldn't
+ # know if this works as expected because wrapper_decorator itself was
+ # modified or because the right thing happened here.
+ def wrapper_decorator(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ return wrapper_decorator
+
+
+@depends("--help")
+@simple_decorator
+def foo(help):
+ global FOO
+ FOO = 1
+
+
+@depends("--help")
+@wrapper_decorator
+def bar(help):
+ global BAR
+ BAR = 1
+
+
+@depends("--help")
+@function_decorator("a", "b", "c")
+def qux(help):
+ global QUX
+ QUX = 1
diff --git a/python/mozbuild/mozbuild/test/configure/data/empty_mozconfig b/python/mozbuild/mozbuild/test/configure/data/empty_mozconfig
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/empty_mozconfig
diff --git a/python/mozbuild/mozbuild/test/configure/data/extra.configure b/python/mozbuild/mozbuild/test/configure/data/extra.configure
new file mode 100644
index 0000000000..e54a93dbc3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/extra.configure
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--extra", help="Extra")
+
+
+@depends("--extra")
+def extra(extra):
+ return extra
+
+
+set_config("EXTRA", extra)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure
new file mode 100644
index 0000000000..f20a4a7149
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+imply_option("--enable-foo", True)
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo", "--help")
+def foo(value, help):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", ("foo", "bar"))
+
+option("--enable-bar", nargs="*", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+imply_option("--enable-baz", "BAZ")
+
+option("--enable-baz", nargs=1, help="enable baz")
+
+
+@depends("--enable-baz")
+def bar(value):
+ if value:
+ return value
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure
new file mode 100644
index 0000000000..b73be9a720
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo", "--help")
+def foo(value, help):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure
new file mode 100644
index 0000000000..9b3761c3c3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--enable-hoge", help="enable hoge")
+
+
+@depends("--enable-hoge")
+def hoge(value):
+ return value
+
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo", hoge)
+def foo(value, hoge):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure
new file mode 100644
index 0000000000..e953231f5e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo")
+def foo(value):
+ if value:
+ return False
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--disable-hoge", help="enable hoge")
+
+
+@depends("--disable-hoge")
+def hoge(value):
+ if not value:
+ return False
+
+
+imply_option("--enable-bar", hoge)
+
+
+option("--enable-bar", default=True, help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if not value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure
new file mode 100644
index 0000000000..6aa225cc45
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo")
+def foo(value):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure
new file mode 100644
index 0000000000..93198a8295
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--enable-foo", nargs="*", help="enable foo")
+
+
+@depends("--enable-foo")
+def foo(value):
+ if value:
+ return value
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", nargs="*", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/included.configure b/python/mozbuild/mozbuild/test/configure/data/included.configure
new file mode 100644
index 0000000000..97166618ec
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/included.configure
@@ -0,0 +1,68 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# For more complex and repetitive things, we can create templates
+@template
+def check_compiler_flag(flag):
+ @depends(is_gcc)
+ def check(value):
+ if value:
+ return [flag]
+
+ set_config("CFLAGS", check)
+ return check
+
+
+check_compiler_flag("-Werror=foobar")
+
+# Normal functions can be used in @depends functions.
+def fortytwo():
+ return 42
+
+
+def twentyone():
+ yield 21
+
+
+@depends(is_gcc)
+def check(value):
+ if value:
+ return fortytwo()
+
+
+set_config("TEMPLATE_VALUE", check)
+
+
+@depends(is_gcc)
+def check(value):
+ if value:
+ for val in twentyone():
+ return val
+
+
+set_config("TEMPLATE_VALUE_2", check)
+
+# Normal functions can use @imports too to import modules.
+@imports("sys")
+def platform():
+ return sys.platform
+
+
+option("--enable-imports-in-template", help="Imports in template")
+
+
+@depends("--enable-imports-in-template")
+def check(value):
+ if value:
+ return platform()
+
+
+set_config("PLATFORM", check)
+
+
+@template
+def indirectly_define_option(*args, **kwargs):
+ option(*args, **kwargs)
diff --git a/python/mozbuild/mozbuild/test/configure/data/moz.configure b/python/mozbuild/mozbuild/test/configure/data/moz.configure
new file mode 100644
index 0000000000..4d57eabbb9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/moz.configure
@@ -0,0 +1,205 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--enable-simple", help="Enable simple")
+
+# Setting MOZ_WITH_ENV in the environment has the same effect as passing
+# --enable-with-env.
+option("--enable-with-env", env="MOZ_WITH_ENV", help="Enable with env")
+
+# Optional values
+option("--enable-values", nargs="*", help="Enable values")
+
+# Everything supported in the Option class is supported in option(). Assume
+# the tests of the Option class are extensive about this.
+
+# Alternatively to --enable/--disable, there also is --with/--without. The
+# difference is semantic only. Behavior is the same as --enable/--disable.
+
+# When the option name starts with --disable/--without, the default is for
+# the option to be enabled.
+option("--without-thing", help="Build without thing")
+
+# A --enable/--with option with a default of False is equivalent to a
+# --disable/--without option. This can be used to change the defaults
+# depending on e.g. the target or the built application.
+option("--with-stuff", default=False, help="Build with stuff")
+
+# Other kinds of arbitrary options are also allowed. This is effectively
+# equivalent to --enable/--with, with no possibility of --disable/--without.
+option("--option", env="MOZ_OPTION", help="Option")
+
+# It is also possible to pass options through the environment only.
+option(env="CC", nargs=1, help="C Compiler")
+
+# Call the function when the --enable-simple option is processed, with its
+# OptionValue as argument.
+@depends("--enable-simple")
+def simple(simple):
+ if simple:
+ return simple
+
+
+set_config("ENABLED_SIMPLE", simple)
+
+# There can be multiple functions depending on the same option.
+@depends("--enable-simple")
+def simple(simple):
+ return simple
+
+
+set_config("SIMPLE", simple)
+
+
+@depends("--enable-with-env")
+def with_env(with_env):
+ return with_env
+
+
+set_config("WITH_ENV", with_env)
+
+# It doesn't matter if the dependency is on --enable or --disable
+@depends("--disable-values")
+def with_env2(values):
+ return values
+
+
+set_config("VALUES", with_env2)
+
+# It is possible to @depends on environment-only options.
+@depends("CC")
+def is_gcc(cc):
+ return cc and "gcc" in cc[0]
+
+
+set_config("IS_GCC", is_gcc)
+
+# It is possible to depend on the result from another function.
+@depends(with_env2)
+def with_env3(values):
+ return values
+
+
+set_config("VALUES2", with_env3)
+
+# @depends functions can also return results for use as input to another
+# @depends.
+@depends(with_env3)
+def with_env4(values):
+ return values
+
+
+@depends(with_env4)
+def with_env5(values):
+ return values
+
+
+set_config("VALUES3", with_env5)
+
+# The result from @depends functions can also be used as input to options.
+# The result must be returned, not implied.
+@depends("--enable-simple")
+def simple(simple):
+ return "simple" if simple else "not-simple"
+
+
+option("--with-returned-default", default=simple, help="Returned default")
+
+
+@depends("--with-returned-default")
+def default(value):
+ return value
+
+
+set_config("DEFAULTED", default)
+
+
+@depends("--enable-values")
+def choices(values):
+ if len(values):
+ return {
+ "alpha": ("a", "b", "c"),
+ "numeric": ("0", "1", "2"),
+ }.get(values[0])
+
+
+option("--returned-choices", choices=choices, help="Choices")
+
+
+@depends("--returned-choices")
+def returned_choices(values):
+ return values
+
+
+set_config("CHOICES", returned_choices)
+
+# All options must be referenced by some @depends function.
+# It is possible to depend on multiple options/functions
+@depends("--without-thing", "--with-stuff", with_env4, "--option")
+def remainder(*args):
+ return args
+
+
+set_config("REMAINDER", remainder)
+
+# It is possible to include other files to extend the configuration script.
+include("included.configure")
+
+# It is also possible for the include file path to come from the result of a
+# @depends function.
+option("--enable-include", nargs=1, help="Include")
+
+
+@depends("--enable-include")
+def include_path(path):
+ return path[0] if path else None
+
+
+include(include_path)
+
+# Sandboxed functions can import from modules through the use of the @imports
+# decorator.
+# The order of the decorators matter: @imports needs to appear after other
+# decorators.
+option("--with-imports", nargs="?", help="Imports")
+
+# A limited set of functions from os.path are exposed by default.
+@depends("--with-imports")
+def with_imports(value):
+ if len(value):
+ return hasattr(os.path, "abspath")
+
+
+set_config("HAS_ABSPATH", with_imports)
+
+# It is still possible to import the full set from os.path.
+# It is also possible to cherry-pick builtins.
+@depends("--with-imports")
+@imports("os.path")
+def with_imports(value):
+ if len(value):
+ return hasattr(os.path, "getatime")
+
+
+set_config("HAS_GETATIME", with_imports)
+
+
+@depends("--with-imports")
+def with_imports(value):
+ if len(value):
+ return hasattr(os.path, "getatime")
+
+
+set_config("HAS_GETATIME2", with_imports)
+
+# This option should be attributed to this file in the --help output even though
+# included.configure is the actual file that defines the option.
+indirectly_define_option("--indirect-option", help="Indirectly defined option")
+
+
+@depends("--indirect-option")
+def indirect_option(option):
+ return option
diff --git a/python/mozbuild/mozbuild/test/configure/data/set_config.configure b/python/mozbuild/mozbuild/test/configure/data/set_config.configure
new file mode 100644
index 0000000000..0ae5fef6d6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_config.configure
@@ -0,0 +1,51 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--set-foo", help="set foo")
+
+
+@depends("--set-foo")
+def foo(value):
+ if value:
+ return True
+
+
+set_config("FOO", foo)
+
+
+option("--set-bar", help="set bar")
+
+
+@depends("--set-bar")
+def bar(value):
+ return bool(value)
+
+
+set_config("BAR", bar)
+
+
+option("--set-value", nargs=1, help="set value")
+
+
+@depends("--set-value")
+def set_value(value):
+ if value:
+ return value[0]
+
+
+set_config("VALUE", set_value)
+
+
+option("--set-name", nargs=1, help="set name")
+
+
+@depends("--set-name")
+def set_name(value):
+ if value:
+ return value[0]
+
+
+set_config(set_name, True)
diff --git a/python/mozbuild/mozbuild/test/configure/data/set_define.configure b/python/mozbuild/mozbuild/test/configure/data/set_define.configure
new file mode 100644
index 0000000000..ce9a60d7f1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_define.configure
@@ -0,0 +1,51 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option("--set-foo", help="set foo")
+
+
+@depends("--set-foo")
+def foo(value):
+ if value:
+ return True
+
+
+set_define("FOO", foo)
+
+
+option("--set-bar", help="set bar")
+
+
+@depends("--set-bar")
+def bar(value):
+ return bool(value)
+
+
+set_define("BAR", bar)
+
+
+option("--set-value", nargs=1, help="set value")
+
+
+@depends("--set-value")
+def set_value(value):
+ if value:
+ return value[0]
+
+
+set_define("VALUE", set_value)
+
+
+option("--set-name", nargs=1, help="set name")
+
+
+@depends("--set-name")
+def set_name(value):
+ if value:
+ return value[0]
+
+
+set_define(set_name, True)
diff --git a/python/mozbuild/mozbuild/test/configure/data/subprocess.configure b/python/mozbuild/mozbuild/test/configure/data/subprocess.configure
new file mode 100644
index 0000000000..3316fee087
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/subprocess.configure
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@depends("--help")
+@imports("codecs")
+@imports(_from="mozbuild.configure.util", _import="getpreferredencoding")
+@imports("os")
+@imports(_from="__builtin__", _import="open")
+def dies_when_logging(_):
+ test_file = "test.txt"
+ quote_char = "'"
+ if getpreferredencoding().lower() == "utf-8":
+ quote_char = "\u00B4"
+ try:
+ with open(test_file, "w+") as fh:
+ fh.write(quote_char)
+ out = check_cmd_output("cat", "test.txt")
+ log.info(out)
+ finally:
+ os.remove(test_file)
diff --git a/python/mozbuild/mozbuild/test/configure/lint.py b/python/mozbuild/mozbuild/test/configure/lint.py
new file mode 100644
index 0000000000..59d41da264
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/lint.py
@@ -0,0 +1,62 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+
+import six
+from buildconfig import topobjdir, topsrcdir
+from mozunit import main
+
+from mozbuild.configure.lint import LintSandbox
+
+test_path = os.path.abspath(__file__)
+
+
+class LintMeta(type):
+ def __new__(mcs, name, bases, attrs):
+ def create_test(project, func):
+ def test(self):
+ return func(self, project)
+
+ return test
+
+ for project in (
+ "browser",
+ "js",
+ "memory",
+ "mobile/android",
+ ):
+ attrs["test_%s" % project.replace("/", "_")] = create_test(
+ project, attrs["lint"]
+ )
+
+ return type.__new__(mcs, name, bases, attrs)
+
+
+# We don't actually need python2 compat, but this makes flake8 happy.
+@six.add_metaclass(LintMeta)
+class Lint(unittest.TestCase):
+ def setUp(self):
+ self._curdir = os.getcwd()
+ os.chdir(topobjdir)
+
+ def tearDown(self):
+ os.chdir(self._curdir)
+
+ def lint(self, project):
+ sandbox = LintSandbox(
+ {
+ "OLD_CONFIGURE": os.path.join(topsrcdir, "old-configure"),
+ "MOZCONFIG": os.path.join(
+ os.path.dirname(test_path), "data", "empty_mozconfig"
+ ),
+ },
+ ["configure", "--enable-project=%s" % project, "--help"],
+ )
+ sandbox.run(os.path.join(topsrcdir, "moz.configure"))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist b/python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist
new file mode 100644
index 0000000000..f0d6e1949f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Version</key>
+ <string>13.3</string>
+</dict>
+</plist>
diff --git a/python/mozbuild/mozbuild/test/configure/test_bootstrap.py b/python/mozbuild/mozbuild/test/configure/test_bootstrap.py
new file mode 100644
index 0000000000..eaa417d566
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_bootstrap.py
@@ -0,0 +1,43 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from mozunit import main
+
+from common import BaseConfigureTest
+
+
+class TestBootstrap(BaseConfigureTest):
+ def test_bootstrap(self):
+ def get_value_for(arg):
+ sandbox = self.get_sandbox({}, {}, [arg], {})
+ return sandbox._value_for(sandbox["enable_bootstrap"])
+
+ self.assertEqual(None, get_value_for("--disable-bootstrap"))
+
+ # With `--enable-bootstrap`, anything is bootstrappable
+ bootstrap = get_value_for("--enable-bootstrap")
+ self.assertTrue(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+
+ # With `--enable-bootstrap=foo,bar`, only foo and bar are bootstrappable
+ bootstrap = get_value_for("--enable-bootstrap=foo,bar")
+ self.assertTrue(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+ self.assertFalse(bootstrap("qux"))
+
+ # With `--enable-bootstrap=-foo`, anything is bootstrappable, except foo
+ bootstrap = get_value_for("--enable-bootstrap=-foo")
+ self.assertFalse(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+ self.assertTrue(bootstrap("qux"))
+
+ # Corner case.
+ bootstrap = get_value_for("--enable-bootstrap=-foo,foo,bar")
+ self.assertFalse(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+ self.assertFalse(bootstrap("qux"))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_checks_configure.py b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
new file mode 100644
index 0000000000..53361ff199
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
@@ -0,0 +1,1169 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+import textwrap
+import unittest
+
+from buildconfig import topsrcdir
+from mozpack import path as mozpath
+from mozunit import MockedOpen, main
+from six import StringIO
+
+from common import ConfigureTestSandbox, ensure_exe_extension, fake_short_path
+from mozbuild.configure import ConfigureError, ConfigureSandbox
+from mozbuild.shellutil import quote as shell_quote
+from mozbuild.util import exec_
+
+
+class TestChecksConfigure(unittest.TestCase):
+ def test_checking(self):
+ def make_test(to_exec):
+ def test(val, msg):
+ out = StringIO()
+ sandbox = ConfigureSandbox({}, stdout=out, stderr=out)
+ base_dir = os.path.join(topsrcdir, "build", "moz.configure")
+ sandbox.include_file(os.path.join(base_dir, "checks.configure"))
+ exec_(to_exec, sandbox)
+ sandbox["foo"](val)
+ self.assertEqual(out.getvalue(), msg)
+
+ return test
+
+ test = make_test(
+ textwrap.dedent(
+ """
+ @checking('for a thing')
+ def foo(value):
+ return value
+ """
+ )
+ )
+ test(True, "checking for a thing... yes\n")
+ test(False, "checking for a thing... no\n")
+ test(42, "checking for a thing... 42\n")
+ test("foo", "checking for a thing... foo\n")
+ data = ["foo", "bar"]
+ test(data, "checking for a thing... %r\n" % data)
+
+ # When the function given to checking does nothing interesting, the
+ # behavior is not altered
+ test = make_test(
+ textwrap.dedent(
+ """
+ @checking('for a thing', lambda x: x)
+ def foo(value):
+ return value
+ """
+ )
+ )
+ test(True, "checking for a thing... yes\n")
+ test(False, "checking for a thing... no\n")
+ test(42, "checking for a thing... 42\n")
+ test("foo", "checking for a thing... foo\n")
+ data = ["foo", "bar"]
+ test(data, "checking for a thing... %r\n" % data)
+
+ test = make_test(
+ textwrap.dedent(
+ """
+ def munge(x):
+ if not x:
+ return 'not found'
+ if isinstance(x, (str, bool, int)):
+ return x
+ return ' '.join(x)
+
+ @checking('for a thing', munge)
+ def foo(value):
+ return value
+ """
+ )
+ )
+ test(True, "checking for a thing... yes\n")
+ test(False, "checking for a thing... not found\n")
+ test(42, "checking for a thing... 42\n")
+ test("foo", "checking for a thing... foo\n")
+ data = ["foo", "bar"]
+ test(data, "checking for a thing... foo bar\n")
+
+ KNOWN_A = ensure_exe_extension(mozpath.abspath("/usr/bin/known-a"))
+ KNOWN_B = ensure_exe_extension(mozpath.abspath("/usr/local/bin/known-b"))
+ KNOWN_C = ensure_exe_extension(mozpath.abspath("/home/user/bin/known c"))
+ OTHER_A = ensure_exe_extension(mozpath.abspath("/lib/other/known-a"))
+
+ def get_result(
+ self,
+ command="",
+ args=[],
+ environ={},
+ prog="/bin/configure",
+ extra_paths=None,
+ includes=("util.configure", "checks.configure"),
+ ):
+ config = {}
+ out = StringIO()
+ paths = {self.KNOWN_A: None, self.KNOWN_B: None, self.KNOWN_C: None}
+ if extra_paths:
+ paths.update(extra_paths)
+ environ = dict(environ)
+ if "PATH" not in environ:
+ environ["PATH"] = os.pathsep.join(os.path.dirname(p) for p in paths)
+ paths[self.OTHER_A] = None
+ sandbox = ConfigureTestSandbox(paths, config, environ, [prog] + args, out, out)
+ base_dir = os.path.join(topsrcdir, "build", "moz.configure")
+ for f in includes:
+ sandbox.include_file(os.path.join(base_dir, f))
+
+ status = 0
+ try:
+ exec_(command, sandbox)
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+
+ return config, out.getvalue(), status
+
+ def test_check_prog(self):
+ config, out, status = self.get_result('check_prog("FOO", ("known-a",))')
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))'
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_B})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_B)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "known c"))'
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": fake_short_path(self.KNOWN_C)})
+ self.assertEqual(
+ out, "checking for foo... %s\n" % shell_quote(fake_short_path(self.KNOWN_C))
+ )
+
+ config, out, status = self.get_result('check_prog("FOO", ("unknown",))')
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for unknown
+ ERROR: Cannot find foo
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"))'
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for unknown
+ DEBUG: foo: Looking for unknown-2
+ DEBUG: foo: Looking for 'unknown 3'
+ ERROR: Cannot find foo
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"), '
+ "allow_missing=True)"
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+ self.assertEqual(out, "checking for foo... not found\n")
+
+ @unittest.skipIf(not sys.platform.startswith("win"), "Windows-only test")
+ def test_check_prog_exe(self):
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))', ["FOO=known-a.exe"]
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))',
+ ["FOO=%s" % os.path.splitext(self.KNOWN_A)[0]],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ def test_check_prog_with_args(self):
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))', ["FOO=known-a"]
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))',
+ ["FOO=%s" % self.KNOWN_A],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ path = self.KNOWN_B.replace("known-b", "known-a")
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))', ["FOO=%s" % path]
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for %s
+ ERROR: Cannot find foo
+ """
+ )
+ % path,
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown",))', ["FOO=known c"]
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": fake_short_path(self.KNOWN_C)})
+ self.assertEqual(
+ out, "checking for foo... %s\n" % shell_quote(fake_short_path(self.KNOWN_C))
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"), '
+ "allow_missing=True)",
+ ["FOO=unknown"],
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for unknown
+ ERROR: Cannot find foo
+ """
+ ),
+ )
+
+ def test_check_prog_what(self):
+ config, out, status = self.get_result(
+ 'check_prog("CC", ("known-a",), what="the target C compiler")'
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(
+ out, "checking for the target C compiler... %s\n" % self.KNOWN_A
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("CC", ("unknown", "unknown-2", "unknown 3"),'
+ ' what="the target C compiler")'
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for the target C compiler... not found
+ DEBUG: cc: Looking for unknown
+ DEBUG: cc: Looking for unknown-2
+ DEBUG: cc: Looking for 'unknown 3'
+ ERROR: Cannot find the target C compiler
+ """
+ ),
+ )
+
+ def test_check_prog_input(self):
+ config, out, status = self.get_result(
+ textwrap.dedent(
+ """
+ option("--with-ccache", nargs=1, help="ccache")
+ check_prog("CCACHE", ("known-a",), input="--with-ccache")
+ """
+ ),
+ ["--with-ccache=known-b"],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CCACHE": self.KNOWN_B})
+ self.assertEqual(out, "checking for ccache... %s\n" % self.KNOWN_B)
+
+ script = textwrap.dedent(
+ """
+ option(env="CC", nargs=1, help="compiler")
+ @depends("CC")
+ def compiler(value):
+ return value[0].split()[0] if value else None
+ check_prog("CC", ("known-a",), input=compiler)
+ """
+ )
+ config, out, status = self.get_result(script)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(script, ["CC=known-b"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_B})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_B)
+
+ config, out, status = self.get_result(script, ["CC=known-b -m32"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_B})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_B)
+
+ def test_check_prog_progs(self):
+ config, out, status = self.get_result('check_prog("FOO", ())')
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+ self.assertEqual(out, "")
+
+ config, out, status = self.get_result('check_prog("FOO", ())', ["FOO=known-a"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ script = textwrap.dedent(
+ """
+ option(env="TARGET", nargs=1, default="linux", help="target")
+ @depends("TARGET")
+ def compiler(value):
+ if value:
+ if value[0] == "linux":
+ return ("gcc", "clang")
+ if value[0] == "winnt":
+ return ("cl", "clang-cl")
+ check_prog("CC", compiler)
+ """
+ )
+ config, out, status = self.get_result(script)
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for cc... not found
+ DEBUG: cc: Looking for gcc
+ DEBUG: cc: Looking for clang
+ ERROR: Cannot find cc
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(script, ["TARGET=linux"])
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for cc... not found
+ DEBUG: cc: Looking for gcc
+ DEBUG: cc: Looking for clang
+ ERROR: Cannot find cc
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(script, ["TARGET=winnt"])
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for cc... not found
+ DEBUG: cc: Looking for cl
+ DEBUG: cc: Looking for clang-cl
+ ERROR: Cannot find cc
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(script, ["TARGET=none"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+ self.assertEqual(out, "")
+
+ config, out, status = self.get_result(script, ["TARGET=winnt", "CC=known-a"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(script, ["TARGET=none", "CC=known-a"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_A)
+
+ def test_check_prog_configure_error(self):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_result('check_prog("FOO", "foo")')
+
+ self.assertEqual(str(e.exception), "progs must resolve to a list or tuple!")
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_result(
+ 'foo = depends(when=True)(lambda: ("a", "b"))\n'
+ 'check_prog("FOO", ("known-a",), input=foo)'
+ )
+
+ self.assertEqual(
+ str(e.exception),
+ "input must resolve to a tuple or a list with a "
+ "single element, or a string",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_result(
+ 'foo = depends(when=True)(lambda: {"a": "b"})\n'
+ 'check_prog("FOO", ("known-a",), input=foo)'
+ )
+
+ self.assertEqual(
+ str(e.exception),
+ "input must resolve to a tuple or a list with a "
+ "single element, or a string",
+ )
+
+ def test_check_prog_with_path(self):
+ config, out, status = self.get_result(
+ 'check_prog("A", ("known-a",), paths=["/some/path"])'
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... not found
+ DEBUG: a: Looking for known-a
+ ERROR: Cannot find a
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("A", ("known-a",), paths=["%s"])'
+ % os.path.dirname(self.OTHER_A)
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"A": self.OTHER_A})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... %s
+ """
+ % self.OTHER_A
+ ),
+ )
+
+ dirs = map(mozpath.dirname, (self.OTHER_A, self.KNOWN_A))
+ config, out, status = self.get_result(
+ textwrap.dedent(
+ """\
+ check_prog("A", ("known-a",), paths=["%s"])
+ """
+ % os.pathsep.join(dirs)
+ )
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"A": self.OTHER_A})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... %s
+ """
+ % self.OTHER_A
+ ),
+ )
+
+ dirs = map(mozpath.dirname, (self.KNOWN_A, self.KNOWN_B))
+ config, out, status = self.get_result(
+ textwrap.dedent(
+ """\
+ check_prog("A", ("known-a",), paths=["%s", "%s"])
+ """
+ % (os.pathsep.join(dirs), self.OTHER_A)
+ )
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"A": self.KNOWN_A})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... %s
+ """
+ % self.KNOWN_A
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("A", ("known-a",), paths="%s")' % os.path.dirname(self.OTHER_A)
+ )
+
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... """ # noqa # trailing whitespace...
+ """
+ DEBUG: a: Looking for known-a
+ ERROR: Paths provided to find_program must be a list of strings, not %r
+ """
+ % mozpath.dirname(self.OTHER_A)
+ ),
+ )
+
+ @unittest.skipIf(
+ not sys.platform.startswith("linux"),
+ "Linux-only test, assumes Java is located from a $PATH",
+ )
+ def test_java_tool_checks_linux(self):
+ def run_configure_java(
+ mock_fs_paths, mock_java_home=None, mock_path=None, args=[]
+ ):
+ script = textwrap.dedent(
+ """\
+ @depends('--help')
+ def host(_):
+ return namespace(os='unknown', kernel='unknown')
+ toolchains_base_dir = depends(when=True)(lambda: '/mozbuild')
+ include('%(topsrcdir)s/build/moz.configure/java.configure')
+ """
+ % {"topsrcdir": topsrcdir}
+ )
+
+ # Don't let system JAVA_HOME influence the test
+ original_java_home = os.environ.pop("JAVA_HOME", None)
+ configure_environ = {}
+
+ if mock_java_home:
+ os.environ["JAVA_HOME"] = mock_java_home
+ configure_environ["JAVA_HOME"] = mock_java_home
+
+ if mock_path:
+ configure_environ["PATH"] = mock_path
+
+ # * Even if the real file sysphabtem has a symlink at the mocked path, don't let
+ # realpath follow it, as it may influence the test.
+ # * When finding a binary, check the mock paths rather than the real filesystem.
+ # Note: Python doesn't allow the different "with" bits to be put in parenthesis,
+ # because then it thinks it's an un-with-able tuple. Additionally, if this is cleanly
+ # lined up with "\", black removes them and autoformats them to the block that is
+ # below.
+ result = self.get_result(
+ args=args,
+ command=script,
+ extra_paths=paths,
+ environ=configure_environ,
+ )
+
+ if original_java_home:
+ os.environ["JAVA_HOME"] = original_java_home
+ return result
+
+ java = mozpath.abspath("/usr/bin/java")
+ javac = mozpath.abspath("/usr/bin/javac")
+ paths = {java: None, javac: None}
+ expected_error_message = (
+ "ERROR: Could not locate Java at /mozbuild/jdk/jdk-17.0.7+7/bin, "
+ "please run ./mach bootstrap --no-system-changes\n"
+ )
+
+ config, out, status = run_configure_java(paths)
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, expected_error_message)
+
+ # An alternative valid set of tools referred to by JAVA_HOME.
+ alt_java = mozpath.abspath("/usr/local/bin/java")
+ alt_javac = mozpath.abspath("/usr/local/bin/javac")
+ alt_java_home = mozpath.dirname(mozpath.dirname(alt_java))
+ paths = {alt_java: None, alt_javac: None, java: None, javac: None}
+
+ alt_path = mozpath.dirname(java)
+ config, out, status = run_configure_java(paths, alt_java_home, alt_path)
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, expected_error_message)
+
+ # We can use --with-java-bin-path instead of JAVA_HOME to similar
+ # effect.
+ config, out, status = run_configure_java(
+ paths,
+ mock_path=mozpath.dirname(java),
+ args=["--with-java-bin-path=%s" % mozpath.dirname(alt_java)],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"JAVA": alt_java, "MOZ_JAVA_CODE_COVERAGE": False})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for java... %s
+ """
+ % alt_java
+ ),
+ )
+
+ # If --with-java-bin-path and JAVA_HOME are both set,
+ # --with-java-bin-path takes precedence.
+ config, out, status = run_configure_java(
+ paths,
+ mock_java_home=mozpath.dirname(mozpath.dirname(java)),
+ mock_path=mozpath.dirname(java),
+ args=["--with-java-bin-path=%s" % mozpath.dirname(alt_java)],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"JAVA": alt_java, "MOZ_JAVA_CODE_COVERAGE": False})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for java... %s
+ """
+ % alt_java
+ ),
+ )
+
+ # --enable-java-coverage should set MOZ_JAVA_CODE_COVERAGE.
+ alt_java_home = mozpath.dirname(mozpath.dirname(java))
+ config, out, status = run_configure_java(
+ paths,
+ mock_java_home=alt_java_home,
+ mock_path=mozpath.dirname(java),
+ args=["--enable-java-coverage"],
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+
+ # Any missing tool is fatal when these checks run.
+ paths = {}
+ config, out, status = run_configure_java(
+ mock_fs_paths={},
+ mock_path=mozpath.dirname(java),
+ args=["--enable-java-coverage"],
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, expected_error_message)
+
+ def test_pkg_check_modules(self):
+ mock_pkg_config_version = "0.10.0"
+ mock_pkg_config_path = mozpath.abspath("/usr/bin/pkg-config")
+
+ seen_flags = set()
+
+ def mock_pkg_config(_, args):
+ if "--dont-define-prefix" in args:
+ args = list(args)
+ seen_flags.add(args.pop(args.index("--dont-define-prefix")))
+ args = tuple(args)
+ if args[0:2] == ("--errors-to-stdout", "--print-errors"):
+ assert len(args) == 3
+ package = args[2]
+ if package == "unknown":
+ return (
+ 1,
+ "Package unknown was not found in the pkg-config search path.\n"
+ "Perhaps you should add the directory containing `unknown.pc'\n"
+ "to the PKG_CONFIG_PATH environment variable\n"
+ "No package 'unknown' found",
+ "",
+ )
+ if package == "valid":
+ return 0, "", ""
+ if package == "new > 1.1":
+ return 1, "Requested 'new > 1.1' but version of new is 1.1", ""
+ if args[0] == "--cflags":
+ assert len(args) == 2
+ return 0, "-I/usr/include/%s" % args[1], ""
+ if args[0] == "--libs":
+ assert len(args) == 2
+ return 0, "-l%s" % args[1], ""
+ if args[0] == "--version":
+ return 0, mock_pkg_config_version, ""
+ if args[0] == "--about":
+ return 1, "Unknown option --about", ""
+ self.fail("Unexpected arguments to mock_pkg_config: %s" % (args,))
+
+ def mock_pkgconf(_, args):
+ if args[0] == "--shared":
+ seen_flags.add(args[0])
+ args = args[1:]
+ if args[0] == "--about":
+ return 0, "pkgconf {}".format(mock_pkg_config_version), ""
+ return mock_pkg_config(_, args)
+
+ def get_result(cmd, args=[], bootstrapped_sysroot=False, extra_paths=None):
+ return self.get_result(
+ textwrap.dedent(
+ """\
+ option('--disable-compile-environment', help='compile env')
+ compile_environment = depends(when='--enable-compile-environment')(lambda: True)
+ toolchain_prefix = depends(when=True)(lambda: None)
+ target_multiarch_dir = depends(when=True)(lambda: None)
+ target_sysroot = depends(when=True)(lambda: %(sysroot)s)
+ target = depends(when=True)(lambda: None)
+ include('%(topsrcdir)s/build/moz.configure/util.configure')
+ include('%(topsrcdir)s/build/moz.configure/checks.configure')
+ # Skip bootstrapping.
+ @template
+ def check_prog(*args, **kwargs):
+ del kwargs["bootstrap"]
+ return check_prog(*args, **kwargs)
+ include('%(topsrcdir)s/build/moz.configure/pkg.configure')
+ """
+ % {
+ "topsrcdir": topsrcdir,
+ "sysroot": "namespace(bootstrapped=True)"
+ if bootstrapped_sysroot
+ else "None",
+ }
+ )
+ + cmd,
+ args=args,
+ extra_paths=extra_paths,
+ includes=(),
+ )
+
+ extra_paths = {mock_pkg_config_path: mock_pkg_config}
+
+ config, output, status = get_result("pkg_check_modules('MOZ_VALID', 'valid')")
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... not found
+ ERROR: *** The pkg-config script could not be found. Make sure it is
+ *** in your path, or set the PKG_CONFIG environment variable
+ *** to the full path to pkg-config.
+ """
+ ),
+ )
+
+ for pkg_config, version, bootstrapped_sysroot, is_pkgconf in (
+ (mock_pkg_config, "0.10.0", False, False),
+ (mock_pkg_config, "0.30.0", False, False),
+ (mock_pkg_config, "0.30.0", True, False),
+ (mock_pkgconf, "1.1.0", True, True),
+ (mock_pkgconf, "1.6.0", False, True),
+ (mock_pkgconf, "1.8.0", False, True),
+ (mock_pkgconf, "1.8.0", True, True),
+ ):
+ seen_flags = set()
+ mock_pkg_config_version = version
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_VALID', 'valid')",
+ bootstrapped_sysroot=bootstrapped_sysroot,
+ extra_paths={mock_pkg_config_path: pkg_config},
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... %s
+ checking for valid... yes
+ checking MOZ_VALID_CFLAGS... -I/usr/include/valid
+ checking MOZ_VALID_LIBS... -lvalid
+ """
+ % (
+ mock_pkg_config_path,
+ mock_pkg_config_version,
+ "yes" if is_pkgconf else "no",
+ )
+ ),
+ )
+ self.assertEqual(
+ config,
+ {
+ "PKG_CONFIG": mock_pkg_config_path,
+ "MOZ_VALID_CFLAGS": ("-I/usr/include/valid",),
+ "MOZ_VALID_LIBS": ("-lvalid",),
+ },
+ )
+ if version == "1.8.0" and bootstrapped_sysroot:
+ self.assertEqual(seen_flags, set(["--shared", "--dont-define-prefix"]))
+ elif version == "1.8.0":
+ self.assertEqual(seen_flags, set(["--shared"]))
+ elif version in ("1.6.0", "0.30.0") and bootstrapped_sysroot:
+ self.assertEqual(seen_flags, set(["--dont-define-prefix"]))
+ else:
+ self.assertEqual(seen_flags, set())
+
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_UKNOWN', 'unknown')", extra_paths=extra_paths
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... no
+ checking for unknown... no
+ ERROR: Package unknown was not found in the pkg-config search path.
+ ERROR: Perhaps you should add the directory containing `unknown.pc'
+ ERROR: to the PKG_CONFIG_PATH environment variable
+ ERROR: No package 'unknown' found
+ """
+ % (mock_pkg_config_path, mock_pkg_config_version)
+ ),
+ )
+ self.assertEqual(config, {"PKG_CONFIG": mock_pkg_config_path})
+
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_NEW', 'new > 1.1')", extra_paths=extra_paths
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... no
+ checking for new > 1.1... no
+ ERROR: Requested 'new > 1.1' but version of new is 1.1
+ """
+ % (mock_pkg_config_path, mock_pkg_config_version)
+ ),
+ )
+ self.assertEqual(config, {"PKG_CONFIG": mock_pkg_config_path})
+
+ # allow_missing makes missing packages non-fatal.
+ cmd = textwrap.dedent(
+ """\
+ have_new_module = pkg_check_modules('MOZ_NEW', 'new > 1.1', allow_missing=True)
+ @depends(have_new_module)
+ def log_new_module_error(mod):
+ if mod is not True:
+ log.info('Module not found.')
+ """
+ )
+
+ config, output, status = get_result(cmd, extra_paths=extra_paths)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... no
+ checking for new > 1.1... no
+ WARNING: Requested 'new > 1.1' but version of new is 1.1
+ Module not found.
+ """
+ % (mock_pkg_config_path, mock_pkg_config_version)
+ ),
+ )
+ self.assertEqual(config, {"PKG_CONFIG": mock_pkg_config_path})
+
+ config, output, status = get_result(
+ cmd, args=["--disable-compile-environment"], extra_paths=extra_paths
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(output, "Module not found.\n")
+ self.assertEqual(config, {})
+
+ def mock_old_pkg_config(_, args):
+ if args[0] == "--version":
+ return 0, "0.8.10", ""
+ if args[0] == "--about":
+ return 1, "Unknown option --about", ""
+ self.fail("Unexpected arguments to mock_old_pkg_config: %s" % args)
+
+ extra_paths = {mock_pkg_config_path: mock_old_pkg_config}
+
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_VALID', 'valid')", extra_paths=extra_paths
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... 0.8.10
+ checking whether pkg-config is pkgconf... no
+ ERROR: *** Your version of pkg-config is too old. You need version 0.9.0 or newer.
+ """
+ % mock_pkg_config_path
+ ),
+ )
+
+ def test_simple_keyfile(self):
+ includes = ("util.configure", "checks.configure", "keyfiles.configure")
+
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... no
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key"})
+
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')",
+ args=["--with-mozilla-api-keyfile=/foo/bar/does/not/exist"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... no
+ ERROR: '/foo/bar/does/not/exist': No such file or directory.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": ""}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')",
+ args=["--with-mozilla-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... no
+ ERROR: 'key' is empty.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": "fake-key\n"}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')",
+ args=["--with-mozilla-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... yes
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "fake-key"})
+
+ with MockedOpen({"default": "default-key\n"}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API', default='default')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... yes
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "default-key"})
+
+ with MockedOpen({"default": "default-key\n", "key": "fake-key\n"}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API', default='key')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... yes
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "fake-key"})
+
+ def test_id_and_secret_keyfile(self):
+ includes = ("util.configure", "checks.configure", "keyfiles.configure")
+
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {
+ "MOZ_BING_API_CLIENTID": "no-bing-api-clientid",
+ "MOZ_BING_API_KEY": "no-bing-api-key",
+ },
+ )
+
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=/foo/bar/does/not/exist"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ ERROR: '/foo/bar/does/not/exist': No such file or directory.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": ""}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ ERROR: 'key' is empty.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": "fake-id fake-key\n"}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... yes
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {"MOZ_BING_API_CLIENTID": "fake-id", "MOZ_BING_API_KEY": "fake-key"},
+ )
+
+ with MockedOpen({"key": "fake-key\n"}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ ERROR: Bing API key file has an invalid format.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"default-key": "default-id default-key\n"}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API', default='default-key')",
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... yes
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {
+ "MOZ_BING_API_CLIENTID": "default-id",
+ "MOZ_BING_API_KEY": "default-key",
+ },
+ )
+
+ with MockedOpen(
+ {"default-key": "default-id default-key\n", "key": "fake-id fake-key\n"}
+ ):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API', default='default-key')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... yes
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {"MOZ_BING_API_CLIENTID": "fake-id", "MOZ_BING_API_KEY": "fake-key"},
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_compile_checks.py b/python/mozbuild/mozbuild/test/configure/test_compile_checks.py
new file mode 100644
index 0000000000..37988d535f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_compile_checks.py
@@ -0,0 +1,599 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import textwrap
+import unittest
+
+import mozpack.path as mozpath
+from buildconfig import topsrcdir
+from mozunit import main
+from six import StringIO
+from test_toolchain_helpers import FakeCompiler
+
+from common import ConfigureTestSandbox
+from mozbuild.util import exec_
+
+
+class BaseCompileChecks(unittest.TestCase):
+ def get_mock_compiler(self, expected_test_content=None, expected_flags=None):
+ expected_flags = expected_flags or []
+
+ def mock_compiler(stdin, args):
+ if args != ["--version"]:
+ test_file = [a for a in args if not a.startswith("-")]
+ self.assertEqual(len(test_file), 1)
+ test_file = test_file[0]
+ args = [a for a in args if a.startswith("-")]
+ self.assertIn("-c", args)
+ for flag in expected_flags:
+ self.assertIn(flag, args)
+
+ if expected_test_content:
+ with open(test_file) as fh:
+ test_content = fh.read()
+ self.assertEqual(test_content, expected_test_content)
+
+ return FakeCompiler()(None, args)
+
+ return mock_compiler
+
+ def do_compile_test(self, command, expected_test_content=None, expected_flags=None):
+
+ paths = {
+ os.path.abspath("/usr/bin/mockcc"): self.get_mock_compiler(
+ expected_test_content=expected_test_content,
+ expected_flags=expected_flags,
+ ),
+ }
+
+ base_dir = os.path.join(topsrcdir, "build", "moz.configure")
+
+ mock_compiler_defs = textwrap.dedent(
+ """\
+ @depends(when=True)
+ def extra_toolchain_flags():
+ return []
+
+ @depends(when=True)
+ def linker_ldflags():
+ return []
+
+ target = depends(when=True)(lambda: True)
+
+ @depends(when=True)
+ def configure_cache():
+
+ class ConfigureCache(dict):
+ pass
+
+ cache_data = {}
+
+ cache = ConfigureCache(cache_data)
+ cache.version_checked_compilers = set()
+
+ return cache
+
+ include('%s/compilers-util.configure')
+
+ @template
+ def wrap_compiler(compiler):
+ return compiler_class(compiler, False)
+
+ @wrap_compiler
+ @depends(when=True)
+ def c_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C',
+ )
+
+ @wrap_compiler
+ @depends(when=True)
+ def host_c_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C',
+ )
+
+ @wrap_compiler
+ @depends(when=True)
+ def cxx_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C++',
+ )
+
+ @wrap_compiler
+ @depends(when=True)
+ def host_cxx_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C++',
+ )
+ """
+ % mozpath.normsep(base_dir)
+ )
+
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureTestSandbox(paths, config, {}, ["/bin/configure"], out, out)
+ sandbox.include_file(os.path.join(base_dir, "util.configure"))
+ sandbox.include_file(os.path.join(base_dir, "checks.configure"))
+ exec_(mock_compiler_defs, sandbox)
+ sandbox.include_file(os.path.join(base_dir, "compile-checks.configure"))
+
+ status = 0
+ try:
+ exec_(command, sandbox)
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+
+ return config, out.getvalue(), status
+
+
+class TestHeaderChecks(BaseCompileChecks):
+ def test_try_compile_include(self):
+ expected_test_content = textwrap.dedent(
+ """\
+ #include <foo.h>
+ #include <bar.h>
+ int
+ main(void)
+ {
+
+ ;
+ return 0;
+ }
+ """
+ )
+
+ cmd = textwrap.dedent(
+ """\
+ try_compile(['foo.h', 'bar.h'], language='C')
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd, expected_test_content)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+
+ def test_try_compile_flags(self):
+ expected_flags = ["--extra", "--flags"]
+
+ cmd = textwrap.dedent(
+ """\
+ try_compile(language='C++', flags=['--flags', '--extra'])
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd, expected_flags=expected_flags)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+
+ def test_try_compile_failure(self):
+ cmd = textwrap.dedent(
+ """\
+ have_fn = try_compile(body='somefn();', flags=['-funknown-flag'])
+ set_config('HAVE_SOMEFN', have_fn)
+
+ have_another = try_compile(body='anotherfn();', language='C')
+ set_config('HAVE_ANOTHERFN', have_another)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "HAVE_ANOTHERFN": True,
+ },
+ )
+
+ def test_try_compile_msg(self):
+ cmd = textwrap.dedent(
+ """\
+ known_flag = try_compile(language='C++', flags=['-fknown-flag'],
+ check_msg='whether -fknown-flag works')
+ set_config('HAVE_KNOWN_FLAG', known_flag)
+ """
+ )
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"HAVE_KNOWN_FLAG": True})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether -fknown-flag works... yes
+ """
+ ),
+ )
+
+ def test_check_header(self):
+ expected_test_content = textwrap.dedent(
+ """\
+ #include <foo.h>
+ int
+ main(void)
+ {
+
+ ;
+ return 0;
+ }
+ """
+ )
+
+ cmd = textwrap.dedent(
+ """\
+ check_header('foo.h')
+ """
+ )
+
+ config, out, status = self.do_compile_test(
+ cmd, expected_test_content=expected_test_content
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"DEFINES": {"HAVE_FOO_H": True}})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo.h... yes
+ """
+ ),
+ )
+
+ def test_check_header_conditional(self):
+ cmd = textwrap.dedent(
+ """\
+ check_headers('foo.h', 'bar.h', when=never)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(out, "")
+ self.assertEqual(config, {"DEFINES": {}})
+
+ def test_check_header_include(self):
+ expected_test_content = textwrap.dedent(
+ """\
+ #include <std.h>
+ #include <bar.h>
+ #include <foo.h>
+ int
+ main(void)
+ {
+
+ ;
+ return 0;
+ }
+ """
+ )
+
+ cmd = textwrap.dedent(
+ """\
+ have_foo = check_header('foo.h', includes=['std.h', 'bar.h'])
+ set_config('HAVE_FOO_H', have_foo)
+ """
+ )
+
+ config, out, status = self.do_compile_test(
+ cmd, expected_test_content=expected_test_content
+ )
+
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "HAVE_FOO_H": True,
+ "DEFINES": {
+ "HAVE_FOO_H": True,
+ },
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo.h... yes
+ """
+ ),
+ )
+
+ def test_check_headers_multiple(self):
+ cmd = textwrap.dedent(
+ """\
+ baz_bar, quux_bar = check_headers('baz/foo-bar.h', 'baz-quux/foo-bar.h')
+ set_config('HAVE_BAZ_BAR', baz_bar)
+ set_config('HAVE_QUUX_BAR', quux_bar)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "HAVE_BAZ_BAR": True,
+ "HAVE_QUUX_BAR": True,
+ "DEFINES": {
+ "HAVE_BAZ_FOO_BAR_H": True,
+ "HAVE_BAZ_QUUX_FOO_BAR_H": True,
+ },
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for baz/foo-bar.h... yes
+ checking for baz-quux/foo-bar.h... yes
+ """
+ ),
+ )
+
+ def test_check_headers_not_found(self):
+
+ cmd = textwrap.dedent(
+ """\
+ baz_bar, quux_bar = check_headers('baz/foo-bar.h', 'baz-quux/foo-bar.h',
+ flags=['-funknown-flag'])
+ set_config('HAVE_BAZ_BAR', baz_bar)
+ set_config('HAVE_QUUX_BAR', quux_bar)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"DEFINES": {}})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for baz/foo-bar.h... no
+ checking for baz-quux/foo-bar.h... no
+ """
+ ),
+ )
+
+
+class TestWarningChecks(BaseCompileChecks):
+ def get_warnings(self):
+ return textwrap.dedent(
+ """\
+ set_config('_WARNINGS_CFLAGS', warnings_flags.cflags)
+ set_config('_WARNINGS_CXXFLAGS', warnings_flags.cxxflags)
+ """
+ )
+
+ def test_check_and_add_warning(self):
+ for flag, expected_flags in (
+ ("-Wfoo", ["-Werror", "-Wfoo"]),
+ ("-Wno-foo", ["-Werror", "-Wfoo"]),
+ ("-Werror=foo", ["-Werror=foo"]),
+ ("-Wno-error=foo", ["-Wno-error=foo"]),
+ ):
+ cmd = (
+ textwrap.dedent(
+ """\
+ check_and_add_warning('%s')
+ """
+ % flag
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(
+ cmd, expected_flags=expected_flags
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [flag],
+ "_WARNINGS_CXXFLAGS": [flag],
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether the C compiler supports {flag}... yes
+ checking whether the C++ compiler supports {flag}... yes
+ """.format(
+ flag=flag
+ )
+ ),
+ )
+
+ def test_check_and_add_warning_one(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ check_and_add_warning('-Wfoo', cxx_compiler)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": ["-Wfoo"],
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether the C++ compiler supports -Wfoo... yes
+ """
+ ),
+ )
+
+ def test_check_and_add_warning_when(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def never():
+ return False
+ check_and_add_warning('-Wfoo', cxx_compiler, when=never)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def always():
+ return True
+ check_and_add_warning('-Wfoo', cxx_compiler, when=always)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": ["-Wfoo"],
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether the C++ compiler supports -Wfoo... yes
+ """
+ ),
+ )
+
+ def test_add_warning(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ add_warning('-Wfoo')
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": ["-Wfoo"],
+ "_WARNINGS_CXXFLAGS": ["-Wfoo"],
+ },
+ )
+ self.assertEqual(out, "")
+
+ def test_add_warning_one(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ add_warning('-Wfoo', c_compiler)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": ["-Wfoo"],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+ def test_add_warning_when(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def never():
+ return False
+ add_warning('-Wfoo', c_compiler, when=never)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def always():
+ return True
+ add_warning('-Wfoo', c_compiler, when=always)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": ["-Wfoo"],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_configure.py b/python/mozbuild/mozbuild/test/configure/test_configure.py
new file mode 100644
index 0000000000..a5e42faae3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -0,0 +1,1986 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+import textwrap
+import unittest
+
+import mozpack.path as mozpath
+import six
+from mozunit import MockedOpen, main
+from six import StringIO
+
+from mozbuild.configure import ConfigureError, ConfigureSandbox
+from mozbuild.configure.options import (
+ InvalidOptionError,
+ NegativeOptionValue,
+ PositiveOptionValue,
+)
+from mozbuild.util import ReadOnlyNamespace, exec_, memoized_property
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+
+class TestConfigure(unittest.TestCase):
+ def get_config(
+ self, options=[], env={}, configure="moz.configure", prog="/bin/configure"
+ ):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, env, [prog] + options, out, out)
+
+ sandbox.run(mozpath.join(test_data_path, configure))
+
+ if "--help" in options:
+ return six.ensure_text(out.getvalue()), config
+ self.assertEqual("", out.getvalue())
+ return config
+
+ def moz_configure(self, source):
+ return MockedOpen(
+ {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)}
+ )
+
+ def test_defaults(self):
+ config = self.get_config()
+ self.maxDiff = None
+ self.assertEqual(
+ {
+ "CHOICES": NegativeOptionValue(),
+ "DEFAULTED": PositiveOptionValue(("not-simple",)),
+ "IS_GCC": NegativeOptionValue(),
+ "REMAINDER": (
+ PositiveOptionValue(),
+ NegativeOptionValue(),
+ NegativeOptionValue(),
+ NegativeOptionValue(),
+ ),
+ "SIMPLE": NegativeOptionValue(),
+ "VALUES": NegativeOptionValue(),
+ "VALUES2": NegativeOptionValue(),
+ "VALUES3": NegativeOptionValue(),
+ "WITH_ENV": NegativeOptionValue(),
+ },
+ config,
+ )
+
+ def test_help(self):
+ help, config = self.get_config(["--help"], prog="configure")
+
+ self.assertEqual({}, config)
+ self.maxDiff = None
+ self.assertEqual(
+ "Usage: configure [options]\n"
+ "\n"
+ "Options: [defaults in brackets after descriptions]\n"
+ " Help options:\n"
+ " --help print this message\n"
+ "\n"
+ " Options from python/mozbuild/mozbuild/test/configure/data/included.configure:\n"
+ " --enable-imports-in-template\n Imports in template\n"
+ "\n"
+ " Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n"
+ " --enable-include Include\n"
+ " --enable-simple Enable simple\n"
+ " --enable-values Enable values\n"
+ " --enable-with-env Enable with env\n"
+ " --indirect-option Indirectly defined option\n"
+ " --option Option\n"
+ " --returned-choices Choices\n"
+ " --with-imports Imports\n"
+ " --with-returned-default Returned default [not-simple]\n"
+ " --with-stuff Build with stuff\n"
+ " --without-thing Build without thing\n"
+ "\n"
+ "\n"
+ "Environment variables:\n"
+ " Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n"
+ " CC C Compiler\n"
+ "\n",
+ help.replace("\\", "/"),
+ )
+
+ def test_unknown(self):
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--unknown"])
+
+ def test_simple(self):
+ for config in (
+ self.get_config(),
+ self.get_config(["--disable-simple"]),
+ # Last option wins.
+ self.get_config(["--enable-simple", "--disable-simple"]),
+ ):
+ self.assertNotIn("ENABLED_SIMPLE", config)
+ self.assertIn("SIMPLE", config)
+ self.assertEqual(NegativeOptionValue(), config["SIMPLE"])
+
+ for config in (
+ self.get_config(["--enable-simple"]),
+ self.get_config(["--disable-simple", "--enable-simple"]),
+ ):
+ self.assertIn("ENABLED_SIMPLE", config)
+ self.assertIn("SIMPLE", config)
+ self.assertEqual(PositiveOptionValue(), config["SIMPLE"])
+ self.assertIs(config["SIMPLE"], config["ENABLED_SIMPLE"])
+
+ # --enable-simple doesn't take values.
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-simple=value"])
+
+ def test_with_env(self):
+ for config in (
+ self.get_config(),
+ self.get_config(["--disable-with-env"]),
+ self.get_config(["--enable-with-env", "--disable-with-env"]),
+ self.get_config(env={"MOZ_WITH_ENV": ""}),
+ # Options win over environment
+ self.get_config(["--disable-with-env"], env={"MOZ_WITH_ENV": "1"}),
+ ):
+ self.assertIn("WITH_ENV", config)
+ self.assertEqual(NegativeOptionValue(), config["WITH_ENV"])
+
+ for config in (
+ self.get_config(["--enable-with-env"]),
+ self.get_config(["--disable-with-env", "--enable-with-env"]),
+ self.get_config(env={"MOZ_WITH_ENV": "1"}),
+ self.get_config(["--enable-with-env"], env={"MOZ_WITH_ENV": ""}),
+ ):
+ self.assertIn("WITH_ENV", config)
+ self.assertEqual(PositiveOptionValue(), config["WITH_ENV"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-with-env=value"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(env={"MOZ_WITH_ENV": "value"})
+
+ def test_values(self, name="VALUES"):
+ for config in (
+ self.get_config(),
+ self.get_config(["--disable-values"]),
+ self.get_config(["--enable-values", "--disable-values"]),
+ ):
+ self.assertIn(name, config)
+ self.assertEqual(NegativeOptionValue(), config[name])
+
+ for config in (
+ self.get_config(["--enable-values"]),
+ self.get_config(["--disable-values", "--enable-values"]),
+ ):
+ self.assertIn(name, config)
+ self.assertEqual(PositiveOptionValue(), config[name])
+
+ config = self.get_config(["--enable-values=foo"])
+ self.assertIn(name, config)
+ self.assertEqual(PositiveOptionValue(("foo",)), config[name])
+
+ config = self.get_config(["--enable-values=foo,bar"])
+ self.assertIn(name, config)
+ self.assertTrue(config[name])
+ self.assertEqual(PositiveOptionValue(("foo", "bar")), config[name])
+
+ def test_values2(self):
+ self.test_values("VALUES2")
+
+ def test_values3(self):
+ self.test_values("VALUES3")
+
+ def test_returned_default(self):
+ config = self.get_config(["--enable-simple"])
+ self.assertIn("DEFAULTED", config)
+ self.assertEqual(PositiveOptionValue(("simple",)), config["DEFAULTED"])
+
+ config = self.get_config(["--disable-simple"])
+ self.assertIn("DEFAULTED", config)
+ self.assertEqual(PositiveOptionValue(("not-simple",)), config["DEFAULTED"])
+
+ def test_returned_choices(self):
+ for val in ("a", "b", "c"):
+ config = self.get_config(
+ ["--enable-values=alpha", "--returned-choices=%s" % val]
+ )
+ self.assertIn("CHOICES", config)
+ self.assertEqual(PositiveOptionValue((val,)), config["CHOICES"])
+
+ for val in ("0", "1", "2"):
+ config = self.get_config(
+ ["--enable-values=numeric", "--returned-choices=%s" % val]
+ )
+ self.assertIn("CHOICES", config)
+ self.assertEqual(PositiveOptionValue((val,)), config["CHOICES"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-values=numeric", "--returned-choices=a"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-values=alpha", "--returned-choices=0"])
+
+ def test_included(self):
+ config = self.get_config(env={"CC": "gcc"})
+ self.assertIn("IS_GCC", config)
+ self.assertEqual(config["IS_GCC"], True)
+
+ config = self.get_config(["--enable-include=extra.configure", "--extra"])
+ self.assertIn("EXTRA", config)
+ self.assertEqual(PositiveOptionValue(), config["EXTRA"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--extra"])
+
+ def test_template(self):
+ config = self.get_config(env={"CC": "gcc"})
+ self.assertIn("CFLAGS", config)
+ self.assertEqual(config["CFLAGS"], ["-Werror=foobar"])
+
+ config = self.get_config(env={"CC": "clang"})
+ self.assertNotIn("CFLAGS", config)
+
+ def test_imports(self):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, {}, ["configure"], out, out)
+
+ with self.assertRaises(ImportError):
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ def foo():
+ import sys
+ foo()"""
+ ),
+ sandbox,
+ )
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('sys')
+ def foo():
+ return sys"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sys)
+
+ # os.path after an import is a mix of vanilla os.path and sandbox os.path.
+ os_path = {}
+ exec_("from os.path import *", {}, os_path)
+ os_path.update(sandbox.OS.path.__dict__)
+ os_path = ReadOnlyNamespace(**os_path)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_from='os', _import='path')
+ def foo():
+ return path"""
+ ),
+ sandbox,
+ )
+
+ self.assertEqual(sandbox["foo"](), os_path)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_from='os', _import='path', _as='os_path')
+ def foo():
+ return os_path"""
+ ),
+ sandbox,
+ )
+
+ self.assertEqual(sandbox["foo"](), os_path)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('__builtin__')
+ def foo():
+ return __builtin__"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), six.moves.builtins)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_from='__builtin__', _import='open')
+ def foo():
+ return open('%s')"""
+ % os.devnull
+ ),
+ sandbox,
+ )
+
+ f = sandbox["foo"]()
+ self.assertEqual(f.name, os.devnull)
+ f.close()
+
+ # This unlocks the sandbox
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_import='__builtin__', _as='__builtins__')
+ def foo():
+ import sys
+ return sys"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sys)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('__sandbox__')
+ def foo():
+ return __sandbox__"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sandbox)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_import='__sandbox__', _as='s')
+ def foo():
+ return s"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sandbox)
+
+ # Nothing leaked from the function being executed
+ self.assertEqual(list(sandbox), ["__builtins__", "foo"])
+ self.assertEqual(sandbox["__builtins__"], ConfigureSandbox.BUILTINS)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('sys')
+ def foo():
+ @depends(when=True)
+ def bar():
+ return sys
+ return bar
+ bar = foo()"""
+ ),
+ sandbox,
+ )
+
+ with self.assertRaises(NameError) as e:
+ sandbox._depends[sandbox["bar"]].result()
+
+ self.assertIn("name 'sys' is not defined", str(e.exception))
+
+ def test_apply_imports(self):
+ imports = []
+
+ class CountApplyImportsSandbox(ConfigureSandbox):
+ def _apply_imports(self, *args, **kwargs):
+ imports.append((args, kwargs))
+ super(CountApplyImportsSandbox, self)._apply_imports(*args, **kwargs)
+
+ config = {}
+ out = StringIO()
+ sandbox = CountApplyImportsSandbox(config, {}, ["configure"], out, out)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('sys')
+ def foo():
+ return sys
+ foo()
+ foo()"""
+ ),
+ sandbox,
+ )
+
+ self.assertEqual(len(imports), 1)
+
+ def test_import_wrapping(self):
+ bar = object()
+ foo = ReadOnlyNamespace(bar=bar)
+
+ class BasicWrappingSandbox(ConfigureSandbox):
+ @memoized_property
+ def _wrapped_foo(self):
+ return foo
+
+ config = {}
+ out = StringIO()
+ sandbox = BasicWrappingSandbox(config, {}, ["configure"], out, out)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('foo')
+ def toplevel():
+ return foo
+ @template
+ @imports('foo.bar')
+ def bar():
+ return foo.bar
+ @template
+ @imports('foo.bar')
+ def bar_upper():
+ return foo
+ @template
+ @imports(_from='foo', _import='bar')
+ def from_import():
+ return bar
+ @template
+ @imports(_from='foo', _import='bar', _as='custom_name')
+ def from_import_as():
+ return custom_name
+ @template
+ @imports(_import='foo', _as='custom_name')
+ def import_as():
+ return custom_name
+ """
+ ),
+ sandbox,
+ )
+ self.assertIs(sandbox["toplevel"](), foo)
+ self.assertIs(sandbox["bar"](), bar)
+ self.assertIs(sandbox["bar_upper"](), foo)
+ self.assertIs(sandbox["from_import"](), bar)
+ self.assertIs(sandbox["from_import_as"](), bar)
+ self.assertIs(sandbox["import_as"](), foo)
+
+ def test_os_path(self):
+ config = self.get_config(["--with-imports=%s" % __file__])
+ self.assertIn("HAS_ABSPATH", config)
+ self.assertEqual(config["HAS_ABSPATH"], True)
+ self.assertIn("HAS_GETATIME", config)
+ self.assertEqual(config["HAS_GETATIME"], True)
+ self.assertIn("HAS_GETATIME2", config)
+ self.assertEqual(config["HAS_GETATIME2"], False)
+
+ def test_template_call(self):
+ config = self.get_config(env={"CC": "gcc"})
+ self.assertIn("TEMPLATE_VALUE", config)
+ self.assertEqual(config["TEMPLATE_VALUE"], 42)
+ self.assertIn("TEMPLATE_VALUE_2", config)
+ self.assertEqual(config["TEMPLATE_VALUE_2"], 21)
+
+ def test_template_imports(self):
+ config = self.get_config(["--enable-imports-in-template"])
+ self.assertIn("PLATFORM", config)
+ self.assertEqual(config["PLATFORM"], sys.platform)
+
+ def test_decorators(self):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, {}, ["configure"], out, out)
+
+ sandbox.include_file(mozpath.join(test_data_path, "decorators.configure"))
+
+ self.assertNotIn("FOO", sandbox)
+ self.assertNotIn("BAR", sandbox)
+ self.assertNotIn("QUX", sandbox)
+
+ def test_set_config(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="set_config.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config(["--set-foo"])
+ self.assertIn("FOO", config)
+ self.assertEqual(config["FOO"], True)
+
+ config = get_config(["--set-bar"])
+ self.assertNotIn("FOO", config)
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], True)
+
+ config = get_config(["--set-value=qux"])
+ self.assertIn("VALUE", config)
+ self.assertEqual(config["VALUE"], "qux")
+
+ config = get_config(["--set-name=hoge"])
+ self.assertIn("hoge", config)
+ self.assertEqual(config["hoge"], True)
+
+ config = get_config([])
+ self.assertEqual(config, {"BAR": False})
+
+ with self.assertRaises(ConfigureError):
+ # Both --set-foo and --set-name=FOO are going to try to
+ # set_config('FOO'...)
+ get_config(["--set-foo", "--set-name=FOO"])
+
+ def test_set_config_when(self):
+ with self.moz_configure(
+ """
+ option('--with-qux', help='qux')
+ set_config('FOO', 'foo', when=True)
+ set_config('BAR', 'bar', when=False)
+ set_config('QUX', 'qux', when='--with-qux')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ },
+ )
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ "QUX": "qux",
+ },
+ )
+
+ def test_set_config_when_disable(self):
+ with self.moz_configure(
+ """
+ option('--disable-baz', help='Disable baz')
+ set_config('BAZ', True, when='--enable-baz')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(config["BAZ"], True)
+ config = self.get_config(["--enable-baz"])
+ self.assertEqual(config["BAZ"], True)
+ config = self.get_config(["--disable-baz"])
+ self.assertEqual(config, {})
+
+ def test_set_define(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="set_define.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {"DEFINES": {}})
+
+ config = get_config(["--set-foo"])
+ self.assertIn("FOO", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["FOO"], True)
+
+ config = get_config(["--set-bar"])
+ self.assertNotIn("FOO", config["DEFINES"])
+ self.assertIn("BAR", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["BAR"], True)
+
+ config = get_config(["--set-value=qux"])
+ self.assertIn("VALUE", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["VALUE"], "qux")
+
+ config = get_config(["--set-name=hoge"])
+ self.assertIn("hoge", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["hoge"], True)
+
+ config = get_config([])
+ self.assertEqual(config["DEFINES"], {"BAR": False})
+
+ with self.assertRaises(ConfigureError):
+ # Both --set-foo and --set-name=FOO are going to try to
+ # set_define('FOO'...)
+ get_config(["--set-foo", "--set-name=FOO"])
+
+ def test_set_define_when(self):
+ with self.moz_configure(
+ """
+ option('--with-qux', help='qux')
+ set_define('FOO', 'foo', when=True)
+ set_define('BAR', 'bar', when=False)
+ set_define('QUX', 'qux', when='--with-qux')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config["DEFINES"],
+ {
+ "FOO": "foo",
+ },
+ )
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config["DEFINES"],
+ {
+ "FOO": "foo",
+ "QUX": "qux",
+ },
+ )
+
+ def test_set_define_when_disable(self):
+ with self.moz_configure(
+ """
+ option('--disable-baz', help='Disable baz')
+ set_define('BAZ', True, when='--enable-baz')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(config["DEFINES"]["BAZ"], True)
+ config = self.get_config(["--enable-baz"])
+ self.assertEqual(config["DEFINES"]["BAZ"], True)
+ config = self.get_config(["--disable-baz"])
+ self.assertEqual(config["DEFINES"], {})
+
+ def test_imply_option_simple(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/simple.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config = get_config(["--enable-foo"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], PositiveOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo", "--disable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--enable-bar' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line",
+ )
+
+ def test_imply_option_negative(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/negative.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config = get_config(["--enable-foo"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo", "--enable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--disable-bar' implied by '--enable-foo' conflicts with "
+ "'--enable-bar' from the command-line",
+ )
+
+ config = get_config(["--disable-hoge"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--disable-hoge", "--enable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--disable-bar' implied by '--disable-hoge' conflicts with "
+ "'--enable-bar' from the command-line",
+ )
+
+ def test_imply_option_values(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/values.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config = get_config(["--enable-foo=a"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], PositiveOptionValue(("a",)))
+
+ config = get_config(["--enable-foo=a,b"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], PositiveOptionValue(("a", "b")))
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo=a,b", "--disable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--enable-bar=a,b' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line",
+ )
+
+ def test_imply_option_infer(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/infer.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo", "--disable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--enable-bar' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config([], configure="imply_option/infer_ko.configure")
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot infer what implies '--enable-bar'. Please add a `reason` "
+ "to the `imply_option` call.",
+ )
+
+ def test_imply_option_immediate_value(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/imm.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config_path = mozpath.abspath(
+ mozpath.join(test_data_path, "imply_option", "imm.configure")
+ )
+
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--enable-foo' implied by 'imply_option at %s:7' conflicts "
+ "with '--disable-foo' from the command-line" % config_path,
+ ):
+ get_config(["--disable-foo"])
+
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--enable-bar=foo,bar' implied by 'imply_option at %s:18' "
+ "conflicts with '--enable-bar=a,b,c' from the command-line" % config_path,
+ ):
+ get_config(["--enable-bar=a,b,c"])
+
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--enable-baz=BAZ' implied by 'imply_option at %s:29' "
+ "conflicts with '--enable-baz=QUUX' from the command-line" % config_path,
+ ):
+ get_config(["--enable-baz=QUUX"])
+
+ def test_imply_option_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ imply_option('--with-foo', ('a',), 'bar')
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "`--with-foo`, emitted from `%s` line 2, is unknown."
+ % mozpath.join(test_data_path, "moz.configure"),
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ imply_option('--with-foo', 42, 'bar')
+
+ option('--with-foo', help='foo')
+ @depends('--with-foo')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ def test_imply_option_when(self):
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+ imply_option('--with-qux', True, when='--with-foo')
+ option('--with-qux', help='qux')
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ def test_imply_option_dependency_loop(self):
+ with self.moz_configure(
+ """
+ option('--without-foo', help='foo')
+
+ @depends('--with-foo')
+ def qux_default(foo):
+ return bool(foo)
+
+ option('--with-qux', default=qux_default, help='qux')
+
+ imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--without-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by '--with-qux' conflicts "
+ "with '--without-foo' from the command-line",
+ )
+
+ config = self.get_config(["--without-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+
+ @depends('--with-foo')
+ def qux_default(foo):
+ return bool(foo)
+
+ option('--with-qux', default=qux_default, help='qux')
+
+ imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by '--with-qux' conflicts "
+ "with '--without-foo' from the default",
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by '--with-qux' conflicts "
+ "with '--without-foo' from the command-line",
+ )
+
+ config = self.get_config(["--without-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure"))
+
+ # Same test as above, but using `when` in the `imply_option`.
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+
+ @depends('--with-foo')
+ def qux_default(foo):
+ return bool(foo)
+
+ option('--with-qux', default=qux_default, help='qux')
+
+ imply_option('--with-foo', True, when='--with-qux')
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by 'imply_option at %s:10' conflicts "
+ "with '--without-foo' from the default" % config_path,
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by 'imply_option at %s:10' conflicts "
+ "with '--without-foo' from the command-line" % config_path,
+ )
+
+ config = self.get_config(["--without-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ def test_imply_option_recursion(self):
+ config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure"))
+
+ message = (
+ "'--without-foo' appears somewhere in the direct or indirect dependencies "
+ "when resolving imply_option at %s:8" % config_path
+ )
+
+ with self.moz_configure(
+ """
+ option('--without-foo', help='foo')
+
+ imply_option('--with-qux', depends('--with-foo')(lambda x: x or None))
+
+ option('--with-qux', help='qux')
+
+ imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ # Note: no error is detected when the depends function in the
+ # imply_options resolve to None, which disables the imply_option.
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(str(e.exception), message)
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config(["--with-qux"])
+
+ self.assertEqual(str(e.exception), message)
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(str(e.exception), message)
+
+ def test_option_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('option("--with-foo", help="foo")'):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Option `--with-foo` is not handled ; reference it with a @depends",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option("--with-foo", help="foo")
+ option("--with-foo", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `--with-foo` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option(env="MOZ_FOO", help="foo")
+ option(env="MOZ_FOO", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--with-foo', env="MOZ_FOO", help="foo")
+ option(env="MOZ_FOO", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option(env="MOZ_FOO", help="foo")
+ option('--with-foo', env="MOZ_FOO", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--with-foo', env="MOZ_FOO", help="foo")
+ option('--with-foo', help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `--with-foo` already defined")
+
+ def test_option_when(self):
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo', when=True)
+ option('--with-bar', help='bar', when=False)
+ option('--with-qux', env="QUX", help='qux', when='--with-foo')
+
+ set_config('FOO', depends('--with-foo', when=True)(lambda x: x))
+ set_config('BAR', depends('--with-bar', when=False)(lambda x: x))
+ set_config('QUX', depends('--with-qux', when='--with-foo')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo", "--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-bar"])
+
+ self.assertEqual(
+ str(e.exception), "--with-bar is not available in this configuration"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception), "--with-qux is not available in this configuration"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["QUX=1"])
+
+ self.assertEqual(
+ str(e.exception), "QUX is not available in this configuration"
+ )
+
+ config = self.get_config(env={"QUX": "1"})
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ },
+ )
+
+ help, config = self.get_config(["--help"])
+ self.assertEqual(
+ help.replace("\\", "/"),
+ textwrap.dedent(
+ """\
+ Usage: configure [options]
+
+ Options: [defaults in brackets after descriptions]
+ Help options:
+ --help print this message
+
+ Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:
+ --with-foo foo
+
+
+ Environment variables:
+ """
+ ),
+ )
+
+ help, config = self.get_config(["--help", "--with-foo"])
+ self.assertEqual(
+ help.replace("\\", "/"),
+ textwrap.dedent(
+ """\
+ Usage: configure [options]
+
+ Options: [defaults in brackets after descriptions]
+ Help options:
+ --help print this message
+
+ Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:
+ --with-foo foo
+ --with-qux qux
+
+
+ Environment variables:
+ """
+ ),
+ )
+
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo', when=True)
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ """
+ ):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "@depends function needs the same `when` as " "options it depends on",
+ )
+
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def always():
+ return True
+ @depends(when=True)
+ def always2():
+ return True
+ option('--with-foo', help='foo', when=always)
+ set_config('FOO', depends('--with-foo', when=always2)(lambda x: x))
+ """
+ ):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "@depends function needs the same `when` as " "options it depends on",
+ )
+
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def always():
+ return True
+ @depends(when=True)
+ def always2():
+ return True
+ with only_when(always2):
+ option('--with-foo', help='foo', when=always)
+ # include() triggers resolution of its dependencies, and their
+ # side effects.
+ include(depends('--with-foo', when=always)(lambda x: x))
+ # The sandbox should figure that the `when` here is
+ # appropriate. Bad behavior in CombinedDependsFunction.__eq__
+ # made this fail in the past.
+ set_config('FOO', depends('--with-foo', when=always)(lambda x: x))
+ """
+ ):
+ self.get_config()
+
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+ option('--without-bar', help='bar', when='--with-foo')
+ option('--with-qux', help='qux', when='--with-bar')
+ set_config('QUX', True, when='--with-qux')
+ """
+ ):
+ # These are valid:
+ self.get_config(["--with-foo"])
+ self.get_config(["--with-foo", "--with-bar"])
+ self.get_config(["--with-foo", "--without-bar"])
+ self.get_config(["--with-foo", "--with-bar", "--with-qux"])
+ self.get_config(["--with-foo", "--with-bar", "--without-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-bar"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--without-bar"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--without-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-foo", "--without-bar", "--with-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-foo", "--without-bar", "--without-qux"])
+
+ def test_include_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('include("../foo.configure")'):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot include `%s` because it is not in a subdirectory of `%s`"
+ % (
+ mozpath.normpath(mozpath.join(test_data_path, "..", "foo.configure")),
+ mozpath.normsep(test_data_path),
+ ),
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ include('extra.configure')
+ include('extra.configure')
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot include `%s` because it was included already."
+ % mozpath.normpath(mozpath.join(test_data_path, "extra.configure")),
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ include(42)
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ def test_include_when(self):
+ with MockedOpen(
+ {
+ os.path.join(test_data_path, "moz.configure"): textwrap.dedent(
+ """
+ option('--with-foo', help='foo')
+
+ include('always.configure', when=True)
+ include('never.configure', when=False)
+ include('foo.configure', when='--with-foo')
+
+ set_config('FOO', foo)
+ set_config('BAR', bar)
+ set_config('QUX', qux)
+ """
+ ),
+ os.path.join(test_data_path, "always.configure"): textwrap.dedent(
+ """
+ option('--with-bar', help='bar')
+ @depends('--with-bar')
+ def bar(x):
+ if x:
+ return 'bar'
+ """
+ ),
+ os.path.join(test_data_path, "never.configure"): textwrap.dedent(
+ """
+ option('--with-qux', help='qux')
+ @depends('--with-qux')
+ def qux(x):
+ if x:
+ return 'qux'
+ """
+ ),
+ os.path.join(test_data_path, "foo.configure"): textwrap.dedent(
+ """
+ option('--with-foo-really', help='really foo')
+ @depends('--with-foo-really')
+ def foo(x):
+ if x:
+ return 'foo'
+
+ include('foo2.configure', when='--with-foo-really')
+ """
+ ),
+ os.path.join(test_data_path, "foo2.configure"): textwrap.dedent(
+ """
+ set_config('FOO2', True)
+ """
+ ),
+ }
+ ):
+ config = self.get_config()
+ self.assertEqual(config, {})
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(config, {})
+
+ config = self.get_config(["--with-bar"])
+ self.assertEqual(
+ config,
+ {
+ "BAR": "bar",
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception), "--with-qux is not available in this configuration"
+ )
+
+ config = self.get_config(["--with-foo", "--with-foo-really"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ "FOO2": True,
+ },
+ )
+
+ def test_sandbox_failures(self):
+ with self.assertRaises(KeyError) as e:
+ with self.moz_configure(
+ """
+ include = 42
+ """
+ ):
+ self.get_config()
+
+ self.assertIn("Cannot reassign builtins", str(e.exception))
+
+ with self.assertRaises(KeyError) as e:
+ with self.moz_configure(
+ """
+ foo = 42
+ """
+ ):
+ self.get_config()
+
+ self.assertIn(
+ "Cannot assign `foo` because it is neither a @depends nor a " "@template",
+ str(e.exception),
+ )
+
+ def test_depends_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends()
+ def foo():
+ return
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "@depends needs at least one argument")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends('--with-foo')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' is not a known option. Maybe it's " "declared too late?",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends('--with-foo=42')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option must not contain an '='")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @depends(42)
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot use object of type 'int' as argument " "to @depends",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends('--help')
+ def foo(value):
+ yield
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "Cannot decorate generator functions with @depends"
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @depends('--help')
+ def foo(value):
+ return value
+
+ depends('--help')(foo)
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Cannot nest @depends functions")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @template
+ def foo(f):
+ pass
+
+ depends('--help')(foo)
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Cannot use a @template function here")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ foo()
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "The `foo` function may not be called")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @depends('--help', foo=42)
+ def foo(_):
+ return
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "depends_impl() got an unexpected keyword argument 'foo'"
+ )
+
+ def test_depends_when(self):
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def foo():
+ return 'foo'
+
+ set_config('FOO', foo)
+
+ @depends(when=False)
+ def bar():
+ return 'bar'
+
+ set_config('BAR', bar)
+
+ option('--with-qux', help='qux')
+ @depends(when='--with-qux')
+ def qux():
+ return 'qux'
+
+ set_config('QUX', qux)
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ },
+ )
+
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ "QUX": "qux",
+ },
+ )
+
+ def test_depends_value(self):
+ with self.moz_configure(
+ """
+ foo = depends(when=True)('foo')
+
+ set_config('FOO', foo)
+
+ bar = depends(when=False)('bar')
+
+ set_config('BAR', bar)
+
+ option('--with-qux', help='qux')
+ @depends(when='--with-qux')
+ def qux():
+ return 'qux'
+
+ set_config('QUX', qux)
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ },
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+
+ depends('--foo')('foo')
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "Cannot wrap literal values in @depends with dependencies"
+ )
+
+ def test_imports_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @imports('os')
+ @template
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "@imports must appear after @template")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @imports('os')
+ @depends('--foo')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "@imports must appear after @depends")
+
+ for import_ in (
+ "42",
+ "_from=42, _import='os'",
+ "_from='os', _import='path', _as=42",
+ ):
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @imports(%s)
+ @template
+ def foo(value):
+ return value
+ """
+ % import_
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @imports('os', 42)
+ @template
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ with self.assertRaises(ValueError) as e:
+ with self.moz_configure(
+ """
+ @imports('os*')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Invalid argument to @imports: 'os*'")
+
+ def test_only_when(self):
+ moz_configure = """
+ option('--enable-when', help='when')
+ @depends('--enable-when', '--help')
+ def when(value, _):
+ return bool(value)
+
+ with only_when(when):
+ option('--foo', nargs='*', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ set_config('FOO', foo)
+ set_define('FOO', foo)
+
+ # It is possible to depend on a function defined in a only_when
+ # block. It then resolves to `None`.
+ set_config('BAR', depends(foo)(lambda x: x))
+ set_define('BAR', depends(foo)(lambda x: x))
+ """
+
+ with self.moz_configure(moz_configure):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "DEFINES": {},
+ },
+ )
+
+ config = self.get_config(["--enable-when"])
+ self.assertEqual(
+ config,
+ {
+ "BAR": NegativeOptionValue(),
+ "FOO": NegativeOptionValue(),
+ "DEFINES": {
+ "BAR": NegativeOptionValue(),
+ "FOO": NegativeOptionValue(),
+ },
+ },
+ )
+
+ config = self.get_config(["--enable-when", "--foo=bar"])
+ self.assertEqual(
+ config,
+ {
+ "BAR": PositiveOptionValue(["bar"]),
+ "FOO": PositiveOptionValue(["bar"]),
+ "DEFINES": {
+ "BAR": PositiveOptionValue(["bar"]),
+ "FOO": PositiveOptionValue(["bar"]),
+ },
+ },
+ )
+
+ # The --foo option doesn't exist when --enable-when is not given.
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--foo"])
+
+ self.assertEqual(
+ str(e.exception), "--foo is not available in this configuration"
+ )
+
+ # Cannot depend on an option defined in a only_when block, because we
+ # don't know what OptionValue would make sense.
+ with self.moz_configure(
+ moz_configure
+ + """
+ set_config('QUX', depends('--foo')(lambda x: x))
+ """
+ ):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "@depends function needs the same `when` as " "options it depends on",
+ )
+
+ with self.moz_configure(
+ moz_configure
+ + """
+ set_config('QUX', depends('--foo', when=when)(lambda x: x))
+ """
+ ):
+ self.get_config(["--enable-when"])
+
+ # Using imply_option for an option defined in a only_when block fails
+ # similarly if the imply_option happens outside the block.
+ with self.moz_configure(
+ """
+ imply_option('--foo', True)
+ """
+ + moz_configure
+ ):
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "--foo is not available in this configuration"
+ )
+
+ # And similarly doesn't fail when the condition is true.
+ with self.moz_configure(
+ """
+ imply_option('--foo', True)
+ """
+ + moz_configure
+ ):
+ self.get_config(["--enable-when"])
+
+ def test_depends_binary_ops(self):
+ with self.moz_configure(
+ """
+ option('--foo', nargs=1, help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value or 0
+
+ option('--bar', nargs=1, help='bar')
+ @depends('--bar')
+ def bar(value):
+ return value or ''
+
+ option('--baz', nargs=1, help='baz')
+ @depends('--baz')
+ def baz(value):
+ return value
+
+ set_config('FOOorBAR', foo | bar)
+ set_config('FOOorBARorBAZ', foo | bar | baz)
+ set_config('FOOandBAR', foo & bar)
+ set_config('FOOandBARandBAZ', foo & bar & baz)
+ """
+ ):
+ for foo_opt, foo_value in (
+ ("", 0),
+ ("--foo=foo", PositiveOptionValue(("foo",))),
+ ):
+ for bar_opt, bar_value in (
+ ("", ""),
+ ("--bar=bar", PositiveOptionValue(("bar",))),
+ ):
+ for baz_opt, baz_value in (
+ ("", NegativeOptionValue()),
+ ("--baz=baz", PositiveOptionValue(("baz",))),
+ ):
+ config = self.get_config(
+ [x for x in (foo_opt, bar_opt, baz_opt) if x]
+ )
+ self.assertEqual(
+ config,
+ {
+ "FOOorBAR": foo_value or bar_value,
+ "FOOorBARorBAZ": foo_value or bar_value or baz_value,
+ "FOOandBAR": foo_value and bar_value,
+ "FOOandBARandBAZ": foo_value
+ and bar_value
+ and baz_value,
+ },
+ )
+
+ def test_depends_getattr(self):
+ with self.moz_configure(
+ """
+ @imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
+ def namespace(**kwargs):
+ return ReadOnlyNamespace(**kwargs)
+
+ option('--foo', nargs=1, help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ option('--bar', nargs=1, help='bar')
+ @depends('--bar')
+ def bar(value):
+ return value or None
+
+ @depends(foo, bar)
+ def foobar(foo, bar):
+ return namespace(foo=foo, bar=bar)
+
+ set_config('FOO', foobar.foo)
+ set_config('BAR', foobar.bar)
+ set_config('BAZ', foobar.baz)
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--foo=foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(("foo",)),
+ },
+ )
+
+ config = self.get_config(["--bar=bar"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "BAR": PositiveOptionValue(("bar",)),
+ },
+ )
+
+ config = self.get_config(["--foo=foo", "--bar=bar"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(("foo",)),
+ "BAR": PositiveOptionValue(("bar",)),
+ },
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_lint.py b/python/mozbuild/mozbuild/test/configure/test_lint.py
new file mode 100644
index 0000000000..7ecac769c3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_lint.py
@@ -0,0 +1,487 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import contextlib
+import os
+import sys
+import textwrap
+import traceback
+import unittest
+
+import mozpack.path as mozpath
+from mozunit import MockedOpen, main
+
+from mozbuild.configure import ConfigureError
+from mozbuild.configure.lint import LintSandbox
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+
+class TestLint(unittest.TestCase):
+ def lint_test(self, options=[], env={}):
+ sandbox = LintSandbox(env, ["configure"] + options)
+
+ sandbox.run(mozpath.join(test_data_path, "moz.configure"))
+
+ def moz_configure(self, source):
+ return MockedOpen(
+ {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)}
+ )
+
+ @contextlib.contextmanager
+ def assertRaisesFromLine(self, exc_type, line):
+ with self.assertRaises(exc_type) as e:
+ yield e
+
+ _, _, tb = sys.exc_info()
+ self.assertEqual(
+ traceback.extract_tb(tb)[-1][:2],
+ (mozpath.join(test_data_path, "moz.configure"), line),
+ )
+
+ def test_configure_testcase(self):
+ # Lint python/mozbuild/mozbuild/test/configure/data/moz.configure
+ self.lint_test()
+
+ def test_depends_failures(self):
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ @imports('os')
+ def bar(help, foo):
+ return foo
+ """
+ ):
+ self.lint_test()
+
+ with self.assertRaisesFromLine(ConfigureError, 7) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ def bar(help, foo):
+ return foo
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `--help` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ @imports('os')
+ def bar(help, foo):
+ return foo
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(
+ str(e.exception),
+ "Missing '--help' dependency because `bar` depends on '--help' and `foo`",
+ )
+
+ with self.assertRaisesFromLine(ConfigureError, 7) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ qux = 42
+
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ qux
+ return value
+
+ @depends('--help', foo)
+ @imports('os')
+ def bar(help, foo):
+ return foo
+ tmpl()
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(
+ str(e.exception),
+ "Missing '--help' dependency because `bar` depends on '--help' and `foo`",
+ )
+
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ @depends(foo)
+ def bar(value):
+ return value
+
+ include(bar)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ option('--bar', help='bar', when=foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ # This would have failed with "Missing '--help' dependency"
+ # in the past, because of the reference to the builtin False.
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return False or value
+
+ option('--bar', help='bar', when=foo)
+ """
+ ):
+ self.lint_test()
+
+ # However, when something that is normally a builtin is overridden,
+ # we should still want the dependency on --help.
+ with self.assertRaisesFromLine(ConfigureError, 7) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ sorted = 42
+
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return sorted
+
+ option('--bar', help='bar', when=foo)
+ tmpl()
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ # There is a default restricted `os` module when there is no explicit
+ # @imports, and it's fine to use it without a dependency on --help.
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ os
+ return value
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `--foo` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 5) as e:
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def bar():
+ return
+ @depends(bar)
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `bar` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ @depends(depends(when=True)(lambda: None))
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `<lambda>` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 9) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ @depends(when=True)
+ def bar():
+ return
+ return bar
+ qux = tmpl()
+ @depends(qux)
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `qux` is unused")
+
+ def test_default_enable(self):
+ # --enable-* with default=True is not allowed.
+ with self.moz_configure(
+ """
+ option('--enable-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--enable-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--disable-foo should be used instead of " "--enable-foo with default=True",
+ )
+
+ def test_default_disable(self):
+ # --disable-* with default=False is not allowed.
+ with self.moz_configure(
+ """
+ option('--disable-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--disable-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--enable-foo should be used instead of "
+ "--disable-foo with default=False",
+ )
+
+ def test_default_with(self):
+ # --with-* with default=True is not allowed.
+ with self.moz_configure(
+ """
+ option('--with-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--with-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--without-foo should be used instead of " "--with-foo with default=True",
+ )
+
+ def test_default_without(self):
+ # --without-* with default=False is not allowed.
+ with self.moz_configure(
+ """
+ option('--without-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--without-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--with-foo should be used instead of " "--without-foo with default=False",
+ )
+
+ def test_default_func(self):
+ # Help text for an option with variable default should contain
+ # {enable|disable} rule.
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),
+ help='{Enable|Disable} bar')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\
+ help='Enable bar')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ '`help` should contain "{Enable|Disable}" because of '
+ "non-constant default",
+ )
+
+ def test_large_offset(self):
+ with self.assertRaisesFromLine(ConfigureError, 375):
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ """
+ + "\n" * 371
+ + """
+ option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\
+ help='Enable bar')
+ """
+ ):
+ self.lint_test()
+
+ def test_undefined_global(self):
+ with self.assertRaisesFromLine(NameError, 6) as e:
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ @depends('FOO')
+ def foo(value):
+ if value:
+ return unknown
+ return value
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "global name 'unknown' is not defined")
+
+ # Ideally, this would raise on line 4, where `unknown` is used, but
+ # python disassembly doesn't give use the information.
+ with self.assertRaisesFromLine(NameError, 2) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ @depends(unknown)
+ def foo(value):
+ if value:
+ return True
+ return foo
+ tmpl()
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "global name 'unknown' is not defined")
+
+ def test_unnecessary_imports(self):
+ with self.assertRaisesFromLine(NameError, 3) as e:
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ @depends('FOO')
+ @imports(_from='__builtin__', _import='list')
+ def foo(value):
+ if value:
+ return list()
+ return value
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "builtin 'list' doesn't need to be imported")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_moz_configure.py b/python/mozbuild/mozbuild/test/configure/test_moz_configure.py
new file mode 100644
index 0000000000..22129a3970
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_moz_configure.py
@@ -0,0 +1,185 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from mozunit import main
+
+from common import BaseConfigureTest, ConfigureTestSandbox
+from mozbuild.util import ReadOnlyNamespace, exec_, memoized_property
+
+
+def sandbox_class(platform):
+ class ConfigureTestSandboxOverridingPlatform(ConfigureTestSandbox):
+ @memoized_property
+ def _wrapped_sys(self):
+ sys = {}
+ exec_("from sys import *", sys)
+ sys["platform"] = platform
+ return ReadOnlyNamespace(**sys)
+
+ return ConfigureTestSandboxOverridingPlatform
+
+
+class TargetTest(BaseConfigureTest):
+ def get_target(self, args, env={}):
+ if "linux" in self.HOST:
+ platform = "linux2"
+ elif "mingw" in self.HOST or "windows" in self.HOST:
+ platform = "win32"
+ elif "openbsd6" in self.HOST:
+ platform = "openbsd6"
+ else:
+ raise Exception("Missing platform for HOST {}".format(self.HOST))
+ sandbox = self.get_sandbox({}, {}, args, env, cls=sandbox_class(platform))
+ return sandbox._value_for(sandbox["target"]).alias
+
+
+class TestTargetLinux(TargetTest):
+ def test_target(self):
+ self.assertEqual(self.get_target([]), self.HOST)
+ self.assertEqual(self.get_target(["--target=i686"]), "i686-pc-linux-gnu")
+ self.assertEqual(
+ self.get_target(["--target=i686-unknown-linux-gnu"]),
+ "i686-unknown-linux-gnu",
+ )
+ self.assertEqual(
+ self.get_target(["--target=i686-pc-windows-msvc"]), "i686-pc-windows-msvc"
+ )
+
+
+class TestTargetWindows(TargetTest):
+ # BaseConfigureTest uses this as the return value for config.guess
+ HOST = "i686-pc-windows-msvc"
+
+ def test_target(self):
+ self.assertEqual(self.get_target([]), self.HOST)
+ self.assertEqual(
+ self.get_target(["--target=x86_64-pc-windows-msvc"]),
+ "x86_64-pc-windows-msvc",
+ )
+ self.assertEqual(self.get_target(["--target=x86_64"]), "x86_64-pc-windows-msvc")
+
+ # The tests above are actually not realistic, because most Windows
+ # machines will have a few environment variables that make us not
+ # use config.guess.
+
+ # 32-bits process on x86_64 host.
+ env = {
+ "PROCESSOR_ARCHITECTURE": "x86",
+ "PROCESSOR_ARCHITEW6432": "AMD64",
+ }
+ self.assertEqual(self.get_target([], env), "x86_64-pc-windows-msvc")
+ self.assertEqual(
+ self.get_target(["--target=i686-pc-windows-msvc"]), "i686-pc-windows-msvc"
+ )
+ self.assertEqual(self.get_target(["--target=i686"]), "i686-pc-windows-msvc")
+
+ # 64-bits process on x86_64 host.
+ env = {
+ "PROCESSOR_ARCHITECTURE": "AMD64",
+ }
+ self.assertEqual(self.get_target([], env), "x86_64-pc-windows-msvc")
+ self.assertEqual(
+ self.get_target(["--target=i686-pc-windows-msvc"]), "i686-pc-windows-msvc"
+ )
+ self.assertEqual(self.get_target(["--target=i686"]), "i686-pc-windows-msvc")
+
+ # 32-bits process on x86 host.
+ env = {
+ "PROCESSOR_ARCHITECTURE": "x86",
+ }
+ self.assertEqual(self.get_target([], env), "i686-pc-windows-msvc")
+ self.assertEqual(
+ self.get_target(["--target=x86_64-pc-windows-msvc"]),
+ "x86_64-pc-windows-msvc",
+ )
+ self.assertEqual(self.get_target(["--target=x86_64"]), "x86_64-pc-windows-msvc")
+
+ # While host autodection will give us a -windows-msvc triplet, setting host
+ # is expecting to implicitly set the target.
+ self.assertEqual(
+ self.get_target(["--host=x86_64-pc-windows-gnu"]), "x86_64-pc-windows-gnu"
+ )
+ self.assertEqual(
+ self.get_target(["--host=x86_64-pc-mingw32"]), "x86_64-pc-mingw32"
+ )
+
+
+class TestTargetAndroid(TargetTest):
+ HOST = "x86_64-pc-linux-gnu"
+
+ def test_target(self):
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android"]),
+ "arm-unknown-linux-androideabi",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=i686"]),
+ "i686-unknown-linux-android",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=x86_64"]),
+ "x86_64-unknown-linux-android",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=aarch64"]),
+ "aarch64-unknown-linux-android",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=arm"]),
+ "arm-unknown-linux-androideabi",
+ )
+
+
+class TestTargetOpenBSD(TargetTest):
+ # config.guess returns amd64 on OpenBSD, which we need to pass through to
+ # config.sub so that it canonicalizes to x86_64.
+ HOST = "amd64-unknown-openbsd6.4"
+
+ def test_target(self):
+ self.assertEqual(self.get_target([]), "x86_64-unknown-openbsd6.4")
+
+ def config_sub(self, stdin, args):
+ if args[0] == "amd64-unknown-openbsd6.4":
+ return 0, "x86_64-unknown-openbsd6.4", ""
+ return super(TestTargetOpenBSD, self).config_sub(stdin, args)
+
+
+class TestMozConfigure(BaseConfigureTest):
+ def test_nsis_version(self):
+ this = self
+
+ class FakeNSIS(object):
+ def __init__(self, version):
+ self.version = version
+
+ def __call__(self, stdin, args):
+ this.assertEqual(args, ("-version",))
+ return 0, self.version, ""
+
+ def check_nsis_version(version):
+ sandbox = self.get_sandbox(
+ {"/usr/bin/makensis": FakeNSIS(version)},
+ {},
+ ["--target=x86_64-pc-windows-msvc", "--disable-bootstrap"],
+ {"PATH": "/usr/bin", "MAKENSISU": "/usr/bin/makensis"},
+ )
+ return sandbox._value_for(sandbox["nsis_version"])
+
+ with self.assertRaises(SystemExit):
+ check_nsis_version("v2.5")
+
+ with self.assertRaises(SystemExit):
+ check_nsis_version("v3.0a2")
+
+ self.assertEqual(check_nsis_version("v3.0b1"), "3.0b1")
+ self.assertEqual(check_nsis_version("v3.0b2"), "3.0b2")
+ self.assertEqual(check_nsis_version("v3.0rc1"), "3.0rc1")
+ self.assertEqual(check_nsis_version("v3.0"), "3.0")
+ self.assertEqual(check_nsis_version("v3.0-2"), "3.0")
+ self.assertEqual(check_nsis_version("v3.0.1"), "3.0")
+ self.assertEqual(check_nsis_version("v3.1"), "3.1")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_options.py b/python/mozbuild/mozbuild/test/configure/test_options.py
new file mode 100644
index 0000000000..59ba616355
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_options.py
@@ -0,0 +1,905 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import unittest
+
+from mozunit import main
+
+from mozbuild.configure.options import (
+ CommandLineHelper,
+ ConflictingOptionError,
+ InvalidOptionError,
+ NegativeOptionValue,
+ Option,
+ OptionValue,
+ PositiveOptionValue,
+)
+
+
+class Option(Option):
+ def __init__(self, *args, **kwargs):
+ kwargs["help"] = "Dummy help"
+ super(Option, self).__init__(*args, **kwargs)
+
+
+class TestOption(unittest.TestCase):
+ def test_option(self):
+ option = Option("--option")
+ self.assertEqual(option.prefix, "")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option("--enable-option")
+ self.assertEqual(option.prefix, "enable")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option("--disable-option")
+ self.assertEqual(option.prefix, "disable")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertTrue(option.default)
+
+ option = Option("--with-option")
+ self.assertEqual(option.prefix, "with")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option("--without-option")
+ self.assertEqual(option.prefix, "without")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertTrue(option.default)
+
+ option = Option("--without-option-foo", env="MOZ_OPTION")
+ self.assertEqual(option.env, "MOZ_OPTION")
+
+ option = Option(env="MOZ_OPTION")
+ self.assertEqual(option.prefix, "")
+ self.assertEqual(option.name, None)
+ self.assertEqual(option.env, "MOZ_OPTION")
+ self.assertFalse(option.default)
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=0, default=("a",))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=1, default=())
+ self.assertEqual(
+ str(e.exception), "default must be a bool, a string or a tuple of strings"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=1, default=True)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=1, default=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=2, default=())
+ self.assertEqual(
+ str(e.exception), "default must be a bool, a string or a tuple of strings"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=2, default=True)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=2, default=("a",))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="?", default=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="+", default=())
+ self.assertEqual(
+ str(e.exception), "default must be a bool, a string or a tuple of strings"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="+", default=True)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ # --disable options with a nargs value that requires at least one
+ # argument need to be given a default.
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs=1)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs="+")
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ # Test nargs inference from default value
+ option = Option("--with-foo", default=True)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option("--with-foo", default=False)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option("--with-foo", default="a")
+ self.assertEqual(option.nargs, "?")
+
+ option = Option("--with-foo", default=("a",))
+ self.assertEqual(option.nargs, "?")
+
+ option = Option("--with-foo", default=("a", "b"))
+ self.assertEqual(option.nargs, "*")
+
+ option = Option(env="FOO", default=True)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option(env="FOO", default=False)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option(env="FOO", default="a")
+ self.assertEqual(option.nargs, "?")
+
+ option = Option(env="FOO", default=("a",))
+ self.assertEqual(option.nargs, "?")
+
+ option = Option(env="FOO", default=("a", "b"))
+ self.assertEqual(option.nargs, "*")
+
+ def test_option_option(self):
+ for option in (
+ "--option",
+ "--enable-option",
+ "--disable-option",
+ "--with-option",
+ "--without-option",
+ ):
+ self.assertEqual(Option(option).option, option)
+ self.assertEqual(Option(option, env="FOO").option, option)
+
+ opt = Option(option, default=False)
+ self.assertEqual(
+ opt.option,
+ option.replace("-disable-", "-enable-").replace("-without-", "-with-"),
+ )
+
+ opt = Option(option, default=True)
+ self.assertEqual(
+ opt.option,
+ option.replace("-enable-", "-disable-").replace("-with-", "-without-"),
+ )
+
+ self.assertEqual(Option(env="FOO").option, "FOO")
+
+ def test_option_choices(self):
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=3, choices=("a", "b"))
+ self.assertEqual(str(e.exception), "Not enough `choices` for `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", nargs=1, choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "A `default` must be given along with `choices`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", nargs="+", choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "A `default` must be given along with `choices`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", default="c", choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The `default` value must be one of 'a', 'b'"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option(
+ "--without-option",
+ default=(
+ "a",
+ "c",
+ ),
+ choices=("a", "b"),
+ )
+ self.assertEqual(
+ str(e.exception), "The `default` value must be one of 'a', 'b'"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", default=("c",), choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The `default` value must be one of 'a', 'b'"
+ )
+
+ option = Option("--with-option", nargs="+", choices=("a", "b"))
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=c")
+ self.assertEqual(str(e.exception), "'c' is not one of 'a', 'b'")
+
+ value = option.get_value("--with-option=b,a")
+ self.assertTrue(value)
+ self.assertEqual(PositiveOptionValue(("b", "a")), value)
+
+ option = Option("--without-option", nargs="*", default="a", choices=("a", "b"))
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=c")
+ self.assertEqual(str(e.exception), "'c' is not one of 'a', 'b'")
+
+ value = option.get_value("--with-option=b,a")
+ self.assertTrue(value)
+ self.assertEqual(PositiveOptionValue(("b", "a")), value)
+
+ # Test nargs inference from choices
+ option = Option("--with-option", choices=("a", "b"))
+ self.assertEqual(option.nargs, 1)
+
+ # Test "relative" values
+ option = Option(
+ "--with-option", nargs="*", default=("b", "c"), choices=("a", "b", "c", "d")
+ )
+
+ value = option.get_value("--with-option=+d")
+ self.assertEqual(PositiveOptionValue(("b", "c", "d")), value)
+
+ value = option.get_value("--with-option=-b")
+ self.assertEqual(PositiveOptionValue(("c",)), value)
+
+ value = option.get_value("--with-option=-b,+d")
+ self.assertEqual(PositiveOptionValue(("c", "d")), value)
+
+ # Adding something that is in the default is fine
+ value = option.get_value("--with-option=+b")
+ self.assertEqual(PositiveOptionValue(("b", "c")), value)
+
+ # Removing something that is not in the default is fine, as long as it
+ # is one of the choices
+ value = option.get_value("--with-option=-a")
+ self.assertEqual(PositiveOptionValue(("b", "c")), value)
+
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=-e")
+ self.assertEqual(str(e.exception), "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ # Other "not a choice" errors.
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=+e")
+ self.assertEqual(str(e.exception), "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=e")
+ self.assertEqual(str(e.exception), "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ def test_option_value_compare(self):
+ # OptionValue are tuple and equivalence should compare as tuples.
+ val = PositiveOptionValue(("foo",))
+
+ self.assertEqual(val[0], "foo")
+ self.assertEqual(val, PositiveOptionValue(("foo",)))
+ self.assertNotEqual(val, PositiveOptionValue(("foo", "bar")))
+
+ # Can compare a tuple to an OptionValue.
+ self.assertEqual(val, ("foo",))
+ self.assertNotEqual(val, ("foo", "bar"))
+
+ # Different OptionValue types are never equal.
+ self.assertNotEqual(val, OptionValue(("foo",)))
+
+ # For usability reasons, we raise TypeError when attempting to compare
+ # against a non-tuple.
+ with self.assertRaisesRegexp(TypeError, "cannot compare a"):
+ val == "foo"
+
+ # But we allow empty option values to compare otherwise we can't
+ # easily compare value-less types like PositiveOptionValue and
+ # NegativeOptionValue.
+ empty_positive = PositiveOptionValue()
+ empty_negative = NegativeOptionValue()
+ self.assertEqual(empty_positive, ())
+ self.assertEqual(empty_positive, PositiveOptionValue())
+ self.assertEqual(empty_negative, ())
+ self.assertEqual(empty_negative, NegativeOptionValue())
+ self.assertNotEqual(empty_positive, "foo")
+ self.assertNotEqual(empty_positive, ("foo",))
+ self.assertNotEqual(empty_negative, "foo")
+ self.assertNotEqual(empty_negative, ("foo",))
+
+ def test_option_value_format(self):
+ val = PositiveOptionValue()
+ self.assertEqual("--with-value", val.format("--with-value"))
+ self.assertEqual("--with-value", val.format("--without-value"))
+ self.assertEqual("--enable-value", val.format("--enable-value"))
+ self.assertEqual("--enable-value", val.format("--disable-value"))
+ self.assertEqual("--value", val.format("--value"))
+ self.assertEqual("VALUE=1", val.format("VALUE"))
+
+ val = PositiveOptionValue(("a",))
+ self.assertEqual("--with-value=a", val.format("--with-value"))
+ self.assertEqual("--with-value=a", val.format("--without-value"))
+ self.assertEqual("--enable-value=a", val.format("--enable-value"))
+ self.assertEqual("--enable-value=a", val.format("--disable-value"))
+ self.assertEqual("--value=a", val.format("--value"))
+ self.assertEqual("VALUE=a", val.format("VALUE"))
+
+ val = PositiveOptionValue(("a", "b"))
+ self.assertEqual("--with-value=a,b", val.format("--with-value"))
+ self.assertEqual("--with-value=a,b", val.format("--without-value"))
+ self.assertEqual("--enable-value=a,b", val.format("--enable-value"))
+ self.assertEqual("--enable-value=a,b", val.format("--disable-value"))
+ self.assertEqual("--value=a,b", val.format("--value"))
+ self.assertEqual("VALUE=a,b", val.format("VALUE"))
+
+ val = NegativeOptionValue()
+ self.assertEqual("--without-value", val.format("--with-value"))
+ self.assertEqual("--without-value", val.format("--without-value"))
+ self.assertEqual("--disable-value", val.format("--enable-value"))
+ self.assertEqual("--disable-value", val.format("--disable-value"))
+ self.assertEqual("", val.format("--value"))
+ self.assertEqual("VALUE=", val.format("VALUE"))
+
+ def test_option_value(self, name="option", nargs=0, default=None):
+ disabled = name.startswith(("disable-", "without-"))
+ if disabled:
+ negOptionValue = PositiveOptionValue
+ posOptionValue = NegativeOptionValue
+ else:
+ posOptionValue = PositiveOptionValue
+ negOptionValue = NegativeOptionValue
+ defaultValue = PositiveOptionValue(default) if default else negOptionValue()
+
+ option = Option("--%s" % name, nargs=nargs, default=default)
+
+ if nargs in (0, "?", "*") or disabled:
+ value = option.get_value("--%s" % name, "option")
+ self.assertEqual(value, posOptionValue())
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s" % name)
+ if nargs == 1:
+ self.assertEqual(str(e.exception), "--%s takes 1 value" % name)
+ elif nargs == "+":
+ self.assertEqual(str(e.exception), "--%s takes 1 or more values" % name)
+ else:
+ self.assertEqual(str(e.exception), "--%s takes 2 values" % name)
+
+ value = option.get_value("")
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = option.get_value(None)
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("MOZ_OPTION=", "environment")
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("MOZ_OPTION=1", "environment")
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("--foo")
+
+ if nargs in (1, "?", "*", "+") and not disabled:
+ value = option.get_value("--%s=" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("",)))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=" % name)
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s takes %d values" % (name, nargs)
+ )
+
+ if nargs in (1, "?", "*", "+") and not disabled:
+ value = option.get_value("--%s=foo" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("foo",)))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=foo" % name)
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s takes %d values" % (name, nargs)
+ )
+
+ if nargs in (2, "*", "+") and not disabled:
+ value = option.get_value("--%s=foo,bar" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("foo", "bar")))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=foo,bar" % name, "option")
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ elif nargs == "?":
+ self.assertEqual(str(e.exception), "--%s takes 0 or 1 values" % name)
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "--%s takes %d value%s" % (name, nargs, "s" if nargs != 1 else ""),
+ )
+
+ option = Option("--%s" % name, env="MOZ_OPTION", nargs=nargs, default=default)
+ if nargs in (0, "?", "*") or disabled:
+ value = option.get_value("--%s" % name, "option")
+ self.assertEqual(value, posOptionValue())
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s" % name)
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ elif nargs == "+":
+ self.assertEqual(str(e.exception), "--%s takes 1 or more values" % name)
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "--%s takes %d value%s" % (name, nargs, "s" if nargs != 1 else ""),
+ )
+
+ value = option.get_value("")
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = option.get_value(None)
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = option.get_value("MOZ_OPTION=", "environment")
+ self.assertEqual(value, NegativeOptionValue())
+ self.assertEqual(value.origin, "environment")
+
+ if nargs in (0, "?", "*"):
+ value = option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, PositiveOptionValue())
+ self.assertEqual(value.origin, "environment")
+ elif nargs in (1, "+"):
+ value = option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, PositiveOptionValue(("1",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 2 values")
+
+ if nargs in (1, "?", "*", "+") and not disabled:
+ value = option.get_value("--%s=" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("",)))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=" % name, "option")
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s takes %d values" % (name, nargs)
+ )
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("--foo", "option")
+
+ if nargs in (1, "?", "*", "+"):
+ value = option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes %d values" % nargs)
+
+ if nargs in (2, "*", "+"):
+ value = option.get_value("MOZ_OPTION=foo,bar", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo", "bar")))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("MOZ_OPTION=foo,bar", "environment")
+ if nargs == "?":
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 0 or 1 values")
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "MOZ_OPTION takes %d value%s" % (nargs, "s" if nargs != 1 else ""),
+ )
+
+ if disabled:
+ return option
+
+ env_option = Option(env="MOZ_OPTION", nargs=nargs, default=default)
+ with self.assertRaises(AssertionError):
+ env_option.get_value("--%s" % name)
+
+ value = env_option.get_value("")
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = env_option.get_value("MOZ_OPTION=", "environment")
+ self.assertEqual(value, negOptionValue())
+ self.assertEqual(value.origin, "environment")
+
+ if nargs in (0, "?", "*"):
+ value = env_option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, posOptionValue())
+ self.assertTrue(value)
+ self.assertEqual(value.origin, "environment")
+ elif nargs in (1, "+"):
+ value = env_option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, PositiveOptionValue(("1",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 2 values")
+
+ with self.assertRaises(AssertionError) as e:
+ env_option.get_value("--%s" % name)
+
+ with self.assertRaises(AssertionError) as e:
+ env_option.get_value("--foo")
+
+ if nargs in (1, "?", "*", "+"):
+ value = env_option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes %d values" % nargs)
+
+ if nargs in (2, "*", "+"):
+ value = env_option.get_value("MOZ_OPTION=foo,bar", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo", "bar")))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value("MOZ_OPTION=foo,bar", "environment")
+ if nargs == "?":
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 0 or 1 values")
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "MOZ_OPTION takes %d value%s" % (nargs, "s" if nargs != 1 else ""),
+ )
+
+ return option
+
+ def test_option_value_enable(
+ self, enable="enable", disable="disable", nargs=0, default=None
+ ):
+ option = self.test_option_value(
+ "%s-option" % enable, nargs=nargs, default=default
+ )
+
+ value = option.get_value("--%s-option" % disable, "option")
+ self.assertEqual(value, NegativeOptionValue())
+ self.assertEqual(value.origin, "option")
+
+ option = self.test_option_value(
+ "%s-option" % disable, nargs=nargs, default=default
+ )
+
+ if nargs in (0, "?", "*"):
+ value = option.get_value("--%s-option" % enable, "option")
+ self.assertEqual(value, PositiveOptionValue())
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s-option" % enable, "option")
+ if nargs == 1:
+ self.assertEqual(str(e.exception), "--%s-option takes 1 value" % enable)
+ elif nargs == "+":
+ self.assertEqual(
+ str(e.exception), "--%s-option takes 1 or more values" % enable
+ )
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s-option takes 2 values" % enable
+ )
+
+ def test_option_value_with(self):
+ self.test_option_value_enable("with", "without")
+
+ def test_option_value_invalid_nargs(self):
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="foo")
+ self.assertEqual(
+ str(e.exception), "nargs must be a positive integer, '?', '*' or '+'"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=-2)
+ self.assertEqual(
+ str(e.exception), "nargs must be a positive integer, '?', '*' or '+'"
+ )
+
+ def test_option_value_nargs_1(self):
+ self.test_option_value(nargs=1)
+ self.test_option_value(nargs=1, default=("a",))
+ self.test_option_value_enable(nargs=1, default=("a",))
+
+ # A default is required
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs=1)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ def test_option_value_nargs_2(self):
+ self.test_option_value(nargs=2)
+ self.test_option_value(nargs=2, default=("a", "b"))
+ self.test_option_value_enable(nargs=2, default=("a", "b"))
+
+ # A default is required
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs=2)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ def test_option_value_nargs_0_or_1(self):
+ self.test_option_value(nargs="?")
+ self.test_option_value(nargs="?", default=("a",))
+ self.test_option_value_enable(nargs="?")
+ self.test_option_value_enable(nargs="?", default=("a",))
+
+ def test_option_value_nargs_0_or_more(self):
+ self.test_option_value(nargs="*")
+ self.test_option_value(nargs="*", default=("a",))
+ self.test_option_value(nargs="*", default=("a", "b"))
+ self.test_option_value_enable(nargs="*")
+ self.test_option_value_enable(nargs="*", default=("a",))
+ self.test_option_value_enable(nargs="*", default=("a", "b"))
+
+ def test_option_value_nargs_1_or_more(self):
+ self.test_option_value(nargs="+")
+ self.test_option_value(nargs="+", default=("a",))
+ self.test_option_value(nargs="+", default=("a", "b"))
+ self.test_option_value_enable(nargs="+", default=("a",))
+ self.test_option_value_enable(nargs="+", default=("a", "b"))
+
+ # A default is required
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs="+")
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+
+class TestCommandLineHelper(unittest.TestCase):
+ def test_basic(self):
+ helper = CommandLineHelper({}, ["cmd", "--foo", "--bar"])
+
+ self.assertEqual(["--foo", "--bar"], list(helper))
+
+ helper.add("--enable-qux")
+
+ self.assertEqual(["--foo", "--bar", "--enable-qux"], list(helper))
+
+ value, option = helper.handle(Option("--bar"))
+ self.assertEqual(["--foo", "--enable-qux"], list(helper))
+ self.assertEqual(PositiveOptionValue(), value)
+ self.assertEqual("--bar", option)
+
+ value, option = helper.handle(Option("--baz"))
+ self.assertEqual(["--foo", "--enable-qux"], list(helper))
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual(None, option)
+
+ with self.assertRaises(AssertionError):
+ CommandLineHelper({}, ["--foo", "--bar"])
+
+ def test_precedence(self):
+ foo = Option("--with-foo", nargs="*")
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b", "--without-foo"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--without-foo", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--without-foo", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ foo = Option("--with-foo", env="FOO", nargs="*")
+ helper = CommandLineHelper({"FOO": ""}, ["cmd", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ helper = CommandLineHelper({"FOO": "a,b"}, ["cmd", "--without-foo"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--without-foo", option)
+
+ helper = CommandLineHelper({"FOO": ""}, ["cmd", "--with-bar=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("environment", value.origin)
+ self.assertEqual("FOO=", option)
+
+ helper = CommandLineHelper({"FOO": "a,b"}, ["cmd", "--without-bar"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("environment", value.origin)
+ self.assertEqual("FOO=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b", "FOO="])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("FOO=", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--without-foo", "FOO=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("FOO=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "FOO=", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "FOO=a,b", "--without-foo"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--without-foo", option)
+
+ def test_extra_args(self):
+ foo = Option("--with-foo", env="FOO", nargs="*")
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("FOO=a,b,c", option)
+
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--with-foo=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("--with-foo=a,b,c", option)
+
+ # Adding conflicting options is not allowed.
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.add("FOO=", "other-origin")
+ self.assertEqual("FOO=", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.add("FOO=a,b", "other-origin")
+ self.assertEqual("FOO=a,b", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ # But adding the same is allowed.
+ helper.add("FOO=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("FOO=a,b,c", option)
+
+ # The same rule as above applies when using the option form vs. the
+ # variable form. But we can't detect it when .add is called.
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--without-foo", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("--without-foo", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--with-foo=a,b", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("--with-foo=a,b", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--with-foo=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("--with-foo=a,b,c", option)
+
+ # Conflicts are also not allowed against what is in the
+ # environment/on the command line.
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b"])
+ helper.add("FOO=a,b,c", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("FOO=a,b,c", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("--with-foo=a,b", cm.exception.old_arg)
+ self.assertEqual("command-line", cm.exception.old_origin)
+
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b"])
+ helper.add("--without-foo", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("--without-foo", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("--with-foo=a,b", cm.exception.old_arg)
+ self.assertEqual("command-line", cm.exception.old_origin)
+
+ def test_possible_origins(self):
+ with self.assertRaises(InvalidOptionError):
+ Option("--foo", possible_origins="command-line")
+
+ helper = CommandLineHelper({"BAZ": "1"}, ["cmd", "--foo", "--bar"])
+ foo = Option("--foo", possible_origins=("command-line",))
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--foo", option)
+
+ bar = Option("--bar", possible_origins=("mozconfig",))
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--bar can not be set by command-line. Values are accepted from: mozconfig",
+ ):
+ helper.handle(bar)
+
+ baz = Option(env="BAZ", possible_origins=("implied",))
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "BAZ=1 can not be set by environment. Values are accepted from: implied",
+ ):
+ helper.handle(baz)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
new file mode 100644
index 0000000000..c6af3d99d4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
@@ -0,0 +1,2056 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import logging
+import os
+
+import six
+from mozboot.util import MINIMUM_RUST_VERSION
+from mozpack import path as mozpath
+from mozunit import main
+from six import StringIO
+from test_toolchain_helpers import CompilerResult, FakeCompiler, PrependFlags
+
+from common import BaseConfigureTest
+from mozbuild.configure.util import Version
+from mozbuild.util import ReadOnlyNamespace, memoize
+
+DEFAULT_C99 = {"__STDC_VERSION__": "199901L"}
+
+DEFAULT_C11 = {"__STDC_VERSION__": "201112L"}
+
+DEFAULT_C17 = {"__STDC_VERSION__": "201710L"}
+
+DEFAULT_CXX_97 = {"__cplusplus": "199711L"}
+
+DEFAULT_CXX_11 = {"__cplusplus": "201103L"}
+
+DRAFT_CXX_14 = {"__cplusplus": "201300L"}
+
+DEFAULT_CXX_14 = {"__cplusplus": "201402L"}
+
+DRAFT_CXX17_201500 = {"__cplusplus": "201500L"}
+
+DRAFT_CXX17_201406 = {"__cplusplus": "201406L"}
+
+DEFAULT_CXX_17 = {"__cplusplus": "201703L"}
+
+SUPPORTS_GNU99 = {"-std=gnu99": DEFAULT_C99}
+
+SUPPORTS_GNUXX11 = {"-std=gnu++11": DEFAULT_CXX_11}
+
+SUPPORTS_GNUXX14 = {"-std=gnu++14": DEFAULT_CXX_14}
+
+SUPPORTS_CXX14 = {"-std=c++14": DEFAULT_CXX_14}
+
+SUPPORTS_GNUXX17 = {"-std=gnu++17": DEFAULT_CXX_17}
+
+SUPPORTS_CXX17 = {"-std=c++17": DEFAULT_CXX_17}
+
+
+@memoize
+def GCC_BASE(version):
+ version = Version(version)
+ return FakeCompiler(
+ {
+ "__GNUC__": version.major,
+ "__GNUC_MINOR__": version.minor,
+ "__GNUC_PATCHLEVEL__": version.patch,
+ "__STDC__": 1,
+ }
+ )
+
+
+@memoize
+def GCC(version):
+ return GCC_BASE(version) + SUPPORTS_GNU99
+
+
+@memoize
+def GXX(version):
+ return GCC_BASE(version) + DEFAULT_CXX_97 + SUPPORTS_GNUXX11
+
+
+SUPPORTS_DRAFT_CXX14_VERSION = {"-std=gnu++14": DRAFT_CXX_14}
+
+SUPPORTS_GNUXX1Z = {"-std=gnu++1z": DRAFT_CXX17_201406}
+
+SUPPORTS_DRAFT_CXX17_201500_VERSION = {"-std=gnu++17": DRAFT_CXX17_201500}
+
+GCC_4_9 = GCC("4.9.3")
+GXX_4_9 = GXX("4.9.3") + SUPPORTS_DRAFT_CXX14_VERSION
+GCC_5 = GCC("5.2.1") + DEFAULT_C11
+GXX_5 = GXX("5.2.1") + SUPPORTS_GNUXX14
+GCC_6 = GCC("6.4.0") + DEFAULT_C11
+GXX_6 = (
+ GXX("6.4.0")
+ + DEFAULT_CXX_14
+ + SUPPORTS_GNUXX17
+ + SUPPORTS_DRAFT_CXX17_201500_VERSION
+)
+GCC_7 = GCC("7.3.0") + DEFAULT_C11
+GXX_7 = GXX("7.3.0") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17 + SUPPORTS_CXX17
+GCC_8 = GCC("8.3.0") + DEFAULT_C11
+GXX_8 = GXX("8.3.0") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17 + SUPPORTS_CXX17
+GCC_10 = GCC("10.2.1") + DEFAULT_C17
+GXX_10 = GXX("10.2.1") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17 + SUPPORTS_CXX17
+
+DEFAULT_GCC = GCC_8
+DEFAULT_GXX = GXX_8
+
+GCC_PLATFORM_LITTLE_ENDIAN = {
+ "__ORDER_LITTLE_ENDIAN__": 1234,
+ "__ORDER_BIG_ENDIAN__": 4321,
+ "__BYTE_ORDER__": 1234,
+}
+
+GCC_PLATFORM_BIG_ENDIAN = {
+ "__ORDER_LITTLE_ENDIAN__": 1234,
+ "__ORDER_BIG_ENDIAN__": 4321,
+ "__BYTE_ORDER__": 4321,
+}
+
+GCC_PLATFORM_X86 = FakeCompiler(GCC_PLATFORM_LITTLE_ENDIAN) + {
+ None: {"__i386__": 1},
+ "-m64": {"__i386__": False, "__x86_64__": 1},
+}
+
+GCC_PLATFORM_X86_64 = FakeCompiler(GCC_PLATFORM_LITTLE_ENDIAN) + {
+ None: {"__x86_64__": 1},
+ "-m32": {"__x86_64__": False, "__i386__": 1},
+}
+
+GCC_PLATFORM_ARM = FakeCompiler(GCC_PLATFORM_LITTLE_ENDIAN) + {"__arm__": 1}
+
+GCC_PLATFORM_LINUX = {"__linux__": 1}
+
+GCC_PLATFORM_DARWIN = {"__APPLE__": 1}
+
+GCC_PLATFORM_WIN = {"_WIN32": 1, "WINNT": 1}
+
+GCC_PLATFORM_OPENBSD = {"__OpenBSD__": 1}
+
+GCC_PLATFORM_X86_LINUX = FakeCompiler(GCC_PLATFORM_X86, GCC_PLATFORM_LINUX)
+GCC_PLATFORM_X86_64_LINUX = FakeCompiler(GCC_PLATFORM_X86_64, GCC_PLATFORM_LINUX)
+GCC_PLATFORM_ARM_LINUX = FakeCompiler(GCC_PLATFORM_ARM, GCC_PLATFORM_LINUX)
+GCC_PLATFORM_X86_OSX = FakeCompiler(GCC_PLATFORM_X86, GCC_PLATFORM_DARWIN)
+GCC_PLATFORM_X86_64_OSX = FakeCompiler(GCC_PLATFORM_X86_64, GCC_PLATFORM_DARWIN)
+GCC_PLATFORM_X86_WIN = FakeCompiler(GCC_PLATFORM_X86, GCC_PLATFORM_WIN)
+GCC_PLATFORM_X86_64_WIN = FakeCompiler(GCC_PLATFORM_X86_64, GCC_PLATFORM_WIN)
+
+
+@memoize
+def CLANG_BASE(version):
+ version = Version(version)
+ return FakeCompiler(
+ {
+ "__clang__": 1,
+ "__clang_major__": version.major,
+ "__clang_minor__": version.minor,
+ "__clang_patchlevel__": version.patch,
+ }
+ )
+
+
+@memoize
+def CLANG(version):
+ return GCC_BASE("4.2.1") + CLANG_BASE(version) + SUPPORTS_GNU99
+
+
+@memoize
+def CLANGXX(version):
+ return (
+ GCC_BASE("4.2.1")
+ + CLANG_BASE(version)
+ + DEFAULT_CXX_97
+ + SUPPORTS_GNUXX11
+ + SUPPORTS_GNUXX14
+ )
+
+
+CLANG_3_3 = CLANG("3.3.0") + DEFAULT_C99
+CLANGXX_3_3 = CLANGXX("3.3.0")
+CLANG_4_0 = CLANG("4.0.2") + DEFAULT_C11
+CLANGXX_4_0 = CLANGXX("4.0.2") + SUPPORTS_GNUXX1Z
+CLANG_7_0 = CLANG("7.0.0") + DEFAULT_C11
+CLANGXX_7_0 = CLANGXX("7.0.0") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17
+XCODE_CLANG_3_3 = (
+ CLANG("5.0")
+ + DEFAULT_C99
+ + {
+ # Real Xcode clang has a full version here, but we don't care about it.
+ "__apple_build_version__": "1"
+ }
+)
+XCODE_CLANGXX_3_3 = CLANGXX("5.0") + {"__apple_build_version__": "1"}
+XCODE_CLANG_4_0 = CLANG("9.0.0") + DEFAULT_C11 + {"__apple_build_version__": "1"}
+XCODE_CLANGXX_4_0 = (
+ CLANGXX("9.0.0") + SUPPORTS_GNUXX1Z + {"__apple_build_version__": "1"}
+)
+XCODE_CLANG_7_0 = CLANG("10.0.1") + DEFAULT_C11 + {"__apple_build_version__": "1"}
+XCODE_CLANGXX_7_0 = (
+ CLANGXX("10.0.1") + SUPPORTS_GNUXX17 + {"__apple_build_version__": "1"}
+)
+DEFAULT_CLANG = CLANG_7_0
+DEFAULT_CLANGXX = CLANGXX_7_0
+
+
+def CLANG_PLATFORM(gcc_platform):
+ base = {
+ "--target=x86_64-linux-gnu": GCC_PLATFORM_X86_64_LINUX[None],
+ "--target=x86_64-apple-darwin11.2.0": GCC_PLATFORM_X86_64_OSX[None],
+ "--target=i686-linux-gnu": GCC_PLATFORM_X86_LINUX[None],
+ "--target=i686-apple-darwin11.2.0": GCC_PLATFORM_X86_OSX[None],
+ "--target=arm-linux-gnu": GCC_PLATFORM_ARM_LINUX[None],
+ }
+ undo_gcc_platform = {
+ k: {symbol: False for symbol in gcc_platform[None]} for k in base
+ }
+ return FakeCompiler(gcc_platform, undo_gcc_platform, base)
+
+
+CLANG_PLATFORM_X86_LINUX = CLANG_PLATFORM(GCC_PLATFORM_X86_LINUX)
+CLANG_PLATFORM_X86_64_LINUX = CLANG_PLATFORM(GCC_PLATFORM_X86_64_LINUX)
+CLANG_PLATFORM_X86_OSX = CLANG_PLATFORM(GCC_PLATFORM_X86_OSX)
+CLANG_PLATFORM_X86_64_OSX = CLANG_PLATFORM(GCC_PLATFORM_X86_64_OSX)
+CLANG_PLATFORM_X86_WIN = CLANG_PLATFORM(GCC_PLATFORM_X86_WIN)
+CLANG_PLATFORM_X86_64_WIN = CLANG_PLATFORM(GCC_PLATFORM_X86_64_WIN)
+
+
+@memoize
+def VS(version):
+ version = Version(version)
+ return FakeCompiler(
+ {
+ None: {
+ "_MSC_VER": "%02d%02d" % (version.major, version.minor),
+ "_MSC_FULL_VER": "%02d%02d%05d"
+ % (version.major, version.minor, version.patch),
+ "_MT": "1",
+ },
+ "*.cpp": DEFAULT_CXX_97,
+ }
+ )
+
+
+VS_2017u8 = VS("19.15.26726")
+
+VS_PLATFORM_X86 = {"_M_IX86": 600, "_WIN32": 1}
+
+VS_PLATFORM_X86_64 = {"_M_X64": 100, "_WIN32": 1, "_WIN64": 1}
+
+# Despite the 32 in the name, this macro is defined for 32- and 64-bit.
+MINGW32 = {"__MINGW32__": True}
+
+# Note: In reality, the -std=gnu* options are only supported when preceded by
+# -Xclang.
+CLANG_CL_3_9 = (
+ CLANG_BASE("3.9.0")
+ + VS("18.00.00000")
+ + DEFAULT_C11
+ + SUPPORTS_GNU99
+ + SUPPORTS_GNUXX11
+ + SUPPORTS_CXX14
+) + {"*.cpp": {"__STDC_VERSION__": False, "__cplusplus": "201103L"}}
+CLANG_CL_9_0 = (
+ CLANG_BASE("9.0.0")
+ + VS("18.00.00000")
+ + DEFAULT_C11
+ + SUPPORTS_GNU99
+ + SUPPORTS_GNUXX11
+ + SUPPORTS_CXX14
+ + SUPPORTS_CXX17
+) + {"*.cpp": {"__STDC_VERSION__": False, "__cplusplus": "201103L"}}
+
+CLANG_CL_PLATFORM_X86 = FakeCompiler(
+ VS_PLATFORM_X86, GCC_PLATFORM_X86[None], GCC_PLATFORM_LITTLE_ENDIAN
+)
+CLANG_CL_PLATFORM_X86_64 = FakeCompiler(
+ VS_PLATFORM_X86_64, GCC_PLATFORM_X86_64[None], GCC_PLATFORM_LITTLE_ENDIAN
+)
+
+LIBRARY_NAME_INFOS = {
+ "linux-gnu": {
+ "DLL_PREFIX": "lib",
+ "DLL_SUFFIX": ".so",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "",
+ "OBJ_SUFFIX": "o",
+ },
+ "darwin11.2.0": {
+ "DLL_PREFIX": "lib",
+ "DLL_SUFFIX": ".dylib",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "",
+ "OBJ_SUFFIX": "o",
+ },
+ "mingw32": {
+ "DLL_PREFIX": "",
+ "DLL_SUFFIX": ".dll",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "a",
+ "OBJ_SUFFIX": "o",
+ },
+ "windows-msvc": {
+ "DLL_PREFIX": "",
+ "DLL_SUFFIX": ".dll",
+ "LIB_PREFIX": "",
+ "LIB_SUFFIX": "lib",
+ "IMPORT_LIB_SUFFIX": "lib",
+ "OBJ_SUFFIX": "obj",
+ },
+ "windows-gnu": {
+ "DLL_PREFIX": "",
+ "DLL_SUFFIX": ".dll",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "a",
+ "OBJ_SUFFIX": "o",
+ },
+ "openbsd6.1": {
+ "DLL_PREFIX": "lib",
+ "DLL_SUFFIX": ".so.1.0",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "",
+ "OBJ_SUFFIX": "o",
+ },
+}
+
+
+class BaseToolchainTest(BaseConfigureTest):
+ def setUp(self):
+ super(BaseToolchainTest, self).setUp()
+ self.out = StringIO()
+ self.logger = logging.getLogger("BaseToolchainTest")
+ self.logger.setLevel(logging.ERROR)
+ self.handler = logging.StreamHandler(self.out)
+ self.logger.addHandler(self.handler)
+
+ def tearDown(self):
+ self.logger.removeHandler(self.handler)
+ del self.handler
+ del self.out
+ super(BaseToolchainTest, self).tearDown()
+
+ def do_toolchain_test(self, paths, results, args=[], environ={}):
+ """Helper to test the toolchain checks from toolchain.configure.
+
+ - `paths` is a dict associating compiler paths to FakeCompiler
+ definitions from above.
+ - `results` is a dict associating result variable names from
+ toolchain.configure (c_compiler, cxx_compiler, host_c_compiler,
+ host_cxx_compiler) with a result.
+ The result can either be an error string, or a CompilerResult
+ corresponding to the object returned by toolchain.configure checks.
+ When the results for host_c_compiler are identical to c_compiler,
+ they can be omitted. Likewise for host_cxx_compiler vs.
+ cxx_compiler.
+ """
+ environ = dict(environ)
+ if "PATH" not in environ:
+ environ["PATH"] = os.pathsep.join(
+ mozpath.abspath(p) for p in ("/bin", "/usr/bin")
+ )
+
+ args = args + ["--enable-release", "--disable-bootstrap"]
+
+ sandbox = self.get_sandbox(paths, {}, args, environ, logger=self.logger)
+
+ for var in (
+ "c_compiler",
+ "cxx_compiler",
+ "host_c_compiler",
+ "host_cxx_compiler",
+ ):
+ if var in results:
+ result = results[var]
+ elif var.startswith("host_"):
+ result = results.get(var[5:], {})
+ else:
+ result = {}
+ try:
+ self.out.truncate(0)
+ self.out.seek(0)
+ compiler = sandbox._value_for(sandbox[var])
+ # Add var on both ends to make it clear which of the
+ # variables is failing the test when that happens.
+ self.assertEqual((var, compiler), (var, result))
+ except SystemExit:
+ self.assertEqual((var, result), (var, self.out.getvalue().strip()))
+ return
+
+ # Normalize the target os to match what we have as keys in
+ # LIBRARY_NAME_INFOS.
+ target_os = getattr(self, "TARGET", self.HOST).split("-", 2)[2]
+ if target_os == "mingw32":
+ compiler_type = sandbox._value_for(sandbox["c_compiler"]).type
+ if compiler_type == "clang-cl":
+ target_os = "windows-msvc"
+ elif target_os == "linux-gnuabi64":
+ target_os = "linux-gnu"
+
+ self.do_library_name_info_test(target_os, sandbox)
+
+ # Try again on artifact builds. In that case, we always get library
+ # name info for msvc on Windows
+ if target_os == "mingw32":
+ target_os = "windows-msvc"
+
+ sandbox = self.get_sandbox(
+ paths, {}, args + ["--enable-artifact-builds"], environ, logger=self.logger
+ )
+
+ self.do_library_name_info_test(target_os, sandbox)
+
+ def do_library_name_info_test(self, target_os, sandbox):
+ library_name_info = LIBRARY_NAME_INFOS[target_os]
+ for k in (
+ "DLL_PREFIX",
+ "DLL_SUFFIX",
+ "LIB_PREFIX",
+ "LIB_SUFFIX",
+ "IMPORT_LIB_SUFFIX",
+ "OBJ_SUFFIX",
+ ):
+ self.assertEqual(
+ "%s=%s" % (k, sandbox.get_config(k)),
+ "%s=%s" % (k, library_name_info[k]),
+ )
+
+
+def old_gcc_message(old_ver):
+ return "Only GCC 8.1 or newer is supported (found version {}).".format(old_ver)
+
+
+class LinuxToolchainTest(BaseToolchainTest):
+ PATHS = {
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-4.9": GCC_4_9 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-4.9": GXX_4_9 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-6": GCC_6 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-6": GXX_6 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-7": GCC_7 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-7": GXX_7 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-8": GCC_8 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-8": GXX_8 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-10": GCC_10 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-10": GXX_10 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang-7.0": CLANG_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++-7.0": CLANGXX_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang-4.0": CLANG_4_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++-4.0": CLANGXX_4_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang-3.3": CLANG_3_3 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++-3.3": CLANGXX_3_3 + CLANG_PLATFORM_X86_64_LINUX,
+ }
+
+ GCC_4_7_RESULT = old_gcc_message("4.7.3")
+ GXX_4_7_RESULT = GCC_4_7_RESULT
+ GCC_4_9_RESULT = old_gcc_message("4.9.3")
+ GXX_4_9_RESULT = GCC_4_9_RESULT
+ GCC_5_RESULT = old_gcc_message("5.2.1")
+ GXX_5_RESULT = GCC_5_RESULT
+ GCC_6_RESULT = old_gcc_message("6.4.0")
+ GXX_6_RESULT = GCC_6_RESULT
+ GCC_7_RESULT = old_gcc_message("7.3.0")
+ GXX_7_RESULT = GCC_7_RESULT
+ GCC_8_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="8.3.0",
+ type="gcc",
+ compiler="/usr/bin/gcc-8",
+ language="C",
+ )
+ GXX_8_RESULT = CompilerResult(
+ flags=["-std=gnu++17"],
+ version="8.3.0",
+ type="gcc",
+ compiler="/usr/bin/g++-8",
+ language="C++",
+ )
+ DEFAULT_GCC_RESULT = GCC_8_RESULT + {"compiler": "/usr/bin/gcc"}
+ DEFAULT_GXX_RESULT = GXX_8_RESULT + {"compiler": "/usr/bin/g++"}
+
+ CLANG_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 3.3.0)."
+ )
+ CLANGXX_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 3.3.0)."
+ )
+ CLANG_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.2)."
+ )
+ CLANGXX_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.2)."
+ )
+ CLANG_7_0_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang-7.0",
+ language="C",
+ )
+ CLANGXX_7_0_RESULT = CompilerResult(
+ flags=["-std=gnu++17"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang++-7.0",
+ language="C++",
+ )
+ DEFAULT_CLANG_RESULT = CLANG_7_0_RESULT + {"compiler": "/usr/bin/clang"}
+ DEFAULT_CLANGXX_RESULT = CLANGXX_7_0_RESULT + {"compiler": "/usr/bin/clang++"}
+
+ def test_default(self):
+ # We'll try clang and gcc, and find clang first.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "gcc", "CXX": "g++"},
+ )
+
+ def test_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_4_9_RESULT},
+ environ={"CC": "gcc-4.9", "CXX": "g++-4.9"},
+ )
+
+ # Maybe this should be reporting the mismatched version instead.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.GXX_4_9_RESULT,
+ },
+ environ={"CC": "gcc", "CXX": "g++-4.9"},
+ )
+
+ def test_overridden_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_7_RESULT, "cxx_compiler": self.GXX_7_RESULT},
+ environ={"CC": "gcc-7", "CXX": "g++-7"},
+ )
+
+ def test_guess_cxx(self):
+ # When CXX is not set, we guess it from CC.
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_7_RESULT, "cxx_compiler": self.GXX_7_RESULT},
+ environ={"CC": "gcc-7"},
+ )
+
+ def test_mismatched_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": (
+ "The target C compiler is version 8.3.0, while the target "
+ "C++ compiler is version 10.2.1. Need to use the same compiler "
+ "version."
+ ),
+ },
+ environ={"CC": "gcc", "CXX": "g++-10"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": (
+ "The host C compiler is version 8.3.0, while the host "
+ "C++ compiler is version 10.2.1. Need to use the same compiler "
+ "version."
+ ),
+ },
+ environ={"CC": "gcc", "HOST_CXX": "g++-10"},
+ )
+
+ def test_mismatched_compiler(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": (
+ "The target C compiler is clang, while the target C++ compiler "
+ "is gcc. Need to use the same compiler suite."
+ ),
+ },
+ environ={"CXX": "g++"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": (
+ "The host C compiler is clang, while the host C++ compiler "
+ "is gcc. Need to use the same compiler suite."
+ ),
+ },
+ environ={"HOST_CXX": "g++"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": "`%s` is not a C compiler."
+ % mozpath.abspath("/usr/bin/g++")
+ },
+ environ={"CC": "g++"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": "`%s` is not a C++ compiler."
+ % mozpath.abspath("/usr/bin/clang"),
+ },
+ environ={"CXX": "clang"},
+ )
+
+ def test_clang(self):
+ # We'll try gcc and clang, but since there is no gcc (gcc-x.y doesn't
+ # count), find clang.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("gcc", "g++")
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_guess_cxx_clang(self):
+ # When CXX is not set, we guess it from CC.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_7_0_RESULT,
+ "cxx_compiler": self.CLANGXX_7_0_RESULT,
+ },
+ environ={"CC": "clang-7.0"},
+ )
+
+ def test_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_3_3_RESULT,
+ "cxx_compiler": self.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_4_0_RESULT,
+ "cxx_compiler": self.CLANGXX_4_0_RESULT,
+ },
+ environ={"CC": "clang-4.0", "CXX": "clang++-4.0"},
+ )
+
+ def test_no_supported_compiler(self):
+ # Even if there are gcc-x.y or clang-x.y compilers available, we
+ # don't try them. This could be considered something to improve.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("gcc", "g++", "clang", "clang++")
+ }
+ self.do_toolchain_test(
+ paths, {"c_compiler": "Cannot find the target C compiler"}
+ )
+
+ def test_absolute_path(self):
+ paths = dict(self.PATHS)
+ paths.update(
+ {
+ "/opt/clang/bin/clang": paths["/usr/bin/clang"],
+ "/opt/clang/bin/clang++": paths["/usr/bin/clang++"],
+ }
+ )
+ result = {
+ "c_compiler": self.DEFAULT_CLANG_RESULT
+ + {"compiler": "/opt/clang/bin/clang"},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT
+ + {"compiler": "/opt/clang/bin/clang++"},
+ }
+ self.do_toolchain_test(
+ paths,
+ result,
+ environ={"CC": "/opt/clang/bin/clang", "CXX": "/opt/clang/bin/clang++"},
+ )
+ # With CXX guess too.
+ self.do_toolchain_test(paths, result, environ={"CC": "/opt/clang/bin/clang"})
+
+ def test_atypical_name(self):
+ paths = dict(self.PATHS)
+ paths.update(
+ {
+ "/usr/bin/afl-clang-fast": paths["/usr/bin/clang"],
+ "/usr/bin/afl-clang-fast++": paths["/usr/bin/clang++"],
+ }
+ )
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT
+ + {"compiler": "/usr/bin/afl-clang-fast"},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT
+ + {"compiler": "/usr/bin/afl-clang-fast++"},
+ },
+ environ={"CC": "afl-clang-fast", "CXX": "afl-clang-fast++"},
+ )
+
+ def test_mixed_compilers(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "clang", "HOST_CC": "gcc"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "clang", "CXX": "clang++", "HOST_CC": "gcc"},
+ )
+
+
+class LinuxSimpleCrossToolchainTest(BaseToolchainTest):
+ TARGET = "i686-pc-linux-gnu"
+ PATHS = LinuxToolchainTest.PATHS
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT + {"flags": ["-m32"]},
+ "cxx_compiler": self.DEFAULT_GXX_RESULT + {"flags": ["-m32"]},
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "gcc"},
+ )
+
+ def test_cross_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT + {"flags": ["-m32"]},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT + {"flags": ["-m32"]},
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+
+class LinuxX86_64CrossToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-linux-gnu"
+ TARGET = "x86_64-pc-linux-gnu"
+ PATHS = {
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_LINUX,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_LINUX,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_LINUX,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_LINUX,
+ }
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT + {"flags": ["-m64"]},
+ "cxx_compiler": self.DEFAULT_GXX_RESULT + {"flags": ["-m64"]},
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "gcc"},
+ )
+
+ def test_cross_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT + {"flags": ["-m64"]},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT + {"flags": ["-m64"]},
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+
+def xcrun(stdin, args):
+ if args == ("--show-sdk-path",):
+ return (
+ 0,
+ mozpath.join(os.path.abspath(os.path.dirname(__file__)), "macos_fake_sdk"),
+ "",
+ )
+ raise NotImplementedError()
+
+
+class OSXToolchainTest(BaseToolchainTest):
+ HOST = "x86_64-apple-darwin11.2.0"
+ PATHS = {
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/gcc-8": GCC_8 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/g++-8": GXX_8 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang": XCODE_CLANG_7_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang++": XCODE_CLANGXX_7_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang-4.0": XCODE_CLANG_4_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang++-4.0": XCODE_CLANGXX_4_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang-3.3": XCODE_CLANG_3_3 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang++-3.3": XCODE_CLANGXX_3_3 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/xcrun": xcrun,
+ }
+ CLANG_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ CLANGXX_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ CLANG_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ CLANGXX_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ DEFAULT_CLANG_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang",
+ language="C",
+ )
+ DEFAULT_CLANGXX_RESULT = CompilerResult(
+ flags=["-stdlib=libc++", "-std=gnu++17"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang++",
+ language="C++",
+ )
+ GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT
+ GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT
+ GCC_8_RESULT = LinuxToolchainTest.GCC_8_RESULT
+ GXX_8_RESULT = LinuxToolchainTest.GXX_8_RESULT
+ SYSROOT_FLAGS = {
+ "flags": PrependFlags(
+ [
+ "-isysroot",
+ xcrun("", ("--show-sdk-path",))[1],
+ "-mmacosx-version-min=10.12",
+ ]
+ )
+ }
+
+ def test_clang(self):
+ # We only try clang because gcc is known not to work.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT + self.SYSROOT_FLAGS,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT + self.SYSROOT_FLAGS,
+ },
+ )
+
+ def test_not_gcc(self):
+ # We won't pick GCC if it's the only thing available.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang", "clang++")
+ }
+ self.do_toolchain_test(
+ paths, {"c_compiler": "Cannot find the target C compiler"}
+ )
+
+ def test_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_3_3_RESULT,
+ "cxx_compiler": self.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+ # When targeting mac, we require at least version 5.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_4_0_RESULT,
+ "cxx_compiler": self.CLANGXX_4_0_RESULT,
+ },
+ environ={"CC": "clang-4.0", "CXX": "clang++-4.0"},
+ )
+
+ def test_forced_gcc(self):
+ # GCC can still be forced if the user really wants it.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.GCC_8_RESULT + self.SYSROOT_FLAGS,
+ "cxx_compiler": self.GXX_8_RESULT + self.SYSROOT_FLAGS,
+ },
+ environ={"CC": "gcc-8", "CXX": "g++-8"},
+ )
+
+ def test_forced_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_5_RESULT},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+
+class MingwToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-mingw32"
+
+ # For the purpose of this test, it doesn't matter that the paths are not
+ # real Windows paths.
+ PATHS = {
+ "/usr/bin/cl": VS_2017u8 + VS_PLATFORM_X86,
+ "/usr/bin/clang-cl-3.9": CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86,
+ "/usr/bin/clang-cl": CLANG_CL_9_0 + CLANG_CL_PLATFORM_X86,
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-4.9": GCC_4_9 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-4.9": GXX_4_9 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-6": GCC_6 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-6": GXX_6 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-7": GCC_7 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-7": GXX_7 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang-7.0": CLANG_7_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++-7.0": CLANGXX_7_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang-4.0": CLANG_4_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++-4.0": CLANGXX_4_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang-3.3": CLANG_3_3 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++-3.3": CLANGXX_3_3 + CLANG_PLATFORM_X86_WIN,
+ }
+
+ CLANG_CL_3_9_RESULT = (
+ "Only clang-cl 9.0 or newer is supported (found version 3.9.0)"
+ )
+ CLANG_CL_9_0_RESULT = CompilerResult(
+ version="9.0.0",
+ flags=["-Xclang", "-std=gnu99"],
+ type="clang-cl",
+ compiler="/usr/bin/clang-cl",
+ language="C",
+ )
+ CLANGXX_CL_3_9_RESULT = (
+ "Only clang-cl 9.0 or newer is supported (found version 3.9.0)"
+ )
+ CLANGXX_CL_9_0_RESULT = CompilerResult(
+ version="9.0.0",
+ flags=["-Xclang", "-std=c++17"],
+ type="clang-cl",
+ compiler="/usr/bin/clang-cl",
+ language="C++",
+ )
+ CLANG_3_3_RESULT = LinuxToolchainTest.CLANG_3_3_RESULT
+ CLANGXX_3_3_RESULT = LinuxToolchainTest.CLANGXX_3_3_RESULT
+ CLANG_4_0_RESULT = LinuxToolchainTest.CLANG_4_0_RESULT
+ CLANGXX_4_0_RESULT = LinuxToolchainTest.CLANGXX_4_0_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_unsupported_msvc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "/usr/bin/cl"},
+ )
+
+ def test_unsupported_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.CLANG_CL_3_9_RESULT},
+ environ={"CC": "/usr/bin/clang-cl-3.9"},
+ )
+
+ def test_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_CL_9_0_RESULT,
+ "cxx_compiler": self.CLANGXX_CL_9_0_RESULT,
+ },
+ )
+
+ def test_gcc(self):
+ # GCC is unsupported, if you try it should find clang.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) != "clang-cl"
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ # This test is not perfect, as the GCC version needs to be updated when we
+ # bump the minimum GCC version, but the idea is that even supported GCC
+ # on other platforms should not be supported on Windows.
+ def test_overridden_supported_elsewhere_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-7", "CXX": "g++-7"},
+ )
+
+ def test_overridden_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+ def test_clang(self):
+ # We'll pick clang if nothing else is found.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang-cl", "gcc")
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_overridden_unsupported_clang(self):
+ # clang 3.3 C compiler is perfectly fine, but we need more for C++.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_3_3_RESULT,
+ "cxx_compiler": self.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+
+
+class Mingw64ToolchainTest(MingwToolchainTest):
+ HOST = "x86_64-pc-mingw32"
+
+ # For the purpose of this test, it doesn't matter that the paths are not
+ # real Windows paths.
+ PATHS = {
+ "/usr/bin/cl": VS_2017u8 + VS_PLATFORM_X86_64,
+ "/usr/bin/clang-cl": CLANG_CL_9_0 + CLANG_CL_PLATFORM_X86_64,
+ "/usr/bin/clang-cl-3.9": CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86_64,
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-4.9": GCC_4_9 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-4.9": GXX_4_9 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-6": GCC_6 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-6": GXX_6 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-7": GCC_7 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-7": GXX_7 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang-7.0": CLANG_7_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++-7.0": CLANGXX_7_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang-4.0": CLANG_4_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++-4.0": CLANGXX_4_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang-3.3": CLANG_3_3 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++-3.3": CLANGXX_3_3 + CLANG_PLATFORM_X86_64_WIN,
+ }
+
+
+class WindowsToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-windows-msvc"
+
+ PATHS = MingwToolchainTest.PATHS
+
+ def test_unsupported_msvc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "/usr/bin/cl"},
+ )
+
+ def test_unsupported_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": MingwToolchainTest.CLANG_CL_3_9_RESULT},
+ environ={"CC": "/usr/bin/clang-cl-3.9"},
+ )
+
+ def test_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": MingwToolchainTest.CLANG_CL_9_0_RESULT,
+ "cxx_compiler": MingwToolchainTest.CLANGXX_CL_9_0_RESULT,
+ },
+ )
+
+ def test_unsupported_gcc(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) != "clang-cl"
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+ def test_unsupported_clang(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang-cl", "gcc")
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+
+
+class Windows64ToolchainTest(WindowsToolchainTest):
+ HOST = "x86_64-pc-windows-msvc"
+
+ PATHS = Mingw64ToolchainTest.PATHS
+
+
+class WindowsGnuToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-windows-gnu"
+
+ PATHS = MingwToolchainTest.PATHS
+
+ def test_unsupported_msvc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "/usr/bin/cl"},
+ )
+
+ def test_unsupported_clang_cl(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) == "clang-cl"
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "clang-cl", "CXX": "clang-cl"},
+ )
+
+ def test_unsupported_gcc(self):
+ paths = {
+ k: v for k, v in six.iteritems(self.PATHS) if os.path.basename(k) == "gcc"
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+ def test_clang(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang-cl", "gcc")
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": MingwToolchainTest.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": MingwToolchainTest.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_overridden_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": MingwToolchainTest.CLANG_3_3_RESULT,
+ "cxx_compiler": MingwToolchainTest.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+
+
+class WindowsGnu64ToolchainTest(WindowsGnuToolchainTest):
+ HOST = "x86_64-pc-windows-gnu"
+
+ PATHS = Mingw64ToolchainTest.PATHS
+
+
+class LinuxCrossCompileToolchainTest(BaseToolchainTest):
+ TARGET = "arm-unknown-linux-gnu"
+ PATHS = {
+ "/usr/bin/arm-linux-gnu-gcc-4.9": GCC_4_9 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++-4.9": GXX_4_9 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-gcc-5": GCC_5 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++-5": GXX_5 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-gcc": DEFAULT_GCC + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++": DEFAULT_GXX + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-gcc-7": GCC_7 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++-7": GXX_7 + GCC_PLATFORM_ARM_LINUX,
+ }
+ PATHS.update(LinuxToolchainTest.PATHS)
+ ARM_GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+ ARM_GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
+ ARM_GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT
+ ARM_GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT
+ ARM_DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT + {
+ "compiler": "/usr/bin/arm-linux-gnu-gcc"
+ }
+ ARM_DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT + {
+ "compiler": "/usr/bin/arm-linux-gnu-g++"
+ }
+ ARM_GCC_7_RESULT = LinuxToolchainTest.GCC_7_RESULT
+ ARM_GXX_7_RESULT = LinuxToolchainTest.GXX_7_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+
+ little_endian = FakeCompiler(GCC_PLATFORM_LINUX, GCC_PLATFORM_LITTLE_ENDIAN)
+ big_endian = FakeCompiler(GCC_PLATFORM_LINUX, GCC_PLATFORM_BIG_ENDIAN)
+
+ PLATFORMS = {
+ "i686-pc-linux-gnu": GCC_PLATFORM_X86_LINUX,
+ "x86_64-pc-linux-gnu": GCC_PLATFORM_X86_64_LINUX,
+ "arm-unknown-linux-gnu": GCC_PLATFORM_ARM_LINUX,
+ "aarch64-unknown-linux-gnu": little_endian + {"__aarch64__": 1},
+ "ia64-unknown-linux-gnu": little_endian + {"__ia64__": 1},
+ "s390x-unknown-linux-gnu": big_endian + {"__s390x__": 1, "__s390__": 1},
+ "s390-unknown-linux-gnu": big_endian + {"__s390__": 1},
+ "powerpc64-unknown-linux-gnu": big_endian
+ + {
+ None: {"__powerpc64__": 1, "__powerpc__": 1},
+ "-m32": {"__powerpc64__": False},
+ },
+ "powerpc-unknown-linux-gnu": big_endian
+ + {None: {"__powerpc__": 1}, "-m64": {"__powerpc64__": 1}},
+ "alpha-unknown-linux-gnu": little_endian + {"__alpha__": 1},
+ "hppa-unknown-linux-gnu": big_endian + {"__hppa__": 1},
+ "sparc64-unknown-linux-gnu": big_endian
+ + {None: {"__arch64__": 1, "__sparc__": 1}, "-m32": {"__arch64__": False}},
+ "sparc-unknown-linux-gnu": big_endian
+ + {None: {"__sparc__": 1}, "-m64": {"__arch64__": 1}},
+ "m68k-unknown-linux-gnu": big_endian + {"__m68k__": 1},
+ "mips64-unknown-linux-gnuabi64": big_endian + {"__mips64": 1, "__mips__": 1},
+ "mips-unknown-linux-gnu": big_endian + {"__mips__": 1},
+ "riscv64-unknown-linux-gnu": little_endian + {"__riscv": 1, "__riscv_xlen": 64},
+ "sh4-unknown-linux-gnu": little_endian + {"__sh__": 1},
+ }
+
+ PLATFORMS["powerpc64le-unknown-linux-gnu"] = (
+ PLATFORMS["powerpc64-unknown-linux-gnu"] + GCC_PLATFORM_LITTLE_ENDIAN
+ )
+ PLATFORMS["mips64el-unknown-linux-gnuabi64"] = (
+ PLATFORMS["mips64-unknown-linux-gnuabi64"] + GCC_PLATFORM_LITTLE_ENDIAN
+ )
+ PLATFORMS["mipsel-unknown-linux-gnu"] = (
+ PLATFORMS["mips-unknown-linux-gnu"] + GCC_PLATFORM_LITTLE_ENDIAN
+ )
+
+ def do_test_cross_gcc_32_64(self, host, target):
+ self.HOST = host
+ self.TARGET = target
+ paths = {
+ "/usr/bin/gcc": DEFAULT_GCC + self.PLATFORMS[host],
+ "/usr/bin/g++": DEFAULT_GXX + self.PLATFORMS[host],
+ }
+ cross_flags = {"flags": ["-m64" if "64" in target else "-m32"]}
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT + cross_flags,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT + cross_flags,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ )
+ self.HOST = LinuxCrossCompileToolchainTest.HOST
+ self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+ def test_cross_x86_x64(self):
+ self.do_test_cross_gcc_32_64("i686-pc-linux-gnu", "x86_64-pc-linux-gnu")
+ self.do_test_cross_gcc_32_64("x86_64-pc-linux-gnu", "i686-pc-linux-gnu")
+
+ def test_cross_sparc_sparc64(self):
+ self.do_test_cross_gcc_32_64(
+ "sparc-unknown-linux-gnu", "sparc64-unknown-linux-gnu"
+ )
+ self.do_test_cross_gcc_32_64(
+ "sparc64-unknown-linux-gnu", "sparc-unknown-linux-gnu"
+ )
+
+ def test_cross_ppc_ppc64(self):
+ self.do_test_cross_gcc_32_64(
+ "powerpc-unknown-linux-gnu", "powerpc64-unknown-linux-gnu"
+ )
+ self.do_test_cross_gcc_32_64(
+ "powerpc64-unknown-linux-gnu", "powerpc-unknown-linux-gnu"
+ )
+
+ def do_test_cross_gcc(self, host, target):
+ self.HOST = host
+ self.TARGET = target
+ host_cpu = host.split("-")[0]
+ cpu, manufacturer, os = target.split("-", 2)
+ toolchain_prefix = "/usr/bin/%s-%s" % (cpu, os)
+ paths = {
+ "/usr/bin/gcc": DEFAULT_GCC + self.PLATFORMS[host],
+ "/usr/bin/g++": DEFAULT_GXX + self.PLATFORMS[host],
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": (
+ "Target C compiler target CPU (%s) "
+ "does not match --target CPU (%s)" % (host_cpu, cpu)
+ )
+ },
+ )
+
+ paths.update(
+ {
+ "%s-gcc" % toolchain_prefix: DEFAULT_GCC + self.PLATFORMS[target],
+ "%s-g++" % toolchain_prefix: DEFAULT_GXX + self.PLATFORMS[target],
+ }
+ )
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT
+ + {"compiler": "%s-gcc" % toolchain_prefix},
+ "cxx_compiler": self.DEFAULT_GXX_RESULT
+ + {"compiler": "%s-g++" % toolchain_prefix},
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ )
+ self.HOST = LinuxCrossCompileToolchainTest.HOST
+ self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+ def test_cross_gcc_misc(self):
+ for target in self.PLATFORMS:
+ if not target.endswith("-pc-linux-gnu"):
+ self.do_test_cross_gcc("x86_64-pc-linux-gnu", target)
+
+ def test_cannot_cross(self):
+ self.TARGET = "mipsel-unknown-linux-gnu"
+
+ paths = {
+ "/usr/bin/gcc": DEFAULT_GCC + self.PLATFORMS["mips-unknown-linux-gnu"],
+ "/usr/bin/g++": DEFAULT_GXX + self.PLATFORMS["mips-unknown-linux-gnu"],
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": (
+ "Target C compiler target endianness (big) "
+ "does not match --target endianness (little)"
+ )
+ },
+ )
+ self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+ def test_overridden_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_GCC_7_RESULT,
+ "cxx_compiler": self.ARM_GXX_7_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "arm-linux-gnu-gcc-7", "CXX": "arm-linux-gnu-g++-7"},
+ )
+
+ def test_overridden_unsupported_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.ARM_GCC_4_9_RESULT},
+ environ={"CC": "arm-linux-gnu-gcc-4.9", "CXX": "arm-linux-gnu-g++-4.9"},
+ )
+
+ def test_guess_cross_cxx(self):
+ # When CXX is not set, we guess it from CC.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_GCC_7_RESULT,
+ "cxx_compiler": self.ARM_GXX_7_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "arm-linux-gnu-gcc-7"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.ARM_DEFAULT_GXX_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "arm-linux-gnu-gcc", "HOST_CC": "clang"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.ARM_DEFAULT_GXX_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={
+ "CC": "arm-linux-gnu-gcc",
+ "CXX": "arm-linux-gnu-g++",
+ "HOST_CC": "clang",
+ },
+ )
+
+ def test_cross_clang(self):
+ cross_clang_result = self.DEFAULT_CLANG_RESULT + {
+ "flags": ["--target=arm-linux-gnu"]
+ }
+ cross_clangxx_result = self.DEFAULT_CLANGXX_RESULT + {
+ "flags": ["--target=arm-linux-gnu"]
+ }
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": cross_clang_result,
+ "cxx_compiler": cross_clangxx_result,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "clang", "HOST_CC": "clang"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": cross_clang_result,
+ "cxx_compiler": cross_clangxx_result,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "clang"},
+ )
+
+ def test_cross_atypical_clang(self):
+ paths = dict(self.PATHS)
+ paths.update(
+ {
+ "/usr/bin/afl-clang-fast": paths["/usr/bin/clang"],
+ "/usr/bin/afl-clang-fast++": paths["/usr/bin/clang++"],
+ }
+ )
+ afl_clang_result = self.DEFAULT_CLANG_RESULT + {
+ "compiler": "/usr/bin/afl-clang-fast"
+ }
+ afl_clangxx_result = self.DEFAULT_CLANGXX_RESULT + {
+ "compiler": "/usr/bin/afl-clang-fast++"
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": afl_clang_result + {"flags": ["--target=arm-linux-gnu"]},
+ "cxx_compiler": afl_clangxx_result
+ + {"flags": ["--target=arm-linux-gnu"]},
+ "host_c_compiler": afl_clang_result,
+ "host_cxx_compiler": afl_clangxx_result,
+ },
+ environ={"CC": "afl-clang-fast", "CXX": "afl-clang-fast++"},
+ )
+
+
+class OSXCrossToolchainTest(BaseToolchainTest):
+ TARGET = "i686-apple-darwin11.2.0"
+ PATHS = dict(LinuxToolchainTest.PATHS)
+ PATHS.update(
+ {
+ "/usr/bin/clang": CLANG_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++": CLANGXX_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ }
+ )
+ DEFAULT_CLANG_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang",
+ language="C",
+ )
+ DEFAULT_CLANGXX_RESULT = CompilerResult(
+ flags=["-std=gnu++17"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang++",
+ language="C++",
+ )
+
+ def test_osx_cross(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT
+ + OSXToolchainTest.SYSROOT_FLAGS
+ + {"flags": ["--target=i686-apple-darwin11.2.0"]},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT
+ + {"flags": PrependFlags(["-stdlib=libc++"])}
+ + OSXToolchainTest.SYSROOT_FLAGS
+ + {"flags": ["--target=i686-apple-darwin11.2.0"]},
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "clang"},
+ args=["--with-macos-sdk=%s" % OSXToolchainTest.SYSROOT_FLAGS["flags"][1]],
+ )
+
+ def test_cannot_osx_cross(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": "Target C compiler target kernel (Linux) does not "
+ "match --target kernel (Darwin)"
+ },
+ environ={"CC": "gcc"},
+ args=["--with-macos-sdk=%s" % OSXToolchainTest.SYSROOT_FLAGS["flags"][1]],
+ )
+
+
+class WindowsCrossToolchainTest(BaseToolchainTest):
+ TARGET = "x86_64-pc-windows-msvc"
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_clang_cl_cross(self):
+ paths = {"/usr/bin/clang-cl": CLANG_CL_9_0 + CLANG_CL_PLATFORM_X86_64}
+ paths.update(LinuxToolchainTest.PATHS)
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": MingwToolchainTest.CLANG_CL_9_0_RESULT,
+ "cxx_compiler": MingwToolchainTest.CLANGXX_CL_9_0_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+
+class OpenBSDToolchainTest(BaseToolchainTest):
+ HOST = "x86_64-unknown-openbsd6.1"
+ TARGET = "x86_64-unknown-openbsd6.1"
+ PATHS = {
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_64 + GCC_PLATFORM_OPENBSD,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_64 + GCC_PLATFORM_OPENBSD,
+ }
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+
+ def test_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ )
+
+
+@memoize
+def gen_invoke_cargo(version, rustup_wrapper=False):
+ def invoke_cargo(stdin, args):
+ args = tuple(args)
+ if not rustup_wrapper and args == ("+stable",):
+ return (101, "", "we are the real thing")
+ if args == ("--version", "--verbose"):
+ return 0, "cargo %s\nrelease: %s" % (version, version), ""
+ raise NotImplementedError("unsupported arguments")
+
+ return invoke_cargo
+
+
+@memoize
+def gen_invoke_rustc(version, rustup_wrapper=False):
+ def invoke_rustc(stdin, args):
+ args = tuple(args)
+ # TODO: we don't have enough machinery set up to test the `rustup which`
+ # fallback yet.
+ if not rustup_wrapper and args == ("+stable",):
+ return (1, "", "error: couldn't read +stable: No such file or directory")
+ if args == ("--version", "--verbose"):
+ return (
+ 0,
+ "rustc %s\nrelease: %s\nhost: x86_64-unknown-linux-gnu"
+ % (version, version),
+ "",
+ )
+ if args == ("--print", "target-list"):
+ # Raw list returned by rustc version 1.32, + ios, which somehow
+ # don't appear in the default list.
+ # https://github.com/rust-lang/rust/issues/36156
+ rust_targets = [
+ "aarch64-apple-ios",
+ "aarch64-fuchsia",
+ "aarch64-linux-android",
+ "aarch64-pc-windows-msvc",
+ "aarch64-unknown-cloudabi",
+ "aarch64-unknown-freebsd",
+ "aarch64-unknown-hermit",
+ "aarch64-unknown-linux-gnu",
+ "aarch64-unknown-linux-musl",
+ "aarch64-unknown-netbsd",
+ "aarch64-unknown-none",
+ "aarch64-unknown-openbsd",
+ "arm-linux-androideabi",
+ "arm-unknown-linux-gnueabi",
+ "arm-unknown-linux-gnueabihf",
+ "arm-unknown-linux-musleabi",
+ "arm-unknown-linux-musleabihf",
+ "armebv7r-none-eabi",
+ "armebv7r-none-eabihf",
+ "armv4t-unknown-linux-gnueabi",
+ "armv5te-unknown-linux-gnueabi",
+ "armv5te-unknown-linux-musleabi",
+ "armv6-unknown-netbsd-eabihf",
+ "armv7-linux-androideabi",
+ "armv7-unknown-cloudabi-eabihf",
+ "armv7-unknown-linux-gnueabihf",
+ "armv7-unknown-linux-musleabihf",
+ "armv7-unknown-netbsd-eabihf",
+ "armv7r-none-eabi",
+ "armv7r-none-eabihf",
+ "armv7s-apple-ios",
+ "asmjs-unknown-emscripten",
+ "i386-apple-ios",
+ "i586-pc-windows-msvc",
+ "i586-unknown-linux-gnu",
+ "i586-unknown-linux-musl",
+ "i686-apple-darwin",
+ "i686-linux-android",
+ "i686-pc-windows-gnu",
+ "i686-pc-windows-msvc",
+ "i686-unknown-cloudabi",
+ "i686-unknown-dragonfly",
+ "i686-unknown-freebsd",
+ "i686-unknown-haiku",
+ "i686-unknown-linux-gnu",
+ "i686-unknown-linux-musl",
+ "i686-unknown-netbsd",
+ "i686-unknown-openbsd",
+ "mips-unknown-linux-gnu",
+ "mips-unknown-linux-musl",
+ "mips-unknown-linux-uclibc",
+ "mips64-unknown-linux-gnuabi64",
+ "mips64el-unknown-linux-gnuabi64",
+ "mipsel-unknown-linux-gnu",
+ "mipsel-unknown-linux-musl",
+ "mipsel-unknown-linux-uclibc",
+ "msp430-none-elf",
+ "powerpc-unknown-linux-gnu",
+ "powerpc-unknown-linux-gnuspe",
+ "powerpc-unknown-linux-musl",
+ "powerpc-unknown-netbsd",
+ "powerpc64-unknown-linux-gnu",
+ "powerpc64-unknown-linux-musl",
+ "powerpc64le-unknown-linux-gnu",
+ "powerpc64le-unknown-linux-musl",
+ "riscv32imac-unknown-none-elf",
+ "riscv32imc-unknown-none-elf",
+ "s390x-unknown-linux-gnu",
+ "sparc-unknown-linux-gnu",
+ "sparc64-unknown-linux-gnu",
+ "sparc64-unknown-netbsd",
+ "sparcv9-sun-solaris",
+ "thumbv6m-none-eabi",
+ "thumbv7a-pc-windows-msvc",
+ "thumbv7em-none-eabi",
+ "thumbv7em-none-eabihf",
+ "thumbv7m-none-eabi",
+ "thumbv8m.base-none-eabi",
+ "wasm32-experimental-emscripten",
+ "wasm32-unknown-emscripten",
+ "wasm32-unknown-unknown",
+ "x86_64-apple-darwin",
+ "x86_64-apple-ios",
+ "x86_64-fortanix-unknown-sgx",
+ "x86_64-fuchsia",
+ "x86_64-linux-android",
+ "x86_64-pc-windows-gnu",
+ "x86_64-pc-windows-msvc",
+ "x86_64-rumprun-netbsd",
+ "x86_64-sun-solaris",
+ "x86_64-unknown-bitrig",
+ "x86_64-unknown-cloudabi",
+ "x86_64-unknown-dragonfly",
+ "x86_64-unknown-freebsd",
+ "x86_64-unknown-haiku",
+ "x86_64-unknown-hermit",
+ "x86_64-unknown-l4re-uclibc",
+ "x86_64-unknown-linux-gnu",
+ "x86_64-unknown-linux-gnux32",
+ "x86_64-unknown-linux-musl",
+ "x86_64-unknown-netbsd",
+ "x86_64-unknown-openbsd",
+ "x86_64-unknown-redox",
+ ]
+ # Additional targets from 1.33
+ if Version(version) >= "1.33.0":
+ rust_targets += [
+ "thumbv7neon-linux-androideabi",
+ "thumbv7neon-unknown-linux-gnueabihf",
+ "x86_64-unknown-uefi",
+ "thumbv8m.main-none-eabi",
+ "thumbv8m.main-none-eabihf",
+ ]
+ # Additional targets from 1.34
+ if Version(version) >= "1.34.0":
+ rust_targets += [
+ "nvptx64-nvidia-cuda",
+ "powerpc64-unknown-freebsd",
+ "riscv64gc-unknown-none-elf",
+ "riscv64imac-unknown-none-elf",
+ ]
+ # Additional targets from 1.35
+ if Version(version) >= "1.35.0":
+ rust_targets += [
+ "armv6-unknown-freebsd",
+ "armv7-unknown-freebsd",
+ "mipsisa32r6-unknown-linux-gnu",
+ "mipsisa32r6el-unknown-linux-gnu",
+ "mipsisa64r6-unknown-linux-gnuabi64",
+ "mipsisa64r6el-unknown-linux-gnuabi64",
+ "wasm32-unknown-wasi",
+ ]
+ # Additional targets from 1.36
+ if Version(version) >= "1.36.0":
+ rust_targets += ["wasm32-wasi"]
+ rust_targets.remove("wasm32-unknown-wasi")
+ rust_targets.remove("x86_64-unknown-bitrig")
+ # Additional targets from 1.37
+ if Version(version) >= "1.37.0":
+ rust_targets += ["x86_64-pc-solaris"]
+ # Additional targets from 1.38
+ if Version(version) >= "1.38.0":
+ rust_targets += [
+ "aarch64-unknown-redox",
+ "aarch64-wrs-vxworks",
+ "armv7-unknown-linux-gnueabi",
+ "armv7-unknown-linux-musleabi",
+ "armv7-wrs-vxworks",
+ "hexagon-unknown-linux-musl",
+ "i586-wrs-vxworks",
+ "i686-uwp-windows-gnu",
+ "i686-wrs-vxworks",
+ "powerpc-wrs-vxworks",
+ "powerpc-wrs-vxworks-spe",
+ "powerpc64-wrs-vxworks",
+ "riscv32i-unknown-none-elf",
+ "x86_64-uwp-windows-gnu",
+ "x86_64-wrs-vxworks",
+ ]
+ # Additional targets from 1.38
+ if Version(version) >= "1.39.0":
+ rust_targets += [
+ "aarch64-uwp-windows-msvc",
+ "armv7-wrs-vxworks-eabihf",
+ "i686-unknown-uefi",
+ "i686-uwp-windows-msvc",
+ "mips64-unknown-linux-muslabi64",
+ "mips64el-unknown-linux-muslabi64",
+ "sparc64-unknown-openbsd",
+ "x86_64-linux-kernel",
+ "x86_64-uwp-windows-msvc",
+ ]
+ rust_targets.remove("armv7-wrs-vxworks")
+ rust_targets.remove("i586-wrs-vxworks")
+
+ return 0, "\n".join(sorted(rust_targets)), ""
+ if (
+ len(args) == 6
+ and args[:2] == ("--crate-type", "staticlib")
+ and args[2].startswith("--target=")
+ and args[3] == "-o"
+ ):
+ with open(args[4], "w") as fh:
+ fh.write("foo")
+ return 0, "", ""
+ raise NotImplementedError("unsupported arguments")
+
+ return invoke_rustc
+
+
+class RustTest(BaseConfigureTest):
+ def get_rust_target(
+ self, target, compiler_type="gcc", version=MINIMUM_RUST_VERSION, arm_target=None
+ ):
+ environ = {
+ "PATH": os.pathsep.join(mozpath.abspath(p) for p in ("/bin", "/usr/bin"))
+ }
+
+ paths = {
+ mozpath.abspath("/usr/bin/cargo"): gen_invoke_cargo(version),
+ mozpath.abspath("/usr/bin/rustc"): gen_invoke_rustc(version),
+ }
+
+ self.TARGET = target
+ sandbox = self.get_sandbox(paths, {}, [], environ)
+
+ # Trick the sandbox into not running the target compiler check
+ dep = sandbox._depends[sandbox["c_compiler"]]
+ getattr(sandbox, "__value_for_depends")[(dep,)] = CompilerResult(
+ type=compiler_type
+ )
+ # Same for the arm_target checks.
+ dep = sandbox._depends[sandbox["arm_target"]]
+ getattr(sandbox, "__value_for_depends")[
+ (dep,)
+ ] = arm_target or ReadOnlyNamespace(
+ arm_arch=7, thumb2=False, fpu="vfpv2", float_abi="softfp"
+ )
+ return sandbox._value_for(sandbox["rust_target_triple"])
+
+ def test_rust_target(self):
+ # Cases where the output of config.sub matches a rust target
+ for straightforward in (
+ "x86_64-unknown-dragonfly",
+ "aarch64-unknown-freebsd",
+ "i686-unknown-freebsd",
+ "x86_64-unknown-freebsd",
+ "sparc64-unknown-netbsd",
+ "i686-unknown-netbsd",
+ "x86_64-unknown-netbsd",
+ "i686-unknown-openbsd",
+ "x86_64-unknown-openbsd",
+ "aarch64-unknown-linux-gnu",
+ "sparc64-unknown-linux-gnu",
+ "i686-unknown-linux-gnu",
+ "i686-apple-darwin",
+ "x86_64-apple-darwin",
+ "mips-unknown-linux-gnu",
+ "mipsel-unknown-linux-gnu",
+ "mips64-unknown-linux-gnuabi64",
+ "mips64el-unknown-linux-gnuabi64",
+ "powerpc64-unknown-linux-gnu",
+ "powerpc64le-unknown-linux-gnu",
+ "i686-pc-windows-msvc",
+ "x86_64-pc-windows-msvc",
+ "aarch64-pc-windows-msvc",
+ "i686-pc-windows-gnu",
+ "x86_64-pc-windows-gnu",
+ ):
+ self.assertEqual(self.get_rust_target(straightforward), straightforward)
+
+ # Cases where the output of config.sub is different
+ for autoconf, rust in (
+ ("aarch64-unknown-linux-android", "aarch64-linux-android"),
+ ("arm-unknown-linux-androideabi", "armv7-linux-androideabi"),
+ ("armv7-unknown-linux-androideabi", "armv7-linux-androideabi"),
+ ("i386-unknown-linux-android", "i686-linux-android"),
+ ("i686-unknown-linux-android", "i686-linux-android"),
+ ("i686-pc-linux-gnu", "i686-unknown-linux-gnu"),
+ ("x86_64-unknown-linux-android", "x86_64-linux-android"),
+ ("x86_64-pc-linux-gnu", "x86_64-unknown-linux-gnu"),
+ ("sparcv9-sun-solaris2", "sparcv9-sun-solaris"),
+ ("x86_64-sun-solaris2", "x86_64-sun-solaris"),
+ ):
+ self.assertEqual(self.get_rust_target(autoconf), rust)
+
+ # Windows
+ for autoconf, building_with_gcc, rust in (
+ ("i686-pc-mingw32", "clang-cl", "i686-pc-windows-msvc"),
+ ("x86_64-pc-mingw32", "clang-cl", "x86_64-pc-windows-msvc"),
+ ("i686-pc-mingw32", "clang", "i686-pc-windows-gnu"),
+ ("x86_64-pc-mingw32", "clang", "x86_64-pc-windows-gnu"),
+ ("i686-w64-mingw32", "clang", "i686-pc-windows-gnu"),
+ ("x86_64-w64-mingw32", "clang", "x86_64-pc-windows-gnu"),
+ ("aarch64-windows-mingw32", "clang-cl", "aarch64-pc-windows-msvc"),
+ ):
+ self.assertEqual(self.get_rust_target(autoconf, building_with_gcc), rust)
+
+ # Arm special cases
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-androideabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=True, float_abi="softfp"
+ ),
+ ),
+ "thumbv7neon-linux-androideabi",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-androideabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=False, float_abi="softfp"
+ ),
+ ),
+ "armv7-linux-androideabi",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-androideabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="vfpv2", thumb2=True, float_abi="softfp"
+ ),
+ ),
+ "armv7-linux-androideabi",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "armv7-unknown-linux-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=True, float_abi="hard"
+ ),
+ ),
+ "thumbv7neon-unknown-linux-gnueabihf",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "armv7-unknown-linux-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=False, float_abi="hard"
+ ),
+ ),
+ "armv7-unknown-linux-gnueabihf",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "armv7-unknown-linux-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="vfpv2", thumb2=True, float_abi="hard"
+ ),
+ ),
+ "armv7-unknown-linux-gnueabihf",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-freebsd13.0-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="vfpv2", thumb2=True, float_abi="hard"
+ ),
+ ),
+ "armv7-unknown-freebsd",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-freebsd13.0-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=6, fpu=None, thumb2=False, float_abi="hard"
+ ),
+ ),
+ "armv6-unknown-freebsd",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-gnueabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=4, fpu=None, thumb2=False, float_abi="softfp"
+ ),
+ ),
+ "armv4t-unknown-linux-gnueabi",
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py b/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
new file mode 100644
index 0000000000..f42778215b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
@@ -0,0 +1,433 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import copy
+import re
+import unittest
+from fnmatch import fnmatch
+from textwrap import dedent
+
+import six
+from mozpack import path as mozpath
+from mozunit import MockedOpen, main
+from six import StringIO
+
+from mozbuild.preprocessor import Preprocessor
+from mozbuild.util import ReadOnlyNamespace
+
+
+class CompilerPreprocessor(Preprocessor):
+ # The C preprocessor only expands macros when they are not in C strings.
+ # For now, we don't look very hard for C strings because they don't matter
+ # that much for our unit tests, but we at least avoid expanding in the
+ # simple "FOO" case.
+ VARSUBST = re.compile('(?<!")(?P<VAR>\w+)(?!")', re.U)
+ NON_WHITESPACE = re.compile("\S")
+ HAS_FEATURE_OR_BUILTIN = re.compile(
+ '(__has_(?:feature|builtin|attribute|warning))\("?([^"\)]*)"?\)'
+ )
+
+ def __init__(self, *args, **kwargs):
+ Preprocessor.__init__(self, *args, **kwargs)
+ self.do_filter("c_substitution")
+ self.setMarker("#\s*")
+
+ def do_if(self, expression, **kwargs):
+ # The C preprocessor handles numbers following C rules, which is a
+ # different handling than what our Preprocessor does out of the box.
+ # Hack around it enough that the configure tests work properly.
+ context = self.context
+
+ def normalize_numbers(value):
+ if isinstance(value, six.string_types):
+ if value[-1:] == "L" and value[:-1].isdigit():
+ value = int(value[:-1])
+ return value
+
+ # Our Preprocessor doesn't handle macros with parameters, so we hack
+ # around that for __has_feature()-like things.
+
+ def normalize_has_feature_or_builtin(expr):
+ return (
+ self.HAS_FEATURE_OR_BUILTIN.sub(r"\1\2", expr)
+ .replace("-", "_")
+ .replace("+", "_")
+ )
+
+ self.context = self.Context(
+ (normalize_has_feature_or_builtin(k), normalize_numbers(v))
+ for k, v in six.iteritems(context)
+ )
+ try:
+ return Preprocessor.do_if(
+ self, normalize_has_feature_or_builtin(expression), **kwargs
+ )
+ finally:
+ self.context = context
+
+ class Context(dict):
+ def __missing__(self, key):
+ return None
+
+ def filter_c_substitution(self, line):
+ def repl(matchobj):
+ varname = matchobj.group("VAR")
+ if varname in self.context:
+ result = six.text_type(self.context[varname])
+ # The C preprocessor inserts whitespaces around expanded
+ # symbols.
+ start, end = matchobj.span("VAR")
+ if self.NON_WHITESPACE.match(line[start - 1 : start]):
+ result = " " + result
+ if self.NON_WHITESPACE.match(line[end : end + 1]):
+ result = result + " "
+ return result
+ return matchobj.group(0)
+
+ return self.VARSUBST.sub(repl, line)
+
+
+class TestCompilerPreprocessor(unittest.TestCase):
+ def test_expansion(self):
+ pp = CompilerPreprocessor({"A": 1, "B": "2", "C": "c", "D": "d"})
+ pp.out = StringIO()
+ input = StringIO('A.B.C "D"')
+ input.name = "foo"
+ pp.do_include(input)
+
+ self.assertEqual(pp.out.getvalue(), '1 . 2 . c "D"')
+
+ def test_normalization(self):
+ pp = CompilerPreprocessor(
+ {"__has_attribute(bar)": 1, '__has_warning("-Wc++98-foo")': 1}
+ )
+ pp.out = StringIO()
+ input = StringIO(
+ dedent(
+ """\
+ #if __has_warning("-Wbar")
+ WBAR
+ #endif
+ #if __has_warning("-Wc++98-foo")
+ WFOO
+ #endif
+ #if !__has_warning("-Wc++98-foo")
+ NO_WFOO
+ #endif
+ #if __has_attribute(bar)
+ BAR
+ #else
+ NO_BAR
+ #endif
+ #if !__has_attribute(foo)
+ NO_FOO
+ #endif
+ """
+ )
+ )
+
+ input.name = "foo"
+ pp.do_include(input)
+
+ self.assertEqual(pp.out.getvalue(), "WFOO\nBAR\nNO_FOO\n")
+
+ def test_condition(self):
+ pp = CompilerPreprocessor({"A": 1, "B": "2", "C": "0L"})
+ pp.out = StringIO()
+ input = StringIO(
+ dedent(
+ """\
+ #ifdef A
+ IFDEF_A
+ #endif
+ #if A
+ IF_A
+ #endif
+ # if B
+ IF_B
+ # else
+ IF_NOT_B
+ # endif
+ #if !C
+ IF_NOT_C
+ #else
+ IF_C
+ #endif
+ """
+ )
+ )
+ input.name = "foo"
+ pp.do_include(input)
+
+ self.assertEqual("IFDEF_A\nIF_A\nIF_NOT_B\nIF_NOT_C\n", pp.out.getvalue())
+
+
+class FakeCompiler(dict):
+ """Defines a fake compiler for use in toolchain tests below.
+
+ The definitions given when creating an instance can have one of two
+ forms:
+ - a dict giving preprocessor symbols and their respective value, e.g.
+ { '__GNUC__': 4, '__STDC__': 1 }
+ - a dict associating flags to preprocessor symbols. An entry for `None`
+ is required in this case. Those are the baseline preprocessor symbols.
+ Additional entries describe additional flags to set or existing flags
+ to unset (with a value of `False`).
+ {
+ None: { '__GNUC__': 4, '__STDC__': 1, '__STRICT_ANSI__': 1 },
+ '-std=gnu99': { '__STDC_VERSION__': '199901L',
+ '__STRICT_ANSI__': False },
+ }
+ With the dict above, invoking the preprocessor with no additional flags
+ would define __GNUC__, __STDC__ and __STRICT_ANSI__, and with -std=gnu99,
+ __GNUC__, __STDC__, and __STDC_VERSION__ (__STRICT_ANSI__ would be
+ unset).
+ It is also possible to have different symbols depending on the source
+ file extension. In this case, the key is '*.ext'. e.g.
+ {
+ '*.c': { '__STDC__': 1 },
+ '*.cpp': { '__cplusplus': '199711L' },
+ }
+
+ All the given definitions are merged together.
+
+ A FakeCompiler instance itself can be used as a definition to create
+ another FakeCompiler.
+
+ For convenience, FakeCompiler instances can be added (+) to one another.
+ """
+
+ def __init__(self, *definitions):
+ for definition in definitions:
+ if all(not isinstance(d, dict) for d in six.itervalues(definition)):
+ definition = {None: definition}
+ for key, value in six.iteritems(definition):
+ self.setdefault(key, {}).update(value)
+
+ def __call__(self, stdin, args):
+ files = []
+ flags = []
+ args = iter(args)
+ while True:
+ arg = next(args, None)
+ if arg is None:
+ break
+ if arg.startswith("-"):
+ # Ignore -isysroot/--sysroot and the argument that follows it.
+ if arg in ("-isysroot", "--sysroot"):
+ next(args, None)
+ else:
+ flags.append(arg)
+ else:
+ files.append(arg)
+
+ if "-E" in flags:
+ assert len(files) == 1
+ file = files[0]
+ pp = CompilerPreprocessor(self[None])
+
+ def apply_defn(defn):
+ for k, v in six.iteritems(defn):
+ if v is False:
+ if k in pp.context:
+ del pp.context[k]
+ else:
+ pp.context[k] = v
+
+ for glob, defn in six.iteritems(self):
+ if glob and not glob.startswith("-") and fnmatch(file, glob):
+ apply_defn(defn)
+
+ for flag in flags:
+ apply_defn(self.get(flag, {}))
+
+ pp.out = StringIO()
+ pp.do_include(file)
+ return 0, pp.out.getvalue(), ""
+ elif "-c" in flags:
+ if "-funknown-flag" in flags:
+ return 1, "", ""
+ return 0, "", ""
+
+ return 1, "", ""
+
+ def __add__(self, other):
+ return FakeCompiler(self, other)
+
+
+class TestFakeCompiler(unittest.TestCase):
+ def test_fake_compiler(self):
+ with MockedOpen({"file": "A B C", "file.c": "A B C"}):
+ compiler = FakeCompiler({"A": "1", "B": "2"})
+ self.assertEqual(compiler(None, ["-E", "file"]), (0, "1 2 C", ""))
+
+ compiler = FakeCompiler(
+ {
+ None: {"A": "1", "B": "2"},
+ "-foo": {"C": "foo"},
+ "-bar": {"B": "bar", "C": "bar"},
+ "-qux": {"B": False},
+ "*.c": {"B": "42"},
+ }
+ )
+ self.assertEqual(compiler(None, ["-E", "file"]), (0, "1 2 C", ""))
+ self.assertEqual(compiler(None, ["-E", "-foo", "file"]), (0, "1 2 foo", ""))
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "file"]), (0, "1 bar bar", "")
+ )
+ self.assertEqual(compiler(None, ["-E", "-qux", "file"]), (0, "1 B C", ""))
+ self.assertEqual(
+ compiler(None, ["-E", "-foo", "-bar", "file"]), (0, "1 bar bar", "")
+ )
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "-foo", "file"]), (0, "1 bar foo", "")
+ )
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "-qux", "file"]), (0, "1 B bar", "")
+ )
+ self.assertEqual(
+ compiler(None, ["-E", "-qux", "-bar", "file"]), (0, "1 bar bar", "")
+ )
+ self.assertEqual(compiler(None, ["-E", "file.c"]), (0, "1 42 C", ""))
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "file.c"]), (0, "1 bar bar", "")
+ )
+
+ def test_multiple_definitions(self):
+ compiler = FakeCompiler({"A": 1, "B": 2}, {"C": 3})
+
+ self.assertEqual(compiler, {None: {"A": 1, "B": 2, "C": 3}})
+ compiler = FakeCompiler({"A": 1, "B": 2}, {"B": 4, "C": 3})
+
+ self.assertEqual(compiler, {None: {"A": 1, "B": 4, "C": 3}})
+ compiler = FakeCompiler(
+ {"A": 1, "B": 2}, {None: {"B": 4, "C": 3}, "-foo": {"D": 5}}
+ )
+
+ self.assertEqual(compiler, {None: {"A": 1, "B": 4, "C": 3}, "-foo": {"D": 5}})
+
+ compiler = FakeCompiler(
+ {None: {"A": 1, "B": 2}, "-foo": {"D": 5}},
+ {"-foo": {"D": 5}, "-bar": {"E": 6}},
+ )
+
+ self.assertEqual(
+ compiler, {None: {"A": 1, "B": 2}, "-foo": {"D": 5}, "-bar": {"E": 6}}
+ )
+
+
+class PrependFlags(list):
+ """Wrapper to allow to Prepend to flags instead of appending, in
+ CompilerResult.
+ """
+
+
+class CompilerResult(ReadOnlyNamespace):
+ """Helper of convenience to manipulate toolchain results in unit tests
+
+ When adding a dict, the result is a new CompilerResult with the values
+ from the dict replacing those from the CompilerResult, except for `flags`,
+ where the value from the dict extends the `flags` in `self`.
+ """
+
+ def __init__(
+ self, wrapper=None, compiler="", version="", type="", language="", flags=None
+ ):
+ if flags is None:
+ flags = []
+ if wrapper is None:
+ wrapper = []
+ super(CompilerResult, self).__init__(
+ flags=flags,
+ version=version,
+ type=type,
+ compiler=mozpath.abspath(compiler),
+ wrapper=wrapper,
+ language=language,
+ )
+
+ def __add__(self, other):
+ assert isinstance(other, dict)
+ result = copy.deepcopy(self.__dict__)
+ for k, v in six.iteritems(other):
+ if k == "flags":
+ flags = result.setdefault(k, [])
+ if isinstance(v, PrependFlags):
+ flags[:0] = v
+ else:
+ flags.extend(v)
+ else:
+ result[k] = v
+ return CompilerResult(**result)
+
+
+class TestCompilerResult(unittest.TestCase):
+ def test_compiler_result(self):
+ result = CompilerResult()
+ self.assertEqual(
+ result.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath(""),
+ "version": "",
+ "type": "",
+ "language": "",
+ "flags": [],
+ },
+ )
+
+ result = CompilerResult(
+ compiler="/usr/bin/gcc",
+ version="4.2.1",
+ type="gcc",
+ language="C",
+ flags=["-std=gnu99"],
+ )
+ self.assertEqual(
+ result.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath("/usr/bin/gcc"),
+ "version": "4.2.1",
+ "type": "gcc",
+ "language": "C",
+ "flags": ["-std=gnu99"],
+ },
+ )
+
+ result2 = result + {"flags": ["-m32"]}
+ self.assertEqual(
+ result2.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath("/usr/bin/gcc"),
+ "version": "4.2.1",
+ "type": "gcc",
+ "language": "C",
+ "flags": ["-std=gnu99", "-m32"],
+ },
+ )
+ # Original flags are untouched.
+ self.assertEqual(result.flags, ["-std=gnu99"])
+
+ result3 = result + {
+ "compiler": "/usr/bin/gcc-4.7",
+ "version": "4.7.3",
+ "flags": ["-m32"],
+ }
+ self.assertEqual(
+ result3.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath("/usr/bin/gcc-4.7"),
+ "version": "4.7.3",
+ "type": "gcc",
+ "language": "C",
+ "flags": ["-std=gnu99", "-m32"],
+ },
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py b/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py
new file mode 100644
index 0000000000..e6b96b3627
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py
@@ -0,0 +1,102 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+from buildconfig import topsrcdir
+from mozpack import path as mozpath
+from mozunit import MockedOpen, main
+
+from common import BaseConfigureTest
+from mozbuild.configure.options import InvalidOptionError
+
+
+class TestToolkitMozConfigure(BaseConfigureTest):
+ def test_moz_configure_options(self):
+ def get_value_for(args=[], environ={}, mozconfig=""):
+ sandbox = self.get_sandbox({}, {}, args, environ, mozconfig)
+
+ # Add a fake old-configure option
+ sandbox.option_impl(
+ "--with-foo", nargs="*", help="Help missing for old configure options"
+ )
+
+ # Remove all implied options, otherwise, getting
+ # all_configure_options below triggers them, and that triggers
+ # configure parts that aren't expected to run during this test.
+ del sandbox._implied_options[:]
+ result = sandbox._value_for(sandbox["all_configure_options"])
+ shell = mozpath.abspath("/bin/sh")
+ return result.replace("CONFIG_SHELL=%s " % shell, "")
+
+ self.assertEqual(
+ "--enable-application=browser",
+ get_value_for(["--enable-application=browser"]),
+ )
+
+ self.assertEqual(
+ "--enable-application=browser " "MOZ_VTUNE=1",
+ get_value_for(["--enable-application=browser", "MOZ_VTUNE=1"]),
+ )
+
+ value = get_value_for(
+ environ={"MOZ_VTUNE": "1"},
+ mozconfig="ac_add_options --enable-application=browser",
+ )
+
+ self.assertEqual("--enable-application=browser MOZ_VTUNE=1", value)
+
+ # --disable-js-shell is the default, so it's filtered out.
+ self.assertEqual(
+ "--enable-application=browser",
+ get_value_for(["--enable-application=browser", "--disable-js-shell"]),
+ )
+
+ # Normally, --without-foo would be filtered out because that's the
+ # default, but since it is a (fake) old-configure option, it always
+ # appears.
+ self.assertEqual(
+ "--enable-application=browser --without-foo",
+ get_value_for(["--enable-application=browser", "--without-foo"]),
+ )
+ self.assertEqual(
+ "--enable-application=browser --with-foo",
+ get_value_for(["--enable-application=browser", "--with-foo"]),
+ )
+
+ self.assertEqual(
+ "--enable-application=browser '--with-foo=foo bar'",
+ get_value_for(["--enable-application=browser", "--with-foo=foo bar"]),
+ )
+
+ def test_developer_options(self, milestone="42.0a1"):
+ def get_value(args=[], environ={}):
+ sandbox = self.get_sandbox({}, {}, args, environ)
+ return sandbox._value_for(sandbox["developer_options"])
+
+ milestone_path = os.path.join(topsrcdir, "config", "milestone.txt")
+ with MockedOpen({milestone_path: milestone}):
+ # developer options are enabled by default on "nightly" milestone
+ # only
+ self.assertEqual(get_value(), "a" in milestone or None)
+
+ self.assertEqual(get_value(["--enable-release"]), None)
+
+ self.assertEqual(get_value(environ={"MOZILLA_OFFICIAL": 1}), None)
+
+ self.assertEqual(
+ get_value(["--enable-release"], environ={"MOZILLA_OFFICIAL": 1}), None
+ )
+
+ with self.assertRaises(InvalidOptionError):
+ get_value(["--disable-release"], environ={"MOZILLA_OFFICIAL": 1})
+
+ self.assertEqual(get_value(environ={"MOZ_AUTOMATION": 1}), None)
+
+ def test_developer_options_release(self):
+ self.test_developer_options("42.0")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_util.py b/python/mozbuild/mozbuild/test/configure/test_util.py
new file mode 100644
index 0000000000..81c2e2a8bf
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_util.py
@@ -0,0 +1,539 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import logging
+import os
+import sys
+import tempfile
+import textwrap
+import unittest
+
+import six
+from buildconfig import topsrcdir
+from mozpack import path as mozpath
+from mozunit import main
+from six import StringIO
+
+from common import ConfigureTestSandbox
+from mozbuild.configure import ConfigureSandbox
+from mozbuild.configure.util import (
+ ConfigureOutputHandler,
+ LineIO,
+ Version,
+ getpreferredencoding,
+)
+from mozbuild.util import exec_
+
+
+class TestConfigureOutputHandler(unittest.TestCase):
+ def test_separation(self):
+ out = StringIO()
+ err = StringIO()
+ name = "%s.test_separation" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ logger.addHandler(ConfigureOutputHandler(out, err))
+
+ logger.error("foo")
+ logger.warning("bar")
+ logger.info("baz")
+ # DEBUG level is not printed out by this handler
+ logger.debug("qux")
+
+ self.assertEqual(out.getvalue(), "baz\n")
+ self.assertEqual(err.getvalue(), "foo\nbar\n")
+
+ def test_format(self):
+ out = StringIO()
+ err = StringIO()
+ name = "%s.test_format" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, err)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ logger.error("foo")
+ logger.warning("bar")
+ logger.info("baz")
+ # DEBUG level is not printed out by this handler
+ logger.debug("qux")
+
+ self.assertEqual(out.getvalue(), "baz\n")
+ self.assertEqual(err.getvalue(), "ERROR:foo\n" "WARNING:bar\n")
+
+ def test_continuation(self):
+ out = StringIO()
+ name = "%s.test_continuation" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, out)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.info("yes")
+ logger.info("qux")
+
+ self.assertEqual(out.getvalue(), "foo\n" "checking bar... yes\n" "qux\n")
+
+ out.seek(0)
+ out.truncate()
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.warning("hoge")
+ logger.info("no")
+ logger.info("qux")
+
+ self.assertEqual(
+ out.getvalue(),
+ "foo\n" "checking bar... \n" "WARNING:hoge\n" " ... no\n" "qux\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.warning("hoge")
+ logger.warning("fuga")
+ logger.info("no")
+ logger.info("qux")
+
+ self.assertEqual(
+ out.getvalue(),
+ "foo\n"
+ "checking bar... \n"
+ "WARNING:hoge\n"
+ "WARNING:fuga\n"
+ " ... no\n"
+ "qux\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+ err = StringIO()
+
+ logger.removeHandler(handler)
+ handler = ConfigureOutputHandler(out, err)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.warning("hoge")
+ logger.warning("fuga")
+ logger.info("no")
+ logger.info("qux")
+
+ self.assertEqual(out.getvalue(), "foo\n" "checking bar... no\n" "qux\n")
+
+ self.assertEqual(err.getvalue(), "WARNING:hoge\n" "WARNING:fuga\n")
+
+ def test_queue_debug(self):
+ out = StringIO()
+ name = "%s.test_queue_debug" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, out, maxlen=3)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.info("yes")
+ logger.info("qux")
+
+ self.assertEqual(out.getvalue(), "checking bar... yes\n" "qux\n")
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.info("no")
+ logger.error("fail")
+
+ self.assertEqual(
+ out.getvalue(), "checking bar... no\n" "DEBUG:do foo\n" "ERROR:fail\n"
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.debug("do bar")
+ logger.debug("do baz")
+ logger.info("no")
+ logger.error("fail")
+
+ self.assertEqual(
+ out.getvalue(),
+ "checking bar... no\n"
+ "DEBUG:do foo\n"
+ "DEBUG:do bar\n"
+ "DEBUG:do baz\n"
+ "ERROR:fail\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.debug("do bar")
+ logger.debug("do baz")
+ logger.debug("do qux")
+ logger.debug("do hoge")
+ logger.info("no")
+ logger.error("fail")
+
+ self.assertEqual(
+ out.getvalue(),
+ "checking bar... no\n"
+ "DEBUG:<truncated - see config.log for full output>\n"
+ "DEBUG:do baz\n"
+ "DEBUG:do qux\n"
+ "DEBUG:do hoge\n"
+ "ERROR:fail\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ try:
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.debug("do bar")
+ logger.info("no")
+ e = Exception("fail")
+ raise e
+ except Exception as caught:
+ self.assertIs(caught, e)
+
+ self.assertEqual(
+ out.getvalue(), "checking bar... no\n" "DEBUG:do foo\n" "DEBUG:do bar\n"
+ )
+
+ def test_queue_debug_reentrant(self):
+ out = StringIO()
+ name = "%s.test_queue_debug_reentrant" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, out, maxlen=10)
+ handler.setFormatter(logging.Formatter("%(levelname)s| %(message)s"))
+ logger.addHandler(handler)
+
+ try:
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ e = Exception("inner exception")
+ raise e
+ except Exception as caught:
+ self.assertIs(caught, e)
+
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n" "inner info\n" "DEBUG| outer debug\n" "DEBUG| inner debug\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ try:
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ e = Exception("outer exception")
+ raise e
+ except Exception as caught:
+ self.assertIs(caught, e)
+
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n" "inner info\n" "DEBUG| outer debug\n" "DEBUG| inner debug\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ logger.error("inner error")
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n"
+ "inner info\n"
+ "DEBUG| outer debug\n"
+ "DEBUG| inner debug\n"
+ "ERROR| inner error\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ logger.error("outer error")
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n"
+ "inner info\n"
+ "DEBUG| outer debug\n"
+ "DEBUG| inner debug\n"
+ "ERROR| outer error\n",
+ )
+
+ def test_is_same_output(self):
+ fd1 = sys.stderr.fileno()
+ fd2 = os.dup(fd1)
+ try:
+ self.assertTrue(ConfigureOutputHandler._is_same_output(fd1, fd2))
+ finally:
+ os.close(fd2)
+
+ fd2, path = tempfile.mkstemp()
+ try:
+ self.assertFalse(ConfigureOutputHandler._is_same_output(fd1, fd2))
+
+ fd3 = os.dup(fd2)
+ try:
+ self.assertTrue(ConfigureOutputHandler._is_same_output(fd2, fd3))
+ finally:
+ os.close(fd3)
+
+ with open(path, "a") as fh:
+ fd3 = fh.fileno()
+ self.assertTrue(ConfigureOutputHandler._is_same_output(fd2, fd3))
+
+ finally:
+ os.close(fd2)
+ os.remove(path)
+
+
+class TestLineIO(unittest.TestCase):
+ def test_lineio(self):
+ lines = []
+ l = LineIO(lambda l: lines.append(l))
+
+ l.write("a")
+ self.assertEqual(lines, [])
+
+ l.write("b")
+ self.assertEqual(lines, [])
+
+ l.write("\n")
+ self.assertEqual(lines, ["ab"])
+
+ l.write("cdef")
+ self.assertEqual(lines, ["ab"])
+
+ l.write("\n")
+ self.assertEqual(lines, ["ab", "cdef"])
+
+ l.write("ghi\njklm")
+ self.assertEqual(lines, ["ab", "cdef", "ghi"])
+
+ l.write("nop\nqrst\nuv\n")
+ self.assertEqual(lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv"])
+
+ l.write("wx\nyz")
+ self.assertEqual(lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv", "wx"])
+
+ l.close()
+ self.assertEqual(
+ lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv", "wx", "yz"]
+ )
+
+ def test_lineio_contextmanager(self):
+ lines = []
+ with LineIO(lambda l: lines.append(l)) as l:
+ l.write("a\nb\nc")
+
+ self.assertEqual(lines, ["a", "b"])
+
+ self.assertEqual(lines, ["a", "b", "c"])
+
+
+class TestLogSubprocessOutput(unittest.TestCase):
+ def test_non_ascii_subprocess_output(self):
+ out = StringIO()
+ sandbox = ConfigureSandbox({}, {}, ["configure"], out, out)
+
+ sandbox.include_file(
+ mozpath.join(topsrcdir, "build", "moz.configure", "util.configure")
+ )
+ sandbox.include_file(
+ mozpath.join(
+ topsrcdir,
+ "python",
+ "mozbuild",
+ "mozbuild",
+ "test",
+ "configure",
+ "data",
+ "subprocess.configure",
+ )
+ )
+ status = 0
+ try:
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+
+ self.assertEqual(status, 0)
+ quote_char = "'"
+ if getpreferredencoding().lower() == "utf-8":
+ quote_char = "\u00B4"
+ self.assertEqual(six.ensure_text(out.getvalue().strip()), quote_char)
+
+
+class TestVersion(unittest.TestCase):
+ def test_version_simple(self):
+ v = Version("1")
+ self.assertEqual(v, "1")
+ self.assertLess(v, "2")
+ self.assertGreater(v, "0.5")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 0)
+ self.assertEqual(v.patch, 0)
+
+ def test_version_more(self):
+ v = Version("1.2.3b")
+ self.assertLess(v, "2")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 2)
+ self.assertEqual(v.patch, 3)
+
+ def test_version_bad(self):
+ # A version with a letter in the middle doesn't really make sense,
+ # so everything after it should be ignored.
+ v = Version("1.2b.3")
+ self.assertLess(v, "2")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 2)
+ self.assertEqual(v.patch, 0)
+
+ def test_version_badder(self):
+ v = Version("1b.2.3")
+ self.assertLess(v, "2")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 0)
+ self.assertEqual(v.patch, 0)
+
+
+class TestCheckCmdOutput(unittest.TestCase):
+ def get_result(self, command="", paths=None):
+ paths = paths or {}
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureTestSandbox(paths, config, {}, ["/bin/configure"], out, out)
+ sandbox.include_file(
+ mozpath.join(topsrcdir, "build", "moz.configure", "util.configure")
+ )
+ status = 0
+ try:
+ exec_(command, sandbox)
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+ return config, out.getvalue(), status
+
+ def test_simple_program(self):
+ def mock_simple_prog(_, args):
+ if len(args) == 1 and args[0] == "--help":
+ return 0, "simple program help...", ""
+ self.fail("Unexpected arguments to mock_simple_program: %s" % args)
+
+ prog_path = mozpath.abspath("/simple/prog")
+ cmd = "log.info(check_cmd_output('%s', '--help'))" % prog_path
+ config, out, status = self.get_result(cmd, paths={prog_path: mock_simple_prog})
+ self.assertEqual(config, {})
+ self.assertEqual(status, 0)
+ self.assertEqual(out, "simple program help...\n")
+
+ def test_failing_program(self):
+ def mock_error_prog(_, args):
+ if len(args) == 1 and args[0] == "--error":
+ return (127, "simple program output", "simple program error output")
+ self.fail("Unexpected arguments to mock_error_program: %s" % args)
+
+ prog_path = mozpath.abspath("/simple/prog")
+ cmd = "log.info(check_cmd_output('%s', '--error'))" % prog_path
+ config, out, status = self.get_result(cmd, paths={prog_path: mock_error_prog})
+ self.assertEqual(config, {})
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ DEBUG: Executing: `%s --error`
+ DEBUG: The command returned non-zero exit status 127.
+ DEBUG: Its output was:
+ DEBUG: | simple program output
+ DEBUG: Its error output was:
+ DEBUG: | simple program error output
+ ERROR: Command `%s --error` failed with exit status 127.
+ """
+ % (prog_path, prog_path)
+ ),
+ )
+
+ def test_error_callback(self):
+ def mock_error_prog(_, args):
+ if len(args) == 1 and args[0] == "--error":
+ return 127, "simple program error...", ""
+ self.fail("Unexpected arguments to mock_error_program: %s" % args)
+
+ prog_path = mozpath.abspath("/simple/prog")
+ cmd = textwrap.dedent(
+ """\
+ check_cmd_output('%s', '--error',
+ onerror=lambda: die('`prog` produced an error'))
+ """
+ % prog_path
+ )
+ config, out, status = self.get_result(cmd, paths={prog_path: mock_error_prog})
+ self.assertEqual(config, {})
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ DEBUG: Executing: `%s --error`
+ DEBUG: The command returned non-zero exit status 127.
+ DEBUG: Its output was:
+ DEBUG: | simple program error...
+ ERROR: `prog` produced an error
+ """
+ % prog_path
+ ),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/controller/__init__.py b/python/mozbuild/mozbuild/test/controller/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/controller/__init__.py
diff --git a/python/mozbuild/mozbuild/test/controller/test_ccachestats.py b/python/mozbuild/mozbuild/test/controller/test_ccachestats.py
new file mode 100644
index 0000000000..f1efa78c3a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/controller/test_ccachestats.py
@@ -0,0 +1,866 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import time
+import unittest
+
+from mozunit import main
+
+from mozbuild.controller.building import CCacheStats
+
+TIMESTAMP = time.time()
+TIMESTAMP2 = time.time() + 10
+TIMESTAMP_STR = time.strftime("%c", time.localtime(TIMESTAMP))
+TIMESTAMP2_STR = time.strftime("%c", time.localtime(TIMESTAMP2))
+
+
+class TestCcacheStats(unittest.TestCase):
+ STAT_GARBAGE = """A garbage line which should be failed to parse"""
+
+ STAT0 = """
+ cache directory /home/tlin/.ccache
+ cache hit (direct) 0
+ cache hit (preprocessed) 0
+ cache miss 0
+ files in cache 0
+ cache size 0 Kbytes
+ max cache size 16.0 Gbytes"""
+
+ STAT1 = """
+ cache directory /home/tlin/.ccache
+ cache hit (direct) 100
+ cache hit (preprocessed) 200
+ cache miss 2500
+ called for link 180
+ called for preprocessing 6
+ compile failed 11
+ preprocessor error 3
+ bad compiler arguments 6
+ unsupported source language 9
+ autoconf compile/link 60
+ unsupported compiler option 2
+ no input file 21
+ files in cache 7344
+ cache size 1.9 Gbytes
+ max cache size 16.0 Gbytes"""
+
+ STAT2 = """
+ cache directory /home/tlin/.ccache
+ cache hit (direct) 1900
+ cache hit (preprocessed) 300
+ cache miss 2600
+ called for link 361
+ called for preprocessing 12
+ compile failed 22
+ preprocessor error 6
+ bad compiler arguments 12
+ unsupported source language 18
+ autoconf compile/link 120
+ unsupported compiler option 4
+ no input file 48
+ files in cache 7392
+ cache size 2.0 Gbytes
+ max cache size 16.0 Gbytes"""
+
+ STAT3 = """
+ cache directory /Users/tlin/.ccache
+ primary config /Users/tlin/.ccache/ccache.conf
+ secondary config (readonly) /usr/local/Cellar/ccache/3.2/etc/ccache.conf
+ cache hit (direct) 12004
+ cache hit (preprocessed) 1786
+ cache miss 26348
+ called for link 2338
+ called for preprocessing 6313
+ compile failed 399
+ preprocessor error 390
+ bad compiler arguments 86
+ unsupported source language 66
+ autoconf compile/link 2439
+ unsupported compiler option 187
+ no input file 1068
+ files in cache 18044
+ cache size 7.5 GB
+ max cache size 8.6 GB
+ """
+
+ STAT4 = """
+ cache directory /Users/tlin/.ccache
+ primary config /Users/tlin/.ccache/ccache.conf
+ secondary config (readonly) /usr/local/Cellar/ccache/3.2.1/etc/ccache.conf
+ cache hit (direct) 21039
+ cache hit (preprocessed) 2315
+ cache miss 39370
+ called for link 3651
+ called for preprocessing 6693
+ compile failed 723
+ ccache internal error 1
+ preprocessor error 588
+ bad compiler arguments 128
+ unsupported source language 99
+ autoconf compile/link 3669
+ unsupported compiler option 187
+ no input file 1711
+ files in cache 18313
+ cache size 6.3 GB
+ max cache size 6.0 GB
+ """
+
+ STAT5 = """
+ cache directory /Users/tlin/.ccache
+ primary config /Users/tlin/.ccache/ccache.conf
+ secondary config (readonly) /usr/local/Cellar/ccache/3.2.1/etc/ccache.conf
+ cache hit (direct) 21039
+ cache hit (preprocessed) 2315
+ cache miss 39372
+ called for link 3653
+ called for preprocessing 6693
+ compile failed 723
+ ccache internal error 1
+ preprocessor error 588
+ bad compiler arguments 128
+ unsupported source language 99
+ autoconf compile/link 3669
+ unsupported compiler option 187
+ no input file 1711
+ files in cache 17411
+ cache size 6.0 GB
+ max cache size 6.0 GB
+ """
+
+ STAT6 = """
+ cache directory /Users/tlin/.ccache
+ primary config /Users/tlin/.ccache/ccache.conf
+ secondary config (readonly) /usr/local/Cellar/ccache/3.3.2/etc/ccache.conf
+ cache hit (direct) 319287
+ cache hit (preprocessed) 125987
+ cache miss 749959
+ cache hit rate 37.25 %
+ called for link 87978
+ called for preprocessing 418591
+ multiple source files 1861
+ compiler produced no output 122
+ compiler produced empty output 174
+ compile failed 14330
+ ccache internal error 1
+ preprocessor error 9459
+ can't use precompiled header 4
+ bad compiler arguments 2077
+ unsupported source language 18195
+ autoconf compile/link 51485
+ unsupported compiler option 322
+ no input file 309538
+ cleanups performed 1
+ files in cache 17358
+ cache size 15.4 GB
+ max cache size 17.2 GB
+ """
+
+ STAT7 = """
+ cache directory /Users/tlin/.ccache
+ primary config /Users/tlin/.ccache/ccache.conf
+ secondary config (readonly) /usr/local/Cellar/ccache/3.3.3/etc/ccache.conf
+ cache hit (direct) 27035
+ cache hit (preprocessed) 13939
+ cache miss 62630
+ cache hit rate 39.55 %
+ called for link 1280
+ called for preprocessing 736
+ compile failed 550
+ preprocessor error 638
+ bad compiler arguments 20
+ autoconf compile/link 1751
+ unsupported code directive 2
+ no input file 2378
+ cleanups performed 1792
+ files in cache 3479
+ cache size 4.4 GB
+ max cache size 5.0 GB
+ """
+
+ # Substitute a locally-generated timestamp because the timestamp format is
+ # locale-dependent.
+ STAT8 = f"""
+ cache directory /home/psimonyi/.ccache
+ primary config /home/psimonyi/.ccache/ccache.conf
+ secondary config (readonly) /etc/ccache.conf
+ stats zero time {TIMESTAMP_STR}
+ cache hit (direct) 571
+ cache hit (preprocessed) 1203
+ cache miss 11747
+ cache hit rate 13.12 %
+ called for link 623
+ called for preprocessing 7194
+ compile failed 32
+ preprocessor error 137
+ bad compiler arguments 4
+ autoconf compile/link 348
+ no input file 162
+ cleanups performed 77
+ files in cache 13464
+ cache size 6.2 GB
+ max cache size 7.0 GB
+ """
+
+ STAT9 = f"""
+ cache directory /Users/tlin/.ccache
+ primary config /Users/tlin/.ccache/ccache.conf
+ secondary config (readonly) /usr/local/Cellar/ccache/3.5/etc/ccache.conf
+ stats updated {TIMESTAMP2_STR}
+ stats zeroed {TIMESTAMP_STR}
+ cache hit (direct) 80147
+ cache hit (preprocessed) 21413
+ cache miss 191128
+ cache hit rate 34.70 %
+ called for link 5194
+ called for preprocessing 1721
+ compile failed 825
+ preprocessor error 3838
+ cache file missing 4863
+ bad compiler arguments 32
+ autoconf compile/link 3554
+ unsupported code directive 4
+ no input file 5545
+ cleanups performed 3154
+ files in cache 18525
+ cache size 13.4 GB
+ max cache size 15.0 GB
+ """
+
+ VERSION_3_5_GIT = """
+ ccache version 3.5.1+2_gf5309092_dirty
+
+ Copyright (C) 2002-2007 Andrew Tridgell
+ Copyright (C) 2009-2019 Joel Rosdahl
+
+ This program is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3 of the License, or (at your option) any later
+ version.
+ """
+
+ VERSION_4_2 = """
+ ccache version 4.2.1
+
+ Copyright (C) 2002-2007 Andrew Tridgell
+ Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+
+ See <https://ccache.dev/credits.html> for a complete list of contributors.
+
+ This program is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3 of the License, or (at your option) any later
+ version.
+ """
+
+ VERSION_4_4 = """
+ ccache version 4.4
+ Features: file-storage http-storage
+
+ Copyright (C) 2002-2007 Andrew Tridgell
+ Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+
+ See <https://ccache.dev/credits.html> for a complete list of contributors.
+
+ This program is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3 of the License, or (at your option) any later
+ version.
+ """
+
+ VERSION_4_4_2 = """
+ ccache version 4.4.2
+ Features: file-storage http-storage
+
+ Copyright (C) 2002-2007 Andrew Tridgell
+ Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+
+ See <https://ccache.dev/credits.html> for a complete list of contributors.
+
+ This program is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3 of the License, or (at your option) any later
+ version.
+ """
+
+ VERSION_4_5 = """
+ ccache version 4.5.1
+ Features: file-storage http-storage redis-storage
+
+ Copyright (C) 2002-2007 Andrew Tridgell
+ Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+
+ See <https://ccache.dev/credits.html> for a complete list of contributors.
+
+ This program is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3 of the License, or (at your option) any later
+ version.
+ """
+
+ STAT10 = f"""\
+stats_updated_timestamp\t{int(TIMESTAMP)}
+stats_zeroed_timestamp\t0
+direct_cache_hit\t197
+preprocessed_cache_hit\t719
+cache_miss\t8427
+called_for_link\t569
+called_for_preprocessing\t110
+multiple_source_files\t0
+compiler_produced_stdout\t0
+compiler_produced_no_output\t0
+compiler_produced_empty_output\t0
+compile_failed\t49
+internal_error\t1
+preprocessor_error\t90
+could_not_use_precompiled_header\t0
+could_not_use_modules\t0
+could_not_find_compiler\t0
+missing_cache_file\t1
+bad_compiler_arguments\t6
+unsupported_source_language\t0
+compiler_check_failed\t0
+autoconf_test\t418
+unsupported_compiler_option\t0
+unsupported_code_directive\t1
+output_to_stdout\t0
+bad_output_file\t0
+no_input_file\t9
+error_hashing_extra_file\t0
+cleanups_performed\t161
+files_in_cache\t4425
+cache_size_kibibyte\t4624220
+"""
+
+ STAT11 = f"""\
+stats_updated_timestamp\t{int(TIMESTAMP)}
+stats_zeroed_timestamp\t{int(TIMESTAMP2)}
+direct_cache_hit\t0
+preprocessed_cache_hit\t0
+cache_miss\t0
+called_for_link\t0
+called_for_preprocessing\t0
+multiple_source_files\t0
+compiler_produced_stdout\t0
+compiler_produced_no_output\t0
+compiler_produced_empty_output\t0
+compile_failed\t0
+internal_error\t0
+preprocessor_error\t0
+could_not_use_precompiled_header\t0
+could_not_use_modules\t0
+could_not_find_compiler\t0
+missing_cache_file\t0
+bad_compiler_arguments\t0
+unsupported_source_language\t0
+compiler_check_failed\t0
+autoconf_test\t0
+unsupported_compiler_option\t0
+unsupported_code_directive\t0
+output_to_stdout\t0
+bad_output_file\t0
+no_input_file\t0
+error_hashing_extra_file\t0
+cleanups_performed\t16
+files_in_cache\t0
+cache_size_kibibyte\t0
+"""
+
+ STAT12 = """\
+stats_updated_timestamp\t0
+stats_zeroed_timestamp\t0
+direct_cache_hit\t0
+preprocessed_cache_hit\t0
+cache_miss\t0
+called_for_link\t0
+called_for_preprocessing\t0
+multiple_source_files\t0
+compiler_produced_stdout\t0
+compiler_produced_no_output\t0
+compiler_produced_empty_output\t0
+compile_failed\t0
+internal_error\t0
+preprocessor_error\t0
+could_not_use_precompiled_header\t0
+could_not_use_modules\t0
+could_not_find_compiler\t0
+missing_cache_file\t0
+bad_compiler_arguments\t0
+unsupported_source_language\t0
+compiler_check_failed\t0
+autoconf_test\t0
+unsupported_compiler_option\t0
+unsupported_code_directive\t0
+output_to_stdout\t0
+bad_output_file\t0
+no_input_file\t0
+error_hashing_extra_file\t0
+cleanups_performed\t16
+files_in_cache\t0
+cache_size_kibibyte\t0
+"""
+
+ STAT13 = f"""\
+stats_updated_timestamp\t{int(TIMESTAMP)}
+stats_zeroed_timestamp\t{int(TIMESTAMP2)}
+direct_cache_hit\t280542
+preprocessed_cache_hit\t0
+cache_miss\t387653
+called_for_link\t0
+called_for_preprocessing\t0
+multiple_source_files\t0
+compiler_produced_stdout\t0
+compiler_produced_no_output\t0
+compiler_produced_empty_output\t0
+compile_failed\t1665
+internal_error\t1
+preprocessor_error\t0
+could_not_use_precompiled_header\t0
+could_not_use_modules\t0
+could_not_find_compiler\t0
+missing_cache_file\t0
+bad_compiler_arguments\t0
+unsupported_source_language\t0
+compiler_check_failed\t0
+autoconf_test\t0
+unsupported_compiler_option\t0
+unsupported_code_directive\t0
+output_to_stdout\t0
+bad_output_file\t0
+no_input_file\t2
+error_hashing_extra_file\t0
+cleanups_performed\t364
+files_in_cache\t335104
+cache_size_kibibyte\t18224250
+"""
+
+ maxDiff = None
+
+ def test_parse_garbage_stats_message(self):
+ self.assertRaises(ValueError, CCacheStats, self.STAT_GARBAGE)
+
+ def test_parse_zero_stats_message(self):
+ stats = CCacheStats(self.STAT0)
+ self.assertEqual(stats.hit_rates(), (0, 0, 0))
+
+ def test_hit_rate_of_diff_stats(self):
+ stats1 = CCacheStats(self.STAT1)
+ stats2 = CCacheStats(self.STAT2)
+ stats_diff = stats2 - stats1
+ self.assertEqual(stats_diff.hit_rates(), (0.9, 0.05, 0.05))
+
+ def test_stats_contains_data(self):
+ stats0 = CCacheStats(self.STAT0)
+ stats1 = CCacheStats(self.STAT1)
+ stats2 = CCacheStats(self.STAT2)
+ stats_diff_zero = stats1 - stats1
+ stats_diff_negative1 = stats0 - stats1
+ stats_diff_negative2 = stats1 - stats2
+
+ self.assertFalse(stats0)
+ self.assertTrue(stats1)
+ self.assertTrue(stats2)
+ self.assertFalse(stats_diff_zero)
+ self.assertFalse(stats_diff_negative1)
+ self.assertFalse(stats_diff_negative2)
+
+ def test_stats_version32(self):
+ stat2 = CCacheStats(self.STAT2)
+ stat3 = CCacheStats(self.STAT3)
+ stats_diff = stat3 - stat2
+ self.assertEqual(
+ str(stat3),
+ "cache hit (direct) 12004\n"
+ "cache hit (preprocessed) 1786\n"
+ "cache miss 26348\n"
+ "called for link 2338\n"
+ "called for preprocessing 6313\n"
+ "compile failed 399\n"
+ "preprocessor error 390\n"
+ "bad compiler arguments 86\n"
+ "unsupported source language 66\n"
+ "autoconf compile/link 2439\n"
+ "unsupported compiler option 187\n"
+ "no input file 1068\n"
+ "files in cache 18044\n"
+ "cache size 7.5 Gbytes\n"
+ "max cache size 8.6 Gbytes",
+ )
+ self.assertEqual(
+ str(stats_diff),
+ "cache hit (direct) 10104\n"
+ "cache hit (preprocessed) 1486\n"
+ "cache miss 23748\n"
+ "called for link 1977\n"
+ "called for preprocessing 6301\n"
+ "compile failed 377\n"
+ "preprocessor error 384\n"
+ "bad compiler arguments 74\n"
+ "unsupported source language 48\n"
+ "autoconf compile/link 2319\n"
+ "unsupported compiler option 183\n"
+ "no input file 1020\n"
+ "files in cache 18044\n"
+ "cache size 7.5 Gbytes\n"
+ "max cache size 8.6 Gbytes",
+ )
+
+ def test_cache_size_shrinking(self):
+ stat4 = CCacheStats(self.STAT4)
+ stat5 = CCacheStats(self.STAT5)
+ stats_diff = stat5 - stat4
+ self.assertEqual(
+ str(stat4),
+ "cache hit (direct) 21039\n"
+ "cache hit (preprocessed) 2315\n"
+ "cache miss 39370\n"
+ "called for link 3651\n"
+ "called for preprocessing 6693\n"
+ "compile failed 723\n"
+ "ccache internal error 1\n"
+ "preprocessor error 588\n"
+ "bad compiler arguments 128\n"
+ "unsupported source language 99\n"
+ "autoconf compile/link 3669\n"
+ "unsupported compiler option 187\n"
+ "no input file 1711\n"
+ "files in cache 18313\n"
+ "cache size 6.3 Gbytes\n"
+ "max cache size 6.0 Gbytes",
+ )
+ self.assertEqual(
+ str(stat5),
+ "cache hit (direct) 21039\n"
+ "cache hit (preprocessed) 2315\n"
+ "cache miss 39372\n"
+ "called for link 3653\n"
+ "called for preprocessing 6693\n"
+ "compile failed 723\n"
+ "ccache internal error 1\n"
+ "preprocessor error 588\n"
+ "bad compiler arguments 128\n"
+ "unsupported source language 99\n"
+ "autoconf compile/link 3669\n"
+ "unsupported compiler option 187\n"
+ "no input file 1711\n"
+ "files in cache 17411\n"
+ "cache size 6.0 Gbytes\n"
+ "max cache size 6.0 Gbytes",
+ )
+ self.assertEqual(
+ str(stats_diff),
+ "cache hit (direct) 0\n"
+ "cache hit (preprocessed) 0\n"
+ "cache miss 2\n"
+ "called for link 2\n"
+ "called for preprocessing 0\n"
+ "compile failed 0\n"
+ "ccache internal error 0\n"
+ "preprocessor error 0\n"
+ "bad compiler arguments 0\n"
+ "unsupported source language 0\n"
+ "autoconf compile/link 0\n"
+ "unsupported compiler option 0\n"
+ "no input file 0\n"
+ "files in cache 17411\n"
+ "cache size 6.0 Gbytes\n"
+ "max cache size 6.0 Gbytes",
+ )
+
+ def test_stats_version33(self):
+ # Test stats for 3.3.2.
+ stat3 = CCacheStats(self.STAT3)
+ stat6 = CCacheStats(self.STAT6)
+ stats_diff = stat6 - stat3
+ self.assertEqual(
+ str(stat6),
+ "cache hit (direct) 319287\n"
+ "cache hit (preprocessed) 125987\n"
+ "cache hit rate 37\n"
+ "cache miss 749959\n"
+ "called for link 87978\n"
+ "called for preprocessing 418591\n"
+ "multiple source files 1861\n"
+ "compiler produced no output 122\n"
+ "compiler produced empty output 174\n"
+ "compile failed 14330\n"
+ "ccache internal error 1\n"
+ "preprocessor error 9459\n"
+ "can't use precompiled header 4\n"
+ "bad compiler arguments 2077\n"
+ "unsupported source language 18195\n"
+ "autoconf compile/link 51485\n"
+ "unsupported compiler option 322\n"
+ "no input file 309538\n"
+ "cleanups performed 1\n"
+ "files in cache 17358\n"
+ "cache size 15.4 Gbytes\n"
+ "max cache size 17.2 Gbytes",
+ )
+ self.assertEqual(
+ str(stat3),
+ "cache hit (direct) 12004\n"
+ "cache hit (preprocessed) 1786\n"
+ "cache miss 26348\n"
+ "called for link 2338\n"
+ "called for preprocessing 6313\n"
+ "compile failed 399\n"
+ "preprocessor error 390\n"
+ "bad compiler arguments 86\n"
+ "unsupported source language 66\n"
+ "autoconf compile/link 2439\n"
+ "unsupported compiler option 187\n"
+ "no input file 1068\n"
+ "files in cache 18044\n"
+ "cache size 7.5 Gbytes\n"
+ "max cache size 8.6 Gbytes",
+ )
+ self.assertEqual(
+ str(stats_diff),
+ "cache hit (direct) 307283\n"
+ "cache hit (preprocessed) 124201\n"
+ "cache hit rate 37\n"
+ "cache miss 723611\n"
+ "called for link 85640\n"
+ "called for preprocessing 412278\n"
+ "multiple source files 1861\n"
+ "compiler produced no output 122\n"
+ "compiler produced empty output 174\n"
+ "compile failed 13931\n"
+ "ccache internal error 1\n"
+ "preprocessor error 9069\n"
+ "can't use precompiled header 4\n"
+ "bad compiler arguments 1991\n"
+ "unsupported source language 18129\n"
+ "autoconf compile/link 49046\n"
+ "unsupported compiler option 135\n"
+ "no input file 308470\n"
+ "cleanups performed 1\n"
+ "files in cache 17358\n"
+ "cache size 15.4 Gbytes\n"
+ "max cache size 17.2 Gbytes",
+ )
+
+ # Test stats for 3.3.3.
+ stat7 = CCacheStats(self.STAT7)
+ self.assertEqual(
+ str(stat7),
+ "cache hit (direct) 27035\n"
+ "cache hit (preprocessed) 13939\n"
+ "cache hit rate 39\n"
+ "cache miss 62630\n"
+ "called for link 1280\n"
+ "called for preprocessing 736\n"
+ "compile failed 550\n"
+ "preprocessor error 638\n"
+ "bad compiler arguments 20\n"
+ "autoconf compile/link 1751\n"
+ "unsupported code directive 2\n"
+ "no input file 2378\n"
+ "cleanups performed 1792\n"
+ "files in cache 3479\n"
+ "cache size 4.4 Gbytes\n"
+ "max cache size 5.0 Gbytes",
+ )
+
+ def test_stats_version34(self):
+ # Test parsing 3.4 output.
+ stat8 = CCacheStats(self.STAT8)
+ self.assertEqual(
+ str(stat8),
+ f"stats zeroed {int(TIMESTAMP)}\n"
+ "cache hit (direct) 571\n"
+ "cache hit (preprocessed) 1203\n"
+ "cache hit rate 13\n"
+ "cache miss 11747\n"
+ "called for link 623\n"
+ "called for preprocessing 7194\n"
+ "compile failed 32\n"
+ "preprocessor error 137\n"
+ "bad compiler arguments 4\n"
+ "autoconf compile/link 348\n"
+ "no input file 162\n"
+ "cleanups performed 77\n"
+ "files in cache 13464\n"
+ "cache size 6.2 Gbytes\n"
+ "max cache size 7.0 Gbytes",
+ )
+
+ def test_stats_version35(self):
+ # Test parsing 3.5 output.
+ stat9 = CCacheStats(self.STAT9)
+ self.assertEqual(
+ str(stat9),
+ f"stats zeroed {int(TIMESTAMP)}\n"
+ f"stats updated {int(TIMESTAMP2)}\n"
+ "cache hit (direct) 80147\n"
+ "cache hit (preprocessed) 21413\n"
+ "cache hit rate 34\n"
+ "cache miss 191128\n"
+ "called for link 5194\n"
+ "called for preprocessing 1721\n"
+ "compile failed 825\n"
+ "preprocessor error 3838\n"
+ "cache file missing 4863\n"
+ "bad compiler arguments 32\n"
+ "autoconf compile/link 3554\n"
+ "unsupported code directive 4\n"
+ "no input file 5545\n"
+ "cleanups performed 3154\n"
+ "files in cache 18525\n"
+ "cache size 13.4 Gbytes\n"
+ "max cache size 15.0 Gbytes",
+ )
+
+ def test_stats_version37(self):
+ # verify version checks
+ self.assertFalse(CCacheStats._is_version_3_7_or_newer(self.VERSION_3_5_GIT))
+ self.assertTrue(CCacheStats._is_version_3_7_or_newer(self.VERSION_4_2))
+ self.assertTrue(CCacheStats._is_version_3_7_or_newer(self.VERSION_4_4))
+ self.assertTrue(CCacheStats._is_version_3_7_or_newer(self.VERSION_4_4_2))
+ self.assertTrue(CCacheStats._is_version_3_7_or_newer(self.VERSION_4_5))
+
+ # Test parsing 3.7+ output.
+ stat10 = CCacheStats(self.STAT10, True)
+ self.assertEqual(
+ str(stat10),
+ "stats zeroed 0\n"
+ f"stats updated {int(TIMESTAMP)}\n"
+ "cache hit (direct) 197\n"
+ "cache hit (preprocessed) 719\n"
+ "cache hit rate 9\n"
+ "cache miss 8427\n"
+ "called for link 569\n"
+ "called for preprocessing 110\n"
+ "multiple source files 0\n"
+ "compiler produced stdout 0\n"
+ "compiler produced no output 0\n"
+ "compiler produced empty output 0\n"
+ "compile failed 49\n"
+ "ccache internal error 1\n"
+ "preprocessor error 90\n"
+ "can't use precompiled header 0\n"
+ "couldn't find the compiler 0\n"
+ "cache file missing 1\n"
+ "bad compiler arguments 6\n"
+ "unsupported source language 0\n"
+ "compiler check failed 0\n"
+ "autoconf compile/link 418\n"
+ "unsupported code directive 1\n"
+ "unsupported compiler option 0\n"
+ "output to stdout 0\n"
+ "no input file 9\n"
+ "error hashing extra file 0\n"
+ "cleanups performed 161\n"
+ "files in cache 4425\n"
+ "cache size 4.4 Gbytes",
+ )
+
+ stat11 = CCacheStats(self.STAT11, True)
+ self.assertEqual(
+ str(stat11),
+ f"stats zeroed {int(TIMESTAMP2)}\n"
+ f"stats updated {int(TIMESTAMP)}\n"
+ "cache hit (direct) 0\n"
+ "cache hit (preprocessed) 0\n"
+ "cache hit rate 0\n"
+ "cache miss 0\n"
+ "called for link 0\n"
+ "called for preprocessing 0\n"
+ "multiple source files 0\n"
+ "compiler produced stdout 0\n"
+ "compiler produced no output 0\n"
+ "compiler produced empty output 0\n"
+ "compile failed 0\n"
+ "ccache internal error 0\n"
+ "preprocessor error 0\n"
+ "can't use precompiled header 0\n"
+ "couldn't find the compiler 0\n"
+ "cache file missing 0\n"
+ "bad compiler arguments 0\n"
+ "unsupported source language 0\n"
+ "compiler check failed 0\n"
+ "autoconf compile/link 0\n"
+ "unsupported code directive 0\n"
+ "unsupported compiler option 0\n"
+ "output to stdout 0\n"
+ "no input file 0\n"
+ "error hashing extra file 0\n"
+ "cleanups performed 16\n"
+ "files in cache 0\n"
+ "cache size 0.0 Kbytes",
+ )
+
+ stat12 = CCacheStats(self.STAT12, True)
+ self.assertEqual(
+ str(stat12),
+ "stats zeroed 0\n"
+ "stats updated 0\n"
+ "cache hit (direct) 0\n"
+ "cache hit (preprocessed) 0\n"
+ "cache hit rate 0\n"
+ "cache miss 0\n"
+ "called for link 0\n"
+ "called for preprocessing 0\n"
+ "multiple source files 0\n"
+ "compiler produced stdout 0\n"
+ "compiler produced no output 0\n"
+ "compiler produced empty output 0\n"
+ "compile failed 0\n"
+ "ccache internal error 0\n"
+ "preprocessor error 0\n"
+ "can't use precompiled header 0\n"
+ "couldn't find the compiler 0\n"
+ "cache file missing 0\n"
+ "bad compiler arguments 0\n"
+ "unsupported source language 0\n"
+ "compiler check failed 0\n"
+ "autoconf compile/link 0\n"
+ "unsupported code directive 0\n"
+ "unsupported compiler option 0\n"
+ "output to stdout 0\n"
+ "no input file 0\n"
+ "error hashing extra file 0\n"
+ "cleanups performed 16\n"
+ "files in cache 0\n"
+ "cache size 0.0 Kbytes",
+ )
+
+ stat13 = CCacheStats(self.STAT13, True)
+ self.assertEqual(
+ str(stat13),
+ f"stats zeroed {int(TIMESTAMP2)}\n"
+ f"stats updated {int(TIMESTAMP)}\n"
+ "cache hit (direct) 280542\n"
+ "cache hit (preprocessed) 0\n"
+ "cache hit rate 41\n"
+ "cache miss 387653\n"
+ "called for link 0\n"
+ "called for preprocessing 0\n"
+ "multiple source files 0\n"
+ "compiler produced stdout 0\n"
+ "compiler produced no output 0\n"
+ "compiler produced empty output 0\n"
+ "compile failed 1665\n"
+ "ccache internal error 1\n"
+ "preprocessor error 0\n"
+ "can't use precompiled header 0\n"
+ "couldn't find the compiler 0\n"
+ "cache file missing 0\n"
+ "bad compiler arguments 0\n"
+ "unsupported source language 0\n"
+ "compiler check failed 0\n"
+ "autoconf compile/link 0\n"
+ "unsupported code directive 0\n"
+ "unsupported compiler option 0\n"
+ "output to stdout 0\n"
+ "no input file 2\n"
+ "error hashing extra file 0\n"
+ "cleanups performed 364\n"
+ "files in cache 335104\n"
+ "cache size 17.4 Gbytes",
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/controller/test_clobber.py b/python/mozbuild/mozbuild/test/controller/test_clobber.py
new file mode 100644
index 0000000000..fff3c5a438
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/controller/test_clobber.py
@@ -0,0 +1,214 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import shutil
+import tempfile
+import unittest
+
+from mozunit import main
+
+from mozbuild.base import MozbuildObject
+from mozbuild.controller.building import BuildDriver
+from mozbuild.controller.clobber import Clobberer
+from mozbuild.test.common import prepare_tmp_topsrcdir
+
+
+class TestClobberer(unittest.TestCase):
+ def setUp(self):
+ self._temp_dirs = []
+ self._old_env = dict(os.environ)
+ os.environ.pop("MOZCONFIG", None)
+ os.environ.pop("MOZ_OBJDIR", None)
+
+ return unittest.TestCase.setUp(self)
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ for d in self._temp_dirs:
+ shutil.rmtree(d, ignore_errors=True)
+
+ return unittest.TestCase.tearDown(self)
+
+ def get_tempdir(self):
+ t = tempfile.mkdtemp()
+ self._temp_dirs.append(t)
+ return t
+
+ def get_topsrcdir(self):
+ t = self.get_tempdir()
+ prepare_tmp_topsrcdir(t)
+ p = os.path.join(t, "CLOBBER")
+ with open(p, "a"):
+ pass
+
+ return t
+
+ def test_no_objdir(self):
+ """If topobjdir does not exist, no clobber is needed."""
+
+ tmp = os.path.join(self.get_tempdir(), "topobjdir")
+ self.assertFalse(os.path.exists(tmp))
+
+ c = Clobberer(self.get_topsrcdir(), tmp)
+ self.assertFalse(c.clobber_needed())
+
+ required, performed, reason = c.maybe_do_clobber(os.getcwd(), True)
+ self.assertFalse(required)
+ self.assertFalse(performed)
+ self.assertIsNone(reason)
+
+ self.assertFalse(os.path.isdir(tmp))
+ self.assertFalse(os.path.exists(os.path.join(tmp, "CLOBBER")))
+
+ def test_objdir_no_clobber_file(self):
+ """If CLOBBER does not exist in topobjdir, treat as empty."""
+
+ c = Clobberer(self.get_topsrcdir(), self.get_tempdir())
+ self.assertFalse(c.clobber_needed())
+
+ required, performed, reason = c.maybe_do_clobber(os.getcwd(), True)
+ self.assertFalse(required)
+ self.assertFalse(performed)
+ self.assertIsNone(reason)
+
+ self.assertFalse(os.path.exists(os.path.join(c.topobjdir, "CLOBBER")))
+
+ def test_objdir_clobber_newer(self):
+ """If CLOBBER in topobjdir is newer, do nothing."""
+
+ c = Clobberer(self.get_topsrcdir(), self.get_tempdir())
+ with open(c.obj_clobber, "a"):
+ pass
+
+ required, performed, reason = c.maybe_do_clobber(os.getcwd(), True)
+ self.assertFalse(required)
+ self.assertFalse(performed)
+ self.assertIsNone(reason)
+
+ def test_objdir_clobber_older(self):
+ """If CLOBBER in topobjdir is older, we clobber."""
+
+ c = Clobberer(self.get_topsrcdir(), self.get_tempdir())
+ with open(c.obj_clobber, "a"):
+ pass
+
+ dummy_path = os.path.join(c.topobjdir, "foo")
+ with open(dummy_path, "a"):
+ pass
+
+ self.assertTrue(os.path.exists(dummy_path))
+
+ old_time = os.path.getmtime(c.src_clobber) - 60
+ os.utime(c.obj_clobber, (old_time, old_time))
+
+ self.assertTrue(c.clobber_needed())
+
+ required, performed, reason = c.maybe_do_clobber(os.getcwd(), True)
+ self.assertTrue(required)
+ self.assertTrue(performed)
+
+ self.assertFalse(os.path.exists(dummy_path))
+ self.assertFalse(os.path.exists(c.obj_clobber))
+
+ def test_objdir_is_srcdir(self):
+ """If topobjdir is the topsrcdir, refuse to clobber."""
+
+ tmp = self.get_topsrcdir()
+ c = Clobberer(tmp, tmp)
+
+ self.assertFalse(c.clobber_needed())
+
+ def test_cwd_is_topobjdir(self):
+ """If cwd is topobjdir, we can still clobber."""
+ c = Clobberer(self.get_topsrcdir(), self.get_tempdir())
+
+ with open(c.obj_clobber, "a"):
+ pass
+
+ dummy_file = os.path.join(c.topobjdir, "dummy_file")
+ with open(dummy_file, "a"):
+ pass
+
+ dummy_dir = os.path.join(c.topobjdir, "dummy_dir")
+ os.mkdir(dummy_dir)
+
+ self.assertTrue(os.path.exists(dummy_file))
+ self.assertTrue(os.path.isdir(dummy_dir))
+
+ old_time = os.path.getmtime(c.src_clobber) - 60
+ os.utime(c.obj_clobber, (old_time, old_time))
+
+ self.assertTrue(c.clobber_needed())
+
+ required, performed, reason = c.maybe_do_clobber(c.topobjdir, True)
+ self.assertTrue(required)
+ self.assertTrue(performed)
+
+ self.assertFalse(os.path.exists(dummy_file))
+ self.assertFalse(os.path.exists(dummy_dir))
+
+ def test_cwd_under_topobjdir(self):
+ """If cwd is under topobjdir, we can't clobber."""
+
+ c = Clobberer(self.get_topsrcdir(), self.get_tempdir())
+
+ with open(c.obj_clobber, "a"):
+ pass
+
+ old_time = os.path.getmtime(c.src_clobber) - 60
+ os.utime(c.obj_clobber, (old_time, old_time))
+
+ d = os.path.join(c.topobjdir, "dummy_dir")
+ os.mkdir(d)
+
+ required, performed, reason = c.maybe_do_clobber(d, True)
+ self.assertTrue(required)
+ self.assertFalse(performed)
+ self.assertIn("Cannot clobber while the shell is inside", reason)
+
+ def test_mozconfig_opt_in(self):
+ """Auto clobber iff AUTOCLOBBER is in the environment."""
+
+ topsrcdir = self.get_topsrcdir()
+ topobjdir = self.get_tempdir()
+
+ obj_clobber = os.path.join(topobjdir, "CLOBBER")
+ with open(obj_clobber, "a"):
+ pass
+
+ dummy_file = os.path.join(topobjdir, "dummy_file")
+ with open(dummy_file, "a"):
+ pass
+
+ self.assertTrue(os.path.exists(dummy_file))
+
+ old_time = os.path.getmtime(os.path.join(topsrcdir, "CLOBBER")) - 60
+ os.utime(obj_clobber, (old_time, old_time))
+
+ # Check auto clobber is off by default
+ env = dict(os.environ)
+ if env.get("AUTOCLOBBER", False):
+ del env["AUTOCLOBBER"]
+
+ mbo = MozbuildObject(topsrcdir, None, None, topobjdir)
+ build = mbo._spawn(BuildDriver)
+
+ status = build._check_clobber(build.mozconfig, env)
+
+ self.assertEqual(status, True)
+ self.assertTrue(os.path.exists(dummy_file))
+
+ # Check auto clobber opt-in works
+ env["AUTOCLOBBER"] = "1"
+
+ status = build._check_clobber(build.mozconfig, env)
+ self.assertFalse(status)
+ self.assertFalse(os.path.exists(dummy_file))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/data/Makefile b/python/mozbuild/mozbuild/test/data/Makefile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/data/Makefile
diff --git a/python/mozbuild/mozbuild/test/data/bad.properties b/python/mozbuild/mozbuild/test/data/bad.properties
new file mode 100644
index 0000000000..d4d8109b69
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/data/bad.properties
@@ -0,0 +1,12 @@
+# A region.properties file with invalid unicode byte sequences. The
+# sequences were cribbed from Markus Kuhn's "UTF-8 decoder capability
+# and stress test", available at
+# http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+
+# 3.5 Impossible bytes |
+# |
+# The following two bytes cannot appear in a correct UTF-8 string |
+# |
+# 3.5.1 fe = "þ" |
+# 3.5.2 ff = "ÿ" |
+# 3.5.3 fe fe ff ff = "þþÿÿ" |
diff --git a/python/mozbuild/mozbuild/test/data/test-dir/Makefile b/python/mozbuild/mozbuild/test/data/test-dir/Makefile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/data/test-dir/Makefile
diff --git a/python/mozbuild/mozbuild/test/data/test-dir/with/Makefile b/python/mozbuild/mozbuild/test/data/test-dir/with/Makefile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/data/test-dir/with/Makefile
diff --git a/python/mozbuild/mozbuild/test/data/test-dir/with/without/with/Makefile b/python/mozbuild/mozbuild/test/data/test-dir/with/without/with/Makefile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/data/test-dir/with/without/with/Makefile
diff --git a/python/mozbuild/mozbuild/test/data/test-dir/without/with/Makefile b/python/mozbuild/mozbuild/test/data/test-dir/without/with/Makefile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/data/test-dir/without/with/Makefile
diff --git a/python/mozbuild/mozbuild/test/data/valid.properties b/python/mozbuild/mozbuild/test/data/valid.properties
new file mode 100644
index 0000000000..db64bf2eed
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/data/valid.properties
@@ -0,0 +1,11 @@
+# A region.properties file with unicode characters.
+
+# Danish.
+# #### ~~ Søren Munk Skrøder, sskroeder - 2009-05-30 @ #mozmae
+
+# Korean.
+A.title=한메ì¼
+
+# Russian.
+list.0 = test
+list.1 = ЯндекÑ
diff --git a/python/mozbuild/mozbuild/test/frontend/__init__.py b/python/mozbuild/mozbuild/test/frontend/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/__init__.py
diff --git a/python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/moz.build b/python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/moz.build
new file mode 100644
index 0000000000..0bf5b55ecb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/moz.build
@@ -0,0 +1,20 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def AllowCompilerWarnings():
+ COMPILE_FLAGS["WARNINGS_AS_ERRORS"] = []
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += ["test1.c"]
+
+AllowCompilerWarnings()
diff --git a/python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/test1.c b/python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/allow-compiler-warnings/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/asflags/moz.build b/python/mozbuild/mozbuild/test/frontend/data/asflags/moz.build
new file mode 100644
index 0000000000..80f48a7d81
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/asflags/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+SOURCES += ["test1.c", "test2.S"]
+
+ASFLAGS += ["-no-integrated-as"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/asflags/test1.c b/python/mozbuild/mozbuild/test/frontend/data/asflags/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/asflags/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/asflags/test2.S b/python/mozbuild/mozbuild/test/frontend/data/asflags/test2.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/asflags/test2.S
diff --git a/python/mozbuild/mozbuild/test/frontend/data/branding-files/bar.ico b/python/mozbuild/mozbuild/test/frontend/data/branding-files/bar.ico
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/branding-files/bar.ico
diff --git a/python/mozbuild/mozbuild/test/frontend/data/branding-files/baz.png b/python/mozbuild/mozbuild/test/frontend/data/branding-files/baz.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/branding-files/baz.png
diff --git a/python/mozbuild/mozbuild/test/frontend/data/branding-files/foo.xpm b/python/mozbuild/mozbuild/test/frontend/data/branding-files/foo.xpm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/branding-files/foo.xpm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/branding-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/branding-files/moz.build
new file mode 100644
index 0000000000..65f22d578b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/branding-files/moz.build
@@ -0,0 +1,12 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+BRANDING_FILES += [
+ "bar.ico",
+ "baz.png",
+ "foo.xpm",
+]
+
+BRANDING_FILES.icons += [
+ "quux.icns",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/branding-files/quux.icns b/python/mozbuild/mozbuild/test/frontend/data/branding-files/quux.icns
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/branding-files/quux.icns
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-defines/moz.build b/python/mozbuild/mozbuild/test/frontend/data/compile-defines/moz.build
new file mode 100644
index 0000000000..65d71dae2b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-defines/moz.build
@@ -0,0 +1,16 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += ["test1.c"]
+
+DEFINES["MOZ_TEST_DEFINE"] = True
+LIBRARY_DEFINES["MOZ_LIBRARY_DEFINE"] = "MOZ_TEST"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-defines/test1.c b/python/mozbuild/mozbuild/test/frontend/data/compile-defines/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-defines/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/moz.build b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/moz.build
new file mode 100644
index 0000000000..70622bc4e1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+COMPILE_FLAGS["STL_FLAGS"] = []
+
+UNIFIED_SOURCES += ["test1.c"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/test1.c b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/moz.build b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/moz.build
new file mode 100644
index 0000000000..6e611fc598
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/moz.build
@@ -0,0 +1,27 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+
+@template
+def DisableStlWrapping():
+ COMPILE_FLAGS["STL"] = []
+
+
+@template
+def NoVisibilityFlags():
+ COMPILE_FLAGS["VISIBILITY"] = []
+
+
+UNIFIED_SOURCES += ["test1.c"]
+
+DisableStlWrapping()
+NoVisibilityFlags()
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/test1.c b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-templates/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/moz.build b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/moz.build
new file mode 100644
index 0000000000..31094736a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+COMPILE_FLAGS["STL"] = [None, 123]
+
+UNIFIED_SOURCES += ["test1.c"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/test1.c b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags/moz.build b/python/mozbuild/mozbuild/test/frontend/data/compile-flags/moz.build
new file mode 100644
index 0000000000..0e6f75cfa1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags/moz.build
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+
+@template
+def DisableStlWrapping():
+ COMPILE_FLAGS["STL"] = []
+
+
+UNIFIED_SOURCES += ["test1.c"]
+
+CXXFLAGS += ["-funroll-loops", "-Wall"]
+CFLAGS += ["-Wall", "-funroll-loops"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-flags/test1.c b/python/mozbuild/mozbuild/test/frontend/data/compile-flags/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-includes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/compile-includes/moz.build
new file mode 100644
index 0000000000..10c28e2833
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-includes/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += ["test1.c"]
+
+LOCAL_INCLUDES += ["subdir"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-includes/subdir/header.h b/python/mozbuild/mozbuild/test/frontend/data/compile-includes/subdir/header.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-includes/subdir/header.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/compile-includes/test1.c b/python/mozbuild/mozbuild/test/frontend/data/compile-includes/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-includes/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/config-file-substitution/moz.build b/python/mozbuild/mozbuild/test/frontend/data/config-file-substitution/moz.build
new file mode 100644
index 0000000000..f42dc0a517
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/config-file-substitution/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+CONFIGURE_SUBST_FILES += ["foo"]
+CONFIGURE_SUBST_FILES += ["bar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/Cargo.toml
new file mode 100644
index 0000000000..b080d53b5a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[dependencies]
+deep-crate = { version = "0.1.0", path = "the/depths" }
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/moz.build b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/moz.build
new file mode 100644
index 0000000000..de1967c519
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+RustLibrary("random-crate")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/shallow/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/shallow/Cargo.toml
new file mode 100644
index 0000000000..e918f9228d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/shallow/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "shallow-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/the/depths/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/the/depths/Cargo.toml
new file mode 100644
index 0000000000..cebcb38ab7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/the/depths/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "deep-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[dependencies]
+shallow-crate = { path = "../../shallow" }
diff --git a/python/mozbuild/mozbuild/test/frontend/data/defines/moz.build b/python/mozbuild/mozbuild/test/frontend/data/defines/moz.build
new file mode 100644
index 0000000000..6085619c58
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/defines/moz.build
@@ -0,0 +1,9 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+value = "xyz"
+DEFINES["FOO"] = True
+DEFINES["BAZ"] = '"abcd"'
+DEFINES["BAR"] = 7
+DEFINES["VALUE"] = value
+DEFINES["QUX"] = False
diff --git a/python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/moz.build b/python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/moz.build
new file mode 100644
index 0000000000..064fa09893
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/moz.build
@@ -0,0 +1,20 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def DisableCompilerWarnings():
+ COMPILE_FLAGS["WARNINGS_CFLAGS"] = []
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += ["test1.c"]
+
+DisableCompilerWarnings()
diff --git a/python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/test1.c b/python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/disable-compiler-warnings/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/moz.build b/python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/moz.build
new file mode 100644
index 0000000000..40cb3e7781
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/moz.build
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+
+@template
+def DisableStlWrapping():
+ COMPILE_FLAGS["STL"] = []
+
+
+UNIFIED_SOURCES += ["test1.c"]
+
+DisableStlWrapping()
diff --git a/python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/test1.c b/python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/install.rdf b/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/install.rdf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/install.rdf
diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build b/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build
new file mode 100644
index 0000000000..25961f149f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET_PP_FILES += [
+ "install.rdf",
+ "main.js",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files/install.rdf b/python/mozbuild/mozbuild/test/frontend/data/dist-files/install.rdf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/dist-files/install.rdf
diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files/main.js b/python/mozbuild/mozbuild/test/frontend/data/dist-files/main.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/dist-files/main.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build
new file mode 100644
index 0000000000..25961f149f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET_PP_FILES += [
+ "install.rdf",
+ "main.js",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-generated/foo.h b/python/mozbuild/mozbuild/test/frontend/data/exports-generated/foo.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-generated/foo.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-generated/moz.build b/python/mozbuild/mozbuild/test/frontend/data/exports-generated/moz.build
new file mode 100644
index 0000000000..bd3507c97b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-generated/moz.build
@@ -0,0 +1,8 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+EXPORTS += ["foo.h"]
+EXPORTS.mozilla += ["mozilla1.h"]
+EXPORTS.mozilla += ["!mozilla2.h"]
+
+GENERATED_FILES += ["mozilla2.h"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-generated/mozilla1.h b/python/mozbuild/mozbuild/test/frontend/data/exports-generated/mozilla1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-generated/mozilla1.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/foo.h b/python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/foo.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/foo.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/moz.build b/python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/moz.build
new file mode 100644
index 0000000000..d81109d37d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-missing-generated/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+EXPORTS += ["foo.h"]
+EXPORTS += ["!bar.h"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-missing/foo.h b/python/mozbuild/mozbuild/test/frontend/data/exports-missing/foo.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-missing/foo.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-missing/moz.build b/python/mozbuild/mozbuild/test/frontend/data/exports-missing/moz.build
new file mode 100644
index 0000000000..3f94fbdccd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-missing/moz.build
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+EXPORTS += ["foo.h"]
+EXPORTS.mozilla += ["mozilla1.h"]
+EXPORTS.mozilla += ["mozilla2.h"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports-missing/mozilla1.h b/python/mozbuild/mozbuild/test/frontend/data/exports-missing/mozilla1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports-missing/mozilla1.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/bar.h b/python/mozbuild/mozbuild/test/frontend/data/exports/bar.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/bar.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/baz.h b/python/mozbuild/mozbuild/test/frontend/data/exports/baz.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/baz.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/dom1.h b/python/mozbuild/mozbuild/test/frontend/data/exports/dom1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/dom1.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/dom2.h b/python/mozbuild/mozbuild/test/frontend/data/exports/dom2.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/dom2.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/dom3.h b/python/mozbuild/mozbuild/test/frontend/data/exports/dom3.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/dom3.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/foo.h b/python/mozbuild/mozbuild/test/frontend/data/exports/foo.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/foo.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/gfx.h b/python/mozbuild/mozbuild/test/frontend/data/exports/gfx.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/gfx.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/mem.h b/python/mozbuild/mozbuild/test/frontend/data/exports/mem.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/mem.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/mem2.h b/python/mozbuild/mozbuild/test/frontend/data/exports/mem2.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/mem2.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/moz.build b/python/mozbuild/mozbuild/test/frontend/data/exports/moz.build
new file mode 100644
index 0000000000..64253b1cf0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/moz.build
@@ -0,0 +1,13 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+EXPORTS += ["foo.h"]
+EXPORTS += ["bar.h", "baz.h"]
+EXPORTS.mozilla += ["mozilla1.h"]
+EXPORTS.mozilla += ["mozilla2.h"]
+EXPORTS.mozilla.dom += ["dom1.h"]
+EXPORTS.mozilla.dom += ["dom2.h", "dom3.h"]
+EXPORTS.mozilla.gfx += ["gfx.h"]
+EXPORTS.vpx = ["mem.h"]
+EXPORTS.vpx += ["mem2.h"]
+EXPORTS.nspr.private = ["pprio.h", "pprthred.h"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/mozilla1.h b/python/mozbuild/mozbuild/test/frontend/data/exports/mozilla1.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/mozilla1.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/mozilla2.h b/python/mozbuild/mozbuild/test/frontend/data/exports/mozilla2.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/mozilla2.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/pprio.h b/python/mozbuild/mozbuild/test/frontend/data/exports/pprio.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/pprio.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/exports/pprthred.h b/python/mozbuild/mozbuild/test/frontend/data/exports/pprthred.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/exports/pprthred.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/bad-assignment/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/bad-assignment/moz.build
new file mode 100644
index 0000000000..693b6cc962
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/bad-assignment/moz.build
@@ -0,0 +1,2 @@
+with Files("*"):
+ BUG_COMPONENT = "bad value"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/different-matchers/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/different-matchers/moz.build
new file mode 100644
index 0000000000..ca5c74fd6a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/different-matchers/moz.build
@@ -0,0 +1,4 @@
+with Files("*.jsm"):
+ BUG_COMPONENT = ("Firefox", "JS")
+with Files("*.cpp"):
+ BUG_COMPONENT = ("Firefox", "C++")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/moz.build
new file mode 100644
index 0000000000..9b1d05a9b0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/moz.build
@@ -0,0 +1,3 @@
+with Files("**/Makefile.in"):
+ BUG_COMPONENT = ("Firefox Build System", "General")
+ FINAL = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/subcomponent/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/subcomponent/moz.build
new file mode 100644
index 0000000000..9b21529812
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/final/subcomponent/moz.build
@@ -0,0 +1,2 @@
+with Files("**"):
+ BUG_COMPONENT = ("Another", "Component")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/moz.build
new file mode 100644
index 0000000000..4bbca3dc09
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/moz.build
@@ -0,0 +1,2 @@
+with Files("**"):
+ BUG_COMPONENT = ("default_product", "default_component")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/simple/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/simple/moz.build
new file mode 100644
index 0000000000..e8b99df68d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/simple/moz.build
@@ -0,0 +1,2 @@
+with Files("*"):
+ BUG_COMPONENT = ("Firefox Build System", "General")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/static/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/static/moz.build
new file mode 100644
index 0000000000..49acf29196
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/static/moz.build
@@ -0,0 +1,5 @@
+with Files("foo"):
+ BUG_COMPONENT = ("FooProduct", "FooComponent")
+
+with Files("bar"):
+ BUG_COMPONENT = ("BarProduct", "BarComponent")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-info/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-info/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/final-target-pp-files-non-srcdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/final-target-pp-files-non-srcdir/moz.build
new file mode 100644
index 0000000000..67e5fb5dce
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/final-target-pp-files-non-srcdir/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET_PP_FILES += [
+ "!foo.js",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/moz.build
new file mode 100644
index 0000000000..860f025eac
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["bar.c"]
+
+bar = GENERATED_FILES["bar.c"]
+bar.script = "/script.py:make_bar"
+bar.inputs = []
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/script.py b/python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/script.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-absolute-script/script.py
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-force/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files-force/moz.build
new file mode 100644
index 0000000000..33f54a17e8
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-force/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += [
+ "bar.c",
+ "foo.c",
+ ("xpidllex.py", "xpidlyacc.py"),
+]
+GENERATED_FILES["bar.c"].force = True
+GENERATED_FILES["foo.c"].force = False
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/moz.build
new file mode 100644
index 0000000000..298513383b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["bar.c", "foo.c"]
+
+bar = GENERATED_FILES["bar.c"]
+bar.script = "script.py:make_bar"
+bar.inputs = []
+
+foo = GENERATED_FILES["foo.c"]
+foo.script = "script.py"
+foo.inputs = []
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/script.py b/python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/script.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/script.py
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/moz.build
new file mode 100644
index 0000000000..50f703c696
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["bar.c", "foo.c"]
+
+foo = GENERATED_FILES["foo.c"]
+foo.script = "script.py"
+foo.inputs = ["datafile"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/script.py b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/script.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-inputs/script.py
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/moz.build
new file mode 100644
index 0000000000..ebdb7bfaf5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["bar.c", "foo.c"]
+
+bar = GENERATED_FILES["bar.c"]
+bar.script = "script.rb"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/script.rb b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/script.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-python-script/script.rb
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-script/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-script/moz.build
new file mode 100644
index 0000000000..258a0f2325
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files-no-script/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["bar.c", "foo.c"]
+
+bar = GENERATED_FILES["bar.c"]
+bar.script = "nonexistent-script.py"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files/moz.build
new file mode 100644
index 0000000000..97267c5d26
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-files/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += [
+ "bar.c",
+ "foo.c",
+ ("xpidllex.py", "xpidlyacc.py"),
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/a.cpp b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/a.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/a.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/b.cc b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/b.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/b.cc
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/c.cxx b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/c.cxx
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/c.cxx
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/d.c b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/d.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/d.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/e.m b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/e.m
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/e.m
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/f.mm b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/f.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/f.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/g.S b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/g.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/g.S
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/h.s b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/h.s
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/h.s
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/i.asm b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/i.asm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/i.asm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated-sources/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/moz.build
new file mode 100644
index 0000000000..e305d9d32f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+SOURCES += [
+ "!a.cpp",
+ "!b.cc",
+ "!c.cxx",
+]
+
+SOURCES += [
+ "!d.c",
+]
+
+SOURCES += [
+ "!e.m",
+]
+
+SOURCES += [
+ "!f.mm",
+]
+
+SOURCES += [
+ "!g.S",
+]
+
+SOURCES += [
+ "!h.s",
+ "!i.asm",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/generated_includes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated_includes/moz.build
new file mode 100644
index 0000000000..31f9042c0a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated_includes/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["!/bar/baz", "!foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/moz.build
new file mode 100644
index 0000000000..4225234c65
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/moz.build
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def HostLibrary(name):
+ """Template for libraries."""
+ HOST_LIBRARY_NAME = name
+
+
+HostLibrary("dummy")
+
+HOST_SOURCES += ["test1.c"]
+
+value = "xyz"
+HOST_DEFINES["FOO"] = True
+HOST_DEFINES["BAZ"] = '"abcd"'
+HOST_DEFINES["BAR"] = 7
+HOST_DEFINES["VALUE"] = value
+HOST_DEFINES["QUX"] = False
+
+HOST_CFLAGS += ["-funroll-loops", "-host-arg"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/test1.c b/python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-compile-flags/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/final-target/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/final-target/moz.build
new file mode 100644
index 0000000000..a2136749dc
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/final-target/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET = "final/target"
+HostProgram("final-target")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/installed/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/installed/moz.build
new file mode 100644
index 0000000000..0d10d35508
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/installed/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+HostProgram("dist-host-bin")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/moz.build
new file mode 100644
index 0000000000..ef9175fa54
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/moz.build
@@ -0,0 +1,14 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def HostProgram(name):
+ HOST_PROGRAM = name
+
+
+DIRS += [
+ "final-target",
+ "installed",
+ "not-installed",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/not-installed/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/not-installed/moz.build
new file mode 100644
index 0000000000..4a8451bc8f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-program-paths/not-installed/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_INSTALL = False
+HostProgram("not-installed")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/Cargo.toml
new file mode 100644
index 0000000000..aefcab3ddb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "host-lib"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/moz.build
new file mode 100644
index 0000000000..37b6728ae3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-rust-libraries/moz.build
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def HostLibrary(name):
+ """Template for libraries."""
+ HOST_LIBRARY_NAME = name
+
+
+@template
+def HostRustLibrary(name, features=None):
+ """Template for Rust libraries."""
+ HostLibrary(name)
+
+ IS_RUST_LIBRARY = True
+
+ if features:
+ RUST_LIBRARY_FEATURES = features
+
+
+HostRustLibrary("host-lib")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-no-cargo-toml/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-no-cargo-toml/moz.build
new file mode 100644
index 0000000000..c60e731d99
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-no-cargo-toml/moz.build
@@ -0,0 +1 @@
+HOST_RUST_PROGRAMS += ["none"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/Cargo.toml
new file mode 100644
index 0000000000..dee335937f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+authors = ["The Mozilla Project Developers"]
+name = "testing"
+version = "0.0.1"
+
+[[bin]]
+name = "some"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/moz.build
new file mode 100644
index 0000000000..c60e731d99
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-rust-program-nonexistent-name/moz.build
@@ -0,0 +1 @@
+HOST_RUST_PROGRAMS += ["none"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/Cargo.toml
new file mode 100644
index 0000000000..dee335937f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+authors = ["The Mozilla Project Developers"]
+name = "testing"
+version = "0.0.1"
+
+[[bin]]
+name = "some"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/moz.build
new file mode 100644
index 0000000000..2d75958b07
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-rust-programs/moz.build
@@ -0,0 +1 @@
+HOST_RUST_PROGRAMS += ["some"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-sources/a.cpp b/python/mozbuild/mozbuild/test/frontend/data/host-sources/a.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/a.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-sources/b.cc b/python/mozbuild/mozbuild/test/frontend/data/host-sources/b.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/b.cc
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-sources/c.cxx b/python/mozbuild/mozbuild/test/frontend/data/host-sources/c.cxx
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/c.cxx
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-sources/d.c b/python/mozbuild/mozbuild/test/frontend/data/host-sources/d.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/d.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-sources/e.mm b/python/mozbuild/mozbuild/test/frontend/data/host-sources/e.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/e.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-sources/f.mm b/python/mozbuild/mozbuild/test/frontend/data/host-sources/f.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/f.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/host-sources/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-sources/moz.build
new file mode 100644
index 0000000000..b1f5b98039
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def HostLibrary(name):
+ """Template for libraries."""
+ HOST_LIBRARY_NAME = name
+
+
+HostLibrary("dummy")
+
+HOST_SOURCES += [
+ "a.cpp",
+ "b.cc",
+ "c.cxx",
+]
+
+HOST_SOURCES += [
+ "d.c",
+]
+
+HOST_SOURCES += [
+ "e.mm",
+ "f.mm",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-basic/included.build b/python/mozbuild/mozbuild/test/frontend/data/include-basic/included.build
new file mode 100644
index 0000000000..3532347e27
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-basic/included.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["bar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-basic/moz.build b/python/mozbuild/mozbuild/test/frontend/data/include-basic/moz.build
new file mode 100644
index 0000000000..b8e37c69ea
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-basic/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo"]
+
+include("included.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-1.build b/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-1.build
new file mode 100644
index 0000000000..b5dc2728c6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-1.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("included-2.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-2.build b/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-2.build
new file mode 100644
index 0000000000..9bfc65481d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/included-2.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+ILLEGAL = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/moz.build b/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/moz.build
new file mode 100644
index 0000000000..def43513c7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-file-stack/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("included-1.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-missing/moz.build b/python/mozbuild/mozbuild/test/frontend/data/include-missing/moz.build
new file mode 100644
index 0000000000..34129f7c93
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-missing/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("missing.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-outside-topsrcdir/relative.build b/python/mozbuild/mozbuild/test/frontend/data/include-outside-topsrcdir/relative.build
new file mode 100644
index 0000000000..714a044436
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-outside-topsrcdir/relative.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("../moz.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child.build b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child.build
new file mode 100644
index 0000000000..ecae03ca7d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("../parent.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child2.build b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child2.build
new file mode 100644
index 0000000000..36210ba96b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/child2.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("grandchild/grandchild.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/grandchild/grandchild.build b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/grandchild/grandchild.build
new file mode 100644
index 0000000000..76dcdb899f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/child/grandchild/grandchild.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("../../parent.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/parent.build b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/parent.build
new file mode 100644
index 0000000000..eb1477d0df
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-relative-from-child/parent.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/moz.build b/python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/moz.build
new file mode 100644
index 0000000000..879b832ed8
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("/sibling.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/sibling.build b/python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/sibling.build
new file mode 100644
index 0000000000..eb1477d0df
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/include-topsrcdir-relative/sibling.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/bar/moz.build
new file mode 100644
index 0000000000..568f361a54
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/bar/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
diff --git a/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build
new file mode 100644
index 0000000000..9c392681c7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_MODULE = "baz"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/moz.build
new file mode 100644
index 0000000000..f3368867ad
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ["baz"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/moz.build b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/moz.build
new file mode 100644
index 0000000000..169e9d1554
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_MODULE = "foobar"
+export("XPIDL_MODULE")
+
+DIRS += ["foo", "bar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build
new file mode 100644
index 0000000000..b49ec1216b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+PREPROCESSED_IPDL_SOURCES += [
+ "bar1.ipdl",
+]
+
+IPDL_SOURCES += [
+ "bar.ipdl",
+ "bar2.ipdlh",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build
new file mode 100644
index 0000000000..c2e891572b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+PREPROCESSED_IPDL_SOURCES += [
+ "foo1.ipdl",
+]
+
+IPDL_SOURCES += [
+ "foo.ipdl",
+ "foo2.ipdlh",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/moz.build b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/moz.build
new file mode 100644
index 0000000000..9fe7699519
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ "bar",
+ "foo",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/jar-manifests-multiple-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/jar-manifests-multiple-files/moz.build
new file mode 100644
index 0000000000..fa61c94006
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/jar-manifests-multiple-files/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn", "other.jar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/jar-manifests/moz.build b/python/mozbuild/mozbuild/test/frontend/data/jar-manifests/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/jar-manifests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/library-defines/liba/moz.build b/python/mozbuild/mozbuild/test/frontend/data/library-defines/liba/moz.build
new file mode 100644
index 0000000000..65fcc6d08e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/library-defines/liba/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Library("liba")
+LIBRARY_DEFINES["IN_LIBA"] = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/library-defines/libb/moz.build b/python/mozbuild/mozbuild/test/frontend/data/library-defines/libb/moz.build
new file mode 100644
index 0000000000..f4cf7b31a0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/library-defines/libb/moz.build
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Library("libb")
+FINAL_LIBRARY = "liba"
+LIBRARY_DEFINES["IN_LIBB"] = True
+USE_LIBS += ["libd"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/library-defines/libc/moz.build b/python/mozbuild/mozbuild/test/frontend/data/library-defines/libc/moz.build
new file mode 100644
index 0000000000..022a67559d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/library-defines/libc/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Library("libc")
+FINAL_LIBRARY = "libb"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/library-defines/libd/moz.build b/python/mozbuild/mozbuild/test/frontend/data/library-defines/libd/moz.build
new file mode 100644
index 0000000000..0bd94be069
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/library-defines/libd/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Library("libd")
+FORCE_STATIC_LIB = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/library-defines/moz.build b/python/mozbuild/mozbuild/test/frontend/data/library-defines/moz.build
new file mode 100644
index 0000000000..dcc955cf28
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/library-defines/moz.build
@@ -0,0 +1,11 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+DIRS = ["liba", "libb", "libc", "libd"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/link-flags/moz.build b/python/mozbuild/mozbuild/test/frontend/data/link-flags/moz.build
new file mode 100644
index 0000000000..9e25efdcbf
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/link-flags/moz.build
@@ -0,0 +1,16 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += ["test1.c"]
+
+LDFLAGS += ["-Wl,-U_foo"]
+LDFLAGS += ["-framework Foo", "-x"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/link-flags/test1.c b/python/mozbuild/mozbuild/test/frontend/data/link-flags/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/link-flags/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/foo.h b/python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/foo.h
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/foo.h
diff --git a/python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/moz.build b/python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/moz.build
new file mode 100644
index 0000000000..70259db75b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/local_includes-filename/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["foo.h"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/objdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/objdir/moz.build
new file mode 100644
index 0000000000..6dcbab537d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/objdir/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["!/"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/srcdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/srcdir/moz.build
new file mode 100644
index 0000000000..6d8f6cd2af
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/local_includes-invalid/srcdir/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["/"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory b/python/mozbuild/mozbuild/test/frontend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/local_includes/bar/baz/dummy_file_for_nonempty_directory
diff --git a/python/mozbuild/mozbuild/test/frontend/data/local_includes/foo/dummy_file_for_nonempty_directory b/python/mozbuild/mozbuild/test/frontend/data/local_includes/foo/dummy_file_for_nonempty_directory
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/local_includes/foo/dummy_file_for_nonempty_directory
diff --git a/python/mozbuild/mozbuild/test/frontend/data/local_includes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/local_includes/moz.build
new file mode 100644
index 0000000000..1c29ac2ea2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/local_includes/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["/bar/baz", "foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files-from-generated/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-files-from-generated/moz.build
new file mode 100644
index 0000000000..491a026419
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files-from-generated/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_GENERATED_FILES += ["abc.ini"]
+LOCALIZED_FILES += ["!abc.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/en-US/bar.ini b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/en-US/bar.ini
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/en-US/bar.ini
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/foo.js b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/inner/locales/en-US/bar.ini b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/inner/locales/en-US/bar.ini
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/inner/locales/en-US/bar.ini
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/moz.build
new file mode 100644
index 0000000000..5c3efc8117
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files-no-en-us/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_FILES.foo += [
+ "en-US/bar.ini",
+ "foo.js",
+ "inner/locales/en-US/bar.ini",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files-not-localized-generated/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-files-not-localized-generated/moz.build
new file mode 100644
index 0000000000..678f503174
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files-not-localized-generated/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+GENERATED_FILES += ["abc.ini"]
+LOCALIZED_FILES += ["!abc.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/bar.ini b/python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/bar.ini
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/bar.ini
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/foo.js b/python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files/en-US/foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-files/moz.build
new file mode 100644
index 0000000000..25a9030881
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-files/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_FILES.foo += [
+ "en-US/bar.ini",
+ "en-US/code/*.js",
+ "en-US/foo.js",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-final-target-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-final-target-files/moz.build
new file mode 100644
index 0000000000..48acff1447
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-final-target-files/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_GENERATED_FILES += ["abc.ini"]
+FINAL_TARGET_FILES += ["!abc.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-force/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-force/moz.build
new file mode 100644
index 0000000000..73685545de
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files-force/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_GENERATED_FILES += ["abc.ini", ("bar", "baz")]
+LOCALIZED_GENERATED_FILES["abc.ini"].force = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files/moz.build
new file mode 100644
index 0000000000..cc306d5991
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-generated-files/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_GENERATED_FILES += ["abc.ini", ("bar", "baz")]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/bar.ini b/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/bar.ini
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/bar.ini
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/foo.js b/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/en-US/foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/moz.build
new file mode 100644
index 0000000000..b2916a1226
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/localized-pp-files/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCALIZED_PP_FILES.foo += [
+ "en-US/bar.ini",
+ "en-US/foo.js",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/missing-local-includes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/missing-local-includes/moz.build
new file mode 100644
index 0000000000..1c29ac2ea2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/missing-local-includes/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+LOCAL_INCLUDES += ["/bar/baz", "foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/missing-xpidl/moz.build b/python/mozbuild/mozbuild/test/frontend/data/missing-xpidl/moz.build
new file mode 100644
index 0000000000..e3a2a69d07
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/missing-xpidl/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPIDL_MODULE = "my_module"
+XPIDL_SOURCES = ["nonexistant.idl"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/moz.build b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/moz.build
new file mode 100644
index 0000000000..7956580d14
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/moz.build
@@ -0,0 +1,29 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+Library("test")
+
+DIRS += [
+ "rust1",
+ "rust2",
+]
+
+USE_LIBS += [
+ "rust1",
+ "rust2",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/Cargo.toml
new file mode 100644
index 0000000000..56273d5cf7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "rust1"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/moz.build b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/moz.build
new file mode 100644
index 0000000000..0cc01e1e24
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust1/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+RustLibrary("rust1")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/Cargo.toml
new file mode 100644
index 0000000000..9c557f6c08
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "rust2"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/moz.build b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/moz.build
new file mode 100644
index 0000000000..4ec4ea9c79
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/rust2/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+RustLibrary("rust2")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.c b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.cpp b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/Test.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/moz.build b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/moz.build
new file mode 100644
index 0000000000..44610a781c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/1/moz.build
@@ -0,0 +1,4 @@
+SOURCES += [
+ "Test.c",
+ "Test.cpp",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/Test.cpp b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/Test.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/Test.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/moz.build b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/moz.build
new file mode 100644
index 0000000000..b1064ae0c0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/moz.build
@@ -0,0 +1,4 @@
+SOURCES += [
+ "subdir/Test.cpp",
+ "Test.cpp",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/subdir/Test.cpp b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/subdir/Test.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/2/subdir/Test.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.c b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.cpp b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/Test.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/moz.build b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/moz.build
new file mode 100644
index 0000000000..a225907cae
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/3/moz.build
@@ -0,0 +1,7 @@
+SOURCES += [
+ "Test.c",
+]
+
+UNIFIED_SOURCES += [
+ "Test.cpp",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.c b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.cpp b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/Test.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/moz.build b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/moz.build
new file mode 100644
index 0000000000..ea5da28d88
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/object-conflicts/4/moz.build
@@ -0,0 +1,4 @@
+UNIFIED_SOURCES += [
+ "Test.c",
+ "Test.cpp",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-bin/moz.build b/python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-bin/moz.build
new file mode 100644
index 0000000000..d8b952c014
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-bin/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+Program("dist-bin")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-subdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-subdir/moz.build
new file mode 100644
index 0000000000..fc2f664c01
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program-paths/dist-subdir/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_SUBDIR = "foo"
+Program("dist-subdir")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program-paths/final-target/moz.build b/python/mozbuild/mozbuild/test/frontend/data/program-paths/final-target/moz.build
new file mode 100644
index 0000000000..a0d5805262
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program-paths/final-target/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET = "final/target"
+Program("final-target")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program-paths/moz.build b/python/mozbuild/mozbuild/test/frontend/data/program-paths/moz.build
new file mode 100644
index 0000000000..d1d087fd45
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program-paths/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Program(name):
+ PROGRAM = name
+
+
+DIRS += [
+ "dist-bin",
+ "dist-subdir",
+ "final-target",
+ "not-installed",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program-paths/not-installed/moz.build b/python/mozbuild/mozbuild/test/frontend/data/program-paths/not-installed/moz.build
new file mode 100644
index 0000000000..c725ab7326
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program-paths/not-installed/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_INSTALL = False
+Program("not-installed")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program/moz.build b/python/mozbuild/mozbuild/test/frontend/data/program/moz.build
new file mode 100644
index 0000000000..b3f7062732
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program/moz.build
@@ -0,0 +1,18 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Program(name):
+ PROGRAM = name
+
+
+@template
+def SimplePrograms(names, ext=".cpp"):
+ SIMPLE_PROGRAMS += names
+ SOURCES += ["%s%s" % (name, ext) for name in names]
+
+
+Program("test_program")
+
+SimplePrograms(["test_program1", "test_program2"])
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program/test_program1.cpp b/python/mozbuild/mozbuild/test/frontend/data/program/test_program1.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program/test_program1.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/program/test_program2.cpp b/python/mozbuild/mozbuild/test/frontend/data/program/test_program2.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program/test_program2.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-bad-dir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-bad-dir/moz.build
new file mode 100644
index 0000000000..68581574b1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-bad-dir/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-basic/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-basic/moz.build
new file mode 100644
index 0000000000..0a91c4692b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-basic/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+ILLEGAL = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-empty-list/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-empty-list/moz.build
new file mode 100644
index 0000000000..4dfba1c60f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-empty-list/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = []
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-error-func/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-error-func/moz.build
new file mode 100644
index 0000000000..d0f35c4c1d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-error-func/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+error("Some error.")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/child.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/child.build
new file mode 100644
index 0000000000..9bfc65481d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/child.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+ILLEGAL = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/moz.build
new file mode 100644
index 0000000000..603f3a7204
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-included-from/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("child.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-missing-include/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-missing-include/moz.build
new file mode 100644
index 0000000000..34129f7c93
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-missing-include/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("missing.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-outside-topsrcdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-outside-topsrcdir/moz.build
new file mode 100644
index 0000000000..040c1f5df1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-outside-topsrcdir/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+include("../include-basic/moz.build")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-read-unknown-global/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-read-unknown-global/moz.build
new file mode 100644
index 0000000000..6fc10f766a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-read-unknown-global/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+l = FOO
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-repeated-dir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-repeated-dir/moz.build
new file mode 100644
index 0000000000..91845b337f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-repeated-dir/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo"]
+
+DIRS += ["foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-script-error/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-script-error/moz.build
new file mode 100644
index 0000000000..a91d38b415
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-script-error/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+foo = True + None
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-syntax/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-syntax/moz.build
new file mode 100644
index 0000000000..70a0d2c066
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-syntax/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+foo =
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-write-bad-value/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-write-bad-value/moz.build
new file mode 100644
index 0000000000..2e8194b223
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-write-bad-value/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = "dir"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-error-write-unknown-global/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-error-write-unknown-global/moz.build
new file mode 100644
index 0000000000..5675031753
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-write-unknown-global/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["dir1", "dir2"]
+
+FOO = "bar"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/file b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/file b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file1 b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file1
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file1
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file2 b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file2
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file2
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/file b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/dir1/dir2/dir3/file b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/dir1/dir2/dir3/file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/dir1/dir2/dir3/file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/file b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/file b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/file b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/moz.build b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/moz.build b/python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/moz.build
new file mode 100644
index 0000000000..d4b9a3075d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/moz.build
@@ -0,0 +1,17 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += ["test1.c"]
+
+DEFINES["MOZ_TEST_DEFINE"] = True
+LIBRARY_DEFINES["MOZ_LIBRARY_DEFINE"] = "MOZ_TEST"
+COMPILE_FLAGS["DEFINES"] = ["-DFOO"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/test1.c b/python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/resolved-flags-error/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/Cargo.toml
new file mode 100644
index 0000000000..fbb4ae087d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/moz.build
new file mode 100644
index 0000000000..de1967c519
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+RustLibrary("random-crate")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/Cargo.toml
new file mode 100644
index 0000000000..fbb4ae087d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/moz.build
new file mode 100644
index 0000000000..ccd8ede3c0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-duplicate-features/moz.build
@@ -0,0 +1,20 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name, features):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+ RUST_LIBRARY_FEATURES = features
+
+
+RustLibrary("random-crate", ["musthave", "cantlivewithout", "musthave"])
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-features/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-features/Cargo.toml
new file mode 100644
index 0000000000..fbb4ae087d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-features/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-features/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-features/moz.build
new file mode 100644
index 0000000000..9d88bdea08
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-features/moz.build
@@ -0,0 +1,20 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name, features):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+ RUST_LIBRARY_FEATURES = features
+
+
+RustLibrary("random-crate", ["musthave", "cantlivewithout"])
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/Cargo.toml
new file mode 100644
index 0000000000..3572550b76
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[lib]
+crate-type = ["dylib"]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/moz.build
new file mode 100644
index 0000000000..de1967c519
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+RustLibrary("random-crate")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/Cargo.toml
new file mode 100644
index 0000000000..9e05fe5cb1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "deterministic-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/moz.build
new file mode 100644
index 0000000000..de1967c519
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+RustLibrary("random-crate")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-cargo-toml/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-cargo-toml/moz.build
new file mode 100644
index 0000000000..de1967c519
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-cargo-toml/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+RustLibrary("random-crate")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/Cargo.toml
new file mode 100644
index 0000000000..0934afcc4f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "The Mozilla Project Developers",
+]
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/moz.build
new file mode 100644
index 0000000000..de1967c519
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+@template
+def RustLibrary(name):
+ """Template for Rust libraries."""
+ Library(name)
+
+ IS_RUST_LIBRARY = True
+
+
+RustLibrary("random-crate")
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-program-no-cargo-toml/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-program-no-cargo-toml/moz.build
new file mode 100644
index 0000000000..56601854f9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-program-no-cargo-toml/moz.build
@@ -0,0 +1 @@
+RUST_PROGRAMS += ["none"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/Cargo.toml
new file mode 100644
index 0000000000..dee335937f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+authors = ["The Mozilla Project Developers"]
+name = "testing"
+version = "0.0.1"
+
+[[bin]]
+name = "some"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/moz.build
new file mode 100644
index 0000000000..56601854f9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-program-nonexistent-name/moz.build
@@ -0,0 +1 @@
+RUST_PROGRAMS += ["none"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-programs/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-programs/Cargo.toml
new file mode 100644
index 0000000000..dee335937f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-programs/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+authors = ["The Mozilla Project Developers"]
+name = "testing"
+version = "0.0.1"
+
+[[bin]]
+name = "some"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-programs/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-programs/moz.build
new file mode 100644
index 0000000000..80dc15120a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-programs/moz.build
@@ -0,0 +1 @@
+RUST_PROGRAMS += ["some"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/schedules/moz.build b/python/mozbuild/mozbuild/test/frontend/data/schedules/moz.build
new file mode 100644
index 0000000000..3f4f450d37
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/schedules/moz.build
@@ -0,0 +1,19 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+with Files("*.win"):
+ SCHEDULES.exclusive = ["windows"]
+
+with Files("*.osx"):
+ SCHEDULES.exclusive = ["macosx"]
+
+with Files("win.and.osx"):
+ # this conflicts with the previous clause and will cause an error
+ # when read
+ SCHEDULES.exclusive = ["macosx", "windows"]
+
+with Files("subd/**.py"):
+ SCHEDULES.inclusive += ["py-lint"]
+
+with Files("**/*.js"):
+ SCHEDULES.inclusive += ["js-lint"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/schedules/subd/moz.build b/python/mozbuild/mozbuild/test/frontend/data/schedules/subd/moz.build
new file mode 100644
index 0000000000..b9c3bf6c74
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/schedules/subd/moz.build
@@ -0,0 +1,5 @@
+with Files("yaml.py"):
+ SCHEDULES.inclusive += ["yaml-lint"]
+
+with Files("win.js"):
+ SCHEDULES.exclusive = ["windows"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/d.c b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/d.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/d.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/e.m b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/e.m
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/e.m
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/g.S b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/g.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/g.S
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/h.s b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/h.s
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/h.s
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/i.asm b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/i.asm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/i.asm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/moz.build b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/moz.build
new file mode 100644
index 0000000000..29abd6de5d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+SOURCES += [
+ "d.c",
+]
+
+SOURCES += [
+ "e.m",
+]
+
+SOURCES += [
+ "g.S",
+]
+
+SOURCES += [
+ "h.s",
+ "i.asm",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/a.cpp b/python/mozbuild/mozbuild/test/frontend/data/sources/a.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/a.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/b.cc b/python/mozbuild/mozbuild/test/frontend/data/sources/b.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/b.cc
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/c.cxx b/python/mozbuild/mozbuild/test/frontend/data/sources/c.cxx
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/c.cxx
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/d.c b/python/mozbuild/mozbuild/test/frontend/data/sources/d.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/d.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/e.m b/python/mozbuild/mozbuild/test/frontend/data/sources/e.m
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/e.m
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/f.mm b/python/mozbuild/mozbuild/test/frontend/data/sources/f.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/f.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/g.S b/python/mozbuild/mozbuild/test/frontend/data/sources/g.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/g.S
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/h.s b/python/mozbuild/mozbuild/test/frontend/data/sources/h.s
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/h.s
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/i.asm b/python/mozbuild/mozbuild/test/frontend/data/sources/i.asm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/i.asm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sources/moz.build b/python/mozbuild/mozbuild/test/frontend/data/sources/moz.build
new file mode 100644
index 0000000000..e25f865f72
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+SOURCES += [
+ "a.cpp",
+ "b.cc",
+ "c.cxx",
+]
+
+SOURCES += [
+ "d.c",
+]
+
+SOURCES += [
+ "e.m",
+]
+
+SOURCES += [
+ "f.mm",
+]
+
+SOURCES += [
+ "g.S",
+]
+
+SOURCES += [
+ "h.s",
+ "i.asm",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/templates/templates.mozbuild b/python/mozbuild/mozbuild/test/frontend/data/templates/templates.mozbuild
new file mode 100644
index 0000000000..290104bc72
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/templates/templates.mozbuild
@@ -0,0 +1,21 @@
+@template
+def Template(foo, bar=[]):
+ SOURCES += foo
+ DIRS += bar
+
+@template
+def TemplateError(foo):
+ ILLEGAL = foo
+
+@template
+def TemplateGlobalVariable():
+ SOURCES += illegal
+
+@template
+def TemplateGlobalUPPERVariable():
+ SOURCES += DIRS
+
+@template
+def TemplateInherit(foo):
+ USE_LIBS += ['foo']
+ Template(foo)
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-harness-files-root/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files-root/moz.build
new file mode 100644
index 0000000000..d7f6377d0d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files-root/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+TEST_HARNESS_FILES += ["foo.py"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.ini
new file mode 100644
index 0000000000..d87114ac7d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.ini
@@ -0,0 +1 @@
+# dummy file so the existence checks for TEST_HARNESS_FILES succeed
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.py b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.py
new file mode 100644
index 0000000000..d87114ac7d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/mochitest.py
@@ -0,0 +1 @@
+# dummy file so the existence checks for TEST_HARNESS_FILES succeed
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/moz.build
new file mode 100644
index 0000000000..ff3fed0ee0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/moz.build
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+TEST_HARNESS_FILES.mochitest += ["runtests.py"]
+TEST_HARNESS_FILES.mochitest += ["utils.py"]
+TEST_HARNESS_FILES.testing.mochitest += ["mochitest.py"]
+TEST_HARNESS_FILES.testing.mochitest += ["mochitest.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/runtests.py b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/runtests.py
new file mode 100644
index 0000000000..d87114ac7d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/runtests.py
@@ -0,0 +1 @@
+# dummy file so the existence checks for TEST_HARNESS_FILES succeed
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/utils.py b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/utils.py
new file mode 100644
index 0000000000..d87114ac7d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-harness-files/utils.py
@@ -0,0 +1 @@
+# dummy file so the existence checks for TEST_HARNESS_FILES succeed
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-install-shared-lib/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-install-shared-lib/moz.build
new file mode 100644
index 0000000000..fa592c72a3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-install-shared-lib/moz.build
@@ -0,0 +1,16 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def SharedLibrary(name):
+ LIBRARY_NAME = name
+ FORCE_SHARED_LIB = True
+
+
+DIST_INSTALL = False
+SharedLibrary("foo")
+
+TEST_HARNESS_FILES.foo.bar += [
+ "!%sfoo%s" % (CONFIG["DLL_PREFIX"], CONFIG["DLL_SUFFIX"])
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build
new file mode 100644
index 0000000000..0f84eb5554
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build
@@ -0,0 +1,14 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["one", "two", "three"]
+
+
+@template
+def SharedLibrary(name):
+ LIBRARY_NAME = name
+ FORCE_SHARED_LIB = True
+
+
+SharedLibrary("cxx_shared")
+USE_LIBS += ["cxx_static"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/foo.cpp b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/foo.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/foo.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/moz.build
new file mode 100644
index 0000000000..f03a34c33f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/moz.build
@@ -0,0 +1,11 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ LIBRARY_NAME = name
+
+
+Library("cxx_static")
+SOURCES += ["foo.cpp"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/three/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/three/moz.build
new file mode 100644
index 0000000000..08e26c4eb3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/three/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SharedLibrary("just_c_shared")
+USE_LIBS += ["just_c_static"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/foo.c b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/foo.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/foo.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/moz.build
new file mode 100644
index 0000000000..d3bb738ba4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/moz.build
@@ -0,0 +1,11 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ LIBRARY_NAME = name
+
+
+Library("just_c_static")
+SOURCES += ["foo.c"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/absolute-support.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/absolute-support.ini
new file mode 100644
index 0000000000..900f421584
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/absolute-support.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = /.well-known/foo.txt
+
+[test_file.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/foo.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/foo.txt
new file mode 100644
index 0000000000..ce01362503
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/foo.txt
@@ -0,0 +1 @@
+hello
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/moz.build
new file mode 100644
index 0000000000..5ccb97c1bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["absolute-support.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/test_file.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/test_file.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-absolute-support/test_file.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/bar.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/bar.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/bar.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/foo.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/mochitest.ini
new file mode 100644
index 0000000000..2f1fc406a0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/mochitest.ini
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+[DEFAULT]
+support-files = bar.js foo.js bar.js
+
+[test_baz.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/moz.build
new file mode 100644
index 0000000000..4cc0c3d4cf
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/test_baz.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/test_baz.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-dupes/test_baz.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/included-reftest.list b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/included-reftest.list
new file mode 100644
index 0000000000..1caf9cc391
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/included-reftest.list
@@ -0,0 +1 @@
+!= reftest2.html reftest2-ref.html \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/moz.build
new file mode 100644
index 0000000000..8f321387af
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/moz.build
@@ -0,0 +1 @@
+REFTEST_MANIFESTS += ["reftest.list"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/reftest.list b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/reftest.list
new file mode 100644
index 0000000000..80caf8ffa4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/reftest.list
@@ -0,0 +1,2 @@
+== reftest1.html reftest1-ref.html
+include included-reftest.list
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/empty.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/empty.ini
new file mode 100644
index 0000000000..83a0cec0c6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/empty.ini
@@ -0,0 +1,2 @@
+[DEFAULT]
+foo = bar
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/moz.build
new file mode 100644
index 0000000000..486e879241
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-empty/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["empty.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-inactive-ignored/test_inactive.html b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-inactive-ignored/test_inactive.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-inactive-ignored/test_inactive.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/common.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/common.ini
new file mode 100644
index 0000000000..753cd0ec0d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/common.ini
@@ -0,0 +1 @@
+[test_foo.html]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/mochitest.ini
new file mode 100644
index 0000000000..fe0af1cd86
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/mochitest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[include:common.ini]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/moz.build
new file mode 100644
index 0000000000..4cc0c3d4cf
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/test_foo.html b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/test_foo.html
new file mode 100644
index 0000000000..18ecdcb795
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/test_foo.html
@@ -0,0 +1 @@
+<html></html>
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/foo.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/foo.txt
new file mode 100644
index 0000000000..ce01362503
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/foo.txt
@@ -0,0 +1 @@
+hello
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/just-support.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/just-support.ini
new file mode 100644
index 0000000000..efa2d4bc05
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/just-support.ini
@@ -0,0 +1,2 @@
+[DEFAULT]
+support-files = foo.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/moz.build
new file mode 100644
index 0000000000..adf2a0d91c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-just-support/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["just-support.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/dir1/bar b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/dir1/bar
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/dir1/bar
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/foo b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/foo
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y-support/foo
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y.ini
new file mode 100644
index 0000000000..9cf7989185
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/a11y.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = a11y-support/**
+
+[test_a11y.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/browser.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/browser.ini
new file mode 100644
index 0000000000..a81ee3acbb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = support1 support2
+
+[test_browser.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/chrome.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/chrome.ini
new file mode 100644
index 0000000000..1db07cfac9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/chrome.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_chrome.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/crashtest.list b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/crashtest.list
new file mode 100644
index 0000000000..b9d7f2685a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/crashtest.list
@@ -0,0 +1 @@
+== crashtest1.html crashtest1-ref.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/metro.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/metro.ini
new file mode 100644
index 0000000000..a7eb6def41
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/metro.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_metro.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/mochitest.ini
new file mode 100644
index 0000000000..69fd71de0b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files = external1 external2
+generated-files = external1 external2
+
+[test_mochitest.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build
new file mode 100644
index 0000000000..9de10add3c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build
@@ -0,0 +1,12 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+A11Y_MANIFESTS += ["a11y.ini"]
+BROWSER_CHROME_MANIFESTS += ["browser.ini"]
+METRO_CHROME_MANIFESTS += ["metro.ini"]
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+MOCHITEST_CHROME_MANIFESTS += ["chrome.ini"]
+XPCSHELL_TESTS_MANIFESTS += ["xpcshell.ini"]
+REFTEST_MANIFESTS += ["reftest.list"]
+CRASHTEST_MANIFESTS += ["crashtest.list"]
+PYTHON_UNITTEST_MANIFESTS += ["python.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/python.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/python.ini
new file mode 100644
index 0000000000..97a9db6920
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/python.ini
@@ -0,0 +1 @@
+[test_foo.py]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest.list b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest.list
new file mode 100644
index 0000000000..3fc25b2966
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest.list
@@ -0,0 +1 @@
+== reftest1.html reftest1-ref.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_a11y.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_a11y.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_a11y.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_browser.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_browser.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_chrome.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_chrome.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_chrome.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_foo.py b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_foo.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_foo.py
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_metro.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_metro.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_metro.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_mochitest.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_mochitest.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_mochitest.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_xpcshell.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_xpcshell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/test_xpcshell.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/xpcshell.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/xpcshell.ini
new file mode 100644
index 0000000000..c228c24ac1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head = head1 head2
+dupe-manifest =
+
+[test_xpcshell.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-manifest/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-manifest/moz.build
new file mode 100644
index 0000000000..ec33a37d3d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-manifest/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPCSHELL_TESTS_MANIFESTS += ["does_not_exist.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build
new file mode 100644
index 0000000000..d3878746bd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPCSHELL_TESTS_MANIFESTS += ["xpcshell.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini
new file mode 100644
index 0000000000..9ab85c0cef
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = support/**
+
+[missing.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/mochitest.ini
new file mode 100644
index 0000000000..e3ef6216b7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/mochitest.ini
@@ -0,0 +1 @@
+[test_missing.html]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/moz.build
new file mode 100644
index 0000000000..4cc0c3d4cf
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/mochitest.ini
new file mode 100644
index 0000000000..c788224291
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/mochitest.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = ../support-file.txt
+
+[test_foo.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/test_foo.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/test_foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/child/test_foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/moz.build
new file mode 100644
index 0000000000..275a810a5e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["child/mochitest.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/support-file.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/support-file.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-parent-support-files-dir/support-file.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/another-file.sjs b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/another-file.sjs
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/another-file.sjs
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/browser.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/browser.ini
new file mode 100644
index 0000000000..4f1335d6b1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ another-file.sjs
+ data/**
+
+[test_sub.js] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/one.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/one.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/one.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/two.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/two.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/data/two.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/test_sub.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/test_sub.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/child/test_sub.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/mochitest.ini
new file mode 100644
index 0000000000..ada59d387d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/mochitest.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ support-file.txt
+ !/child/test_sub.js
+ !/child/another-file.sjs
+ !/child/data/**
+ !/does/not/exist.sjs
+
+[test_foo.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/moz.build
new file mode 100644
index 0000000000..9df54dbc99
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+BROWSER_CHROME_MANIFESTS += ["child/browser.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/support-file.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/support-file.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/support-file.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/test_foo.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/test_foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-missing/test_foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/another-file.sjs b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/another-file.sjs
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/another-file.sjs
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/browser.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/browser.ini
new file mode 100644
index 0000000000..4f1335d6b1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ another-file.sjs
+ data/**
+
+[test_sub.js] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/one.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/one.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/one.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/two.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/two.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/data/two.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/test_sub.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/test_sub.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/child/test_sub.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/mochitest.ini
new file mode 100644
index 0000000000..a9860f3de8
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/mochitest.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ support-file.txt
+ !/child/test_sub.js
+ !/child/another-file.sjs
+ !/child/data/**
+
+[test_foo.js]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/moz.build
new file mode 100644
index 0000000000..9df54dbc99
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/moz.build
@@ -0,0 +1,5 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+BROWSER_CHROME_MANIFESTS += ["child/browser.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/support-file.txt b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/support-file.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/support-file.txt
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/test_foo.js b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/test_foo.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-shared-support/test_foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/moz.build
new file mode 100644
index 0000000000..9d098e0eab
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ["test.ini"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test.ini
new file mode 100644
index 0000000000..caf3911864
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+generated-files = does_not_exist
+
+[test_foo]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test_foo b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test_foo
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-unmatched-generated/test_foo
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir-missing-generated/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir-missing-generated/moz.build
new file mode 100644
index 0000000000..450af01d9a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir-missing-generated/moz.build
@@ -0,0 +1,12 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def SharedLibrary(name):
+ LIBRARY_NAME = name
+ FORCE_SHARED_LIB = True
+
+
+SharedLibrary("foo")
+SYMBOLS_FILE = "!foo.symbols"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/foo.py b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/foo.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/foo.py
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/moz.build
new file mode 100644
index 0000000000..7ea07b4ee9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def SharedLibrary(name):
+ LIBRARY_NAME = name
+ FORCE_SHARED_LIB = True
+
+
+SharedLibrary("foo")
+SYMBOLS_FILE = "!foo.symbols"
+
+GENERATED_FILES += ["foo.symbols"]
+GENERATED_FILES["foo.symbols"].script = "foo.py"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/foo.symbols b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/foo.symbols
new file mode 100644
index 0000000000..257cc5642c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/foo.symbols
@@ -0,0 +1 @@
+foo
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/moz.build
new file mode 100644
index 0000000000..47e435dbf5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/moz.build
@@ -0,0 +1,12 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def SharedLibrary(name):
+ LIBRARY_NAME = name
+ FORCE_SHARED_LIB = True
+
+
+SharedLibrary("foo")
+SYMBOLS_FILE = "foo.symbols"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/moz.build
new file mode 100644
index 0000000000..480808eb8a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ["regular"]
+TEST_DIRS += ["test"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/parallel/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/parallel/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/parallel/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/regular/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/regular/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/regular/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/test/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/test/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-all-vars/test/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-outside-topsrcdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-outside-topsrcdir/moz.build
new file mode 100644
index 0000000000..dbdc694a6a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-outside-topsrcdir/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["../../foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/bar/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/bar/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/foo/moz.build
new file mode 100644
index 0000000000..4b42bbc5ab
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/foo/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["../bar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/moz.build
new file mode 100644
index 0000000000..68581574b1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-relative-dirs/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/bar/moz.build
new file mode 100644
index 0000000000..f204e245b4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/bar/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["../foo"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/foo/moz.build
new file mode 100644
index 0000000000..4b42bbc5ab
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/foo/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["../bar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/moz.build
new file mode 100644
index 0000000000..5a9445a6e6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-repeated-dirs/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo", "bar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/bar/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/bar/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/biz/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/biz/moz.build
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/biz/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/moz.build
new file mode 100644
index 0000000000..3ad8a1501d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/foo/moz.build
@@ -0,0 +1,2 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+DIRS = ["biz"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/moz.build b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/moz.build
new file mode 100644
index 0000000000..5a9445a6e6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS = ["foo", "bar"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/bar.cxx b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/bar.cxx
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/bar.cxx
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c1.c b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c2.c b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c2.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/c2.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/foo.cpp b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/foo.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/foo.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/moz.build b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/moz.build
new file mode 100644
index 0000000000..217e43831f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += [
+ "bar.cxx",
+ "foo.cpp",
+ "quux.cc",
+]
+
+UNIFIED_SOURCES += [
+ "objc1.mm",
+ "objc2.mm",
+]
+
+UNIFIED_SOURCES += [
+ "c1.c",
+ "c2.c",
+]
+
+FILES_PER_UNIFIED_FILE = 1
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc1.mm b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc1.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc1.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc2.mm b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc2.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/objc2.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/quux.cc b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/quux.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/quux.cc
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/bar.cxx b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/bar.cxx
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/bar.cxx
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/c1.c b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/c1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/c1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/c2.c b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/c2.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/c2.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/foo.cpp b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/foo.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/foo.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/moz.build b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/moz.build
new file mode 100644
index 0000000000..8a86e055da
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+UNIFIED_SOURCES += [
+ "bar.cxx",
+ "foo.cpp",
+ "quux.cc",
+]
+
+UNIFIED_SOURCES += [
+ "objc1.mm",
+ "objc2.mm",
+]
+
+UNIFIED_SOURCES += [
+ "c1.c",
+ "c2.c",
+]
+
+FILES_PER_UNIFIED_FILE = 32
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc1.mm b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc1.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc1.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc2.mm b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc2.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/objc2.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/unified-sources/quux.cc b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/quux.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/quux.cc
diff --git a/python/mozbuild/mozbuild/test/frontend/data/use-nasm/moz.build b/python/mozbuild/mozbuild/test/frontend/data/use-nasm/moz.build
new file mode 100644
index 0000000000..63ac5283f6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/use-nasm/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+USE_NASM = True
+
+SOURCES += ["test1.S"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/use-nasm/test1.S b/python/mozbuild/mozbuild/test/frontend/data/use-nasm/test1.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/use-nasm/test1.S
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/bans.S b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/bans.S
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/bans.S
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/baz.def b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/baz.def
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/baz.def
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
new file mode 100644
index 0000000000..d080b00c92
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_INSTALL = False
+
+DELAYLOAD_DLLS = ["foo.dll", "bar.dll"]
+
+RCFILE = "foo.rc"
+RCINCLUDE = "bar.rc"
+DEFFILE = "baz.def"
+
+WIN32_EXE_LDFLAGS += ["-subsystem:console"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.c b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.cpp b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.mm b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test1.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.c b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.cpp b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.mm b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.mm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.mm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/visibility-flags/moz.build b/python/mozbuild/mozbuild/test/frontend/data/visibility-flags/moz.build
new file mode 100644
index 0000000000..630a3afd80
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/visibility-flags/moz.build
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+@template
+def Library(name):
+ """Template for libraries."""
+ LIBRARY_NAME = name
+
+
+Library("dummy")
+
+
+@template
+def NoVisibilityFlags():
+ COMPILE_FLAGS["VISIBILITY"] = []
+
+
+UNIFIED_SOURCES += ["test1.c"]
+
+NoVisibilityFlags()
diff --git a/python/mozbuild/mozbuild/test/frontend/data/visibility-flags/test1.c b/python/mozbuild/mozbuild/test/frontend/data/visibility-flags/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/visibility-flags/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/moz.build b/python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/moz.build
new file mode 100644
index 0000000000..e7cf13088f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/moz.build
@@ -0,0 +1,14 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SANDBOXED_WASM_LIBRARY_NAME = "dummy"
+
+WASM_SOURCES += ["test1.c"]
+
+value = "xyz"
+WASM_DEFINES["FOO"] = True
+WASM_DEFINES["BAZ"] = '"abcd"'
+WASM_DEFINES["BAR"] = 7
+WASM_DEFINES["VALUE"] = value
+WASM_DEFINES["QUX"] = False
+WASM_CFLAGS += ["-funroll-loops", "-wasm-arg"]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/test1.c b/python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/test1.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/wasm-compile-flags/test1.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/a.cpp b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/a.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/a.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/b.cc b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/b.cc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/b.cc
diff --git a/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/c.cxx b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/c.cxx
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/c.cxx
diff --git a/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/d.c b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/d.c
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/d.c
diff --git a/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/moz.build b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/moz.build
new file mode 100644
index 0000000000..e266bcb0dd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/wasm-sources/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SANDBOXED_WASM_LIBRARY_NAME = "wasmSources"
+
+WASM_SOURCES += [
+ "a.cpp",
+ "b.cc",
+ "c.cxx",
+]
+
+WASM_SOURCES += [
+ "d.c",
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/xpidl-module-no-sources/moz.build b/python/mozbuild/mozbuild/test/frontend/data/xpidl-module-no-sources/moz.build
new file mode 100644
index 0000000000..f0abd45382
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/xpidl-module-no-sources/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPIDL_MODULE = "xpidl_module"
diff --git a/python/mozbuild/mozbuild/test/frontend/test_context.py b/python/mozbuild/mozbuild/test/frontend/test_context.py
new file mode 100644
index 0000000000..fbf35e1c8c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_context.py
@@ -0,0 +1,736 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+
+import six
+from mozpack import path as mozpath
+from mozunit import main
+
+from mozbuild.frontend.context import (
+ FUNCTIONS,
+ SPECIAL_VARIABLES,
+ SUBCONTEXTS,
+ VARIABLES,
+ AbsolutePath,
+ Context,
+ ContextDerivedTypedHierarchicalStringList,
+ ContextDerivedTypedList,
+ ContextDerivedTypedListWithItems,
+ ContextDerivedTypedRecord,
+ Files,
+ ObjDirPath,
+ Path,
+ SourcePath,
+)
+from mozbuild.util import StrictOrderingOnAppendListWithFlagsFactory
+
+
+class TestContext(unittest.TestCase):
+ def test_defaults(self):
+ test = Context(
+ {
+ "foo": (int, int, ""),
+ "bar": (bool, bool, ""),
+ "baz": (dict, dict, ""),
+ }
+ )
+
+ self.assertEqual(list(test), [])
+
+ self.assertEqual(test["foo"], 0)
+
+ self.assertEqual(set(test.keys()), {"foo"})
+
+ self.assertEqual(test["bar"], False)
+
+ self.assertEqual(set(test.keys()), {"foo", "bar"})
+
+ self.assertEqual(test["baz"], {})
+
+ self.assertEqual(set(test.keys()), {"foo", "bar", "baz"})
+
+ with self.assertRaises(KeyError):
+ test["qux"]
+
+ self.assertEqual(set(test.keys()), {"foo", "bar", "baz"})
+
+ def test_type_check(self):
+ test = Context(
+ {
+ "foo": (int, int, ""),
+ "baz": (dict, list, ""),
+ }
+ )
+
+ test["foo"] = 5
+
+ self.assertEqual(test["foo"], 5)
+
+ with self.assertRaises(ValueError):
+ test["foo"] = {}
+
+ self.assertEqual(test["foo"], 5)
+
+ with self.assertRaises(KeyError):
+ test["bar"] = True
+
+ test["baz"] = [("a", 1), ("b", 2)]
+
+ self.assertEqual(test["baz"], {"a": 1, "b": 2})
+
+ def test_update(self):
+ test = Context(
+ {
+ "foo": (int, int, ""),
+ "bar": (bool, bool, ""),
+ "baz": (dict, list, ""),
+ }
+ )
+
+ self.assertEqual(list(test), [])
+
+ with self.assertRaises(ValueError):
+ test.update(bar=True, foo={})
+
+ self.assertEqual(list(test), [])
+
+ test.update(bar=True, foo=1)
+
+ self.assertEqual(set(test.keys()), {"foo", "bar"})
+ self.assertEqual(test["foo"], 1)
+ self.assertEqual(test["bar"], True)
+
+ test.update([("bar", False), ("foo", 2)])
+ self.assertEqual(test["foo"], 2)
+ self.assertEqual(test["bar"], False)
+
+ test.update([("foo", 0), ("baz", {"a": 1, "b": 2})])
+ self.assertEqual(test["foo"], 0)
+ self.assertEqual(test["baz"], {"a": 1, "b": 2})
+
+ test.update([("foo", 42), ("baz", [("c", 3), ("d", 4)])])
+ self.assertEqual(test["foo"], 42)
+ self.assertEqual(test["baz"], {"c": 3, "d": 4})
+
+ def test_context_paths(self):
+ test = Context()
+
+ # Newly created context has no paths.
+ self.assertIsNone(test.main_path)
+ self.assertIsNone(test.current_path)
+ self.assertEqual(test.all_paths, set())
+ self.assertEqual(test.source_stack, [])
+
+ foo = os.path.abspath("foo")
+ test.add_source(foo)
+
+ # Adding the first source makes it the main and current path.
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, foo)
+ self.assertEqual(test.all_paths, set([foo]))
+ self.assertEqual(test.source_stack, [foo])
+
+ bar = os.path.abspath("bar")
+ test.add_source(bar)
+
+ # Adding the second source makes leaves main and current paths alone.
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, foo)
+ self.assertEqual(test.all_paths, set([bar, foo]))
+ self.assertEqual(test.source_stack, [foo])
+
+ qux = os.path.abspath("qux")
+ test.push_source(qux)
+
+ # Pushing a source makes it the current path
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, qux)
+ self.assertEqual(test.all_paths, set([bar, foo, qux]))
+ self.assertEqual(test.source_stack, [foo, qux])
+
+ hoge = os.path.abspath("hoge")
+ test.push_source(hoge)
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, hoge)
+ self.assertEqual(test.all_paths, set([bar, foo, hoge, qux]))
+ self.assertEqual(test.source_stack, [foo, qux, hoge])
+
+ fuga = os.path.abspath("fuga")
+
+ # Adding a source after pushing doesn't change the source stack
+ test.add_source(fuga)
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, hoge)
+ self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
+ self.assertEqual(test.source_stack, [foo, qux, hoge])
+
+ # Adding a source twice doesn't change anything
+ test.add_source(qux)
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, hoge)
+ self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
+ self.assertEqual(test.source_stack, [foo, qux, hoge])
+
+ last = test.pop_source()
+
+ # Popping a source returns the last pushed one, not the last added one.
+ self.assertEqual(last, hoge)
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, qux)
+ self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
+ self.assertEqual(test.source_stack, [foo, qux])
+
+ last = test.pop_source()
+ self.assertEqual(last, qux)
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, foo)
+ self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
+ self.assertEqual(test.source_stack, [foo])
+
+ # Popping the main path is allowed.
+ last = test.pop_source()
+ self.assertEqual(last, foo)
+ self.assertEqual(test.main_path, foo)
+ self.assertIsNone(test.current_path)
+ self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux]))
+ self.assertEqual(test.source_stack, [])
+
+ # Popping past the main path asserts.
+ with self.assertRaises(AssertionError):
+ test.pop_source()
+
+ # Pushing after the main path was popped asserts.
+ with self.assertRaises(AssertionError):
+ test.push_source(foo)
+
+ test = Context()
+ test.push_source(foo)
+ test.push_source(bar)
+
+ # Pushing the same file twice is allowed.
+ test.push_source(bar)
+ test.push_source(foo)
+ self.assertEqual(last, foo)
+ self.assertEqual(test.main_path, foo)
+ self.assertEqual(test.current_path, foo)
+ self.assertEqual(test.all_paths, set([bar, foo]))
+ self.assertEqual(test.source_stack, [foo, bar, bar, foo])
+
+ def test_context_dirs(self):
+ class Config(object):
+ pass
+
+ config = Config()
+ config.topsrcdir = mozpath.abspath(os.curdir)
+ config.topobjdir = mozpath.abspath("obj")
+ test = Context(config=config)
+ foo = mozpath.abspath("foo")
+ test.push_source(foo)
+
+ self.assertEqual(test.srcdir, config.topsrcdir)
+ self.assertEqual(test.relsrcdir, "")
+ self.assertEqual(test.objdir, config.topobjdir)
+ self.assertEqual(test.relobjdir, "")
+
+ foobar = os.path.abspath("foo/bar")
+ test.push_source(foobar)
+ self.assertEqual(test.srcdir, mozpath.join(config.topsrcdir, "foo"))
+ self.assertEqual(test.relsrcdir, "foo")
+ self.assertEqual(test.objdir, config.topobjdir)
+ self.assertEqual(test.relobjdir, "")
+
+
+class TestSymbols(unittest.TestCase):
+ def _verify_doc(self, doc):
+ # Documentation should be of the format:
+ # """SUMMARY LINE
+ #
+ # EXTRA PARAGRAPHS
+ # """
+
+ self.assertNotIn("\r", doc)
+
+ lines = doc.split("\n")
+
+ # No trailing whitespace.
+ for line in lines[0:-1]:
+ self.assertEqual(line, line.rstrip())
+
+ self.assertGreater(len(lines), 0)
+ self.assertGreater(len(lines[0].strip()), 0)
+
+ # Last line should be empty.
+ self.assertEqual(lines[-1].strip(), "")
+
+ def test_documentation_formatting(self):
+ for typ, inp, doc in VARIABLES.values():
+ self._verify_doc(doc)
+
+ for attr, args, doc in FUNCTIONS.values():
+ self._verify_doc(doc)
+
+ for func, typ, doc in SPECIAL_VARIABLES.values():
+ self._verify_doc(doc)
+
+ for name, cls in SUBCONTEXTS.items():
+ self._verify_doc(cls.__doc__)
+
+ for name, v in cls.VARIABLES.items():
+ self._verify_doc(v[2])
+
+
+class TestPaths(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ class Config(object):
+ pass
+
+ cls.config = config = Config()
+ config.topsrcdir = mozpath.abspath(os.curdir)
+ config.topobjdir = mozpath.abspath("obj")
+
+ def test_path(self):
+ config = self.config
+ ctxt1 = Context(config=config)
+ ctxt1.push_source(mozpath.join(config.topsrcdir, "foo", "moz.build"))
+ ctxt2 = Context(config=config)
+ ctxt2.push_source(mozpath.join(config.topsrcdir, "bar", "moz.build"))
+
+ path1 = Path(ctxt1, "qux")
+ self.assertIsInstance(path1, SourcePath)
+ self.assertEqual(path1, "qux")
+ self.assertEqual(path1.full_path, mozpath.join(config.topsrcdir, "foo", "qux"))
+
+ path2 = Path(ctxt2, "../foo/qux")
+ self.assertIsInstance(path2, SourcePath)
+ self.assertEqual(path2, "../foo/qux")
+ self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir, "foo", "qux"))
+
+ self.assertEqual(path1, path2)
+
+ self.assertEqual(
+ path1.join("../../bar/qux").full_path,
+ mozpath.join(config.topsrcdir, "bar", "qux"),
+ )
+
+ path1 = Path(ctxt1, "/qux/qux")
+ self.assertIsInstance(path1, SourcePath)
+ self.assertEqual(path1, "/qux/qux")
+ self.assertEqual(path1.full_path, mozpath.join(config.topsrcdir, "qux", "qux"))
+
+ path2 = Path(ctxt2, "/qux/qux")
+ self.assertIsInstance(path2, SourcePath)
+ self.assertEqual(path2, "/qux/qux")
+ self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir, "qux", "qux"))
+
+ self.assertEqual(path1, path2)
+
+ path1 = Path(ctxt1, "!qux")
+ self.assertIsInstance(path1, ObjDirPath)
+ self.assertEqual(path1, "!qux")
+ self.assertEqual(path1.full_path, mozpath.join(config.topobjdir, "foo", "qux"))
+
+ path2 = Path(ctxt2, "!../foo/qux")
+ self.assertIsInstance(path2, ObjDirPath)
+ self.assertEqual(path2, "!../foo/qux")
+ self.assertEqual(path2.full_path, mozpath.join(config.topobjdir, "foo", "qux"))
+
+ self.assertEqual(path1, path2)
+
+ path1 = Path(ctxt1, "!/qux/qux")
+ self.assertIsInstance(path1, ObjDirPath)
+ self.assertEqual(path1, "!/qux/qux")
+ self.assertEqual(path1.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ path2 = Path(ctxt2, "!/qux/qux")
+ self.assertIsInstance(path2, ObjDirPath)
+ self.assertEqual(path2, "!/qux/qux")
+ self.assertEqual(path2.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ self.assertEqual(path1, path2)
+
+ path1 = Path(ctxt1, path1)
+ self.assertIsInstance(path1, ObjDirPath)
+ self.assertEqual(path1, "!/qux/qux")
+ self.assertEqual(path1.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ path2 = Path(ctxt2, path2)
+ self.assertIsInstance(path2, ObjDirPath)
+ self.assertEqual(path2, "!/qux/qux")
+ self.assertEqual(path2.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ self.assertEqual(path1, path2)
+
+ path1 = Path(path1)
+ self.assertIsInstance(path1, ObjDirPath)
+ self.assertEqual(path1, "!/qux/qux")
+ self.assertEqual(path1.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ self.assertEqual(path1, path2)
+
+ path2 = Path(path2)
+ self.assertIsInstance(path2, ObjDirPath)
+ self.assertEqual(path2, "!/qux/qux")
+ self.assertEqual(path2.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ self.assertEqual(path1, path2)
+
+ def test_source_path(self):
+ config = self.config
+ ctxt = Context(config=config)
+ ctxt.push_source(mozpath.join(config.topsrcdir, "foo", "moz.build"))
+
+ path = SourcePath(ctxt, "qux")
+ self.assertEqual(path, "qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topsrcdir, "foo", "qux"))
+ self.assertEqual(path.translated, mozpath.join(config.topobjdir, "foo", "qux"))
+
+ path = SourcePath(ctxt, "../bar/qux")
+ self.assertEqual(path, "../bar/qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topsrcdir, "bar", "qux"))
+ self.assertEqual(path.translated, mozpath.join(config.topobjdir, "bar", "qux"))
+
+ path = SourcePath(ctxt, "/qux/qux")
+ self.assertEqual(path, "/qux/qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topsrcdir, "qux", "qux"))
+ self.assertEqual(path.translated, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ with self.assertRaises(ValueError):
+ SourcePath(ctxt, "!../bar/qux")
+
+ with self.assertRaises(ValueError):
+ SourcePath(ctxt, "!/qux/qux")
+
+ path = SourcePath(path)
+ self.assertIsInstance(path, SourcePath)
+ self.assertEqual(path, "/qux/qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topsrcdir, "qux", "qux"))
+ self.assertEqual(path.translated, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ path = Path(path)
+ self.assertIsInstance(path, SourcePath)
+
+ def test_objdir_path(self):
+ config = self.config
+ ctxt = Context(config=config)
+ ctxt.push_source(mozpath.join(config.topsrcdir, "foo", "moz.build"))
+
+ path = ObjDirPath(ctxt, "!qux")
+ self.assertEqual(path, "!qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topobjdir, "foo", "qux"))
+
+ path = ObjDirPath(ctxt, "!../bar/qux")
+ self.assertEqual(path, "!../bar/qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topobjdir, "bar", "qux"))
+
+ path = ObjDirPath(ctxt, "!/qux/qux")
+ self.assertEqual(path, "!/qux/qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ with self.assertRaises(ValueError):
+ path = ObjDirPath(ctxt, "../bar/qux")
+
+ with self.assertRaises(ValueError):
+ path = ObjDirPath(ctxt, "/qux/qux")
+
+ path = ObjDirPath(path)
+ self.assertIsInstance(path, ObjDirPath)
+ self.assertEqual(path, "!/qux/qux")
+ self.assertEqual(path.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ path = Path(path)
+ self.assertIsInstance(path, ObjDirPath)
+
+ def test_absolute_path(self):
+ config = self.config
+ ctxt = Context(config=config)
+ ctxt.push_source(mozpath.join(config.topsrcdir, "foo", "moz.build"))
+
+ path = AbsolutePath(ctxt, "%/qux")
+ self.assertEqual(path, "%/qux")
+ self.assertEqual(path.full_path, "/qux")
+
+ with self.assertRaises(ValueError):
+ path = AbsolutePath(ctxt, "%qux")
+
+ def test_path_with_mixed_contexts(self):
+ config = self.config
+ ctxt1 = Context(config=config)
+ ctxt1.push_source(mozpath.join(config.topsrcdir, "foo", "moz.build"))
+ ctxt2 = Context(config=config)
+ ctxt2.push_source(mozpath.join(config.topsrcdir, "bar", "moz.build"))
+
+ path1 = Path(ctxt1, "qux")
+ path2 = Path(ctxt2, path1)
+ self.assertEqual(path2, path1)
+ self.assertEqual(path2, "qux")
+ self.assertEqual(path2.context, ctxt1)
+ self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir, "foo", "qux"))
+
+ path1 = Path(ctxt1, "../bar/qux")
+ path2 = Path(ctxt2, path1)
+ self.assertEqual(path2, path1)
+ self.assertEqual(path2, "../bar/qux")
+ self.assertEqual(path2.context, ctxt1)
+ self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir, "bar", "qux"))
+
+ path1 = Path(ctxt1, "/qux/qux")
+ path2 = Path(ctxt2, path1)
+ self.assertEqual(path2, path1)
+ self.assertEqual(path2, "/qux/qux")
+ self.assertEqual(path2.context, ctxt1)
+ self.assertEqual(path2.full_path, mozpath.join(config.topsrcdir, "qux", "qux"))
+
+ path1 = Path(ctxt1, "!qux")
+ path2 = Path(ctxt2, path1)
+ self.assertEqual(path2, path1)
+ self.assertEqual(path2, "!qux")
+ self.assertEqual(path2.context, ctxt1)
+ self.assertEqual(path2.full_path, mozpath.join(config.topobjdir, "foo", "qux"))
+
+ path1 = Path(ctxt1, "!../bar/qux")
+ path2 = Path(ctxt2, path1)
+ self.assertEqual(path2, path1)
+ self.assertEqual(path2, "!../bar/qux")
+ self.assertEqual(path2.context, ctxt1)
+ self.assertEqual(path2.full_path, mozpath.join(config.topobjdir, "bar", "qux"))
+
+ path1 = Path(ctxt1, "!/qux/qux")
+ path2 = Path(ctxt2, path1)
+ self.assertEqual(path2, path1)
+ self.assertEqual(path2, "!/qux/qux")
+ self.assertEqual(path2.context, ctxt1)
+ self.assertEqual(path2.full_path, mozpath.join(config.topobjdir, "qux", "qux"))
+
+ def test_path_typed_list(self):
+ config = self.config
+ ctxt1 = Context(config=config)
+ ctxt1.push_source(mozpath.join(config.topsrcdir, "foo", "moz.build"))
+ ctxt2 = Context(config=config)
+ ctxt2.push_source(mozpath.join(config.topsrcdir, "bar", "moz.build"))
+
+ paths = [
+ "!../bar/qux",
+ "!/qux/qux",
+ "!qux",
+ "../bar/qux",
+ "/qux/qux",
+ "qux",
+ ]
+
+ MyList = ContextDerivedTypedList(Path)
+ l = MyList(ctxt1)
+ l += paths
+
+ for p_str, p_path in zip(paths, l):
+ self.assertEqual(p_str, p_path)
+ self.assertEqual(p_path, Path(ctxt1, p_str))
+ self.assertEqual(
+ p_path.join("foo"), Path(ctxt1, mozpath.join(p_str, "foo"))
+ )
+
+ l2 = MyList(ctxt2)
+ l2 += paths
+
+ for p_str, p_path in zip(paths, l2):
+ self.assertEqual(p_str, p_path)
+ self.assertEqual(p_path, Path(ctxt2, p_str))
+
+ # Assigning with Paths from another context doesn't rebase them
+ l2 = MyList(ctxt2)
+ l2 += l
+
+ for p_str, p_path in zip(paths, l2):
+ self.assertEqual(p_str, p_path)
+ self.assertEqual(p_path, Path(ctxt1, p_str))
+
+ MyListWithFlags = ContextDerivedTypedListWithItems(
+ Path,
+ StrictOrderingOnAppendListWithFlagsFactory(
+ {
+ "foo": bool,
+ }
+ ),
+ )
+ l = MyListWithFlags(ctxt1)
+ l += paths
+
+ for p in paths:
+ l[p].foo = True
+
+ for p_str, p_path in zip(paths, l):
+ self.assertEqual(p_str, p_path)
+ self.assertEqual(p_path, Path(ctxt1, p_str))
+ self.assertEqual(l[p_str].foo, True)
+ self.assertEqual(l[p_path].foo, True)
+
+ def test_path_typed_hierarchy_list(self):
+ config = self.config
+ ctxt1 = Context(config=config)
+ ctxt1.push_source(mozpath.join(config.topsrcdir, "foo", "moz.build"))
+ ctxt2 = Context(config=config)
+ ctxt2.push_source(mozpath.join(config.topsrcdir, "bar", "moz.build"))
+
+ paths = [
+ "!../bar/qux",
+ "!/qux/qux",
+ "!qux",
+ "../bar/qux",
+ "/qux/qux",
+ "qux",
+ ]
+
+ MyList = ContextDerivedTypedHierarchicalStringList(Path)
+ l = MyList(ctxt1)
+ l += paths
+ l.subdir += paths
+
+ for _, files in l.walk():
+ for p_str, p_path in zip(paths, files):
+ self.assertEqual(p_str, p_path)
+ self.assertEqual(p_path, Path(ctxt1, p_str))
+ self.assertEqual(
+ p_path.join("foo"), Path(ctxt1, mozpath.join(p_str, "foo"))
+ )
+
+ l2 = MyList(ctxt2)
+ l2 += paths
+ l2.subdir += paths
+
+ for _, files in l2.walk():
+ for p_str, p_path in zip(paths, files):
+ self.assertEqual(p_str, p_path)
+ self.assertEqual(p_path, Path(ctxt2, p_str))
+
+ # Assigning with Paths from another context doesn't rebase them
+ l2 = MyList(ctxt2)
+ l2 += l
+
+ for _, files in l2.walk():
+ for p_str, p_path in zip(paths, files):
+ self.assertEqual(p_str, p_path)
+ self.assertEqual(p_path, Path(ctxt1, p_str))
+
+
+class TestTypedRecord(unittest.TestCase):
+ def test_fields(self):
+ T = ContextDerivedTypedRecord(("field1", six.text_type), ("field2", list))
+ inst = T(None)
+ self.assertEqual(inst.field1, "")
+ self.assertEqual(inst.field2, [])
+
+ inst.field1 = "foo"
+ inst.field2 += ["bar"]
+
+ self.assertEqual(inst.field1, "foo")
+ self.assertEqual(inst.field2, ["bar"])
+
+ with self.assertRaises(AttributeError):
+ inst.field3 = []
+
+ def test_coercion(self):
+ T = ContextDerivedTypedRecord(("field1", six.text_type), ("field2", list))
+ inst = T(None)
+ inst.field1 = 3
+ inst.field2 += ("bar",)
+ self.assertEqual(inst.field1, "3")
+ self.assertEqual(inst.field2, ["bar"])
+
+ with self.assertRaises(TypeError):
+ inst.field2 = object()
+
+
+class TestFiles(unittest.TestCase):
+ def test_aggregate_empty(self):
+ c = Context({})
+
+ files = {"moz.build": Files(c, "**")}
+
+ self.assertEqual(
+ Files.aggregate(files),
+ {
+ "bug_component_counts": [],
+ "recommended_bug_component": None,
+ },
+ )
+
+ def test_single_bug_component(self):
+ c = Context({})
+ f = Files(c, "**")
+ f["BUG_COMPONENT"] = ("Product1", "Component1")
+
+ files = {"moz.build": f}
+ self.assertEqual(
+ Files.aggregate(files),
+ {
+ "bug_component_counts": [(("Product1", "Component1"), 1)],
+ "recommended_bug_component": ("Product1", "Component1"),
+ },
+ )
+
+ def test_multiple_bug_components(self):
+ c = Context({})
+ f1 = Files(c, "**")
+ f1["BUG_COMPONENT"] = ("Product1", "Component1")
+
+ f2 = Files(c, "**")
+ f2["BUG_COMPONENT"] = ("Product2", "Component2")
+
+ files = {"a": f1, "b": f2, "c": f1}
+ self.assertEqual(
+ Files.aggregate(files),
+ {
+ "bug_component_counts": [
+ (("Product1", "Component1"), 2),
+ (("Product2", "Component2"), 1),
+ ],
+ "recommended_bug_component": ("Product1", "Component1"),
+ },
+ )
+
+ def test_no_recommended_bug_component(self):
+ """If there is no clear count winner, we don't recommend a bug component."""
+ c = Context({})
+ f1 = Files(c, "**")
+ f1["BUG_COMPONENT"] = ("Product1", "Component1")
+
+ f2 = Files(c, "**")
+ f2["BUG_COMPONENT"] = ("Product2", "Component2")
+
+ files = {"a": f1, "b": f2}
+ self.assertEqual(
+ Files.aggregate(files),
+ {
+ "bug_component_counts": [
+ (("Product1", "Component1"), 1),
+ (("Product2", "Component2"), 1),
+ ],
+ "recommended_bug_component": None,
+ },
+ )
+
+ def test_multiple_patterns(self):
+ c = Context({})
+ f1 = Files(c, "a/**")
+ f1["BUG_COMPONENT"] = ("Product1", "Component1")
+ f2 = Files(c, "b/**", "a/bar")
+ f2["BUG_COMPONENT"] = ("Product2", "Component2")
+
+ files = {"a/foo": f1, "a/bar": f2, "b/foo": f2}
+ self.assertEqual(
+ Files.aggregate(files),
+ {
+ "bug_component_counts": [
+ (("Product2", "Component2"), 2),
+ (("Product1", "Component1"), 1),
+ ],
+ "recommended_bug_component": ("Product2", "Component2"),
+ },
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/frontend/test_emitter.py b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
new file mode 100644
index 0000000000..4bbab3942a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -0,0 +1,1877 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+
+import mozpack.path as mozpath
+import six
+from mozunit import main
+
+from mozbuild.frontend.context import ObjDirPath, Path
+from mozbuild.frontend.data import (
+ ComputedFlags,
+ ConfigFileSubstitution,
+ Defines,
+ DirectoryTraversal,
+ Exports,
+ FinalTargetPreprocessedFiles,
+ GeneratedFile,
+ HostProgram,
+ HostRustLibrary,
+ HostRustProgram,
+ HostSources,
+ IPDLCollection,
+ JARManifest,
+ LocalInclude,
+ LocalizedFiles,
+ LocalizedPreprocessedFiles,
+ Program,
+ RustLibrary,
+ RustProgram,
+ SharedLibrary,
+ SimpleProgram,
+ Sources,
+ StaticLibrary,
+ TestHarnessFiles,
+ TestManifest,
+ UnifiedSources,
+ VariablePassthru,
+ WasmSources,
+)
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import (
+ BuildReader,
+ BuildReaderError,
+ SandboxValidationError,
+)
+from mozbuild.test.common import MockConfig
+
+data_path = mozpath.abspath(mozpath.dirname(__file__))
+data_path = mozpath.join(data_path, "data")
+
+
+class TestEmitterBasic(unittest.TestCase):
+ def setUp(self):
+ self._old_env = dict(os.environ)
+ os.environ.pop("MOZ_OBJDIR", None)
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ def reader(self, name, enable_tests=False, extra_substs=None):
+ substs = dict(
+ ENABLE_TESTS="1" if enable_tests else "",
+ BIN_SUFFIX=".prog",
+ HOST_BIN_SUFFIX=".hostprog",
+ OS_TARGET="WINNT",
+ COMPILE_ENVIRONMENT="1",
+ STL_FLAGS=["-I/path/to/topobjdir/dist/stl_wrappers"],
+ VISIBILITY_FLAGS=["-include", "$(topsrcdir)/config/gcc_hidden.h"],
+ OBJ_SUFFIX="obj",
+ WASM_OBJ_SUFFIX="wasm",
+ WASM_CFLAGS=["-foo"],
+ )
+ if extra_substs:
+ substs.update(extra_substs)
+ config = MockConfig(mozpath.join(data_path, name), extra_substs=substs)
+
+ return BuildReader(config)
+
+ def read_topsrcdir(self, reader, filter_common=True):
+ emitter = TreeMetadataEmitter(reader.config)
+ objs = list(emitter.emit(reader.read_topsrcdir()))
+ self.assertGreater(len(objs), 0)
+
+ filtered = []
+ for obj in objs:
+ if filter_common and isinstance(obj, DirectoryTraversal):
+ continue
+
+ filtered.append(obj)
+
+ return filtered
+
+ def test_dirs_traversal_simple(self):
+ reader = self.reader("traversal-simple")
+ objs = self.read_topsrcdir(reader, filter_common=False)
+ self.assertEqual(len(objs), 4)
+
+ for o in objs:
+ self.assertIsInstance(o, DirectoryTraversal)
+ self.assertTrue(os.path.isabs(o.context_main_path))
+ self.assertEqual(len(o.context_all_paths), 1)
+
+ reldirs = [o.relsrcdir for o in objs]
+ self.assertEqual(reldirs, ["", "foo", "foo/biz", "bar"])
+
+ dirs = [[d.full_path for d in o.dirs] for o in objs]
+ self.assertEqual(
+ dirs,
+ [
+ [
+ mozpath.join(reader.config.topsrcdir, "foo"),
+ mozpath.join(reader.config.topsrcdir, "bar"),
+ ],
+ [mozpath.join(reader.config.topsrcdir, "foo", "biz")],
+ [],
+ [],
+ ],
+ )
+
+ def test_traversal_all_vars(self):
+ reader = self.reader("traversal-all-vars")
+ objs = self.read_topsrcdir(reader, filter_common=False)
+ self.assertEqual(len(objs), 2)
+
+ for o in objs:
+ self.assertIsInstance(o, DirectoryTraversal)
+
+ reldirs = set([o.relsrcdir for o in objs])
+ self.assertEqual(reldirs, set(["", "regular"]))
+
+ for o in objs:
+ reldir = o.relsrcdir
+
+ if reldir == "":
+ self.assertEqual(
+ [d.full_path for d in o.dirs],
+ [mozpath.join(reader.config.topsrcdir, "regular")],
+ )
+
+ def test_traversal_all_vars_enable_tests(self):
+ reader = self.reader("traversal-all-vars", enable_tests=True)
+ objs = self.read_topsrcdir(reader, filter_common=False)
+ self.assertEqual(len(objs), 3)
+
+ for o in objs:
+ self.assertIsInstance(o, DirectoryTraversal)
+
+ reldirs = set([o.relsrcdir for o in objs])
+ self.assertEqual(reldirs, set(["", "regular", "test"]))
+
+ for o in objs:
+ reldir = o.relsrcdir
+
+ if reldir == "":
+ self.assertEqual(
+ [d.full_path for d in o.dirs],
+ [
+ mozpath.join(reader.config.topsrcdir, "regular"),
+ mozpath.join(reader.config.topsrcdir, "test"),
+ ],
+ )
+
+ def test_config_file_substitution(self):
+ reader = self.reader("config-file-substitution")
+ objs = self.read_topsrcdir(reader)
+ self.assertEqual(len(objs), 2)
+
+ self.assertIsInstance(objs[0], ConfigFileSubstitution)
+ self.assertIsInstance(objs[1], ConfigFileSubstitution)
+
+ topobjdir = mozpath.abspath(reader.config.topobjdir)
+ self.assertEqual(objs[0].relpath, "foo")
+ self.assertEqual(
+ mozpath.normpath(objs[0].output_path),
+ mozpath.normpath(mozpath.join(topobjdir, "foo")),
+ )
+ self.assertEqual(
+ mozpath.normpath(objs[1].output_path),
+ mozpath.normpath(mozpath.join(topobjdir, "bar")),
+ )
+
+ def test_variable_passthru(self):
+ reader = self.reader("variable-passthru")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], VariablePassthru)
+
+ wanted = {
+ "NO_DIST_INSTALL": True,
+ "RCFILE": "foo.rc",
+ "RCINCLUDE": "bar.rc",
+ "WIN32_EXE_LDFLAGS": ["-subsystem:console"],
+ }
+
+ variables = objs[0].variables
+ maxDiff = self.maxDiff
+ self.maxDiff = None
+ self.assertEqual(wanted, variables)
+ self.maxDiff = maxDiff
+
+ def test_compile_flags(self):
+ reader = self.reader(
+ "compile-flags", extra_substs={"WARNINGS_AS_ERRORS": "-Werror"}
+ )
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["STL"], reader.config.substs["STL_FLAGS"])
+ self.assertEqual(
+ flags.flags["VISIBILITY"], reader.config.substs["VISIBILITY_FLAGS"]
+ )
+ self.assertEqual(flags.flags["WARNINGS_AS_ERRORS"], ["-Werror"])
+ self.assertEqual(flags.flags["MOZBUILD_CFLAGS"], ["-Wall", "-funroll-loops"])
+ self.assertEqual(flags.flags["MOZBUILD_CXXFLAGS"], ["-funroll-loops", "-Wall"])
+
+ def test_asflags(self):
+ reader = self.reader("asflags", extra_substs={"ASFLAGS": ["-safeseh"]})
+ as_sources, sources, ldflags, lib, flags, asflags = self.read_topsrcdir(reader)
+ self.assertIsInstance(asflags, ComputedFlags)
+ self.assertEqual(asflags.flags["OS"], reader.config.substs["ASFLAGS"])
+ self.assertEqual(asflags.flags["MOZBUILD"], ["-no-integrated-as"])
+
+ def test_debug_flags(self):
+ reader = self.reader(
+ "compile-flags",
+ extra_substs={"MOZ_DEBUG_FLAGS": "-g", "MOZ_DEBUG_SYMBOLS": "1"},
+ )
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["DEBUG"], ["-g"])
+
+ def test_disable_debug_flags(self):
+ reader = self.reader(
+ "compile-flags",
+ extra_substs={"MOZ_DEBUG_FLAGS": "-g", "MOZ_DEBUG_SYMBOLS": ""},
+ )
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["DEBUG"], [])
+
+ def test_link_flags(self):
+ reader = self.reader(
+ "link-flags",
+ extra_substs={
+ "OS_LDFLAGS": ["-Wl,rpath-link=/usr/lib"],
+ "MOZ_OPTIMIZE": "",
+ "MOZ_OPTIMIZE_LDFLAGS": ["-Wl,-dead_strip"],
+ "MOZ_DEBUG_LDFLAGS": ["-framework ExceptionHandling"],
+ },
+ )
+ sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertEqual(ldflags.flags["OS"], reader.config.substs["OS_LDFLAGS"])
+ self.assertEqual(
+ ldflags.flags["MOZBUILD"], ["-Wl,-U_foo", "-framework Foo", "-x"]
+ )
+ self.assertEqual(ldflags.flags["OPTIMIZE"], [])
+
+ def test_debug_ldflags(self):
+ reader = self.reader(
+ "link-flags",
+ extra_substs={
+ "MOZ_DEBUG_SYMBOLS": "1",
+ "MOZ_DEBUG_LDFLAGS": ["-framework ExceptionHandling"],
+ },
+ )
+ sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertEqual(ldflags.flags["OS"], reader.config.substs["MOZ_DEBUG_LDFLAGS"])
+
+ def test_windows_opt_link_flags(self):
+ reader = self.reader(
+ "link-flags",
+ extra_substs={
+ "OS_ARCH": "WINNT",
+ "GNU_CC": "",
+ "MOZ_OPTIMIZE": "1",
+ "MOZ_DEBUG_LDFLAGS": ["-DEBUG"],
+ "MOZ_DEBUG_SYMBOLS": "1",
+ "MOZ_OPTIMIZE_FLAGS": [],
+ "MOZ_OPTIMIZE_LDFLAGS": [],
+ },
+ )
+ sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIn("-DEBUG", ldflags.flags["OS"])
+ self.assertIn("-OPT:REF,ICF", ldflags.flags["OS"])
+
+ def test_windows_dmd_link_flags(self):
+ reader = self.reader(
+ "link-flags",
+ extra_substs={
+ "OS_ARCH": "WINNT",
+ "GNU_CC": "",
+ "MOZ_DMD": "1",
+ "MOZ_DEBUG_LDFLAGS": ["-DEBUG"],
+ "MOZ_DEBUG_SYMBOLS": "1",
+ "MOZ_OPTIMIZE": "1",
+ "MOZ_OPTIMIZE_FLAGS": [],
+ },
+ )
+ sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertEqual(ldflags.flags["OS"], ["-DEBUG", "-OPT:REF,ICF"])
+
+ def test_host_compile_flags(self):
+ reader = self.reader(
+ "host-compile-flags",
+ extra_substs={
+ "HOST_CXXFLAGS": ["-Wall", "-Werror"],
+ "HOST_CFLAGS": ["-Werror", "-Wall"],
+ },
+ )
+ sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(
+ flags.flags["HOST_CXXFLAGS"], reader.config.substs["HOST_CXXFLAGS"]
+ )
+ self.assertEqual(
+ flags.flags["HOST_CFLAGS"], reader.config.substs["HOST_CFLAGS"]
+ )
+ self.assertEqual(
+ set(flags.flags["HOST_DEFINES"]),
+ set(["-DFOO", '-DBAZ="abcd"', "-UQUX", "-DBAR=7", "-DVALUE=xyz"]),
+ )
+ self.assertEqual(
+ flags.flags["MOZBUILD_HOST_CFLAGS"], ["-funroll-loops", "-host-arg"]
+ )
+ self.assertEqual(flags.flags["MOZBUILD_HOST_CXXFLAGS"], [])
+
+ def test_host_no_optimize_flags(self):
+ reader = self.reader(
+ "host-compile-flags",
+ extra_substs={"MOZ_OPTIMIZE": "", "MOZ_OPTIMIZE_FLAGS": ["-O2"]},
+ )
+ sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["HOST_OPTIMIZE"], [])
+
+ def test_host_optimize_flags(self):
+ reader = self.reader(
+ "host-compile-flags",
+ extra_substs={"MOZ_OPTIMIZE": "1", "MOZ_OPTIMIZE_FLAGS": ["-O2"]},
+ )
+ sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["HOST_OPTIMIZE"], ["-O2"])
+
+ def test_cross_optimize_flags(self):
+ reader = self.reader(
+ "host-compile-flags",
+ extra_substs={
+ "MOZ_OPTIMIZE": "1",
+ "MOZ_OPTIMIZE_FLAGS": ["-O2"],
+ "HOST_OPTIMIZE_FLAGS": ["-O3"],
+ "CROSS_COMPILE": "1",
+ },
+ )
+ sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["HOST_OPTIMIZE"], ["-O3"])
+
+ def test_host_rtl_flag(self):
+ reader = self.reader(
+ "host-compile-flags", extra_substs={"OS_ARCH": "WINNT", "MOZ_DEBUG": "1"}
+ )
+ sources, ldflags, flags, lib, target_flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["RTL"], ["-MDd"])
+
+ def test_compile_flags_validation(self):
+ reader = self.reader("compile-flags-field-validation")
+
+ with six.assertRaisesRegex(self, BuildReaderError, "Invalid value."):
+ self.read_topsrcdir(reader)
+
+ reader = self.reader("compile-flags-type-validation")
+ with six.assertRaisesRegex(
+ self, BuildReaderError, "A list of strings must be provided"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_compile_flags_templates(self):
+ reader = self.reader(
+ "compile-flags-templates",
+ extra_substs={
+ "NSPR_CFLAGS": ["-I/nspr/path"],
+ "NSS_CFLAGS": ["-I/nss/path"],
+ "MOZ_JPEG_CFLAGS": ["-I/jpeg/path"],
+ "MOZ_PNG_CFLAGS": ["-I/png/path"],
+ "MOZ_ZLIB_CFLAGS": ["-I/zlib/path"],
+ "MOZ_PIXMAN_CFLAGS": ["-I/pixman/path"],
+ },
+ )
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["STL"], [])
+ self.assertEqual(flags.flags["VISIBILITY"], [])
+ self.assertEqual(
+ flags.flags["OS_INCLUDES"],
+ [
+ "-I/nspr/path",
+ "-I/nss/path",
+ "-I/jpeg/path",
+ "-I/png/path",
+ "-I/zlib/path",
+ "-I/pixman/path",
+ ],
+ )
+
+ def test_disable_stl_wrapping(self):
+ reader = self.reader("disable-stl-wrapping")
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["STL"], [])
+
+ def test_visibility_flags(self):
+ reader = self.reader("visibility-flags")
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(flags.flags["VISIBILITY"], [])
+
+ def test_defines_in_flags(self):
+ reader = self.reader("compile-defines")
+ defines, sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(
+ flags.flags["LIBRARY_DEFINES"], ["-DMOZ_LIBRARY_DEFINE=MOZ_TEST"]
+ )
+ self.assertEqual(flags.flags["DEFINES"], ["-DMOZ_TEST_DEFINE"])
+
+ def test_resolved_flags_error(self):
+ reader = self.reader("resolved-flags-error")
+ with six.assertRaisesRegex(
+ self,
+ BuildReaderError,
+ "`DEFINES` may not be set in COMPILE_FLAGS from moz.build",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_includes_in_flags(self):
+ reader = self.reader("compile-includes")
+ defines, sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(
+ flags.flags["BASE_INCLUDES"],
+ ["-I%s" % reader.config.topsrcdir, "-I%s" % reader.config.topobjdir],
+ )
+ self.assertEqual(
+ flags.flags["EXTRA_INCLUDES"],
+ ["-I%s/dist/include" % reader.config.topobjdir],
+ )
+ self.assertEqual(
+ flags.flags["LOCAL_INCLUDES"], ["-I%s/subdir" % reader.config.topsrcdir]
+ )
+
+ def test_allow_compiler_warnings(self):
+ reader = self.reader(
+ "allow-compiler-warnings", extra_substs={"WARNINGS_AS_ERRORS": "-Werror"}
+ )
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertEqual(flags.flags["WARNINGS_AS_ERRORS"], [])
+
+ def test_disable_compiler_warnings(self):
+ reader = self.reader(
+ "disable-compiler-warnings", extra_substs={"WARNINGS_CFLAGS": "-Wall"}
+ )
+ sources, ldflags, lib, flags = self.read_topsrcdir(reader)
+ self.assertEqual(flags.flags["WARNINGS_CFLAGS"], [])
+
+ def test_use_nasm(self):
+ # When nasm is not available, this should raise.
+ reader = self.reader("use-nasm")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "nasm is not available"
+ ):
+ self.read_topsrcdir(reader)
+
+ # When nasm is available, this should work.
+ reader = self.reader(
+ "use-nasm", extra_substs=dict(NASM="nasm", NASM_ASFLAGS="-foo")
+ )
+
+ sources, passthru, ldflags, lib, flags, asflags = self.read_topsrcdir(reader)
+
+ self.assertIsInstance(passthru, VariablePassthru)
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertIsInstance(asflags, ComputedFlags)
+
+ self.assertEqual(asflags.flags["OS"], reader.config.substs["NASM_ASFLAGS"])
+
+ maxDiff = self.maxDiff
+ self.maxDiff = None
+ self.assertEqual(
+ passthru.variables,
+ {"AS": "nasm", "AS_DASH_C_FLAG": "", "ASOUTOPTION": "-o "},
+ )
+ self.maxDiff = maxDiff
+
+ def test_generated_files(self):
+ reader = self.reader("generated-files")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 3)
+ for o in objs:
+ self.assertIsInstance(o, GeneratedFile)
+ self.assertFalse(o.localized)
+ self.assertFalse(o.force)
+
+ expected = ["bar.c", "foo.c", ("xpidllex.py", "xpidlyacc.py")]
+ for o, f in zip(objs, expected):
+ expected_filename = f if isinstance(f, tuple) else (f,)
+ self.assertEqual(o.outputs, expected_filename)
+ self.assertEqual(o.script, None)
+ self.assertEqual(o.method, None)
+ self.assertEqual(o.inputs, [])
+
+ def test_generated_files_force(self):
+ reader = self.reader("generated-files-force")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 3)
+ for o in objs:
+ self.assertIsInstance(o, GeneratedFile)
+ self.assertEqual(o.force, "bar.c" in o.outputs)
+
+ def test_localized_generated_files(self):
+ reader = self.reader("localized-generated-files")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 2)
+ for o in objs:
+ self.assertIsInstance(o, GeneratedFile)
+ self.assertTrue(o.localized)
+
+ expected = ["abc.ini", ("bar", "baz")]
+ for o, f in zip(objs, expected):
+ expected_filename = f if isinstance(f, tuple) else (f,)
+ self.assertEqual(o.outputs, expected_filename)
+ self.assertEqual(o.script, None)
+ self.assertEqual(o.method, None)
+ self.assertEqual(o.inputs, [])
+
+ def test_localized_generated_files_force(self):
+ reader = self.reader("localized-generated-files-force")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 2)
+ for o in objs:
+ self.assertIsInstance(o, GeneratedFile)
+ self.assertTrue(o.localized)
+ self.assertEqual(o.force, "abc.ini" in o.outputs)
+
+ def test_localized_files_from_generated(self):
+ """Test that using LOCALIZED_GENERATED_FILES and then putting the output in
+ LOCALIZED_FILES as an objdir path works.
+ """
+ reader = self.reader("localized-files-from-generated")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 2)
+ self.assertIsInstance(objs[0], GeneratedFile)
+ self.assertIsInstance(objs[1], LocalizedFiles)
+
+ def test_localized_files_not_localized_generated(self):
+ """Test that using GENERATED_FILES and then putting the output in
+ LOCALIZED_FILES as an objdir path produces an error.
+ """
+ reader = self.reader("localized-files-not-localized-generated")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Objdir file listed in LOCALIZED_FILES not in LOCALIZED_GENERATED_FILES:",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_localized_generated_files_final_target_files(self):
+ """Test that using LOCALIZED_GENERATED_FILES and then putting the output in
+ FINAL_TARGET_FILES as an objdir path produces an error.
+ """
+ reader = self.reader("localized-generated-files-final-target-files")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Outputs of LOCALIZED_GENERATED_FILES cannot be used in FINAL_TARGET_FILES:",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_generated_files_method_names(self):
+ reader = self.reader("generated-files-method-names")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 2)
+ for o in objs:
+ self.assertIsInstance(o, GeneratedFile)
+
+ expected = ["bar.c", "foo.c"]
+ expected_method_names = ["make_bar", "main"]
+ for o, expected_filename, expected_method in zip(
+ objs, expected, expected_method_names
+ ):
+ self.assertEqual(o.outputs, (expected_filename,))
+ self.assertEqual(o.method, expected_method)
+ self.assertEqual(o.inputs, [])
+
+ def test_generated_files_absolute_script(self):
+ reader = self.reader("generated-files-absolute-script")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+
+ o = objs[0]
+ self.assertIsInstance(o, GeneratedFile)
+ self.assertEqual(o.outputs, ("bar.c",))
+ self.assertRegex(o.script, "script.py$")
+ self.assertEqual(o.method, "make_bar")
+ self.assertEqual(o.inputs, [])
+
+ def test_generated_files_no_script(self):
+ reader = self.reader("generated-files-no-script")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "Script for generating bar.c does not exist"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_generated_files_no_inputs(self):
+ reader = self.reader("generated-files-no-inputs")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "Input for generating foo.c does not exist"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_generated_files_no_python_script(self):
+ reader = self.reader("generated-files-no-python-script")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Script for generating bar.c does not end in .py",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_exports(self):
+ reader = self.reader("exports")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], Exports)
+
+ expected = [
+ ("", ["foo.h", "bar.h", "baz.h"]),
+ ("mozilla", ["mozilla1.h", "mozilla2.h"]),
+ ("mozilla/dom", ["dom1.h", "dom2.h", "dom3.h"]),
+ ("mozilla/gfx", ["gfx.h"]),
+ ("nspr/private", ["pprio.h", "pprthred.h"]),
+ ("vpx", ["mem.h", "mem2.h"]),
+ ]
+ for (expect_path, expect_headers), (actual_path, actual_headers) in zip(
+ expected, [(path, list(seq)) for path, seq in objs[0].files.walk()]
+ ):
+ self.assertEqual(expect_path, actual_path)
+ self.assertEqual(expect_headers, actual_headers)
+
+ def test_exports_missing(self):
+ """
+ Missing files in EXPORTS is an error.
+ """
+ reader = self.reader("exports-missing")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "File listed in EXPORTS does not exist:"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_exports_missing_generated(self):
+ """
+ An objdir file in EXPORTS that is not in GENERATED_FILES is an error.
+ """
+ reader = self.reader("exports-missing-generated")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Objdir file listed in EXPORTS not in GENERATED_FILES:",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_exports_generated(self):
+ reader = self.reader("exports-generated")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 2)
+ self.assertIsInstance(objs[0], GeneratedFile)
+ self.assertIsInstance(objs[1], Exports)
+ exports = [(path, list(seq)) for path, seq in objs[1].files.walk()]
+ self.assertEqual(
+ exports, [("", ["foo.h"]), ("mozilla", ["mozilla1.h", "!mozilla2.h"])]
+ )
+ path, files = exports[1]
+ self.assertIsInstance(files[1], ObjDirPath)
+
+ def test_test_harness_files(self):
+ reader = self.reader("test-harness-files")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], TestHarnessFiles)
+
+ expected = {
+ "mochitest": ["runtests.py", "utils.py"],
+ "testing/mochitest": ["mochitest.py", "mochitest.ini"],
+ }
+
+ for path, strings in objs[0].files.walk():
+ self.assertTrue(path in expected)
+ basenames = sorted(mozpath.basename(s) for s in strings)
+ self.assertEqual(sorted(expected[path]), basenames)
+
+ def test_test_harness_files_root(self):
+ reader = self.reader("test-harness-files-root")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Cannot install files to the root of TEST_HARNESS_FILES",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_program(self):
+ reader = self.reader("program")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 6)
+ self.assertIsInstance(objs[0], Sources)
+ self.assertIsInstance(objs[1], ComputedFlags)
+ self.assertIsInstance(objs[2], ComputedFlags)
+ self.assertIsInstance(objs[3], Program)
+ self.assertIsInstance(objs[4], SimpleProgram)
+ self.assertIsInstance(objs[5], SimpleProgram)
+
+ self.assertEqual(objs[3].program, "test_program.prog")
+ self.assertEqual(objs[4].program, "test_program1.prog")
+ self.assertEqual(objs[5].program, "test_program2.prog")
+
+ self.assertEqual(objs[3].name, "test_program.prog")
+ self.assertEqual(objs[4].name, "test_program1.prog")
+ self.assertEqual(objs[5].name, "test_program2.prog")
+
+ self.assertEqual(
+ objs[4].objs,
+ [
+ mozpath.join(
+ reader.config.topobjdir,
+ "test_program1.%s" % reader.config.substs["OBJ_SUFFIX"],
+ )
+ ],
+ )
+ self.assertEqual(
+ objs[5].objs,
+ [
+ mozpath.join(
+ reader.config.topobjdir,
+ "test_program2.%s" % reader.config.substs["OBJ_SUFFIX"],
+ )
+ ],
+ )
+
+ def test_program_paths(self):
+ """Various moz.build settings that change the destination of PROGRAM should be
+ accurately reflected in Program.output_path."""
+ reader = self.reader("program-paths")
+ objs = self.read_topsrcdir(reader)
+ prog_paths = [o.output_path for o in objs if isinstance(o, Program)]
+ self.assertEqual(
+ prog_paths,
+ [
+ "!/dist/bin/dist-bin.prog",
+ "!/dist/bin/foo/dist-subdir.prog",
+ "!/final/target/final-target.prog",
+ "!not-installed.prog",
+ ],
+ )
+
+ def test_host_program_paths(self):
+ """The destination of a HOST_PROGRAM (almost always dist/host/bin)
+ should be accurately reflected in Program.output_path."""
+ reader = self.reader("host-program-paths")
+ objs = self.read_topsrcdir(reader)
+ prog_paths = [o.output_path for o in objs if isinstance(o, HostProgram)]
+ self.assertEqual(
+ prog_paths,
+ [
+ "!/dist/host/bin/final-target.hostprog",
+ "!/dist/host/bin/dist-host-bin.hostprog",
+ "!not-installed.hostprog",
+ ],
+ )
+
+ def test_test_manifest_missing_manifest(self):
+ """A missing manifest file should result in an error."""
+ reader = self.reader("test-manifest-missing-manifest")
+
+ with six.assertRaisesRegex(self, BuildReaderError, "Missing files"):
+ self.read_topsrcdir(reader)
+
+ def test_empty_test_manifest_rejected(self):
+ """A test manifest without any entries is rejected."""
+ reader = self.reader("test-manifest-empty")
+
+ with six.assertRaisesRegex(self, SandboxValidationError, "Empty test manifest"):
+ self.read_topsrcdir(reader)
+
+ def test_test_manifest_just_support_files(self):
+ """A test manifest with no tests but support-files is not supported."""
+ reader = self.reader("test-manifest-just-support")
+
+ with six.assertRaisesRegex(self, SandboxValidationError, "Empty test manifest"):
+ self.read_topsrcdir(reader)
+
+ def test_test_manifest_dupe_support_files(self):
+ """A test manifest with dupe support-files in a single test is not
+ supported.
+ """
+ reader = self.reader("test-manifest-dupes")
+
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "bar.js appears multiple times "
+ "in a test manifest under a support-files field, please omit the duplicate entry.",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_test_manifest_absolute_support_files(self):
+ """Support files starting with '/' are placed relative to the install root"""
+ reader = self.reader("test-manifest-absolute-support")
+
+ objs = self.read_topsrcdir(reader)
+ self.assertEqual(len(objs), 1)
+ o = objs[0]
+ self.assertEqual(len(o.installs), 3)
+ expected = [
+ mozpath.normpath(mozpath.join(o.install_prefix, "../.well-known/foo.txt")),
+ mozpath.join(o.install_prefix, "absolute-support.ini"),
+ mozpath.join(o.install_prefix, "test_file.js"),
+ ]
+ paths = sorted([v[0] for v in o.installs.values()])
+ self.assertEqual(paths, expected)
+
+ @unittest.skip("Bug 1304316 - Items in the second set but not the first")
+ def test_test_manifest_shared_support_files(self):
+ """Support files starting with '!' are given separate treatment, so their
+ installation can be resolved when running tests.
+ """
+ reader = self.reader("test-manifest-shared-support")
+ supported, child = self.read_topsrcdir(reader)
+
+ expected_deferred_installs = {
+ "!/child/test_sub.js",
+ "!/child/another-file.sjs",
+ "!/child/data/**",
+ }
+
+ self.assertEqual(len(supported.installs), 3)
+ self.assertEqual(set(supported.deferred_installs), expected_deferred_installs)
+ self.assertEqual(len(child.installs), 3)
+ self.assertEqual(len(child.pattern_installs), 1)
+
+ def test_test_manifest_deffered_install_missing(self):
+ """A non-existent shared support file reference produces an error."""
+ reader = self.reader("test-manifest-shared-missing")
+
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "entry in support-files not present in the srcdir",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_test_manifest_install_includes(self):
+ """Ensure that any [include:foo.ini] are copied to the objdir."""
+ reader = self.reader("test-manifest-install-includes")
+
+ objs = self.read_topsrcdir(reader)
+ self.assertEqual(len(objs), 1)
+ o = objs[0]
+ self.assertEqual(len(o.installs), 3)
+ self.assertEqual(o.manifest_relpath, "mochitest.ini")
+ self.assertEqual(o.manifest_obj_relpath, "mochitest.ini")
+ expected = [
+ mozpath.normpath(mozpath.join(o.install_prefix, "common.ini")),
+ mozpath.normpath(mozpath.join(o.install_prefix, "mochitest.ini")),
+ mozpath.normpath(mozpath.join(o.install_prefix, "test_foo.html")),
+ ]
+ paths = sorted([v[0] for v in o.installs.values()])
+ self.assertEqual(paths, expected)
+
+ def test_test_manifest_includes(self):
+ """Ensure that manifest objects from the emitter list a correct manifest."""
+ reader = self.reader("test-manifest-emitted-includes")
+ [obj] = self.read_topsrcdir(reader)
+
+ # Expected manifest leafs for our tests.
+ expected_manifests = {
+ "reftest1.html": "reftest.list",
+ "reftest1-ref.html": "reftest.list",
+ "reftest2.html": "included-reftest.list",
+ "reftest2-ref.html": "included-reftest.list",
+ }
+
+ for t in obj.tests:
+ self.assertTrue(t["manifest"].endswith(expected_manifests[t["name"]]))
+
+ def test_test_manifest_keys_extracted(self):
+ """Ensure all metadata from test manifests is extracted."""
+ reader = self.reader("test-manifest-keys-extracted")
+
+ objs = [o for o in self.read_topsrcdir(reader) if isinstance(o, TestManifest)]
+
+ self.assertEqual(len(objs), 8)
+
+ metadata = {
+ "a11y.ini": {
+ "flavor": "a11y",
+ "installs": {"a11y.ini": False, "test_a11y.js": True},
+ "pattern-installs": 1,
+ },
+ "browser.ini": {
+ "flavor": "browser-chrome",
+ "installs": {
+ "browser.ini": False,
+ "test_browser.js": True,
+ "support1": False,
+ "support2": False,
+ },
+ },
+ "mochitest.ini": {
+ "flavor": "mochitest",
+ "installs": {"mochitest.ini": False, "test_mochitest.js": True},
+ "external": {"external1", "external2"},
+ },
+ "chrome.ini": {
+ "flavor": "chrome",
+ "installs": {"chrome.ini": False, "test_chrome.js": True},
+ },
+ "xpcshell.ini": {
+ "flavor": "xpcshell",
+ "dupe": True,
+ "installs": {
+ "xpcshell.ini": False,
+ "test_xpcshell.js": True,
+ "head1": False,
+ "head2": False,
+ },
+ },
+ "reftest.list": {"flavor": "reftest", "installs": {}},
+ "crashtest.list": {"flavor": "crashtest", "installs": {}},
+ "python.ini": {"flavor": "python", "installs": {"python.ini": False}},
+ }
+
+ for o in objs:
+ m = metadata[mozpath.basename(o.manifest_relpath)]
+
+ self.assertTrue(o.path.startswith(o.directory))
+ self.assertEqual(o.flavor, m["flavor"])
+ self.assertEqual(o.dupe_manifest, m.get("dupe", False))
+
+ external_normalized = set(mozpath.basename(p) for p in o.external_installs)
+ self.assertEqual(external_normalized, m.get("external", set()))
+
+ self.assertEqual(len(o.installs), len(m["installs"]))
+ for path in o.installs.keys():
+ self.assertTrue(path.startswith(o.directory))
+ relpath = path[len(o.directory) + 1 :]
+
+ self.assertIn(relpath, m["installs"])
+ self.assertEqual(o.installs[path][1], m["installs"][relpath])
+
+ if "pattern-installs" in m:
+ self.assertEqual(len(o.pattern_installs), m["pattern-installs"])
+
+ def test_test_manifest_unmatched_generated(self):
+ reader = self.reader("test-manifest-unmatched-generated")
+
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "entry in generated-files not present elsewhere",
+ ):
+ self.read_topsrcdir(reader),
+
+ def test_test_manifest_parent_support_files_dir(self):
+ """support-files referencing a file in a parent directory works."""
+ reader = self.reader("test-manifest-parent-support-files-dir")
+
+ objs = [o for o in self.read_topsrcdir(reader) if isinstance(o, TestManifest)]
+
+ self.assertEqual(len(objs), 1)
+
+ o = objs[0]
+
+ expected = mozpath.join(o.srcdir, "support-file.txt")
+ self.assertIn(expected, o.installs)
+ self.assertEqual(
+ o.installs[expected],
+ ("testing/mochitest/tests/child/support-file.txt", False),
+ )
+
+ def test_test_manifest_missing_test_error(self):
+ """Missing test files should result in error."""
+ reader = self.reader("test-manifest-missing-test-file")
+
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "lists test that does not exist: test_missing.html",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_test_manifest_missing_test_error_unfiltered(self):
+ """Missing test files should result in error, even when the test list is not filtered."""
+ reader = self.reader("test-manifest-missing-test-file-unfiltered")
+
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "lists test that does not exist: missing.js"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_ipdl_sources(self):
+ reader = self.reader(
+ "ipdl_sources",
+ extra_substs={"IPDL_ROOT": mozpath.abspath("/path/to/topobjdir")},
+ )
+ objs = self.read_topsrcdir(reader)
+ ipdl_collection = objs[0]
+ self.assertIsInstance(ipdl_collection, IPDLCollection)
+
+ ipdls = set(
+ mozpath.relpath(p, ipdl_collection.topsrcdir)
+ for p in ipdl_collection.all_regular_sources()
+ )
+ expected = set(
+ ["bar/bar.ipdl", "bar/bar2.ipdlh", "foo/foo.ipdl", "foo/foo2.ipdlh"]
+ )
+
+ self.assertEqual(ipdls, expected)
+
+ pp_ipdls = set(
+ mozpath.relpath(p, ipdl_collection.topsrcdir)
+ for p in ipdl_collection.all_preprocessed_sources()
+ )
+ expected = set(["bar/bar1.ipdl", "foo/foo1.ipdl"])
+ self.assertEqual(pp_ipdls, expected)
+
+ def test_local_includes(self):
+ """Test that LOCAL_INCLUDES is emitted correctly."""
+ reader = self.reader("local_includes")
+ objs = self.read_topsrcdir(reader)
+
+ local_includes = [o.path for o in objs if isinstance(o, LocalInclude)]
+ expected = ["/bar/baz", "foo"]
+
+ self.assertEqual(local_includes, expected)
+
+ local_includes = [o.path.full_path for o in objs if isinstance(o, LocalInclude)]
+ expected = [
+ mozpath.join(reader.config.topsrcdir, "bar/baz"),
+ mozpath.join(reader.config.topsrcdir, "foo"),
+ ]
+
+ self.assertEqual(local_includes, expected)
+
+ def test_local_includes_invalid(self):
+ """Test that invalid LOCAL_INCLUDES are properly detected."""
+ reader = self.reader("local_includes-invalid/srcdir")
+
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Path specified in LOCAL_INCLUDES.*resolves to the "
+ "topsrcdir or topobjdir",
+ ):
+ self.read_topsrcdir(reader)
+
+ reader = self.reader("local_includes-invalid/objdir")
+
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Path specified in LOCAL_INCLUDES.*resolves to the "
+ "topsrcdir or topobjdir",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_local_includes_file(self):
+ """Test that a filename can't be used in LOCAL_INCLUDES."""
+ reader = self.reader("local_includes-filename")
+
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Path specified in LOCAL_INCLUDES is a filename",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_generated_includes(self):
+ """Test that GENERATED_INCLUDES is emitted correctly."""
+ reader = self.reader("generated_includes")
+ objs = self.read_topsrcdir(reader)
+
+ generated_includes = [o.path for o in objs if isinstance(o, LocalInclude)]
+ expected = ["!/bar/baz", "!foo"]
+
+ self.assertEqual(generated_includes, expected)
+
+ generated_includes = [
+ o.path.full_path for o in objs if isinstance(o, LocalInclude)
+ ]
+ expected = [
+ mozpath.join(reader.config.topobjdir, "bar/baz"),
+ mozpath.join(reader.config.topobjdir, "foo"),
+ ]
+
+ self.assertEqual(generated_includes, expected)
+
+ def test_defines(self):
+ reader = self.reader("defines")
+ objs = self.read_topsrcdir(reader)
+
+ defines = {}
+ for o in objs:
+ if isinstance(o, Defines):
+ defines = o.defines
+
+ expected = {
+ "BAR": 7,
+ "BAZ": '"abcd"',
+ "FOO": True,
+ "VALUE": "xyz",
+ "QUX": False,
+ }
+
+ self.assertEqual(defines, expected)
+
+ def test_jar_manifests(self):
+ reader = self.reader("jar-manifests")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ for obj in objs:
+ self.assertIsInstance(obj, JARManifest)
+ self.assertIsInstance(obj.path, Path)
+
+ def test_jar_manifests_multiple_files(self):
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "limited to one value"
+ ):
+ reader = self.reader("jar-manifests-multiple-files")
+ self.read_topsrcdir(reader)
+
+ def test_xpidl_module_no_sources(self):
+ """XPIDL_MODULE without XPIDL_SOURCES should be rejected."""
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "XPIDL_MODULE " "cannot be defined"
+ ):
+ reader = self.reader("xpidl-module-no-sources")
+ self.read_topsrcdir(reader)
+
+ def test_xpidl_module_missing_sources(self):
+ """Missing XPIDL_SOURCES should be rejected."""
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "File .* " "from XPIDL_SOURCES does not exist"
+ ):
+ reader = self.reader("missing-xpidl")
+ self.read_topsrcdir(reader)
+
+ def test_missing_local_includes(self):
+ """LOCAL_INCLUDES containing non-existent directories should be rejected."""
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Path specified in " "LOCAL_INCLUDES does not exist",
+ ):
+ reader = self.reader("missing-local-includes")
+ self.read_topsrcdir(reader)
+
+ def test_library_defines(self):
+ """Test that LIBRARY_DEFINES is propagated properly."""
+ reader = self.reader("library-defines")
+ objs = self.read_topsrcdir(reader)
+
+ libraries = [o for o in objs if isinstance(o, StaticLibrary)]
+ library_flags = [
+ o
+ for o in objs
+ if isinstance(o, ComputedFlags) and "LIBRARY_DEFINES" in o.flags
+ ]
+ expected = {
+ "liba": "-DIN_LIBA",
+ "libb": "-DIN_LIBB -DIN_LIBA",
+ "libc": "-DIN_LIBA -DIN_LIBB",
+ "libd": "",
+ }
+ defines = {}
+ for lib in libraries:
+ defines[lib.basename] = " ".join(lib.lib_defines.get_defines())
+ self.assertEqual(expected, defines)
+ defines_in_flags = {}
+ for flags in library_flags:
+ defines_in_flags[flags.relobjdir] = " ".join(
+ flags.flags["LIBRARY_DEFINES"] or []
+ )
+ self.assertEqual(expected, defines_in_flags)
+
+ def test_sources(self):
+ """Test that SOURCES works properly."""
+ reader = self.reader("sources")
+ objs = self.read_topsrcdir(reader)
+
+ as_flags = objs.pop()
+ self.assertIsInstance(as_flags, ComputedFlags)
+ computed_flags = objs.pop()
+ self.assertIsInstance(computed_flags, ComputedFlags)
+ # The third to last object is a Linkable.
+ linkable = objs.pop()
+ self.assertTrue(linkable.cxx_link)
+ ld_flags = objs.pop()
+ self.assertIsInstance(ld_flags, ComputedFlags)
+ self.assertEqual(len(objs), 6)
+ for o in objs:
+ self.assertIsInstance(o, Sources)
+
+ suffix_map = {obj.canonical_suffix: obj for obj in objs}
+ self.assertEqual(len(suffix_map), 6)
+
+ expected = {
+ ".cpp": ["a.cpp", "b.cc", "c.cxx"],
+ ".c": ["d.c"],
+ ".m": ["e.m"],
+ ".mm": ["f.mm"],
+ ".S": ["g.S"],
+ ".s": ["h.s", "i.asm"],
+ }
+ for suffix, files in expected.items():
+ sources = suffix_map[suffix]
+ self.assertEqual(
+ sources.files, [mozpath.join(reader.config.topsrcdir, f) for f in files]
+ )
+
+ for f in files:
+ self.assertIn(
+ mozpath.join(
+ reader.config.topobjdir,
+ "%s.%s"
+ % (mozpath.splitext(f)[0], reader.config.substs["OBJ_SUFFIX"]),
+ ),
+ linkable.objs,
+ )
+
+ def test_sources_just_c(self):
+ """Test that a linkable with no C++ sources doesn't have cxx_link set."""
+ reader = self.reader("sources-just-c")
+ objs = self.read_topsrcdir(reader)
+
+ as_flags = objs.pop()
+ self.assertIsInstance(as_flags, ComputedFlags)
+ flags = objs.pop()
+ self.assertIsInstance(flags, ComputedFlags)
+ # The third to last object is a Linkable.
+ linkable = objs.pop()
+ self.assertFalse(linkable.cxx_link)
+
+ def test_linkables_cxx_link(self):
+ """Test that linkables transitively set cxx_link properly."""
+ reader = self.reader("test-linkables-cxx-link")
+ got_results = 0
+ for obj in self.read_topsrcdir(reader):
+ if isinstance(obj, SharedLibrary):
+ if obj.basename == "cxx_shared":
+ self.assertEqual(
+ obj.name,
+ "%scxx_shared%s"
+ % (reader.config.dll_prefix, reader.config.dll_suffix),
+ )
+ self.assertTrue(obj.cxx_link)
+ got_results += 1
+ elif obj.basename == "just_c_shared":
+ self.assertEqual(
+ obj.name,
+ "%sjust_c_shared%s"
+ % (reader.config.dll_prefix, reader.config.dll_suffix),
+ )
+ self.assertFalse(obj.cxx_link)
+ got_results += 1
+ self.assertEqual(got_results, 2)
+
+ def test_generated_sources(self):
+ """Test that GENERATED_SOURCES works properly."""
+ reader = self.reader("generated-sources")
+ objs = self.read_topsrcdir(reader)
+
+ as_flags = objs.pop()
+ self.assertIsInstance(as_flags, ComputedFlags)
+ flags = objs.pop()
+ self.assertIsInstance(flags, ComputedFlags)
+ # The third to last object is a Linkable.
+ linkable = objs.pop()
+ self.assertTrue(linkable.cxx_link)
+ flags = objs.pop()
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(len(objs), 6)
+
+ generated_sources = [
+ o for o in objs if isinstance(o, Sources) and o.generated_files
+ ]
+ self.assertEqual(len(generated_sources), 6)
+
+ suffix_map = {obj.canonical_suffix: obj for obj in generated_sources}
+ self.assertEqual(len(suffix_map), 6)
+
+ expected = {
+ ".cpp": ["a.cpp", "b.cc", "c.cxx"],
+ ".c": ["d.c"],
+ ".m": ["e.m"],
+ ".mm": ["f.mm"],
+ ".S": ["g.S"],
+ ".s": ["h.s", "i.asm"],
+ }
+ for suffix, files in expected.items():
+ sources = suffix_map[suffix]
+ self.assertEqual(
+ sources.generated_files,
+ [mozpath.join(reader.config.topobjdir, f) for f in files],
+ )
+
+ for f in files:
+ self.assertIn(
+ mozpath.join(
+ reader.config.topobjdir,
+ "%s.%s"
+ % (mozpath.splitext(f)[0], reader.config.substs["OBJ_SUFFIX"]),
+ ),
+ linkable.objs,
+ )
+
+ def test_host_sources(self):
+ """Test that HOST_SOURCES works properly."""
+ reader = self.reader("host-sources")
+ objs = self.read_topsrcdir(reader)
+
+ # This objdir will generate target flags.
+ flags = objs.pop()
+ self.assertIsInstance(flags, ComputedFlags)
+ # The second to last object is a Linkable
+ linkable = objs.pop()
+ self.assertTrue(linkable.cxx_link)
+ # This objdir will also generate host flags.
+ host_flags = objs.pop()
+ self.assertIsInstance(host_flags, ComputedFlags)
+ # ...and ldflags.
+ ldflags = objs.pop()
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertEqual(len(objs), 3)
+ for o in objs:
+ self.assertIsInstance(o, HostSources)
+
+ suffix_map = {obj.canonical_suffix: obj for obj in objs}
+ self.assertEqual(len(suffix_map), 3)
+
+ expected = {
+ ".cpp": ["a.cpp", "b.cc", "c.cxx"],
+ ".c": ["d.c"],
+ ".mm": ["e.mm", "f.mm"],
+ }
+ for suffix, files in expected.items():
+ sources = suffix_map[suffix]
+ self.assertEqual(
+ sources.files, [mozpath.join(reader.config.topsrcdir, f) for f in files]
+ )
+
+ for f in files:
+ self.assertIn(
+ mozpath.join(
+ reader.config.topobjdir,
+ "host_%s.%s"
+ % (mozpath.splitext(f)[0], reader.config.substs["OBJ_SUFFIX"]),
+ ),
+ linkable.objs,
+ )
+
+ def test_wasm_sources(self):
+ """Test that WASM_SOURCES works properly."""
+ reader = self.reader(
+ "wasm-sources", extra_substs={"WASM_CC": "clang", "WASM_CXX": "clang++"}
+ )
+ objs = list(self.read_topsrcdir(reader))
+
+ # The second to last object is a linkable.
+ linkable = objs[-2]
+ # Other than that, we only care about the WasmSources objects.
+ objs = objs[:2]
+ for o in objs:
+ self.assertIsInstance(o, WasmSources)
+
+ suffix_map = {obj.canonical_suffix: obj for obj in objs}
+ self.assertEqual(len(suffix_map), 2)
+
+ expected = {".cpp": ["a.cpp", "b.cc", "c.cxx"], ".c": ["d.c"]}
+ for suffix, files in expected.items():
+ sources = suffix_map[suffix]
+ self.assertEqual(
+ sources.files, [mozpath.join(reader.config.topsrcdir, f) for f in files]
+ )
+ for f in files:
+ self.assertIn(
+ mozpath.join(
+ reader.config.topobjdir,
+ "%s.%s"
+ % (
+ mozpath.splitext(f)[0],
+ reader.config.substs["WASM_OBJ_SUFFIX"],
+ ),
+ ),
+ linkable.objs,
+ )
+
+ def test_unified_sources(self):
+ """Test that UNIFIED_SOURCES works properly."""
+ reader = self.reader("unified-sources")
+ objs = self.read_topsrcdir(reader)
+
+ # The last object is a ComputedFlags, the second to last a Linkable,
+ # followed by ldflags, ignore them.
+ linkable = objs[-2]
+ objs = objs[:-3]
+ self.assertEqual(len(objs), 3)
+ for o in objs:
+ self.assertIsInstance(o, UnifiedSources)
+
+ suffix_map = {obj.canonical_suffix: obj for obj in objs}
+ self.assertEqual(len(suffix_map), 3)
+
+ expected = {
+ ".cpp": ["bar.cxx", "foo.cpp", "quux.cc"],
+ ".mm": ["objc1.mm", "objc2.mm"],
+ ".c": ["c1.c", "c2.c"],
+ }
+ for suffix, files in expected.items():
+ sources = suffix_map[suffix]
+ self.assertEqual(
+ sources.files, [mozpath.join(reader.config.topsrcdir, f) for f in files]
+ )
+
+ # Unified sources are not required
+ if sources.have_unified_mapping:
+
+ for f in dict(sources.unified_source_mapping).keys():
+ self.assertIn(
+ mozpath.join(
+ reader.config.topobjdir,
+ "%s.%s"
+ % (
+ mozpath.splitext(f)[0],
+ reader.config.substs["OBJ_SUFFIX"],
+ ),
+ ),
+ linkable.objs,
+ )
+
+ def test_unified_sources_non_unified(self):
+ """Test that UNIFIED_SOURCES with FILES_PER_UNIFIED_FILE=1 works properly."""
+ reader = self.reader("unified-sources-non-unified")
+ objs = self.read_topsrcdir(reader)
+
+ # The last object is a Linkable, the second to last ComputedFlags,
+ # followed by ldflags, ignore them.
+ objs = objs[:-3]
+ self.assertEqual(len(objs), 3)
+ for o in objs:
+ self.assertIsInstance(o, UnifiedSources)
+
+ suffix_map = {obj.canonical_suffix: obj for obj in objs}
+ self.assertEqual(len(suffix_map), 3)
+
+ expected = {
+ ".cpp": ["bar.cxx", "foo.cpp", "quux.cc"],
+ ".mm": ["objc1.mm", "objc2.mm"],
+ ".c": ["c1.c", "c2.c"],
+ }
+ for suffix, files in expected.items():
+ sources = suffix_map[suffix]
+ self.assertEqual(
+ sources.files, [mozpath.join(reader.config.topsrcdir, f) for f in files]
+ )
+ self.assertFalse(sources.have_unified_mapping)
+
+ def test_object_conflicts(self):
+ """Test that object name conflicts are detected."""
+ reader = self.reader("object-conflicts/1")
+ with self.assertRaisesRegex(
+ SandboxValidationError,
+ "Test.cpp from SOURCES would have the same object name as"
+ " Test.c from SOURCES\.",
+ ):
+ self.read_topsrcdir(reader)
+
+ reader = self.reader("object-conflicts/2")
+ with self.assertRaisesRegex(
+ SandboxValidationError,
+ "Test.cpp from SOURCES would have the same object name as"
+ " subdir/Test.cpp from SOURCES\.",
+ ):
+ self.read_topsrcdir(reader)
+
+ reader = self.reader("object-conflicts/3")
+ with self.assertRaisesRegex(
+ SandboxValidationError,
+ "Test.cpp from UNIFIED_SOURCES would have the same object name as"
+ " Test.c from SOURCES in non-unified builds\.",
+ ):
+ self.read_topsrcdir(reader)
+
+ reader = self.reader("object-conflicts/4")
+ with self.assertRaisesRegex(
+ SandboxValidationError,
+ "Test.cpp from UNIFIED_SOURCES would have the same object name as"
+ " Test.c from UNIFIED_SOURCES in non-unified builds\.",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_final_target_pp_files(self):
+ """Test that FINAL_TARGET_PP_FILES works properly."""
+ reader = self.reader("dist-files")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], FinalTargetPreprocessedFiles)
+
+ # Ideally we'd test hierarchies, but that would just be testing
+ # the HierarchicalStringList class, which we test separately.
+ for path, files in objs[0].files.walk():
+ self.assertEqual(path, "")
+ self.assertEqual(len(files), 2)
+
+ expected = {"install.rdf", "main.js"}
+ for f in files:
+ self.assertTrue(six.text_type(f) in expected)
+
+ def test_missing_final_target_pp_files(self):
+ """Test that FINAL_TARGET_PP_FILES with missing files throws errors."""
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "File listed in " "FINAL_TARGET_PP_FILES does not exist",
+ ):
+ reader = self.reader("dist-files-missing")
+ self.read_topsrcdir(reader)
+
+ def test_final_target_pp_files_non_srcdir(self):
+ """Test that non-srcdir paths in FINAL_TARGET_PP_FILES throws errors."""
+ reader = self.reader("final-target-pp-files-non-srcdir")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Only source directory paths allowed in FINAL_TARGET_PP_FILES:",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_localized_files(self):
+ """Test that LOCALIZED_FILES works properly."""
+ reader = self.reader("localized-files")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], LocalizedFiles)
+
+ for path, files in objs[0].files.walk():
+ self.assertEqual(path, "foo")
+ self.assertEqual(len(files), 3)
+
+ expected = {"en-US/bar.ini", "en-US/code/*.js", "en-US/foo.js"}
+ for f in files:
+ self.assertTrue(six.text_type(f) in expected)
+
+ def test_localized_files_no_en_us(self):
+ """Test that LOCALIZED_FILES errors if a path does not start with
+ `en-US/` or contain `locales/en-US/`."""
+ reader = self.reader("localized-files-no-en-us")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "LOCALIZED_FILES paths must start with `en-US/` or contain `locales/en-US/`: "
+ "foo.js",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_localized_pp_files(self):
+ """Test that LOCALIZED_PP_FILES works properly."""
+ reader = self.reader("localized-pp-files")
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], LocalizedPreprocessedFiles)
+
+ for path, files in objs[0].files.walk():
+ self.assertEqual(path, "foo")
+ self.assertEqual(len(files), 2)
+
+ expected = {"en-US/bar.ini", "en-US/foo.js"}
+ for f in files:
+ self.assertTrue(six.text_type(f) in expected)
+
+ def test_rust_library_no_cargo_toml(self):
+ """Test that defining a RustLibrary without a Cargo.toml fails."""
+ reader = self.reader("rust-library-no-cargo-toml")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "No Cargo.toml file found"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_library_name_mismatch(self):
+ """Test that defining a RustLibrary that doesn't match Cargo.toml fails."""
+ reader = self.reader("rust-library-name-mismatch")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "library.*does not match Cargo.toml-defined package",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_library_no_lib_section(self):
+ """Test that a RustLibrary Cargo.toml with no [lib] section fails."""
+ reader = self.reader("rust-library-no-lib-section")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "Cargo.toml for.* has no \\[lib\\] section"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_library_invalid_crate_type(self):
+ """Test that a RustLibrary Cargo.toml has a permitted crate-type."""
+ reader = self.reader("rust-library-invalid-crate-type")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "crate-type.* is not permitted"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_library_dash_folding(self):
+ """Test that on-disk names of RustLibrary objects convert dashes to underscores."""
+ reader = self.reader(
+ "rust-library-dash-folding",
+ extra_substs=dict(RUST_TARGET="i686-pc-windows-msvc"),
+ )
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 4)
+ ldflags, host_cflags, lib, cflags = objs
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(cflags, ComputedFlags)
+ self.assertIsInstance(host_cflags, ComputedFlags)
+ self.assertIsInstance(lib, RustLibrary)
+ self.assertRegex(lib.lib_name, "random_crate")
+ self.assertRegex(lib.import_name, "random_crate")
+ self.assertRegex(lib.basename, "random-crate")
+
+ def test_multiple_rust_libraries(self):
+ """Test that linking multiple Rust libraries throws an error"""
+ reader = self.reader(
+ "multiple-rust-libraries",
+ extra_substs=dict(RUST_TARGET="i686-pc-windows-msvc"),
+ )
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "Cannot link the following Rust libraries"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_library_features(self):
+ """Test that RustLibrary features are correctly emitted."""
+ reader = self.reader(
+ "rust-library-features",
+ extra_substs=dict(RUST_TARGET="i686-pc-windows-msvc"),
+ )
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 4)
+ ldflags, host_cflags, lib, cflags = objs
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(cflags, ComputedFlags)
+ self.assertIsInstance(host_cflags, ComputedFlags)
+ self.assertIsInstance(lib, RustLibrary)
+ self.assertEqual(lib.features, ["musthave", "cantlivewithout"])
+
+ def test_rust_library_duplicate_features(self):
+ """Test that duplicate RustLibrary features are rejected."""
+ reader = self.reader("rust-library-duplicate-features")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "features for .* should not contain duplicates",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_program_no_cargo_toml(self):
+ """Test that specifying RUST_PROGRAMS without a Cargo.toml fails."""
+ reader = self.reader("rust-program-no-cargo-toml")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "No Cargo.toml file found"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_host_rust_program_no_cargo_toml(self):
+ """Test that specifying HOST_RUST_PROGRAMS without a Cargo.toml fails."""
+ reader = self.reader("host-rust-program-no-cargo-toml")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "No Cargo.toml file found"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_program_nonexistent_name(self):
+ """Test that specifying RUST_PROGRAMS that don't exist in Cargo.toml
+ correctly throws an error."""
+ reader = self.reader("rust-program-nonexistent-name")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "Cannot find Cargo.toml definition for"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_host_rust_program_nonexistent_name(self):
+ """Test that specifying HOST_RUST_PROGRAMS that don't exist in
+ Cargo.toml correctly throws an error."""
+ reader = self.reader("host-rust-program-nonexistent-name")
+ with six.assertRaisesRegex(
+ self, SandboxValidationError, "Cannot find Cargo.toml definition for"
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_rust_programs(self):
+ """Test RUST_PROGRAMS emission."""
+ reader = self.reader(
+ "rust-programs",
+ extra_substs=dict(RUST_TARGET="i686-pc-windows-msvc", BIN_SUFFIX=".exe"),
+ )
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 4)
+ ldflags, host_cflags, cflags, prog = objs
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(cflags, ComputedFlags)
+ self.assertIsInstance(host_cflags, ComputedFlags)
+ self.assertIsInstance(prog, RustProgram)
+ self.assertEqual(prog.name, "some")
+
+ def test_host_rust_programs(self):
+ """Test HOST_RUST_PROGRAMS emission."""
+ reader = self.reader(
+ "host-rust-programs",
+ extra_substs=dict(
+ RUST_HOST_TARGET="i686-pc-windows-msvc", HOST_BIN_SUFFIX=".exe"
+ ),
+ )
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 4)
+ print(objs)
+ ldflags, cflags, hostflags, prog = objs
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(cflags, ComputedFlags)
+ self.assertIsInstance(hostflags, ComputedFlags)
+ self.assertIsInstance(prog, HostRustProgram)
+ self.assertEqual(prog.name, "some")
+
+ def test_host_rust_libraries(self):
+ """Test HOST_RUST_LIBRARIES emission."""
+ reader = self.reader(
+ "host-rust-libraries",
+ extra_substs=dict(
+ RUST_HOST_TARGET="i686-pc-windows-msvc", HOST_BIN_SUFFIX=".exe"
+ ),
+ )
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 4)
+ ldflags, host_cflags, lib, cflags = objs
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(cflags, ComputedFlags)
+ self.assertIsInstance(host_cflags, ComputedFlags)
+ self.assertIsInstance(lib, HostRustLibrary)
+ self.assertRegex(lib.lib_name, "host_lib")
+ self.assertRegex(lib.import_name, "host_lib")
+
+ def test_crate_dependency_path_resolution(self):
+ """Test recursive dependencies resolve with the correct paths."""
+ reader = self.reader(
+ "crate-dependency-path-resolution",
+ extra_substs=dict(RUST_TARGET="i686-pc-windows-msvc"),
+ )
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 4)
+ ldflags, host_cflags, lib, cflags = objs
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(cflags, ComputedFlags)
+ self.assertIsInstance(host_cflags, ComputedFlags)
+ self.assertIsInstance(lib, RustLibrary)
+
+ def test_install_shared_lib(self):
+ """Test that we can install a shared library with TEST_HARNESS_FILES"""
+ reader = self.reader("test-install-shared-lib")
+ objs = self.read_topsrcdir(reader)
+ self.assertIsInstance(objs[0], TestHarnessFiles)
+ self.assertIsInstance(objs[1], VariablePassthru)
+ self.assertIsInstance(objs[2], ComputedFlags)
+ self.assertIsInstance(objs[3], SharedLibrary)
+ self.assertIsInstance(objs[4], ComputedFlags)
+ for path, files in objs[0].files.walk():
+ for f in files:
+ self.assertEqual(str(f), "!libfoo.so")
+ self.assertEqual(path, "foo/bar")
+
+ def test_symbols_file(self):
+ """Test that SYMBOLS_FILE works"""
+ reader = self.reader("test-symbols-file")
+ genfile, ldflags, shlib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(genfile, GeneratedFile)
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(shlib, SharedLibrary)
+ # This looks weird but MockConfig sets DLL_{PREFIX,SUFFIX} and
+ # the reader method in this class sets OS_TARGET=WINNT.
+ self.assertEqual(shlib.symbols_file, "libfoo.so.def")
+
+ def test_symbols_file_objdir(self):
+ """Test that a SYMBOLS_FILE in the objdir works"""
+ reader = self.reader("test-symbols-file-objdir")
+ genfile, ldflags, shlib, flags = self.read_topsrcdir(reader)
+ self.assertIsInstance(genfile, GeneratedFile)
+ self.assertEqual(
+ genfile.script, mozpath.join(reader.config.topsrcdir, "foo.py")
+ )
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertIsInstance(ldflags, ComputedFlags)
+ self.assertIsInstance(shlib, SharedLibrary)
+ self.assertEqual(shlib.symbols_file, "foo.symbols")
+
+ def test_symbols_file_objdir_missing_generated(self):
+ """Test that a SYMBOLS_FILE in the objdir that's missing
+ from GENERATED_FILES is an error.
+ """
+ reader = self.reader("test-symbols-file-objdir-missing-generated")
+ with six.assertRaisesRegex(
+ self,
+ SandboxValidationError,
+ "Objdir file specified in SYMBOLS_FILE not in GENERATED_FILES:",
+ ):
+ self.read_topsrcdir(reader)
+
+ def test_wasm_compile_flags(self):
+ reader = self.reader(
+ "wasm-compile-flags",
+ extra_substs={"WASM_CC": "clang", "WASM_CXX": "clang++"},
+ )
+ flags = list(self.read_topsrcdir(reader))[2]
+ self.assertIsInstance(flags, ComputedFlags)
+ self.assertEqual(
+ flags.flags["WASM_CFLAGS"], reader.config.substs["WASM_CFLAGS"]
+ )
+ self.assertEqual(
+ flags.flags["MOZBUILD_WASM_CFLAGS"], ["-funroll-loops", "-wasm-arg"]
+ )
+ self.assertEqual(
+ set(flags.flags["WASM_DEFINES"]),
+ set(["-DFOO", '-DBAZ="abcd"', "-UQUX", "-DBAR=7", "-DVALUE=xyz"]),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/frontend/test_namespaces.py b/python/mozbuild/mozbuild/test/frontend/test_namespaces.py
new file mode 100644
index 0000000000..e8c1a3eb00
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_namespaces.py
@@ -0,0 +1,225 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import unittest
+
+import six
+from mozunit import main
+
+from mozbuild.frontend.context import (
+ Context,
+ ContextDerivedTypedList,
+ ContextDerivedTypedListWithItems,
+ ContextDerivedValue,
+)
+from mozbuild.util import (
+ StrictOrderingOnAppendList,
+ StrictOrderingOnAppendListWithFlagsFactory,
+ UnsortedError,
+)
+
+
+class Fuga(object):
+ def __init__(self, value):
+ self.value = value
+
+
+class Piyo(ContextDerivedValue):
+ def __init__(self, context, value):
+ if not isinstance(value, six.text_type):
+ raise ValueError
+ self.context = context
+ self.value = value
+
+ def lower(self):
+ return self.value.lower()
+
+ def __str__(self):
+ return self.value
+
+ def __eq__(self, other):
+ return self.value == six.text_type(other)
+
+ def __lt__(self, other):
+ return self.value < six.text_type(other)
+
+ def __le__(self, other):
+ return self.value <= six.text_type(other)
+
+ def __gt__(self, other):
+ return self.value > six.text_type(other)
+
+ def __ge__(self, other):
+ return self.value >= six.text_type(other)
+
+ def __hash__(self):
+ return hash(self.value)
+
+
+VARIABLES = {
+ "HOGE": (six.text_type, six.text_type, None),
+ "FUGA": (Fuga, six.text_type, None),
+ "PIYO": (Piyo, six.text_type, None),
+ "HOGERA": (ContextDerivedTypedList(Piyo, StrictOrderingOnAppendList), list, None),
+ "HOGEHOGE": (
+ ContextDerivedTypedListWithItems(
+ Piyo,
+ StrictOrderingOnAppendListWithFlagsFactory(
+ {
+ "foo": bool,
+ }
+ ),
+ ),
+ list,
+ None,
+ ),
+}
+
+
+class TestContext(unittest.TestCase):
+ def test_key_rejection(self):
+ # Lowercase keys should be rejected during normal operation.
+ ns = Context(allowed_variables=VARIABLES)
+
+ with self.assertRaises(KeyError) as ke:
+ ns["foo"] = True
+
+ e = ke.exception.args
+ self.assertEqual(e[0], "global_ns")
+ self.assertEqual(e[1], "set_unknown")
+ self.assertEqual(e[2], "foo")
+ self.assertTrue(e[3])
+
+ # Unknown uppercase keys should be rejected.
+ with self.assertRaises(KeyError) as ke:
+ ns["FOO"] = True
+
+ e = ke.exception.args
+ self.assertEqual(e[0], "global_ns")
+ self.assertEqual(e[1], "set_unknown")
+ self.assertEqual(e[2], "FOO")
+ self.assertTrue(e[3])
+
+ def test_allowed_set(self):
+ self.assertIn("HOGE", VARIABLES)
+
+ ns = Context(allowed_variables=VARIABLES)
+
+ ns["HOGE"] = "foo"
+ self.assertEqual(ns["HOGE"], "foo")
+
+ def test_value_checking(self):
+ ns = Context(allowed_variables=VARIABLES)
+
+ # Setting to a non-allowed type should not work.
+ with self.assertRaises(ValueError) as ve:
+ ns["HOGE"] = True
+
+ e = ve.exception.args
+ self.assertEqual(e[0], "global_ns")
+ self.assertEqual(e[1], "set_type")
+ self.assertEqual(e[2], "HOGE")
+ self.assertEqual(e[3], True)
+ self.assertEqual(e[4], six.text_type)
+
+ def test_key_checking(self):
+ # Checking for existence of a key should not populate the key if it
+ # doesn't exist.
+ g = Context(allowed_variables=VARIABLES)
+
+ self.assertFalse("HOGE" in g)
+ self.assertFalse("HOGE" in g)
+
+ def test_coercion(self):
+ ns = Context(allowed_variables=VARIABLES)
+
+ # Setting to a type different from the allowed input type should not
+ # work.
+ with self.assertRaises(ValueError) as ve:
+ ns["FUGA"] = False
+
+ e = ve.exception.args
+ self.assertEqual(e[0], "global_ns")
+ self.assertEqual(e[1], "set_type")
+ self.assertEqual(e[2], "FUGA")
+ self.assertEqual(e[3], False)
+ self.assertEqual(e[4], six.text_type)
+
+ ns["FUGA"] = "fuga"
+ self.assertIsInstance(ns["FUGA"], Fuga)
+ self.assertEqual(ns["FUGA"].value, "fuga")
+
+ ns["FUGA"] = Fuga("hoge")
+ self.assertIsInstance(ns["FUGA"], Fuga)
+ self.assertEqual(ns["FUGA"].value, "hoge")
+
+ def test_context_derived_coercion(self):
+ ns = Context(allowed_variables=VARIABLES)
+
+ # Setting to a type different from the allowed input type should not
+ # work.
+ with self.assertRaises(ValueError) as ve:
+ ns["PIYO"] = False
+
+ e = ve.exception.args
+ self.assertEqual(e[0], "global_ns")
+ self.assertEqual(e[1], "set_type")
+ self.assertEqual(e[2], "PIYO")
+ self.assertEqual(e[3], False)
+ self.assertEqual(e[4], six.text_type)
+
+ ns["PIYO"] = "piyo"
+ self.assertIsInstance(ns["PIYO"], Piyo)
+ self.assertEqual(ns["PIYO"].value, "piyo")
+ self.assertEqual(ns["PIYO"].context, ns)
+
+ ns["PIYO"] = Piyo(ns, "fuga")
+ self.assertIsInstance(ns["PIYO"], Piyo)
+ self.assertEqual(ns["PIYO"].value, "fuga")
+ self.assertEqual(ns["PIYO"].context, ns)
+
+ def test_context_derived_typed_list(self):
+ ns = Context(allowed_variables=VARIABLES)
+
+ # Setting to a type that's rejected by coercion should not work.
+ with self.assertRaises(ValueError):
+ ns["HOGERA"] = [False]
+
+ ns["HOGERA"] += ["a", "b", "c"]
+
+ self.assertIsInstance(ns["HOGERA"], VARIABLES["HOGERA"][0])
+ for n in range(0, 3):
+ self.assertIsInstance(ns["HOGERA"][n], Piyo)
+ self.assertEqual(ns["HOGERA"][n].value, ["a", "b", "c"][n])
+ self.assertEqual(ns["HOGERA"][n].context, ns)
+
+ with self.assertRaises(UnsortedError):
+ ns["HOGERA"] += ["f", "e", "d"]
+
+ def test_context_derived_typed_list_with_items(self):
+ ns = Context(allowed_variables=VARIABLES)
+
+ # Setting to a type that's rejected by coercion should not work.
+ with self.assertRaises(ValueError):
+ ns["HOGEHOGE"] = [False]
+
+ values = ["a", "b", "c"]
+ ns["HOGEHOGE"] += values
+
+ self.assertIsInstance(ns["HOGEHOGE"], VARIABLES["HOGEHOGE"][0])
+ for v in values:
+ ns["HOGEHOGE"][v].foo = True
+
+ for v, item in zip(values, ns["HOGEHOGE"]):
+ self.assertIsInstance(item, Piyo)
+ self.assertEqual(v, item)
+ self.assertEqual(ns["HOGEHOGE"][v].foo, True)
+ self.assertEqual(ns["HOGEHOGE"][item].foo, True)
+
+ with self.assertRaises(UnsortedError):
+ ns["HOGEHOGE"] += ["f", "e", "d"]
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/frontend/test_reader.py b/python/mozbuild/mozbuild/test/frontend/test_reader.py
new file mode 100644
index 0000000000..a15bb15d7e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_reader.py
@@ -0,0 +1,531 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+import unittest
+
+import mozpack.path as mozpath
+from mozunit import main
+
+from mozbuild import schedules
+from mozbuild.frontend.context import BugzillaComponent
+from mozbuild.frontend.reader import BuildReader, BuildReaderError
+from mozbuild.test.common import MockConfig
+
+if sys.version_info.major == 2:
+ text_type = "unicode"
+else:
+ text_type = "str"
+
+data_path = mozpath.abspath(mozpath.dirname(__file__))
+data_path = mozpath.join(data_path, "data")
+
+
+class TestBuildReader(unittest.TestCase):
+ def setUp(self):
+ self._old_env = dict(os.environ)
+ os.environ.pop("MOZ_OBJDIR", None)
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ def config(self, name, **kwargs):
+ path = mozpath.join(data_path, name)
+
+ return MockConfig(path, **kwargs)
+
+ def reader(self, name, enable_tests=False, error_is_fatal=True, **kwargs):
+ extra = {}
+ if enable_tests:
+ extra["ENABLE_TESTS"] = "1"
+ config = self.config(name, extra_substs=extra, error_is_fatal=error_is_fatal)
+
+ return BuildReader(config, **kwargs)
+
+ def file_path(self, name, *args):
+ return mozpath.join(data_path, name, *args)
+
+ def test_dirs_traversal_simple(self):
+ reader = self.reader("traversal-simple")
+
+ contexts = list(reader.read_topsrcdir())
+
+ self.assertEqual(len(contexts), 4)
+
+ def test_dirs_traversal_no_descend(self):
+ reader = self.reader("traversal-simple")
+
+ path = mozpath.join(reader.config.topsrcdir, "moz.build")
+ self.assertTrue(os.path.exists(path))
+
+ contexts = list(reader.read_mozbuild(path, reader.config, descend=False))
+
+ self.assertEqual(len(contexts), 1)
+
+ def test_dirs_traversal_all_variables(self):
+ reader = self.reader("traversal-all-vars")
+
+ contexts = list(reader.read_topsrcdir())
+ self.assertEqual(len(contexts), 2)
+
+ reader = self.reader("traversal-all-vars", enable_tests=True)
+
+ contexts = list(reader.read_topsrcdir())
+ self.assertEqual(len(contexts), 3)
+
+ def test_relative_dirs(self):
+ # Ensure relative directories are traversed.
+ reader = self.reader("traversal-relative-dirs")
+
+ contexts = list(reader.read_topsrcdir())
+ self.assertEqual(len(contexts), 3)
+
+ def test_repeated_dirs_ignored(self):
+ # Ensure repeated directories are ignored.
+ reader = self.reader("traversal-repeated-dirs")
+
+ contexts = list(reader.read_topsrcdir())
+ self.assertEqual(len(contexts), 3)
+
+ def test_outside_topsrcdir(self):
+ # References to directories outside the topsrcdir should fail.
+ reader = self.reader("traversal-outside-topsrcdir")
+
+ with self.assertRaises(Exception):
+ list(reader.read_topsrcdir())
+
+ def test_error_basic(self):
+ reader = self.reader("reader-error-basic")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertEqual(
+ e.actual_file, self.file_path("reader-error-basic", "moz.build")
+ )
+
+ self.assertIn("The error occurred while processing the", str(e))
+
+ def test_error_included_from(self):
+ reader = self.reader("reader-error-included-from")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertEqual(
+ e.actual_file, self.file_path("reader-error-included-from", "child.build")
+ )
+ self.assertEqual(
+ e.main_file, self.file_path("reader-error-included-from", "moz.build")
+ )
+
+ self.assertIn("This file was included as part of processing", str(e))
+
+ def test_error_syntax_error(self):
+ reader = self.reader("reader-error-syntax")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("Python syntax error on line 5", str(e))
+ self.assertIn(" foo =", str(e))
+ self.assertIn(" ^", str(e))
+
+ def test_error_read_unknown_global(self):
+ reader = self.reader("reader-error-read-unknown-global")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("The error was triggered on line 5", str(e))
+ self.assertIn("The underlying problem is an attempt to read", str(e))
+ self.assertIn(" FOO", str(e))
+
+ def test_error_write_unknown_global(self):
+ reader = self.reader("reader-error-write-unknown-global")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("The error was triggered on line 7", str(e))
+ self.assertIn("The underlying problem is an attempt to write", str(e))
+ self.assertIn(" FOO", str(e))
+
+ def test_error_write_bad_value(self):
+ reader = self.reader("reader-error-write-bad-value")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("The error was triggered on line 5", str(e))
+ self.assertIn("is an attempt to write an illegal value to a special", str(e))
+
+ self.assertIn("variable whose value was rejected is:\n\n DIRS", str(e))
+
+ self.assertIn(
+ "written to it was of the following type:\n\n %s" % text_type, str(e)
+ )
+
+ self.assertIn("expects the following type(s):\n\n list", str(e))
+
+ def test_error_illegal_path(self):
+ reader = self.reader("reader-error-outside-topsrcdir")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("The underlying problem is an illegal file access", str(e))
+
+ def test_error_missing_include_path(self):
+ reader = self.reader("reader-error-missing-include")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("we referenced a path that does not exist", str(e))
+
+ def test_error_script_error(self):
+ reader = self.reader("reader-error-script-error")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("The error appears to be the fault of the script", str(e))
+ self.assertIn(' ["TypeError: unsupported operand', str(e))
+
+ def test_error_bad_dir(self):
+ reader = self.reader("reader-error-bad-dir")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("we referenced a path that does not exist", str(e))
+
+ def test_error_repeated_dir(self):
+ reader = self.reader("reader-error-repeated-dir")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("Directory (foo) registered multiple times", str(e))
+
+ def test_error_error_func(self):
+ reader = self.reader("reader-error-error-func")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("A moz.build file called the error() function.", str(e))
+ self.assertIn(" Some error.", str(e))
+
+ def test_error_error_func_ok(self):
+ reader = self.reader("reader-error-error-func", error_is_fatal=False)
+
+ list(reader.read_topsrcdir())
+
+ def test_error_empty_list(self):
+ reader = self.reader("reader-error-empty-list")
+
+ with self.assertRaises(BuildReaderError) as bre:
+ list(reader.read_topsrcdir())
+
+ e = bre.exception
+ self.assertIn("Variable DIRS assigned an empty value.", str(e))
+
+ def test_inheriting_variables(self):
+ reader = self.reader("inheriting-variables")
+
+ contexts = list(reader.read_topsrcdir())
+
+ self.assertEqual(len(contexts), 4)
+ self.assertEqual(
+ [context.relsrcdir for context in contexts], ["", "foo", "foo/baz", "bar"]
+ )
+ self.assertEqual(
+ [context["XPIDL_MODULE"] for context in contexts],
+ ["foobar", "foobar", "baz", "foobar"],
+ )
+
+ def test_find_relevant_mozbuilds(self):
+ reader = self.reader("reader-relevant-mozbuild")
+
+ # Absolute paths outside topsrcdir are rejected.
+ with self.assertRaises(Exception):
+ reader._find_relevant_mozbuilds(["/foo"])
+
+ # File in root directory.
+ paths = reader._find_relevant_mozbuilds(["file"])
+ self.assertEqual(paths, {"file": ["moz.build"]})
+
+ # File in child directory.
+ paths = reader._find_relevant_mozbuilds(["d1/file1"])
+ self.assertEqual(paths, {"d1/file1": ["moz.build", "d1/moz.build"]})
+
+ # Multiple files in same directory.
+ paths = reader._find_relevant_mozbuilds(["d1/file1", "d1/file2"])
+ self.assertEqual(
+ paths,
+ {
+ "d1/file1": ["moz.build", "d1/moz.build"],
+ "d1/file2": ["moz.build", "d1/moz.build"],
+ },
+ )
+
+ # Missing moz.build from missing intermediate directory.
+ paths = reader._find_relevant_mozbuilds(
+ ["d1/no-intermediate-moz-build/child/file"]
+ )
+ self.assertEqual(
+ paths,
+ {
+ "d1/no-intermediate-moz-build/child/file": [
+ "moz.build",
+ "d1/moz.build",
+ "d1/no-intermediate-moz-build/child/moz.build",
+ ]
+ },
+ )
+
+ # Lots of empty directories.
+ paths = reader._find_relevant_mozbuilds(
+ ["d1/parent-is-far/dir1/dir2/dir3/file"]
+ )
+ self.assertEqual(
+ paths,
+ {
+ "d1/parent-is-far/dir1/dir2/dir3/file": [
+ "moz.build",
+ "d1/moz.build",
+ "d1/parent-is-far/moz.build",
+ ]
+ },
+ )
+
+ # Lots of levels.
+ paths = reader._find_relevant_mozbuilds(
+ ["d1/every-level/a/file", "d1/every-level/b/file"]
+ )
+ self.assertEqual(
+ paths,
+ {
+ "d1/every-level/a/file": [
+ "moz.build",
+ "d1/moz.build",
+ "d1/every-level/moz.build",
+ "d1/every-level/a/moz.build",
+ ],
+ "d1/every-level/b/file": [
+ "moz.build",
+ "d1/moz.build",
+ "d1/every-level/moz.build",
+ "d1/every-level/b/moz.build",
+ ],
+ },
+ )
+
+ # Different root directories.
+ paths = reader._find_relevant_mozbuilds(["d1/file", "d2/file", "file"])
+ self.assertEqual(
+ paths,
+ {
+ "file": ["moz.build"],
+ "d1/file": ["moz.build", "d1/moz.build"],
+ "d2/file": ["moz.build", "d2/moz.build"],
+ },
+ )
+
+ def test_read_relevant_mozbuilds(self):
+ reader = self.reader("reader-relevant-mozbuild")
+
+ paths, contexts = reader.read_relevant_mozbuilds(
+ ["d1/every-level/a/file", "d1/every-level/b/file", "d2/file"]
+ )
+ self.assertEqual(len(paths), 3)
+ self.assertEqual(len(contexts), 6)
+
+ self.assertEqual(
+ [ctx.relsrcdir for ctx in paths["d1/every-level/a/file"]],
+ ["", "d1", "d1/every-level", "d1/every-level/a"],
+ )
+ self.assertEqual(
+ [ctx.relsrcdir for ctx in paths["d1/every-level/b/file"]],
+ ["", "d1", "d1/every-level", "d1/every-level/b"],
+ )
+ self.assertEqual([ctx.relsrcdir for ctx in paths["d2/file"]], ["", "d2"])
+
+ def test_all_mozbuild_paths(self):
+ reader = self.reader("reader-relevant-mozbuild")
+
+ paths = list(reader.all_mozbuild_paths())
+ # Ensure no duplicate paths.
+ self.assertEqual(sorted(paths), sorted(set(paths)))
+ self.assertEqual(len(paths), 10)
+
+ def test_files_bad_bug_component(self):
+ reader = self.reader("files-info")
+
+ with self.assertRaises(BuildReaderError):
+ reader.files_info(["bug_component/bad-assignment/moz.build"])
+
+ def test_files_bug_component_static(self):
+ reader = self.reader("files-info")
+
+ v = reader.files_info(
+ [
+ "bug_component/static/foo",
+ "bug_component/static/bar",
+ "bug_component/static/foo/baz",
+ ]
+ )
+ self.assertEqual(len(v), 3)
+ self.assertEqual(
+ v["bug_component/static/foo"]["BUG_COMPONENT"],
+ BugzillaComponent("FooProduct", "FooComponent"),
+ )
+ self.assertEqual(
+ v["bug_component/static/bar"]["BUG_COMPONENT"],
+ BugzillaComponent("BarProduct", "BarComponent"),
+ )
+ self.assertEqual(
+ v["bug_component/static/foo/baz"]["BUG_COMPONENT"],
+ BugzillaComponent("default_product", "default_component"),
+ )
+
+ def test_files_bug_component_simple(self):
+ reader = self.reader("files-info")
+
+ v = reader.files_info(["bug_component/simple/moz.build"])
+ self.assertEqual(len(v), 1)
+ flags = v["bug_component/simple/moz.build"]
+ self.assertEqual(flags["BUG_COMPONENT"].product, "Firefox Build System")
+ self.assertEqual(flags["BUG_COMPONENT"].component, "General")
+
+ def test_files_bug_component_different_matchers(self):
+ reader = self.reader("files-info")
+
+ v = reader.files_info(
+ [
+ "bug_component/different-matchers/foo.jsm",
+ "bug_component/different-matchers/bar.cpp",
+ "bug_component/different-matchers/baz.misc",
+ ]
+ )
+ self.assertEqual(len(v), 3)
+
+ js_flags = v["bug_component/different-matchers/foo.jsm"]
+ cpp_flags = v["bug_component/different-matchers/bar.cpp"]
+ misc_flags = v["bug_component/different-matchers/baz.misc"]
+
+ self.assertEqual(js_flags["BUG_COMPONENT"], BugzillaComponent("Firefox", "JS"))
+ self.assertEqual(
+ cpp_flags["BUG_COMPONENT"], BugzillaComponent("Firefox", "C++")
+ )
+ self.assertEqual(
+ misc_flags["BUG_COMPONENT"],
+ BugzillaComponent("default_product", "default_component"),
+ )
+
+ def test_files_bug_component_final(self):
+ reader = self.reader("files-info")
+
+ v = reader.files_info(
+ [
+ "bug_component/final/foo",
+ "bug_component/final/Makefile.in",
+ "bug_component/final/subcomponent/Makefile.in",
+ "bug_component/final/subcomponent/bar",
+ ]
+ )
+
+ self.assertEqual(
+ v["bug_component/final/foo"]["BUG_COMPONENT"],
+ BugzillaComponent("default_product", "default_component"),
+ )
+ self.assertEqual(
+ v["bug_component/final/Makefile.in"]["BUG_COMPONENT"],
+ BugzillaComponent("Firefox Build System", "General"),
+ )
+ self.assertEqual(
+ v["bug_component/final/subcomponent/Makefile.in"]["BUG_COMPONENT"],
+ BugzillaComponent("Firefox Build System", "General"),
+ )
+ self.assertEqual(
+ v["bug_component/final/subcomponent/bar"]["BUG_COMPONENT"],
+ BugzillaComponent("Another", "Component"),
+ )
+
+ def test_invalid_flavor(self):
+ reader = self.reader("invalid-files-flavor")
+
+ with self.assertRaises(BuildReaderError):
+ reader.files_info(["foo.js"])
+
+ def test_schedules(self):
+ reader = self.reader("schedules")
+ info = reader.files_info(
+ [
+ "win.and.osx",
+ "somefile",
+ "foo.win",
+ "foo.osx",
+ "subd/aa.py",
+ "subd/yaml.py",
+ "subd/win.js",
+ ]
+ )
+ # default: all exclusive, no inclusive
+ self.assertEqual(info["somefile"]["SCHEDULES"].inclusive, [])
+ self.assertEqual(
+ info["somefile"]["SCHEDULES"].exclusive, schedules.EXCLUSIVE_COMPONENTS
+ )
+ # windows-only
+ self.assertEqual(info["foo.win"]["SCHEDULES"].inclusive, [])
+ self.assertEqual(info["foo.win"]["SCHEDULES"].exclusive, ["windows"])
+ # osx-only
+ self.assertEqual(info["foo.osx"]["SCHEDULES"].inclusive, [])
+ self.assertEqual(info["foo.osx"]["SCHEDULES"].exclusive, ["macosx"])
+ # top-level moz.build specifies subd/**.py with an inclusive option
+ self.assertEqual(info["subd/aa.py"]["SCHEDULES"].inclusive, ["py-lint"])
+ self.assertEqual(
+ info["subd/aa.py"]["SCHEDULES"].exclusive, schedules.EXCLUSIVE_COMPONENTS
+ )
+ # Files('yaml.py') in subd/moz.build combines with Files('subdir/**.py')
+ self.assertEqual(
+ info["subd/yaml.py"]["SCHEDULES"].inclusive, ["py-lint", "yaml-lint"]
+ )
+ self.assertEqual(
+ info["subd/yaml.py"]["SCHEDULES"].exclusive, schedules.EXCLUSIVE_COMPONENTS
+ )
+ # .. but exlusive does not override inclusive
+ self.assertEqual(info["subd/win.js"]["SCHEDULES"].inclusive, ["js-lint"])
+ self.assertEqual(info["subd/win.js"]["SCHEDULES"].exclusive, ["windows"])
+
+ self.assertEqual(
+ set(info["subd/yaml.py"]["SCHEDULES"].components),
+ set(schedules.EXCLUSIVE_COMPONENTS + ["py-lint", "yaml-lint"]),
+ )
+
+ # win.and.osx is defined explicitly, and matches *.osx, and the two have
+ # conflicting SCHEDULES.exclusive settings, so the later one is used
+ self.assertEqual(
+ set(info["win.and.osx"]["SCHEDULES"].exclusive), set(["macosx", "windows"])
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/frontend/test_sandbox.py b/python/mozbuild/mozbuild/test/frontend/test_sandbox.py
new file mode 100644
index 0000000000..017de1ce9c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_sandbox.py
@@ -0,0 +1,536 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import unittest
+
+import mozpack.path as mozpath
+from mozunit import main
+
+from mozbuild.frontend.context import (
+ FUNCTIONS,
+ SPECIAL_VARIABLES,
+ VARIABLES,
+ Context,
+ SourcePath,
+)
+from mozbuild.frontend.reader import MozbuildSandbox, SandboxCalledError
+from mozbuild.frontend.sandbox import Sandbox, SandboxExecutionError, SandboxLoadError
+from mozbuild.test.common import MockConfig
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+
+class TestSandbox(unittest.TestCase):
+ def sandbox(self):
+ return Sandbox(
+ Context(
+ {
+ "DIRS": (list, list, None),
+ }
+ )
+ )
+
+ def test_exec_source_success(self):
+ sandbox = self.sandbox()
+ context = sandbox._context
+
+ sandbox.exec_source("foo = True", mozpath.abspath("foo.py"))
+
+ self.assertNotIn("foo", context)
+ self.assertEqual(context.main_path, mozpath.abspath("foo.py"))
+ self.assertEqual(context.all_paths, set([mozpath.abspath("foo.py")]))
+
+ def test_exec_compile_error(self):
+ sandbox = self.sandbox()
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source("2f23;k;asfj", mozpath.abspath("foo.py"))
+
+ self.assertEqual(se.exception.file_stack, [mozpath.abspath("foo.py")])
+ self.assertIsInstance(se.exception.exc_value, SyntaxError)
+ self.assertEqual(sandbox._context.main_path, mozpath.abspath("foo.py"))
+
+ def test_exec_import_denied(self):
+ sandbox = self.sandbox()
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source("import sys")
+
+ self.assertIsInstance(se.exception, SandboxExecutionError)
+ self.assertEqual(se.exception.exc_type, ImportError)
+
+ def test_exec_source_multiple(self):
+ sandbox = self.sandbox()
+
+ sandbox.exec_source('DIRS = ["foo"]')
+ sandbox.exec_source('DIRS += ["bar"]')
+
+ self.assertEqual(sandbox["DIRS"], ["foo", "bar"])
+
+ def test_exec_source_illegal_key_set(self):
+ sandbox = self.sandbox()
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source("ILLEGAL = True")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, KeyError)
+
+ e = se.exception.exc_value
+ self.assertEqual(e.args[0], "global_ns")
+ self.assertEqual(e.args[1], "set_unknown")
+
+ def test_exec_source_reassign(self):
+ sandbox = self.sandbox()
+
+ sandbox.exec_source('DIRS = ["foo"]')
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source('DIRS = ["bar"]')
+
+ self.assertEqual(sandbox["DIRS"], ["foo"])
+ e = se.exception
+ self.assertIsInstance(e.exc_value, KeyError)
+
+ e = se.exception.exc_value
+ self.assertEqual(e.args[0], "global_ns")
+ self.assertEqual(e.args[1], "reassign")
+ self.assertEqual(e.args[2], "DIRS")
+
+ def test_exec_source_reassign_builtin(self):
+ sandbox = self.sandbox()
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source("sorted = 1")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, KeyError)
+
+ e = se.exception.exc_value
+ self.assertEqual(e.args[0], "Cannot reassign builtins")
+
+
+class TestedSandbox(MozbuildSandbox):
+ """Version of MozbuildSandbox with a little more convenience for testing.
+
+ It automatically normalizes paths given to exec_file and exec_source. This
+ helps simplify the test code.
+ """
+
+ def normalize_path(self, path):
+ return mozpath.normpath(mozpath.join(self._context.config.topsrcdir, path))
+
+ def source_path(self, path):
+ return SourcePath(self._context, path)
+
+ def exec_file(self, path):
+ super(TestedSandbox, self).exec_file(self.normalize_path(path))
+
+ def exec_source(self, source, path=""):
+ super(TestedSandbox, self).exec_source(
+ source, self.normalize_path(path) if path else ""
+ )
+
+
+class TestMozbuildSandbox(unittest.TestCase):
+ def sandbox(self, data_path=None, metadata={}):
+ config = None
+
+ if data_path is not None:
+ config = MockConfig(mozpath.join(test_data_path, data_path))
+ else:
+ config = MockConfig()
+
+ return TestedSandbox(Context(VARIABLES, config), metadata)
+
+ def test_default_state(self):
+ sandbox = self.sandbox()
+ sandbox._context.add_source(sandbox.normalize_path("moz.build"))
+ config = sandbox._context.config
+
+ self.assertEqual(sandbox["TOPSRCDIR"], config.topsrcdir)
+ self.assertEqual(sandbox["TOPOBJDIR"], config.topobjdir)
+ self.assertEqual(sandbox["RELATIVEDIR"], "")
+ self.assertEqual(sandbox["SRCDIR"], config.topsrcdir)
+ self.assertEqual(sandbox["OBJDIR"], config.topobjdir)
+
+ def test_symbol_presence(self):
+ # Ensure no discrepancies between the master symbol table and what's in
+ # the sandbox.
+ sandbox = self.sandbox()
+ sandbox._context.add_source(sandbox.normalize_path("moz.build"))
+
+ all_symbols = set()
+ all_symbols |= set(FUNCTIONS.keys())
+ all_symbols |= set(SPECIAL_VARIABLES.keys())
+
+ for symbol in all_symbols:
+ self.assertIsNotNone(sandbox[symbol])
+
+ def test_path_calculation(self):
+ sandbox = self.sandbox()
+ sandbox._context.add_source(sandbox.normalize_path("foo/bar/moz.build"))
+ config = sandbox._context.config
+
+ self.assertEqual(sandbox["TOPSRCDIR"], config.topsrcdir)
+ self.assertEqual(sandbox["TOPOBJDIR"], config.topobjdir)
+ self.assertEqual(sandbox["RELATIVEDIR"], "foo/bar")
+ self.assertEqual(sandbox["SRCDIR"], mozpath.join(config.topsrcdir, "foo/bar"))
+ self.assertEqual(sandbox["OBJDIR"], mozpath.join(config.topobjdir, "foo/bar"))
+
+ def test_config_access(self):
+ sandbox = self.sandbox()
+ config = sandbox._context.config
+
+ self.assertEqual(sandbox["CONFIG"]["MOZ_TRUE"], "1")
+ self.assertEqual(sandbox["CONFIG"]["MOZ_FOO"], config.substs["MOZ_FOO"])
+
+ # Access to an undefined substitution should return None.
+ self.assertNotIn("MISSING", sandbox["CONFIG"])
+ self.assertIsNone(sandbox["CONFIG"]["MISSING"])
+
+ # Should shouldn't be allowed to assign to the config.
+ with self.assertRaises(Exception):
+ sandbox["CONFIG"]["FOO"] = ""
+
+ def test_special_variables(self):
+ sandbox = self.sandbox()
+ sandbox._context.add_source(sandbox.normalize_path("moz.build"))
+
+ for k in SPECIAL_VARIABLES:
+ with self.assertRaises(KeyError):
+ sandbox[k] = 0
+
+ def test_exec_source_reassign_exported(self):
+ template_sandbox = self.sandbox(data_path="templates")
+
+ # Templates need to be defined in actual files because of
+ # inspect.getsourcelines.
+ template_sandbox.exec_file("templates.mozbuild")
+
+ config = MockConfig()
+
+ exports = {"DIST_SUBDIR": "browser"}
+
+ sandbox = TestedSandbox(
+ Context(VARIABLES, config),
+ metadata={
+ "exports": exports,
+ "templates": template_sandbox.templates,
+ },
+ )
+
+ self.assertEqual(sandbox["DIST_SUBDIR"], "browser")
+
+ # Templates should not interfere
+ sandbox.exec_source("Template([])", "foo.mozbuild")
+
+ sandbox.exec_source('DIST_SUBDIR = "foo"')
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source('DIST_SUBDIR = "bar"')
+
+ self.assertEqual(sandbox["DIST_SUBDIR"], "foo")
+ e = se.exception
+ self.assertIsInstance(e.exc_value, KeyError)
+
+ e = se.exception.exc_value
+ self.assertEqual(e.args[0], "global_ns")
+ self.assertEqual(e.args[1], "reassign")
+ self.assertEqual(e.args[2], "DIST_SUBDIR")
+
+ def test_include_basic(self):
+ sandbox = self.sandbox(data_path="include-basic")
+
+ sandbox.exec_file("moz.build")
+
+ self.assertEqual(
+ sandbox["DIRS"],
+ [
+ sandbox.source_path("foo"),
+ sandbox.source_path("bar"),
+ ],
+ )
+ self.assertEqual(
+ sandbox._context.main_path, sandbox.normalize_path("moz.build")
+ )
+ self.assertEqual(len(sandbox._context.all_paths), 2)
+
+ def test_include_outside_topsrcdir(self):
+ sandbox = self.sandbox(data_path="include-outside-topsrcdir")
+
+ with self.assertRaises(SandboxLoadError) as se:
+ sandbox.exec_file("relative.build")
+
+ self.assertEqual(
+ se.exception.illegal_path, sandbox.normalize_path("../moz.build")
+ )
+
+ def test_include_error_stack(self):
+ # Ensure the path stack is reported properly in exceptions.
+ sandbox = self.sandbox(data_path="include-file-stack")
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_file("moz.build")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, KeyError)
+
+ args = e.exc_value.args
+ self.assertEqual(args[0], "global_ns")
+ self.assertEqual(args[1], "set_unknown")
+ self.assertEqual(args[2], "ILLEGAL")
+
+ expected_stack = [
+ mozpath.join(sandbox._context.config.topsrcdir, p)
+ for p in ["moz.build", "included-1.build", "included-2.build"]
+ ]
+
+ self.assertEqual(e.file_stack, expected_stack)
+
+ def test_include_missing(self):
+ sandbox = self.sandbox(data_path="include-missing")
+
+ with self.assertRaises(SandboxLoadError) as sle:
+ sandbox.exec_file("moz.build")
+
+ self.assertIsNotNone(sle.exception.read_error)
+
+ def test_include_relative_from_child_dir(self):
+ # A relative path from a subdirectory should be relative from that
+ # child directory.
+ sandbox = self.sandbox(data_path="include-relative-from-child")
+ sandbox.exec_file("child/child.build")
+ self.assertEqual(sandbox["DIRS"], [sandbox.source_path("../foo")])
+
+ sandbox = self.sandbox(data_path="include-relative-from-child")
+ sandbox.exec_file("child/child2.build")
+ self.assertEqual(sandbox["DIRS"], [sandbox.source_path("../foo")])
+
+ def test_include_topsrcdir_relative(self):
+ # An absolute path for include() is relative to topsrcdir.
+
+ sandbox = self.sandbox(data_path="include-topsrcdir-relative")
+ sandbox.exec_file("moz.build")
+
+ self.assertEqual(sandbox["DIRS"], [sandbox.source_path("foo")])
+
+ def test_error(self):
+ sandbox = self.sandbox()
+
+ with self.assertRaises(SandboxCalledError) as sce:
+ sandbox.exec_source('error("This is an error.")')
+
+ e = sce.exception.message
+ self.assertIn("This is an error.", str(e))
+
+ def test_substitute_config_files(self):
+ sandbox = self.sandbox()
+ sandbox._context.add_source(sandbox.normalize_path("moz.build"))
+
+ sandbox.exec_source('CONFIGURE_SUBST_FILES += ["bar", "foo"]')
+ self.assertEqual(sandbox["CONFIGURE_SUBST_FILES"], ["bar", "foo"])
+ for item in sandbox["CONFIGURE_SUBST_FILES"]:
+ self.assertIsInstance(item, SourcePath)
+
+ def test_invalid_exports_set_base(self):
+ sandbox = self.sandbox()
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source('EXPORTS = "foo.h"')
+
+ self.assertEqual(se.exception.exc_type, ValueError)
+
+ def test_templates(self):
+ sandbox = self.sandbox(data_path="templates")
+
+ # Templates need to be defined in actual files because of
+ # inspect.getsourcelines.
+ sandbox.exec_file("templates.mozbuild")
+
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+Template([
+ 'foo.cpp',
+])
+"""
+ sandbox2.exec_source(source, "foo.mozbuild")
+
+ self.assertEqual(
+ sandbox2._context,
+ {
+ "SOURCES": ["foo.cpp"],
+ "DIRS": [],
+ },
+ )
+
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+SOURCES += ['qux.cpp']
+Template([
+ 'bar.cpp',
+ 'foo.cpp',
+],[
+ 'foo',
+])
+SOURCES += ['hoge.cpp']
+"""
+ sandbox2.exec_source(source, "foo.mozbuild")
+
+ self.assertEqual(
+ sandbox2._context,
+ {
+ "SOURCES": ["qux.cpp", "bar.cpp", "foo.cpp", "hoge.cpp"],
+ "DIRS": [sandbox2.source_path("foo")],
+ },
+ )
+
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+TemplateError([
+ 'foo.cpp',
+])
+"""
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox2.exec_source(source, "foo.mozbuild")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, KeyError)
+
+ e = se.exception.exc_value
+ self.assertEqual(e.args[0], "global_ns")
+ self.assertEqual(e.args[1], "set_unknown")
+
+ # TemplateGlobalVariable tries to access 'illegal' but that is expected
+ # to throw.
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+illegal = True
+TemplateGlobalVariable()
+"""
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox2.exec_source(source, "foo.mozbuild")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, NameError)
+
+ # TemplateGlobalUPPERVariable sets SOURCES with DIRS, but the context
+ # used when running the template is not expected to access variables
+ # from the global context.
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+DIRS += ['foo']
+TemplateGlobalUPPERVariable()
+"""
+ sandbox2.exec_source(source, "foo.mozbuild")
+ self.assertEqual(
+ sandbox2._context,
+ {
+ "SOURCES": [],
+ "DIRS": [sandbox2.source_path("foo")],
+ },
+ )
+
+ # However, the result of the template is mixed with the global
+ # context.
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+SOURCES += ['qux.cpp']
+TemplateInherit([
+ 'bar.cpp',
+ 'foo.cpp',
+])
+SOURCES += ['hoge.cpp']
+"""
+ sandbox2.exec_source(source, "foo.mozbuild")
+
+ self.assertEqual(
+ sandbox2._context,
+ {
+ "SOURCES": ["qux.cpp", "bar.cpp", "foo.cpp", "hoge.cpp"],
+ "USE_LIBS": ["foo"],
+ "DIRS": [],
+ },
+ )
+
+ # Template names must be CamelCase. Here, we can define the template
+ # inline because the error happens before inspect.getsourcelines.
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+@template
+def foo():
+ pass
+"""
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox2.exec_source(source, "foo.mozbuild")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, NameError)
+
+ e = se.exception.exc_value
+ self.assertIn("Template function names must be CamelCase.", str(e))
+
+ # Template names must not already be registered.
+ sandbox2 = self.sandbox(metadata={"templates": sandbox.templates})
+ source = """
+@template
+def Template():
+ pass
+"""
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox2.exec_source(source, "foo.mozbuild")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, KeyError)
+
+ e = se.exception.exc_value
+ self.assertIn(
+ 'A template named "Template" was already declared in %s.'
+ % sandbox.normalize_path("templates.mozbuild"),
+ str(e),
+ )
+
+ def test_function_args(self):
+ class Foo(int):
+ pass
+
+ def foo(a, b):
+ return type(a), type(b)
+
+ FUNCTIONS.update(
+ {
+ "foo": (lambda self: foo, (Foo, int), ""),
+ }
+ )
+
+ try:
+ sandbox = self.sandbox()
+ source = 'foo("a", "b")'
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source(source, "foo.mozbuild")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, ValueError)
+
+ sandbox = self.sandbox()
+ source = 'foo(1, "b")'
+
+ with self.assertRaises(SandboxExecutionError) as se:
+ sandbox.exec_source(source, "foo.mozbuild")
+
+ e = se.exception
+ self.assertIsInstance(e.exc_value, ValueError)
+
+ sandbox = self.sandbox()
+ source = "a = foo(1, 2)"
+ sandbox.exec_source(source, "foo.mozbuild")
+
+ self.assertEqual(sandbox["a"], (Foo, int))
+ finally:
+ del FUNCTIONS["foo"]
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/python.ini b/python/mozbuild/mozbuild/test/python.ini
new file mode 100644
index 0000000000..b55612c43b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/python.ini
@@ -0,0 +1,64 @@
+[DEFAULT]
+subsuite = mozbuild
+
+[action/test_buildlist.py]
+[action/test_html_fragment_preprocessor.py]
+[action/test_langpack_manifest.py]
+[action/test_node.py]
+[action/test_process_install_manifest.py]
+[backend/test_fastermake.py]
+[backend/test_recursivemake.py]
+[backend/test_build.py]
+[backend/test_database.py]
+[backend/test_configenvironment.py]
+[backend/test_partialconfigenvironment.py]
+[backend/test_test_manifest.py]
+[backend/test_visualstudio.py]
+[code_analysis/test_mach_commands.py]
+[codecoverage/test_lcov_rewrite.py]
+[compilation/test_warnings.py]
+[configure/lint.py]
+[configure/test_bootstrap.py]
+[configure/test_checks_configure.py]
+[configure/test_compile_checks.py]
+[configure/test_configure.py]
+[configure/test_lint.py]
+[configure/test_moz_configure.py]
+[configure/test_options.py]
+[configure/test_toolchain_configure.py]
+[configure/test_toolchain_helpers.py]
+[configure/test_toolkit_moz_configure.py]
+[configure/test_util.py]
+[controller/test_ccachestats.py]
+[controller/test_clobber.py]
+[frontend/test_context.py]
+[frontend/test_emitter.py]
+[frontend/test_namespaces.py]
+[frontend/test_reader.py]
+[frontend/test_sandbox.py]
+[repackaging/test_deb.py]
+[test_artifact_cache.py]
+[test_artifacts.py]
+[test_base.py]
+[test_containers.py]
+[test_dotproperties.py]
+[test_expression.py]
+[test_jarmaker.py]
+[test_licenses.py]
+[test_line_endings.py]
+[test_makeutil.py]
+[test_manifest.py]
+[test_mozconfig.py]
+[test_mozinfo.py]
+[test_preprocessor.py]
+[test_pythonutil.py]
+[test_rewrite_mozbuild.py]
+[test_telemetry.py]
+[test_telemetry_settings.py]
+[test_util.py]
+[test_util_fileavoidwrite.py]
+[test_vendor.py]
+skip-if = true # Bug 1765416
+requirements = python/mozbuild/mozbuild/test/vendor_requirements.txt
+[test_vendor_tools.py]
+skip-if = os == "win" # Windows doesn't have the same path seperator as linux, and we just don't need to run it there
diff --git a/python/mozbuild/mozbuild/test/repackaging/test_deb.py b/python/mozbuild/mozbuild/test/repackaging/test_deb.py
new file mode 100644
index 0000000000..477f7ea346
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/repackaging/test_deb.py
@@ -0,0 +1,551 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import datetime
+import json
+import logging
+import os
+import tarfile
+import tempfile
+import zipfile
+from contextlib import nullcontext as does_not_raise
+from io import StringIO
+from unittest.mock import MagicMock, Mock, call
+
+import mozpack.path as mozpath
+import mozunit
+import pytest
+
+from mozbuild.repackaging import deb
+
+_APPLICATION_INI_CONTENT = """[App]
+Vendor=Mozilla
+Name=Firefox
+RemotingName=firefox-nightly-try
+CodeName=Firefox Nightly
+BuildID=20230222000000
+"""
+
+_APPLICATION_INI_CONTENT_DATA = {
+ "name": "Firefox",
+ "display_name": "Firefox Nightly",
+ "vendor": "Mozilla",
+ "remoting_name": "firefox-nightly-try",
+ "build_id": "20230222000000",
+ "timestamp": datetime.datetime(2023, 2, 22),
+}
+
+
+@pytest.mark.parametrize(
+ "number_of_application_ini_files, expectaction, expected_result",
+ (
+ (0, pytest.raises(ValueError), None),
+ (1, does_not_raise(), _APPLICATION_INI_CONTENT_DATA),
+ (2, pytest.raises(ValueError), None),
+ ),
+)
+def test_extract_application_ini_data(
+ number_of_application_ini_files, expectaction, expected_result
+):
+ with tempfile.TemporaryDirectory() as d:
+ tar_path = os.path.join(d, "input.tar")
+ with tarfile.open(tar_path, "w") as tar:
+ application_ini_path = os.path.join(d, "application.ini")
+ with open(application_ini_path, "w") as application_ini_file:
+ application_ini_file.write(_APPLICATION_INI_CONTENT)
+
+ for i in range(number_of_application_ini_files):
+ tar.add(application_ini_path, f"{i}/application.ini")
+
+ with expectaction:
+ assert deb._extract_application_ini_data(tar_path) == expected_result
+
+
+def test_extract_application_ini_data_from_directory():
+ with tempfile.TemporaryDirectory() as d:
+ with open(os.path.join(d, "application.ini"), "w") as f:
+ f.write(_APPLICATION_INI_CONTENT)
+
+ assert (
+ deb._extract_application_ini_data_from_directory(d)
+ == _APPLICATION_INI_CONTENT_DATA
+ )
+
+
+@pytest.mark.parametrize(
+ "version, build_number, package_name_suffix, description_suffix, expected",
+ (
+ (
+ "112.0a1",
+ 1,
+ "",
+ "",
+ {
+ "DEB_DESCRIPTION": "Mozilla Firefox",
+ "DEB_PKG_INSTALL_PATH": "usr/lib/firefox-nightly-try",
+ "DEB_PKG_NAME": "firefox-nightly-try",
+ "DEB_PKG_VERSION": "112.0a1~20230222000000",
+ },
+ ),
+ (
+ "112.0a1",
+ 1,
+ "-l10n-fr",
+ " - Language pack for Firefox Nightly for fr",
+ {
+ "DEB_DESCRIPTION": "Mozilla Firefox - Language pack for Firefox Nightly for fr",
+ "DEB_PKG_INSTALL_PATH": "usr/lib/firefox-nightly-try",
+ "DEB_PKG_NAME": "firefox-nightly-try-l10n-fr",
+ "DEB_PKG_VERSION": "112.0a1~20230222000000",
+ },
+ ),
+ (
+ "112.0b1",
+ 1,
+ "",
+ "",
+ {
+ "DEB_DESCRIPTION": "Mozilla Firefox",
+ "DEB_PKG_INSTALL_PATH": "usr/lib/firefox-nightly-try",
+ "DEB_PKG_NAME": "firefox-nightly-try",
+ "DEB_PKG_VERSION": "112.0b1~build1",
+ },
+ ),
+ (
+ "112.0",
+ 2,
+ "",
+ "",
+ {
+ "DEB_DESCRIPTION": "Mozilla Firefox",
+ "DEB_PKG_INSTALL_PATH": "usr/lib/firefox-nightly-try",
+ "DEB_PKG_NAME": "firefox-nightly-try",
+ "DEB_PKG_VERSION": "112.0~build2",
+ },
+ ),
+ ),
+)
+def test_get_build_variables(
+ version, build_number, package_name_suffix, description_suffix, expected
+):
+ application_ini_data = {
+ "name": "Firefox",
+ "display_name": "Firefox",
+ "vendor": "Mozilla",
+ "remoting_name": "firefox-nightly-try",
+ "build_id": "20230222000000",
+ "timestamp": datetime.datetime(2023, 2, 22),
+ }
+ assert deb._get_build_variables(
+ application_ini_data,
+ "x86",
+ version,
+ build_number,
+ depends="${shlibs:Depends},",
+ package_name_suffix=package_name_suffix,
+ description_suffix=description_suffix,
+ ) == {
+ **{
+ "DEB_CHANGELOG_DATE": "Wed, 22 Feb 2023 00:00:00 -0000",
+ "DEB_ARCH_NAME": "i386",
+ "DEB_DEPENDS": "${shlibs:Depends},",
+ },
+ **expected,
+ }
+
+
+def test_copy_plain_deb_config(monkeypatch):
+ def mock_listdir(dir):
+ assert dir == "/template_dir"
+ return [
+ "/template_dir/debian_file1.in",
+ "/template_dir/debian_file2.in",
+ "/template_dir/debian_file3",
+ "/template_dir/debian_file4",
+ ]
+
+ monkeypatch.setattr(deb.os, "listdir", mock_listdir)
+
+ def mock_makedirs(dir, exist_ok):
+ assert dir == "/source_dir/debian"
+ assert exist_ok is True
+
+ monkeypatch.setattr(deb.os, "makedirs", mock_makedirs)
+
+ mock_copy = MagicMock()
+ monkeypatch.setattr(deb.shutil, "copy", mock_copy)
+
+ deb._copy_plain_deb_config("/template_dir", "/source_dir")
+ assert mock_copy.call_args_list == [
+ call("/template_dir/debian_file3", "/source_dir/debian/debian_file3"),
+ call("/template_dir/debian_file4", "/source_dir/debian/debian_file4"),
+ ]
+
+
+def test_render_deb_templates():
+ with tempfile.TemporaryDirectory() as template_dir, tempfile.TemporaryDirectory() as source_dir:
+ with open(os.path.join(template_dir, "debian_file1.in"), "w") as f:
+ f.write("${some_build_variable}")
+
+ with open(os.path.join(template_dir, "debian_file2.in"), "w") as f:
+ f.write("Some hardcoded value")
+
+ with open(os.path.join(template_dir, "ignored_file.in"), "w") as f:
+ f.write("Must not be copied")
+
+ deb._render_deb_templates(
+ template_dir,
+ source_dir,
+ {"some_build_variable": "some_value"},
+ exclude_file_names=["ignored_file.in"],
+ )
+
+ with open(os.path.join(source_dir, "debian", "debian_file1")) as f:
+ assert f.read() == "some_value"
+
+ with open(os.path.join(source_dir, "debian", "debian_file2")) as f:
+ assert f.read() == "Some hardcoded value"
+
+ assert not os.path.exists(os.path.join(source_dir, "debian", "ignored_file"))
+ assert not os.path.exists(os.path.join(source_dir, "debian", "ignored_file.in"))
+
+
+def test_inject_deb_distribution_folder(monkeypatch):
+ def mock_check_call(command):
+ global clone_dir
+ clone_dir = command[-1]
+ os.makedirs(os.path.join(clone_dir, "desktop/deb/distribution"))
+
+ monkeypatch.setattr(deb.subprocess, "check_call", mock_check_call)
+
+ def mock_copytree(source_tree, destination_tree):
+ global clone_dir
+ assert source_tree == mozpath.join(clone_dir, "desktop/deb/distribution")
+ assert destination_tree == "/source_dir/firefox/distribution"
+
+ monkeypatch.setattr(deb.shutil, "copytree", mock_copytree)
+
+ deb._inject_deb_distribution_folder("/source_dir", "Firefox")
+
+
+ZH_TW_FTL = """\
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+# These messages are used by the Firefox ".desktop" file on Linux.
+# https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
+
+# The entry name is the label on the desktop icon, among other things.
+desktop-entry-name = { -brand-shortcut-name }
+# The comment usually appears as a tooltip when hovering over application menu entry.
+desktop-entry-comment = ç€è¦½å…¨çƒè³‡è¨Šç¶²
+desktop-entry-generic-name = 網é ç€è¦½å™¨
+# Keywords are search terms used to find this application.
+# The string is a list of keywords separated by semicolons:
+# - Do NOT replace semicolons with other punctuation signs.
+# - The list MUST end with a semicolon.
+desktop-entry-keywords = 網際網路;網路;ç€è¦½å™¨;網é ;上網;Internet;WWW;Browser;Web;Explorer;
+
+## Actions are visible in a context menu after right clicking the
+## taskbar icon, possibly other places depending on the environment.
+
+desktop-action-new-window-name = 開新視窗
+desktop-action-new-private-window-name = é–‹æ–°éš±ç§è¦–窗
+"""
+
+DESKTOP_ENTRY_FILE_TEXT = """\
+[Desktop Entry]
+Version=1.0
+Type=Application
+Exec=firefox-nightly %u
+Terminal=false
+X-MultipleArgs=false
+Icon=firefox-nightly
+StartupWMClass=firefox-nightly
+Categories=GNOME;GTK;Network;WebBrowser;
+MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;
+StartupNotify=true
+Actions=new-window;new-private-window;open-profile-manager;
+Name=en-US-desktop-entry-name
+Name[zh_TW]=zh-TW-desktop-entry-name
+Comment=en-US-desktop-entry-comment
+Comment[zh_TW]=zh-TW-desktop-entry-comment
+GenericName=en-US-desktop-entry-generic-name
+GenericName[zh_TW]=zh-TW-desktop-entry-generic-name
+Keywords=en-US-desktop-entry-keywords
+Keywords[zh_TW]=zh-TW-desktop-entry-keywords
+X-GNOME-FullName=en-US-desktop-entry-x-gnome-full-name
+X-GNOME-FullName[zh_TW]=zh-TW-desktop-entry-x-gnome-full-name
+
+[Desktop Action new-window]
+Exec=firefox-nightly --new-window %u
+Name=en-US-desktop-action-new-window-name
+Name[zh_TW]=zh-TW-desktop-action-new-window-name
+
+[Desktop Action new-private-window]
+Exec=firefox-nightly --private-window %u
+Name=en-US-desktop-action-new-private-window-name
+Name[zh_TW]=zh-TW-desktop-action-new-private-window-name
+
+[Desktop Action open-profile-manager]
+Exec=firefox-nightly --ProfileManager
+Name=en-US-desktop-action-open-profile-manager
+Name[zh_TW]=zh-TW-desktop-action-open-profile-manager
+"""
+
+
+def test_generate_deb_desktop_entry_file_text(monkeypatch):
+ def responsive(url):
+ if "zh-TW" in url:
+ return Mock(
+ **{
+ "status_code": 200,
+ "text": ZH_TW_FTL,
+ }
+ )
+ return Mock(**{"status_code": 404})
+
+ monkeypatch.setattr(deb.requests, "get", responsive)
+
+ output_stream = StringIO()
+ logger = logging.getLogger("mozbuild:test:repackaging")
+ logger.setLevel(logging.DEBUG)
+ stream_handler = logging.StreamHandler(output_stream)
+ logger.addHandler(stream_handler)
+
+ def log(level, action, params, format_str):
+ logger.log(
+ level,
+ format_str.format(**params),
+ extra={"action": action, "params": params},
+ )
+
+ build_variables = {
+ "DEB_PKG_NAME": "firefox-nightly",
+ }
+ release_product = "firefox"
+ release_type = "nightly"
+
+ def fluent_localization(locales, resources, loader):
+ def format_value(resource):
+ return f"{locales[0]}-{resource}"
+
+ return Mock(**{"format_value": format_value})
+
+ fluent_resource_loader = Mock()
+
+ desktop_entry_file_text = deb._generate_browser_desktop_entry_file_text(
+ log,
+ build_variables,
+ release_product,
+ release_type,
+ fluent_localization,
+ fluent_resource_loader,
+ )
+
+ assert desktop_entry_file_text == DESKTOP_ENTRY_FILE_TEXT
+
+ def outage(url):
+ return Mock(**{"status_code": 500})
+
+ monkeypatch.setattr(deb.requests, "get", outage)
+
+ with pytest.raises(deb.HgServerError):
+ desktop_entry_file_text = deb._generate_browser_desktop_entry_file_text(
+ log,
+ build_variables,
+ release_product,
+ release_type,
+ fluent_localization,
+ fluent_resource_loader,
+ )
+
+
+@pytest.mark.parametrize(
+ "does_path_exits, expectation",
+ (
+ (True, does_not_raise()),
+ (False, pytest.raises(deb.NoDebPackageFound)),
+ ),
+)
+def test_generate_deb_archive(
+ monkeypatch,
+ does_path_exits,
+ expectation,
+):
+ monkeypatch.setattr(deb, "_get_command", lambda _: ["mock_command"])
+ monkeypatch.setattr(deb.subprocess, "check_call", lambda *_, **__: None)
+
+ def mock_exists(path):
+ assert path == "/target_dir/firefox_111.0_amd64.deb"
+ return does_path_exits
+
+ monkeypatch.setattr(deb.os.path, "exists", mock_exists)
+
+ def mock_move(source_path, destination_path):
+ assert source_path == "/target_dir/firefox_111.0_amd64.deb"
+ assert destination_path == "/output/target.deb"
+
+ monkeypatch.setattr(deb.shutil, "move", mock_move)
+
+ with expectation:
+ deb._generate_deb_archive(
+ source_dir="/source_dir",
+ target_dir="/target_dir",
+ output_file_path="/output/target.deb",
+ build_variables={
+ "DEB_PKG_NAME": "firefox",
+ "DEB_PKG_VERSION": "111.0",
+ },
+ arch="x86_64",
+ )
+
+
+@pytest.mark.parametrize(
+ "arch, is_chroot_available, expected",
+ (
+ (
+ "all",
+ True,
+ [
+ "chroot",
+ "/srv/jessie-amd64",
+ "bash",
+ "-c",
+ "cd /tmp/*/source; dpkg-buildpackage -us -uc -b",
+ ],
+ ),
+ ("all", False, ["dpkg-buildpackage", "-us", "-uc", "-b"]),
+ (
+ "x86",
+ True,
+ [
+ "chroot",
+ "/srv/jessie-i386",
+ "bash",
+ "-c",
+ "cd /tmp/*/source; dpkg-buildpackage -us -uc -b --host-arch=i386",
+ ],
+ ),
+ ("x86", False, ["dpkg-buildpackage", "-us", "-uc", "-b", "--host-arch=i386"]),
+ (
+ "x86_64",
+ True,
+ [
+ "chroot",
+ "/srv/jessie-amd64",
+ "bash",
+ "-c",
+ "cd /tmp/*/source; dpkg-buildpackage -us -uc -b --host-arch=amd64",
+ ],
+ ),
+ (
+ "x86_64",
+ False,
+ ["dpkg-buildpackage", "-us", "-uc", "-b", "--host-arch=amd64"],
+ ),
+ ),
+)
+def test_get_command(monkeypatch, arch, is_chroot_available, expected):
+ monkeypatch.setattr(deb, "_is_chroot_available", lambda _: is_chroot_available)
+ assert deb._get_command(arch) == expected
+
+
+@pytest.mark.parametrize(
+ "arch, does_dir_exist, expected_path, expected_result",
+ (
+ ("all", False, "/srv/jessie-amd64", False),
+ ("all", True, "/srv/jessie-amd64", True),
+ ("x86", False, "/srv/jessie-i386", False),
+ ("x86_64", False, "/srv/jessie-amd64", False),
+ ("x86", True, "/srv/jessie-i386", True),
+ ("x86_64", True, "/srv/jessie-amd64", True),
+ ),
+)
+def test_is_chroot_available(
+ monkeypatch, arch, does_dir_exist, expected_path, expected_result
+):
+ def _mock_is_dir(path):
+ assert path == expected_path
+ return does_dir_exist
+
+ monkeypatch.setattr(deb.os.path, "isdir", _mock_is_dir)
+ assert deb._is_chroot_available(arch) == expected_result
+
+
+@pytest.mark.parametrize(
+ "arch, expected",
+ (
+ ("all", "/srv/jessie-amd64"),
+ ("x86", "/srv/jessie-i386"),
+ ("x86_64", "/srv/jessie-amd64"),
+ ),
+)
+def test_get_chroot_path(arch, expected):
+ assert deb._get_chroot_path(arch) == expected
+
+
+_MANIFEST_JSON_DATA = {
+ "langpack_id": "fr",
+ "manifest_version": 2,
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "langpack-fr@devedition.mozilla.org",
+ "strict_min_version": "112.0a1",
+ "strict_max_version": "112.0a1",
+ }
+ },
+ "name": "Language: Français (French)",
+ "description": "Firefox Developer Edition Language Pack for Français (fr) – French",
+ "version": "112.0.20230227.181253",
+ "languages": {
+ "fr": {
+ "version": "20230223164410",
+ "chrome_resources": {
+ "app-marketplace-icons": "browser/chrome/browser/locale/fr/app-marketplace-icons/",
+ "branding": "browser/chrome/fr/locale/branding/",
+ "browser": "browser/chrome/fr/locale/browser/",
+ "browser-region": "browser/chrome/fr/locale/browser-region/",
+ "devtools": "browser/chrome/fr/locale/fr/devtools/client/",
+ "devtools-shared": "browser/chrome/fr/locale/fr/devtools/shared/",
+ "formautofill": "browser/features/formautofill@mozilla.org/fr/locale/fr/",
+ "report-site-issue": "browser/features/webcompat-reporter@mozilla.org/fr/locale/fr/",
+ "alerts": "chrome/fr/locale/fr/alerts/",
+ "autoconfig": "chrome/fr/locale/fr/autoconfig/",
+ "global": "chrome/fr/locale/fr/global/",
+ "global-platform": {
+ "macosx": "chrome/fr/locale/fr/global-platform/mac/",
+ "linux": "chrome/fr/locale/fr/global-platform/unix/",
+ "android": "chrome/fr/locale/fr/global-platform/unix/",
+ "win": "chrome/fr/locale/fr/global-platform/win/",
+ },
+ "mozapps": "chrome/fr/locale/fr/mozapps/",
+ "necko": "chrome/fr/locale/fr/necko/",
+ "passwordmgr": "chrome/fr/locale/fr/passwordmgr/",
+ "pdf.js": "chrome/fr/locale/pdfviewer/",
+ "pipnss": "chrome/fr/locale/fr/pipnss/",
+ "pippki": "chrome/fr/locale/fr/pippki/",
+ "places": "chrome/fr/locale/fr/places/",
+ "weave": "chrome/fr/locale/fr/services/",
+ },
+ }
+ },
+ "sources": {"browser": {"base_path": "browser/"}},
+ "author": "mozfr.org (contributors: L’équipe francophone)",
+}
+
+
+def test_extract_langpack_metadata():
+ with tempfile.TemporaryDirectory() as d:
+ langpack_path = os.path.join(d, "langpack.xpi")
+ with zipfile.ZipFile(langpack_path, "w") as zip:
+ zip.writestr("manifest.json", json.dumps(_MANIFEST_JSON_DATA))
+
+ assert deb._extract_langpack_metadata(langpack_path) == _MANIFEST_JSON_DATA
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_android_version_code.py b/python/mozbuild/mozbuild/test/test_android_version_code.py
new file mode 100644
index 0000000000..7600ebe0d8
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_android_version_code.py
@@ -0,0 +1,111 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import unittest
+
+from mozunit import main
+
+from mozbuild.android_version_code import (
+ android_version_code_v0,
+ android_version_code_v1,
+)
+
+
+class TestAndroidVersionCode(unittest.TestCase):
+ def test_android_version_code_v0(self):
+ # From https://treeherder.mozilla.org/#/jobs?repo=mozilla-central&revision=e25de9972a77.
+ buildid = "20150708104620"
+ arm_api9 = 2015070819
+ arm_api11 = 2015070821
+ x86_api9 = 2015070822
+ self.assertEqual(
+ android_version_code_v0(
+ buildid, cpu_arch="armeabi", min_sdk=9, max_sdk=None
+ ),
+ arm_api9,
+ )
+ self.assertEqual(
+ android_version_code_v0(
+ buildid, cpu_arch="armeabi-v7a", min_sdk=11, max_sdk=None
+ ),
+ arm_api11,
+ )
+ self.assertEqual(
+ android_version_code_v0(buildid, cpu_arch="x86", min_sdk=9, max_sdk=None),
+ x86_api9,
+ )
+
+ def test_android_version_code_v1(self):
+ buildid = "20150825141628"
+ arm_api16 = 0b01111000001000000001001001110001
+ arm64_api21 = 0b01111000001000000001001001110100
+ x86_api9 = 0b01111000001000000001001001110100
+ self.assertEqual(
+ android_version_code_v1(
+ buildid, cpu_arch="armeabi-v7a", min_sdk=16, max_sdk=None
+ ),
+ arm_api16,
+ )
+ self.assertEqual(
+ android_version_code_v1(
+ buildid, cpu_arch="arm64-v8a", min_sdk=21, max_sdk=None
+ ),
+ arm64_api21,
+ )
+ self.assertEqual(
+ android_version_code_v1(buildid, cpu_arch="x86", min_sdk=9, max_sdk=None),
+ x86_api9,
+ )
+
+ def test_android_version_code_v1_underflow(self):
+ """Verify that it is an error to ask for v1 codes predating the cutoff."""
+ buildid = "201508010000" # Earliest possible.
+ arm_api9 = 0b01111000001000000000000000000000
+ self.assertEqual(
+ android_version_code_v1(
+ buildid, cpu_arch="armeabi", min_sdk=9, max_sdk=None
+ ),
+ arm_api9,
+ )
+ with self.assertRaises(ValueError) as cm:
+ underflow = "201507310000" # Latest possible (valid) underflowing date.
+ android_version_code_v1(
+ underflow, cpu_arch="armeabi", min_sdk=9, max_sdk=None
+ )
+ self.assertTrue("underflow" in cm.exception.message)
+
+ def test_android_version_code_v1_running_low(self):
+ """Verify there is an informative message if one asks for v1
+ codes that are close to overflow."""
+ with self.assertRaises(ValueError) as cm:
+ overflow = "20290801000000"
+ android_version_code_v1(
+ overflow, cpu_arch="armeabi", min_sdk=9, max_sdk=None
+ )
+ self.assertTrue("Running out of low order bits" in cm.exception.message)
+
+ def test_android_version_code_v1_overflow(self):
+ """Verify that it is an error to ask for v1 codes that actually does overflow."""
+ with self.assertRaises(ValueError) as cm:
+ overflow = "20310801000000"
+ android_version_code_v1(
+ overflow, cpu_arch="armeabi", min_sdk=9, max_sdk=None
+ )
+ self.assertTrue("overflow" in cm.exception.message)
+
+ def test_android_version_code_v0_relative_v1(self):
+ """Verify that the first v1 code is greater than the equivalent v0 code."""
+ buildid = "20150801000000"
+ self.assertGreater(
+ android_version_code_v1(
+ buildid, cpu_arch="armeabi", min_sdk=9, max_sdk=None
+ ),
+ android_version_code_v0(
+ buildid, cpu_arch="armeabi", min_sdk=9, max_sdk=None
+ ),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_artifact_cache.py b/python/mozbuild/mozbuild/test/test_artifact_cache.py
new file mode 100644
index 0000000000..d12d150183
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_artifact_cache.py
@@ -0,0 +1,145 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import time
+import unittest
+from shutil import rmtree
+from tempfile import mkdtemp
+
+import mozunit
+
+from mozbuild import artifact_cache
+from mozbuild.artifact_cache import ArtifactCache
+
+CONTENTS = {
+ "http://server/foo": b"foo",
+ "http://server/bar": b"bar" * 400,
+ "http://server/qux": b"qux" * 400,
+ "http://server/fuga": b"fuga" * 300,
+ "http://server/hoge": b"hoge" * 300,
+ "http://server/larger": b"larger" * 3000,
+}
+
+
+class FakeResponse(object):
+ def __init__(self, content):
+ self._content = content
+
+ @property
+ def headers(self):
+ return {"Content-length": str(len(self._content))}
+
+ def iter_content(self, chunk_size):
+ content = memoryview(self._content)
+ while content:
+ yield content[:chunk_size]
+ content = content[chunk_size:]
+
+ def raise_for_status(self):
+ pass
+
+ def close(self):
+ pass
+
+
+class FakeSession(object):
+ def get(self, url, stream=True):
+ assert stream is True
+ return FakeResponse(CONTENTS[url])
+
+
+class TestArtifactCache(unittest.TestCase):
+ def setUp(self):
+ self.min_cached_artifacts = artifact_cache.MIN_CACHED_ARTIFACTS
+ self.max_cached_artifacts_size = artifact_cache.MAX_CACHED_ARTIFACTS_SIZE
+ artifact_cache.MIN_CACHED_ARTIFACTS = 2
+ artifact_cache.MAX_CACHED_ARTIFACTS_SIZE = 4096
+
+ self._real_utime = os.utime
+ os.utime = self.utime
+ self.timestamp = time.time() - 86400
+
+ self.tmpdir = mkdtemp()
+
+ def tearDown(self):
+ rmtree(self.tmpdir)
+ artifact_cache.MIN_CACHED_ARTIFACTS = self.min_cached_artifacts
+ artifact_cache.MAX_CACHED_ARTIFACTS_SIZE = self.max_cached_artifacts_size
+ os.utime = self._real_utime
+
+ def utime(self, path, times):
+ if times is None:
+ # Ensure all downloaded files have a different timestamp
+ times = (self.timestamp, self.timestamp)
+ self.timestamp += 2
+ self._real_utime(path, times)
+
+ def listtmpdir(self):
+ return [p for p in os.listdir(self.tmpdir) if p != ".metadata_never_index"]
+
+ def test_artifact_cache_persistence(self):
+ cache = ArtifactCache(self.tmpdir)
+ cache._download_manager.session = FakeSession()
+
+ path = cache.fetch("http://server/foo")
+ expected = [os.path.basename(path)]
+ self.assertEqual(self.listtmpdir(), expected)
+
+ path = cache.fetch("http://server/bar")
+ expected.append(os.path.basename(path))
+ self.assertEqual(sorted(self.listtmpdir()), sorted(expected))
+
+ # We're downloading more than the cache allows us, but since it's all
+ # in the same session, no purge happens.
+ path = cache.fetch("http://server/qux")
+ expected.append(os.path.basename(path))
+ self.assertEqual(sorted(self.listtmpdir()), sorted(expected))
+
+ path = cache.fetch("http://server/fuga")
+ expected.append(os.path.basename(path))
+ self.assertEqual(sorted(self.listtmpdir()), sorted(expected))
+
+ cache = ArtifactCache(self.tmpdir)
+ cache._download_manager.session = FakeSession()
+
+ # Downloading a new file in a new session purges the oldest files in
+ # the cache.
+ path = cache.fetch("http://server/hoge")
+ expected.append(os.path.basename(path))
+ expected = expected[2:]
+ self.assertEqual(sorted(self.listtmpdir()), sorted(expected))
+
+ # Downloading a file already in the cache leaves the cache untouched
+ cache = ArtifactCache(self.tmpdir)
+ cache._download_manager.session = FakeSession()
+
+ path = cache.fetch("http://server/qux")
+ self.assertEqual(sorted(self.listtmpdir()), sorted(expected))
+
+ # bar was purged earlier, re-downloading it should purge the oldest
+ # downloaded file, which at this point would be qux, but we also
+ # re-downloaded it in the mean time, so the next one (fuga) should be
+ # the purged one.
+ cache = ArtifactCache(self.tmpdir)
+ cache._download_manager.session = FakeSession()
+
+ path = cache.fetch("http://server/bar")
+ expected.append(os.path.basename(path))
+ expected = [p for p in expected if "fuga" not in p]
+ self.assertEqual(sorted(self.listtmpdir()), sorted(expected))
+
+ # Downloading one file larger than the cache size should still leave
+ # MIN_CACHED_ARTIFACTS files.
+ cache = ArtifactCache(self.tmpdir)
+ cache._download_manager.session = FakeSession()
+
+ path = cache.fetch("http://server/larger")
+ expected.append(os.path.basename(path))
+ expected = expected[-2:]
+ self.assertEqual(sorted(self.listtmpdir()), sorted(expected))
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_artifacts.py b/python/mozbuild/mozbuild/test/test_artifacts.py
new file mode 100644
index 0000000000..397b6dbdb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_artifacts.py
@@ -0,0 +1,115 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from unittest import TestCase
+
+import buildconfig
+import mozunit
+
+from mozbuild.artifacts import ArtifactJob, ThunderbirdMixin
+
+
+class FakeArtifactJob(ArtifactJob):
+ package_re = r""
+
+
+class TestArtifactJob(TestCase):
+ def _assert_candidate_trees(self, version_display, expected_trees):
+ buildconfig.substs["MOZ_APP_VERSION_DISPLAY"] = version_display
+
+ job = FakeArtifactJob()
+ self.assertGreater(len(job.candidate_trees), 0)
+ self.assertEqual(job.candidate_trees, expected_trees)
+
+ def test_candidate_trees_with_empty_file(self):
+ self._assert_candidate_trees(
+ version_display="", expected_trees=ArtifactJob.default_candidate_trees
+ )
+
+ def test_candidate_trees_with_beta_version(self):
+ self._assert_candidate_trees(
+ version_display="92.1b2", expected_trees=ArtifactJob.beta_candidate_trees
+ )
+
+ def test_candidate_trees_with_esr_version(self):
+ self._assert_candidate_trees(
+ version_display="91.3.0esr", expected_trees=ArtifactJob.esr_candidate_trees
+ )
+
+ def test_candidate_trees_with_nightly_version(self):
+ self._assert_candidate_trees(
+ version_display="95.0a1", expected_trees=ArtifactJob.nightly_candidate_trees
+ )
+
+ def test_candidate_trees_with_release_version(self):
+ self._assert_candidate_trees(
+ version_display="93.0.1", expected_trees=ArtifactJob.default_candidate_trees
+ )
+
+ def test_candidate_trees_with_newline_before_version(self):
+ self._assert_candidate_trees(
+ version_display="\n\n91.3.0esr",
+ expected_trees=ArtifactJob.esr_candidate_trees,
+ )
+
+ def test_property_is_cached(self):
+ job = FakeArtifactJob()
+ expected_trees = ArtifactJob.esr_candidate_trees
+
+ buildconfig.substs["MOZ_APP_VERSION_DISPLAY"] = "91.3.0.esr"
+ self.assertEqual(job.candidate_trees, expected_trees)
+ # Because the property is cached, changing the
+ # `MOZ_APP_VERSION_DISPLAY` won't have any impact.
+ buildconfig.substs["MOZ_APP_VERSION_DISPLAY"] = ""
+ self.assertEqual(job.candidate_trees, expected_trees)
+
+
+class FakeThunderbirdJob(ThunderbirdMixin, FakeArtifactJob):
+ pass
+
+
+class TestThunderbirdMixin(TestCase):
+ def _assert_candidate_trees(self, version_display, source_repo, expected_trees):
+ buildconfig.substs["MOZ_APP_VERSION_DISPLAY"] = version_display
+ buildconfig.substs["MOZ_SOURCE_REPO"] = source_repo
+
+ job = FakeThunderbirdJob()
+ self.assertGreater(len(job.candidate_trees), 0)
+ self.assertEqual(job.candidate_trees, expected_trees)
+
+ def test_candidate_trees_with_beta_version(self):
+ self._assert_candidate_trees(
+ version_display="92.1b2",
+ source_repo="https://hg.mozilla.org/releases/comm-beta",
+ expected_trees=ThunderbirdMixin.beta_candidate_trees,
+ )
+
+ def test_candidate_trees_with_esr_version(self):
+ self._assert_candidate_trees(
+ version_display="91.3.0",
+ source_repo="https://hg.mozilla.org/releases/comm-esr91",
+ expected_trees=ThunderbirdMixin.esr_candidate_trees,
+ )
+
+ def test_candidate_trees_with_nightly_version(self):
+ self._assert_candidate_trees(
+ version_display="95.0a1",
+ source_repo="https://hg.mozilla.org/comm-central",
+ expected_trees=ThunderbirdMixin.nightly_candidate_trees,
+ )
+
+ def test_property_is_cached(self):
+ job = FakeThunderbirdJob()
+ expected_trees = ThunderbirdMixin.esr_candidate_trees
+
+ buildconfig.substs["MOZ_APP_VERSION_DISPLAY"] = "91.3.0.esr"
+ self.assertEqual(job.candidate_trees, expected_trees)
+ # Because the property is cached, changing the
+ # `MOZ_APP_VERSION_DISPLAY` won't have any impact.
+ buildconfig.substs["MOZ_APP_VERSION_DISPLAY"] = ""
+ self.assertEqual(job.candidate_trees, expected_trees)
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_base.py b/python/mozbuild/mozbuild/test/test_base.py
new file mode 100644
index 0000000000..c75a71ef5d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_base.py
@@ -0,0 +1,446 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+
+import mozpack.path as mozpath
+from buildconfig import topobjdir, topsrcdir
+from mach.logging import LoggingManager
+from mozfile.mozfile import NamedTemporaryFile
+from mozunit import main
+from six import StringIO
+
+from mozbuild.backend.configenvironment import ConfigEnvironment
+from mozbuild.base import (
+ BadEnvironmentException,
+ MachCommandBase,
+ MozbuildObject,
+ PathArgument,
+)
+from mozbuild.test.common import prepare_tmp_topsrcdir
+
+curdir = os.path.dirname(__file__)
+log_manager = LoggingManager()
+
+
+class TestMozbuildObject(unittest.TestCase):
+ def setUp(self):
+ self._old_cwd = os.getcwd()
+ self._old_env = dict(os.environ)
+ os.environ.pop("MOZCONFIG", None)
+ os.environ.pop("MOZ_OBJDIR", None)
+
+ def tearDown(self):
+ os.chdir(self._old_cwd)
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ def get_base(self, topobjdir=None):
+ return MozbuildObject(topsrcdir, None, log_manager, topobjdir=topobjdir)
+
+ def test_objdir_config_guess(self):
+ base = self.get_base()
+
+ with NamedTemporaryFile(mode="wt") as mozconfig:
+ os.environ["MOZCONFIG"] = mozconfig.name
+
+ self.assertIsNotNone(base.topobjdir)
+ self.assertEqual(len(base.topobjdir.split()), 1)
+ config_guess = base.resolve_config_guess()
+ self.assertTrue(base.topobjdir.endswith(config_guess))
+ self.assertTrue(os.path.isabs(base.topobjdir))
+ self.assertTrue(base.topobjdir.startswith(base.topsrcdir))
+
+ def test_objdir_trailing_slash(self):
+ """Trailing slashes in topobjdir should be removed."""
+ base = self.get_base()
+
+ with NamedTemporaryFile(mode="wt") as mozconfig:
+ mozconfig.write("mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/foo/")
+ mozconfig.flush()
+ os.environ["MOZCONFIG"] = mozconfig.name
+
+ self.assertEqual(base.topobjdir, mozpath.join(base.topsrcdir, "foo"))
+ self.assertTrue(base.topobjdir.endswith("foo"))
+
+ def test_objdir_config_status(self):
+ """Ensure @CONFIG_GUESS@ is handled when loading mozconfig."""
+ base = self.get_base()
+ guess = base.resolve_config_guess()
+
+ # There may be symlinks involved, so we use real paths to ensure
+ # path consistency.
+ d = os.path.realpath(tempfile.mkdtemp())
+ try:
+ mozconfig = os.path.join(d, "mozconfig")
+ with open(mozconfig, "wt") as fh:
+ fh.write("mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/foo/@CONFIG_GUESS@")
+ print("Wrote mozconfig %s" % mozconfig)
+
+ topobjdir = os.path.join(d, "foo", guess)
+ os.makedirs(topobjdir)
+
+ # Create a fake topsrcdir.
+ prepare_tmp_topsrcdir(d)
+
+ mozinfo = os.path.join(topobjdir, "mozinfo.json")
+ with open(mozinfo, "wt") as fh:
+ json.dump(
+ dict(
+ topsrcdir=d,
+ mozconfig=mozconfig,
+ ),
+ fh,
+ )
+
+ os.environ["MOZCONFIG"] = mozconfig
+ os.chdir(topobjdir)
+
+ obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
+
+ self.assertEqual(obj.topobjdir, mozpath.normsep(topobjdir))
+ finally:
+ os.chdir(self._old_cwd)
+ shutil.rmtree(d)
+
+ def test_relative_objdir(self):
+ """Relative defined objdirs are loaded properly."""
+ d = os.path.realpath(tempfile.mkdtemp())
+ try:
+ mozconfig = os.path.join(d, "mozconfig")
+ with open(mozconfig, "wt") as fh:
+ fh.write("mk_add_options MOZ_OBJDIR=./objdir")
+
+ topobjdir = mozpath.join(d, "objdir")
+ os.mkdir(topobjdir)
+
+ mozinfo = os.path.join(topobjdir, "mozinfo.json")
+ with open(mozinfo, "wt") as fh:
+ json.dump(
+ dict(
+ topsrcdir=d,
+ mozconfig=mozconfig,
+ ),
+ fh,
+ )
+
+ os.environ["MOZCONFIG"] = mozconfig
+ child = os.path.join(topobjdir, "foo", "bar")
+ os.makedirs(child)
+ os.chdir(child)
+
+ obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
+
+ self.assertEqual(obj.topobjdir, topobjdir)
+
+ finally:
+ os.chdir(self._old_cwd)
+ shutil.rmtree(d)
+
+ @unittest.skipIf(
+ not hasattr(os, "symlink") or os.name == "nt", "symlinks not available."
+ )
+ def test_symlink_objdir(self):
+ """Objdir that is a symlink is loaded properly."""
+ d = os.path.realpath(tempfile.mkdtemp())
+ try:
+ topobjdir_real = os.path.join(d, "objdir")
+ topobjdir_link = os.path.join(d, "objlink")
+
+ os.mkdir(topobjdir_real)
+ os.symlink(topobjdir_real, topobjdir_link)
+
+ mozconfig = os.path.join(d, "mozconfig")
+ with open(mozconfig, "wt") as fh:
+ fh.write("mk_add_options MOZ_OBJDIR=%s" % topobjdir_link)
+
+ mozinfo = os.path.join(topobjdir_real, "mozinfo.json")
+ with open(mozinfo, "wt") as fh:
+ json.dump(
+ dict(
+ topsrcdir=d,
+ mozconfig=mozconfig,
+ ),
+ fh,
+ )
+
+ os.chdir(topobjdir_link)
+ obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
+ self.assertEqual(obj.topobjdir, topobjdir_real)
+
+ os.chdir(topobjdir_real)
+ obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
+ self.assertEqual(obj.topobjdir, topobjdir_real)
+
+ finally:
+ os.chdir(self._old_cwd)
+ shutil.rmtree(d)
+
+ def test_mach_command_base_inside_objdir(self):
+ """Ensure a MachCommandBase constructed from inside the objdir works."""
+
+ d = os.path.realpath(tempfile.mkdtemp())
+
+ try:
+ topobjdir = os.path.join(d, "objdir")
+ os.makedirs(topobjdir)
+
+ topsrcdir = os.path.join(d, "srcdir")
+ prepare_tmp_topsrcdir(topsrcdir)
+
+ mozinfo = os.path.join(topobjdir, "mozinfo.json")
+ with open(mozinfo, "wt") as fh:
+ json.dump(
+ dict(
+ topsrcdir=topsrcdir,
+ ),
+ fh,
+ )
+
+ os.chdir(topobjdir)
+
+ class MockMachContext(object):
+ pass
+
+ context = MockMachContext()
+ context.cwd = topobjdir
+ context.topdir = topsrcdir
+ context.settings = None
+ context.log_manager = None
+ context.detect_virtualenv_mozinfo = False
+
+ o = MachCommandBase(context, None)
+
+ self.assertEqual(o.topobjdir, mozpath.normsep(topobjdir))
+ self.assertEqual(o.topsrcdir, mozpath.normsep(topsrcdir))
+
+ finally:
+ os.chdir(self._old_cwd)
+ shutil.rmtree(d)
+
+ def test_objdir_is_srcdir_rejected(self):
+ """Ensure the srcdir configurations are rejected."""
+ d = os.path.realpath(tempfile.mkdtemp())
+
+ try:
+ # The easiest way to do this is to create a mozinfo.json with data
+ # that will never happen.
+ mozinfo = os.path.join(d, "mozinfo.json")
+ with open(mozinfo, "wt") as fh:
+ json.dump({"topsrcdir": d}, fh)
+
+ os.chdir(d)
+
+ with self.assertRaises(BadEnvironmentException):
+ MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
+
+ finally:
+ os.chdir(self._old_cwd)
+ shutil.rmtree(d)
+
+ def test_objdir_mismatch(self):
+ """Ensure MachCommandBase throwing on objdir mismatch."""
+ d = os.path.realpath(tempfile.mkdtemp())
+
+ try:
+ real_topobjdir = os.path.join(d, "real-objdir")
+ os.makedirs(real_topobjdir)
+
+ topobjdir = os.path.join(d, "objdir")
+ os.makedirs(topobjdir)
+
+ topsrcdir = os.path.join(d, "srcdir")
+ prepare_tmp_topsrcdir(topsrcdir)
+
+ mozconfig = os.path.join(d, "mozconfig")
+ with open(mozconfig, "wt") as fh:
+ fh.write(
+ "mk_add_options MOZ_OBJDIR=%s" % real_topobjdir.replace("\\", "/")
+ )
+
+ mozinfo = os.path.join(topobjdir, "mozinfo.json")
+ with open(mozinfo, "wt") as fh:
+ json.dump(
+ dict(
+ topsrcdir=topsrcdir,
+ mozconfig=mozconfig,
+ ),
+ fh,
+ )
+
+ os.chdir(topobjdir)
+
+ class MockMachContext(object):
+ pass
+
+ context = MockMachContext()
+ context.cwd = topobjdir
+ context.topdir = topsrcdir
+ context.settings = None
+ context.log_manager = None
+ context.detect_virtualenv_mozinfo = False
+
+ stdout = sys.stdout
+ sys.stdout = StringIO()
+ try:
+ with self.assertRaises(SystemExit):
+ MachCommandBase(context, None)
+
+ self.assertTrue(
+ sys.stdout.getvalue().startswith(
+ "Ambiguous object directory detected."
+ )
+ )
+ finally:
+ sys.stdout = stdout
+
+ finally:
+ os.chdir(self._old_cwd)
+ shutil.rmtree(d)
+
+ def test_config_environment(self):
+ d = os.path.realpath(tempfile.mkdtemp())
+
+ try:
+ with open(os.path.join(d, "config.status"), "w") as fh:
+ fh.write("# coding=utf-8\n")
+ fh.write("from __future__ import unicode_literals\n")
+ fh.write("topobjdir = '%s'\n" % mozpath.normsep(d))
+ fh.write("topsrcdir = '%s'\n" % topsrcdir)
+ fh.write("mozconfig = None\n")
+ fh.write("defines = { 'FOO': 'foo' }\n")
+ fh.write("substs = { 'QUX': 'qux' }\n")
+ fh.write(
+ "__all__ = ['topobjdir', 'topsrcdir', 'defines', "
+ "'substs', 'mozconfig']"
+ )
+
+ base = self.get_base(topobjdir=d)
+
+ ce = base.config_environment
+ self.assertIsInstance(ce, ConfigEnvironment)
+
+ self.assertEqual(base.defines, ce.defines)
+ self.assertEqual(base.substs, ce.substs)
+
+ self.assertEqual(base.defines, {"FOO": "foo"})
+ self.assertEqual(
+ base.substs,
+ {
+ "ACDEFINES": "-DFOO=foo",
+ "ALLEMPTYSUBSTS": "",
+ "ALLSUBSTS": "ACDEFINES = -DFOO=foo\nQUX = qux",
+ "QUX": "qux",
+ },
+ )
+ finally:
+ shutil.rmtree(d)
+
+ def test_get_binary_path(self):
+ base = self.get_base(topobjdir=topobjdir)
+
+ platform = sys.platform
+
+ # We should ideally use the config.status from the build. Let's install
+ # a fake one.
+ substs = [
+ ("MOZ_APP_NAME", "awesomeapp"),
+ ("MOZ_BUILD_APP", "awesomeapp"),
+ ]
+ if sys.platform.startswith("darwin"):
+ substs.append(("OS_ARCH", "Darwin"))
+ substs.append(("BIN_SUFFIX", ""))
+ substs.append(("MOZ_MACBUNDLE_NAME", "Nightly.app"))
+ elif sys.platform.startswith(("win32", "cygwin")):
+ substs.append(("OS_ARCH", "WINNT"))
+ substs.append(("BIN_SUFFIX", ".exe"))
+ else:
+ substs.append(("OS_ARCH", "something"))
+ substs.append(("BIN_SUFFIX", ""))
+
+ base._config_environment = ConfigEnvironment(
+ base.topsrcdir, base.topobjdir, substs=substs
+ )
+
+ p = base.get_binary_path("xpcshell", False)
+ if platform.startswith("darwin"):
+ self.assertTrue(p.endswith("Contents/MacOS/xpcshell"))
+ elif platform.startswith(("win32", "cygwin")):
+ self.assertTrue(p.endswith("xpcshell.exe"))
+ else:
+ self.assertTrue(p.endswith("dist/bin/xpcshell"))
+
+ p = base.get_binary_path(validate_exists=False)
+ if platform.startswith("darwin"):
+ self.assertTrue(p.endswith("Contents/MacOS/awesomeapp"))
+ elif platform.startswith(("win32", "cygwin")):
+ self.assertTrue(p.endswith("awesomeapp.exe"))
+ else:
+ self.assertTrue(p.endswith("dist/bin/awesomeapp"))
+
+ p = base.get_binary_path(validate_exists=False, where="staged-package")
+ if platform.startswith("darwin"):
+ self.assertTrue(
+ p.endswith("awesomeapp/Nightly.app/Contents/MacOS/awesomeapp")
+ )
+ elif platform.startswith(("win32", "cygwin")):
+ self.assertTrue(p.endswith("awesomeapp\\awesomeapp.exe"))
+ else:
+ self.assertTrue(p.endswith("awesomeapp/awesomeapp"))
+
+ self.assertRaises(Exception, base.get_binary_path, where="somewhere")
+
+ p = base.get_binary_path("foobar", validate_exists=False)
+ if platform.startswith("win32"):
+ self.assertTrue(p.endswith("foobar.exe"))
+ else:
+ self.assertTrue(p.endswith("foobar"))
+
+
+class TestPathArgument(unittest.TestCase):
+ def test_path_argument(self):
+ # Absolute path
+ p = PathArgument("/obj/foo", "/src", "/obj", "/src")
+ self.assertEqual(p.relpath(), "foo")
+ self.assertEqual(p.srcdir_path(), "/src/foo")
+ self.assertEqual(p.objdir_path(), "/obj/foo")
+
+ # Relative path within srcdir
+ p = PathArgument("foo", "/src", "/obj", "/src")
+ self.assertEqual(p.relpath(), "foo")
+ self.assertEqual(p.srcdir_path(), "/src/foo")
+ self.assertEqual(p.objdir_path(), "/obj/foo")
+
+ # Relative path within subdirectory
+ p = PathArgument("bar", "/src", "/obj", "/src/foo")
+ self.assertEqual(p.relpath(), "foo/bar")
+ self.assertEqual(p.srcdir_path(), "/src/foo/bar")
+ self.assertEqual(p.objdir_path(), "/obj/foo/bar")
+
+ # Relative path within objdir
+ p = PathArgument("foo", "/src", "/obj", "/obj")
+ self.assertEqual(p.relpath(), "foo")
+ self.assertEqual(p.srcdir_path(), "/src/foo")
+ self.assertEqual(p.objdir_path(), "/obj/foo")
+
+ # "." path
+ p = PathArgument(".", "/src", "/obj", "/src/foo")
+ self.assertEqual(p.relpath(), "foo")
+ self.assertEqual(p.srcdir_path(), "/src/foo")
+ self.assertEqual(p.objdir_path(), "/obj/foo")
+
+ # Nested src/obj directories
+ p = PathArgument("bar", "/src", "/src/obj", "/src/obj/foo")
+ self.assertEqual(p.relpath(), "foo/bar")
+ self.assertEqual(p.srcdir_path(), "/src/foo/bar")
+ self.assertEqual(p.objdir_path(), "/src/obj/foo/bar")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_containers.py b/python/mozbuild/mozbuild/test/test_containers.py
new file mode 100644
index 0000000000..50dd0a4088
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_containers.py
@@ -0,0 +1,224 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import unittest
+from collections import OrderedDict
+
+from mozunit import main
+
+from mozbuild.util import (
+ KeyedDefaultDict,
+ List,
+ OrderedDefaultDict,
+ ReadOnlyDefaultDict,
+ ReadOnlyDict,
+ ReadOnlyKeyedDefaultDict,
+ ReadOnlyNamespace,
+)
+
+
+class TestReadOnlyNamespace(unittest.TestCase):
+ def test_basic(self):
+ test = ReadOnlyNamespace(foo=1, bar=2)
+
+ self.assertEqual(test.foo, 1)
+ self.assertEqual(test.bar, 2)
+ self.assertEqual(
+ sorted(i for i in dir(test) if not i.startswith("__")), ["bar", "foo"]
+ )
+
+ with self.assertRaises(AttributeError):
+ test.missing
+
+ with self.assertRaises(Exception):
+ test.foo = 2
+
+ with self.assertRaises(Exception):
+ del test.foo
+
+ self.assertEqual(test, test)
+ self.assertEqual(test, ReadOnlyNamespace(foo=1, bar=2))
+ self.assertNotEqual(test, ReadOnlyNamespace(foo="1", bar=2))
+ self.assertNotEqual(test, ReadOnlyNamespace(foo=1, bar=2, qux=3))
+ self.assertNotEqual(test, ReadOnlyNamespace(foo=1, qux=3))
+ self.assertNotEqual(test, ReadOnlyNamespace(foo=3, bar="42"))
+
+
+class TestReadOnlyDict(unittest.TestCase):
+ def test_basic(self):
+ original = {"foo": 1, "bar": 2}
+
+ test = ReadOnlyDict(original)
+
+ self.assertEqual(original, test)
+ self.assertEqual(test["foo"], 1)
+
+ with self.assertRaises(KeyError):
+ test["missing"]
+
+ with self.assertRaises(Exception):
+ test["baz"] = True
+
+ def test_update(self):
+ original = {"foo": 1, "bar": 2}
+
+ test = ReadOnlyDict(original)
+
+ with self.assertRaises(Exception):
+ test.update(foo=2)
+
+ self.assertEqual(original, test)
+
+ def test_del(self):
+ original = {"foo": 1, "bar": 2}
+
+ test = ReadOnlyDict(original)
+
+ with self.assertRaises(Exception):
+ del test["foo"]
+
+ self.assertEqual(original, test)
+
+
+class TestReadOnlyDefaultDict(unittest.TestCase):
+ def test_simple(self):
+ original = {"foo": 1, "bar": 2}
+
+ test = ReadOnlyDefaultDict(bool, original)
+
+ self.assertEqual(original, test)
+
+ self.assertEqual(test["foo"], 1)
+
+ def test_assignment(self):
+ test = ReadOnlyDefaultDict(bool, {})
+
+ with self.assertRaises(Exception):
+ test["foo"] = True
+
+ def test_defaults(self):
+ test = ReadOnlyDefaultDict(bool, {"foo": 1})
+
+ self.assertEqual(test["foo"], 1)
+
+ self.assertEqual(test["qux"], False)
+
+
+class TestList(unittest.TestCase):
+ def test_add_list(self):
+ test = List([1, 2, 3])
+
+ test += [4, 5, 6]
+ self.assertIsInstance(test, List)
+ self.assertEqual(test, [1, 2, 3, 4, 5, 6])
+
+ test = test + [7, 8]
+ self.assertIsInstance(test, List)
+ self.assertEqual(test, [1, 2, 3, 4, 5, 6, 7, 8])
+
+ def test_add_string(self):
+ test = List([1, 2, 3])
+
+ with self.assertRaises(ValueError):
+ test += "string"
+
+ def test_none(self):
+ """As a special exception, we allow None to be treated as an empty
+ list."""
+ test = List([1, 2, 3])
+
+ test += None
+ self.assertEqual(test, [1, 2, 3])
+
+ test = test + None
+ self.assertIsInstance(test, List)
+ self.assertEqual(test, [1, 2, 3])
+
+ with self.assertRaises(ValueError):
+ test += False
+
+ with self.assertRaises(ValueError):
+ test = test + False
+
+
+class TestOrderedDefaultDict(unittest.TestCase):
+ def test_simple(self):
+ original = OrderedDict(foo=1, bar=2)
+
+ test = OrderedDefaultDict(bool, original)
+
+ self.assertEqual(original, test)
+
+ self.assertEqual(test["foo"], 1)
+
+ self.assertEqual(list(test), ["foo", "bar"])
+
+ def test_defaults(self):
+ test = OrderedDefaultDict(bool, {"foo": 1})
+
+ self.assertEqual(test["foo"], 1)
+
+ self.assertEqual(test["qux"], False)
+
+ self.assertEqual(list(test), ["foo", "qux"])
+
+
+class TestKeyedDefaultDict(unittest.TestCase):
+ def test_simple(self):
+ original = {"foo": 1, "bar": 2}
+
+ test = KeyedDefaultDict(lambda x: x, original)
+
+ self.assertEqual(original, test)
+
+ self.assertEqual(test["foo"], 1)
+
+ def test_defaults(self):
+ test = KeyedDefaultDict(lambda x: x, {"foo": 1})
+
+ self.assertEqual(test["foo"], 1)
+
+ self.assertEqual(test["qux"], "qux")
+
+ self.assertEqual(test["bar"], "bar")
+
+ test["foo"] = 2
+ test["qux"] = None
+ test["baz"] = "foo"
+
+ self.assertEqual(test["foo"], 2)
+
+ self.assertEqual(test["qux"], None)
+
+ self.assertEqual(test["baz"], "foo")
+
+
+class TestReadOnlyKeyedDefaultDict(unittest.TestCase):
+ def test_defaults(self):
+ test = ReadOnlyKeyedDefaultDict(lambda x: x, {"foo": 1})
+
+ self.assertEqual(test["foo"], 1)
+
+ self.assertEqual(test["qux"], "qux")
+
+ self.assertEqual(test["bar"], "bar")
+
+ copy = dict(test)
+
+ with self.assertRaises(Exception):
+ test["foo"] = 2
+
+ with self.assertRaises(Exception):
+ test["qux"] = None
+
+ with self.assertRaises(Exception):
+ test["baz"] = "foo"
+
+ self.assertEqual(test, copy)
+
+ self.assertEqual(len(test), 3)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_dotproperties.py b/python/mozbuild/mozbuild/test/test_dotproperties.py
new file mode 100644
index 0000000000..4e7a437799
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_dotproperties.py
@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+
+import os
+import unittest
+
+import mozpack.path as mozpath
+from mozunit import main
+from six import StringIO
+
+from mozbuild.dotproperties import DotProperties
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+
+class TestDotProperties(unittest.TestCase):
+ def test_get(self):
+ contents = StringIO(
+ """
+key=value
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get("missing"), None)
+ self.assertEqual(p.get("missing", "default"), "default")
+ self.assertEqual(p.get("key"), "value")
+
+ def test_update(self):
+ contents = StringIO(
+ """
+old=old value
+key=value
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get("old"), "old value")
+ self.assertEqual(p.get("key"), "value")
+
+ new_contents = StringIO(
+ """
+key=new value
+"""
+ )
+ p.update(new_contents)
+ self.assertEqual(p.get("old"), "old value")
+ self.assertEqual(p.get("key"), "new value")
+
+ def test_get_list(self):
+ contents = StringIO(
+ """
+list.0=A
+list.1=B
+list.2=C
+
+order.1=B
+order.0=A
+order.2=C
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get_list("missing"), [])
+ self.assertEqual(p.get_list("list"), ["A", "B", "C"])
+ self.assertEqual(p.get_list("order"), ["A", "B", "C"])
+
+ def test_get_list_with_shared_prefix(self):
+ contents = StringIO(
+ """
+list.0=A
+list.1=B
+list.2=C
+
+list.sublist.1=E
+list.sublist.0=D
+list.sublist.2=F
+
+list.sublist.second.0=G
+
+list.other.0=H
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get_list("list"), ["A", "B", "C"])
+ self.assertEqual(p.get_list("list.sublist"), ["D", "E", "F"])
+ self.assertEqual(p.get_list("list.sublist.second"), ["G"])
+ self.assertEqual(p.get_list("list.other"), ["H"])
+
+ def test_get_dict(self):
+ contents = StringIO(
+ """
+A.title=title A
+
+B.title=title B
+B.url=url B
+
+C=value
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get_dict("missing"), {})
+ self.assertEqual(p.get_dict("A"), {"title": "title A"})
+ self.assertEqual(p.get_dict("B"), {"title": "title B", "url": "url B"})
+ with self.assertRaises(ValueError):
+ p.get_dict("A", required_keys=["title", "url"])
+ with self.assertRaises(ValueError):
+ p.get_dict("missing", required_keys=["key"])
+ # A key=value pair is considered to root an empty dict.
+ self.assertEqual(p.get_dict("C"), {})
+ with self.assertRaises(ValueError):
+ p.get_dict("C", required_keys=["missing_key"])
+
+ def test_get_dict_with_shared_prefix(self):
+ contents = StringIO(
+ """
+A.title=title A
+A.subdict.title=title A subdict
+
+B.title=title B
+B.url=url B
+B.subdict.title=title B subdict
+B.subdict.url=url B subdict
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get_dict("A"), {"title": "title A"})
+ self.assertEqual(p.get_dict("B"), {"title": "title B", "url": "url B"})
+ self.assertEqual(p.get_dict("A.subdict"), {"title": "title A subdict"})
+ self.assertEqual(
+ p.get_dict("B.subdict"),
+ {"title": "title B subdict", "url": "url B subdict"},
+ )
+
+ def test_get_dict_with_value_prefix(self):
+ contents = StringIO(
+ """
+A.default=A
+A.default.B=B
+A.default.B.ignored=B ignored
+A.default.C=C
+A.default.C.ignored=C ignored
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get("A.default"), "A")
+ # This enumerates the properties.
+ self.assertEqual(p.get_dict("A.default"), {"B": "B", "C": "C"})
+ # They can still be fetched directly.
+ self.assertEqual(p.get("A.default.B"), "B")
+ self.assertEqual(p.get("A.default.C"), "C")
+
+ def test_unicode(self):
+ contents = StringIO(
+ """
+# Danish.
+# #### ~~ Søren Munk Skrøder, sskroeder - 2009-05-30 @ #mozmae
+
+# Korean.
+A.title=한메ì¼
+
+# Russian.
+list.0 = test
+list.1 = ЯндекÑ
+"""
+ )
+ p = DotProperties(contents)
+ self.assertEqual(p.get_dict("A"), {"title": "한메ì¼"})
+ self.assertEqual(p.get_list("list"), ["test", "ЯндекÑ"])
+
+ def test_valid_unicode_from_file(self):
+ # The contents of valid.properties is identical to the contents of the
+ # test above. This specifically exercises reading from a file.
+ p = DotProperties(os.path.join(test_data_path, "valid.properties"))
+ self.assertEqual(p.get_dict("A"), {"title": "한메ì¼"})
+ self.assertEqual(p.get_list("list"), ["test", "ЯндекÑ"])
+
+ def test_bad_unicode_from_file(self):
+ # The contents of bad.properties is not valid Unicode; see the comments
+ # in the file itself for details.
+ with self.assertRaises(UnicodeDecodeError):
+ DotProperties(os.path.join(test_data_path, "bad.properties"))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_expression.py b/python/mozbuild/mozbuild/test/test_expression.py
new file mode 100644
index 0000000000..535e62bf43
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_expression.py
@@ -0,0 +1,88 @@
+import unittest
+
+import mozunit
+
+from mozbuild.preprocessor import Context, Expression
+
+
+class TestContext(unittest.TestCase):
+ """
+ Unit tests for the Context class
+ """
+
+ def setUp(self):
+ self.c = Context()
+ self.c["FAIL"] = "PASS"
+
+ def test_string_literal(self):
+ """test string literal, fall-through for undefined var in a Context"""
+ self.assertEqual(self.c["PASS"], "PASS")
+
+ def test_variable(self):
+ """test value for defined var in the Context class"""
+ self.assertEqual(self.c["FAIL"], "PASS")
+
+ def test_in(self):
+ """test 'var in context' to not fall for fallback"""
+ self.assertTrue("FAIL" in self.c)
+ self.assertTrue("PASS" not in self.c)
+
+
+class TestExpression(unittest.TestCase):
+ """
+ Unit tests for the Expression class
+ evaluate() is called with a context {FAIL: 'PASS'}
+ """
+
+ def setUp(self):
+ self.c = Context()
+ self.c["FAIL"] = "PASS"
+
+ def test_string_literal(self):
+ """Test for a string literal in an Expression"""
+ self.assertEqual(Expression("PASS").evaluate(self.c), "PASS")
+
+ def test_variable(self):
+ """Test for variable value in an Expression"""
+ self.assertEqual(Expression("FAIL").evaluate(self.c), "PASS")
+
+ def test_not(self):
+ """Test for the ! operator"""
+ self.assertTrue(Expression("!0").evaluate(self.c))
+ self.assertTrue(not Expression("!1").evaluate(self.c))
+
+ def test_equals(self):
+ """Test for the == operator"""
+ self.assertTrue(Expression("FAIL == PASS").evaluate(self.c))
+
+ def test_notequals(self):
+ """Test for the != operator"""
+ self.assertTrue(Expression("FAIL != 1").evaluate(self.c))
+
+ def test_logical_and(self):
+ """Test for the && operator"""
+ self.assertTrue(Expression("PASS == PASS && PASS != NOTPASS").evaluate(self.c))
+
+ def test_logical_or(self):
+ """Test for the || operator"""
+ self.assertTrue(
+ Expression("PASS == NOTPASS || PASS != NOTPASS").evaluate(self.c)
+ )
+
+ def test_logical_ops(self):
+ """Test for the && and || operators precedence"""
+ # Would evaluate to false if precedence was wrong
+ self.assertTrue(
+ Expression("PASS == PASS || PASS != NOTPASS && PASS == NOTPASS").evaluate(
+ self.c
+ )
+ )
+
+ def test_defined(self):
+ """Test for the defined() value"""
+ self.assertTrue(Expression("defined(FAIL)").evaluate(self.c))
+ self.assertTrue(Expression("!defined(PASS)").evaluate(self.c))
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_jarmaker.py b/python/mozbuild/mozbuild/test/test_jarmaker.py
new file mode 100644
index 0000000000..24a8c7694a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_jarmaker.py
@@ -0,0 +1,493 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import os.path
+import sys
+import unittest
+from filecmp import dircmp
+from shutil import copy2, rmtree
+from tempfile import mkdtemp
+from zipfile import ZipFile
+
+import mozunit
+import six
+from six import StringIO
+
+from mozbuild.jar import JarMaker
+
+if sys.platform == "win32":
+ import ctypes
+ from ctypes import POINTER, WinError
+
+ DWORD = ctypes.c_ulong
+ LPDWORD = POINTER(DWORD)
+ HANDLE = ctypes.c_void_p
+ GENERIC_READ = 0x80000000
+ FILE_SHARE_READ = 0x00000001
+ OPEN_EXISTING = 3
+ MAX_PATH = 260
+
+ class FILETIME(ctypes.Structure):
+ _fields_ = [("dwLowDateTime", DWORD), ("dwHighDateTime", DWORD)]
+
+ class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
+ _fields_ = [
+ ("dwFileAttributes", DWORD),
+ ("ftCreationTime", FILETIME),
+ ("ftLastAccessTime", FILETIME),
+ ("ftLastWriteTime", FILETIME),
+ ("dwVolumeSerialNumber", DWORD),
+ ("nFileSizeHigh", DWORD),
+ ("nFileSizeLow", DWORD),
+ ("nNumberOfLinks", DWORD),
+ ("nFileIndexHigh", DWORD),
+ ("nFileIndexLow", DWORD),
+ ]
+
+ # http://msdn.microsoft.com/en-us/library/aa363858
+ CreateFile = ctypes.windll.kernel32.CreateFileA
+ CreateFile.argtypes = [
+ ctypes.c_char_p,
+ DWORD,
+ DWORD,
+ ctypes.c_void_p,
+ DWORD,
+ DWORD,
+ HANDLE,
+ ]
+ CreateFile.restype = HANDLE
+
+ # http://msdn.microsoft.com/en-us/library/aa364952
+ GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
+ GetFileInformationByHandle.argtypes = [HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)]
+ GetFileInformationByHandle.restype = ctypes.c_int
+
+ # http://msdn.microsoft.com/en-us/library/aa364996
+ GetVolumePathName = ctypes.windll.kernel32.GetVolumePathNameA
+ GetVolumePathName.argtypes = [ctypes.c_char_p, ctypes.c_char_p, DWORD]
+ GetVolumePathName.restype = ctypes.c_int
+
+ # http://msdn.microsoft.com/en-us/library/aa364993
+ GetVolumeInformation = ctypes.windll.kernel32.GetVolumeInformationA
+ GetVolumeInformation.argtypes = [
+ ctypes.c_char_p,
+ ctypes.c_char_p,
+ DWORD,
+ LPDWORD,
+ LPDWORD,
+ LPDWORD,
+ ctypes.c_char_p,
+ DWORD,
+ ]
+ GetVolumeInformation.restype = ctypes.c_int
+
+
+def symlinks_supported(path):
+ if sys.platform == "win32":
+ # Add 1 for a trailing backslash if necessary, and 1 for the terminating
+ # null character.
+ volpath = ctypes.create_string_buffer(len(path) + 2)
+ rv = GetVolumePathName(six.ensure_binary(path), volpath, len(volpath))
+ if rv == 0:
+ raise WinError()
+
+ fsname = ctypes.create_string_buffer(MAX_PATH + 1)
+ rv = GetVolumeInformation(
+ volpath, None, 0, None, None, None, fsname, len(fsname)
+ )
+ if rv == 0:
+ raise WinError()
+
+ # Return true only if the fsname is NTFS
+ return fsname.value == "NTFS"
+ else:
+ return True
+
+
+def _getfileinfo(path):
+ """Return information for the given file. This only works on Windows."""
+ fh = CreateFile(
+ six.ensure_binary(path),
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ None,
+ OPEN_EXISTING,
+ 0,
+ None,
+ )
+ if fh is None:
+ raise WinError()
+ info = BY_HANDLE_FILE_INFORMATION()
+ rv = GetFileInformationByHandle(fh, info)
+ if rv == 0:
+ raise WinError()
+ return info
+
+
+def is_symlink_to(dest, src):
+ if sys.platform == "win32":
+ # Check if both are on the same volume and have the same file ID
+ destinfo = _getfileinfo(dest)
+ srcinfo = _getfileinfo(src)
+ return (
+ destinfo.dwVolumeSerialNumber == srcinfo.dwVolumeSerialNumber
+ and destinfo.nFileIndexHigh == srcinfo.nFileIndexHigh
+ and destinfo.nFileIndexLow == srcinfo.nFileIndexLow
+ )
+ else:
+ # Read the link and check if it is correct
+ if not os.path.islink(dest):
+ return False
+ target = os.path.abspath(os.readlink(dest))
+ abssrc = os.path.abspath(src)
+ return target == abssrc
+
+
+class _TreeDiff(dircmp):
+ """Helper to report rich results on difference between two directories."""
+
+ def _fillDiff(self, dc, rv, basepath="{0}"):
+ rv["right_only"] += map(lambda l: basepath.format(l), dc.right_only)
+ rv["left_only"] += map(lambda l: basepath.format(l), dc.left_only)
+ rv["diff_files"] += map(lambda l: basepath.format(l), dc.diff_files)
+ rv["funny"] += map(lambda l: basepath.format(l), dc.common_funny)
+ rv["funny"] += map(lambda l: basepath.format(l), dc.funny_files)
+ for subdir, _dc in six.iteritems(dc.subdirs):
+ self._fillDiff(_dc, rv, basepath.format(subdir + "/{0}"))
+
+ def allResults(self, left, right):
+ rv = {"right_only": [], "left_only": [], "diff_files": [], "funny": []}
+ self._fillDiff(self, rv)
+ chunks = []
+ if rv["right_only"]:
+ chunks.append("{0} only in {1}".format(", ".join(rv["right_only"]), right))
+ if rv["left_only"]:
+ chunks.append("{0} only in {1}".format(", ".join(rv["left_only"]), left))
+ if rv["diff_files"]:
+ chunks.append("{0} differ".format(", ".join(rv["diff_files"])))
+ if rv["funny"]:
+ chunks.append("{0} don't compare".format(", ".join(rv["funny"])))
+ return "; ".join(chunks)
+
+
+class TestJarMaker(unittest.TestCase):
+ """
+ Unit tests for JarMaker.py
+ """
+
+ debug = False # set to True to debug failing tests on disk
+
+ def setUp(self):
+ self.tmpdir = mkdtemp()
+ self.srcdir = os.path.join(self.tmpdir, "src")
+ os.mkdir(self.srcdir)
+ self.builddir = os.path.join(self.tmpdir, "build")
+ os.mkdir(self.builddir)
+ self.refdir = os.path.join(self.tmpdir, "ref")
+ os.mkdir(self.refdir)
+ self.stagedir = os.path.join(self.tmpdir, "stage")
+ os.mkdir(self.stagedir)
+
+ def tearDown(self):
+ if self.debug:
+ print(self.tmpdir)
+ elif sys.platform != "win32":
+ # can't clean up on windows
+ rmtree(self.tmpdir)
+
+ def _jar_and_compare(self, infile, **kwargs):
+ jm = JarMaker(outputFormat="jar")
+ if "topsourcedir" not in kwargs:
+ kwargs["topsourcedir"] = self.srcdir
+ for attr in ("topsourcedir", "sourcedirs"):
+ if attr in kwargs:
+ setattr(jm, attr, kwargs[attr])
+ jm.makeJar(infile, self.builddir)
+ cwd = os.getcwd()
+ os.chdir(self.builddir)
+ try:
+ # expand build to stage
+ for path, dirs, files in os.walk("."):
+ stagedir = os.path.join(self.stagedir, path)
+ if not os.path.isdir(stagedir):
+ os.mkdir(stagedir)
+ for file in files:
+ if file.endswith(".jar"):
+ # expand jar
+ stagepath = os.path.join(stagedir, file)
+ os.mkdir(stagepath)
+ zf = ZipFile(os.path.join(path, file))
+ # extractall is only in 2.6, do this manually :-(
+ for entry_name in zf.namelist():
+ segs = entry_name.split("/")
+ fname = segs.pop()
+ dname = os.path.join(stagepath, *segs)
+ if not os.path.isdir(dname):
+ os.makedirs(dname)
+ if not fname:
+ # directory, we're done
+ continue
+ _c = zf.read(entry_name)
+ open(os.path.join(dname, fname), "wb").write(_c)
+ zf.close()
+ else:
+ copy2(os.path.join(path, file), stagedir)
+ # compare both dirs
+ os.chdir("..")
+ td = _TreeDiff("ref", "stage")
+ return td.allResults("reference", "build")
+ finally:
+ os.chdir(cwd)
+
+ def _create_simple_setup(self):
+ # create src content
+ jarf = open(os.path.join(self.srcdir, "jar.mn"), "w")
+ jarf.write(
+ """test.jar:
+ dir/foo (bar)
+"""
+ )
+ jarf.close()
+ open(os.path.join(self.srcdir, "bar"), "w").write("content\n")
+ # create reference
+ refpath = os.path.join(self.refdir, "chrome", "test.jar", "dir")
+ os.makedirs(refpath)
+ open(os.path.join(refpath, "foo"), "w").write("content\n")
+
+ def test_a_simple_jar(self):
+ """Test a simple jar.mn"""
+ self._create_simple_setup()
+ # call JarMaker
+ rv = self._jar_and_compare(
+ os.path.join(self.srcdir, "jar.mn"), sourcedirs=[self.srcdir]
+ )
+ self.assertTrue(not rv, rv)
+
+ def test_a_simple_symlink(self):
+ """Test a simple jar.mn with a symlink"""
+ if not symlinks_supported(self.srcdir):
+ raise unittest.SkipTest("symlinks not supported")
+
+ self._create_simple_setup()
+ jm = JarMaker(outputFormat="symlink")
+ jm.sourcedirs = [self.srcdir]
+ jm.topsourcedir = self.srcdir
+ jm.makeJar(os.path.join(self.srcdir, "jar.mn"), self.builddir)
+ # All we do is check that srcdir/bar points to builddir/chrome/test/dir/foo
+ srcbar = os.path.join(self.srcdir, "bar")
+ destfoo = os.path.join(self.builddir, "chrome", "test", "dir", "foo")
+ self.assertTrue(
+ is_symlink_to(destfoo, srcbar),
+ "{0} is not a symlink to {1}".format(destfoo, srcbar),
+ )
+
+ def _create_wildcard_setup(self):
+ # create src content
+ jarf = open(os.path.join(self.srcdir, "jar.mn"), "w")
+ jarf.write(
+ """test.jar:
+ dir/bar (*.js)
+ dir/hoge (qux/*)
+"""
+ )
+ jarf.close()
+ open(os.path.join(self.srcdir, "foo.js"), "w").write("foo.js\n")
+ open(os.path.join(self.srcdir, "bar.js"), "w").write("bar.js\n")
+ os.makedirs(os.path.join(self.srcdir, "qux", "foo"))
+ open(os.path.join(self.srcdir, "qux", "foo", "1"), "w").write("1\n")
+ open(os.path.join(self.srcdir, "qux", "foo", "2"), "w").write("2\n")
+ open(os.path.join(self.srcdir, "qux", "baz"), "w").write("baz\n")
+ # create reference
+ refpath = os.path.join(self.refdir, "chrome", "test.jar", "dir")
+ os.makedirs(os.path.join(refpath, "bar"))
+ os.makedirs(os.path.join(refpath, "hoge", "foo"))
+ open(os.path.join(refpath, "bar", "foo.js"), "w").write("foo.js\n")
+ open(os.path.join(refpath, "bar", "bar.js"), "w").write("bar.js\n")
+ open(os.path.join(refpath, "hoge", "foo", "1"), "w").write("1\n")
+ open(os.path.join(refpath, "hoge", "foo", "2"), "w").write("2\n")
+ open(os.path.join(refpath, "hoge", "baz"), "w").write("baz\n")
+
+ def test_a_wildcard_jar(self):
+ """Test a wildcard in jar.mn"""
+ self._create_wildcard_setup()
+ # call JarMaker
+ rv = self._jar_and_compare(
+ os.path.join(self.srcdir, "jar.mn"), sourcedirs=[self.srcdir]
+ )
+ self.assertTrue(not rv, rv)
+
+ def test_a_wildcard_symlink(self):
+ """Test a wildcard in jar.mn with symlinks"""
+ if not symlinks_supported(self.srcdir):
+ raise unittest.SkipTest("symlinks not supported")
+
+ self._create_wildcard_setup()
+ jm = JarMaker(outputFormat="symlink")
+ jm.sourcedirs = [self.srcdir]
+ jm.topsourcedir = self.srcdir
+ jm.makeJar(os.path.join(self.srcdir, "jar.mn"), self.builddir)
+
+ expected_symlinks = {
+ ("bar", "foo.js"): ("foo.js",),
+ ("bar", "bar.js"): ("bar.js",),
+ ("hoge", "foo", "1"): ("qux", "foo", "1"),
+ ("hoge", "foo", "2"): ("qux", "foo", "2"),
+ ("hoge", "baz"): ("qux", "baz"),
+ }
+ for dest, src in six.iteritems(expected_symlinks):
+ srcpath = os.path.join(self.srcdir, *src)
+ destpath = os.path.join(self.builddir, "chrome", "test", "dir", *dest)
+ self.assertTrue(
+ is_symlink_to(destpath, srcpath),
+ "{0} is not a symlink to {1}".format(destpath, srcpath),
+ )
+
+
+class Test_relativesrcdir(unittest.TestCase):
+ def setUp(self):
+ self.jm = JarMaker()
+ self.jm.topsourcedir = "/TOPSOURCEDIR"
+ self.jm.relativesrcdir = "browser/locales"
+ self.fake_empty_file = StringIO()
+ self.fake_empty_file.name = "fake_empty_file"
+
+ def tearDown(self):
+ del self.jm
+ del self.fake_empty_file
+
+ def test_en_US(self):
+ jm = self.jm
+ jm.makeJar(self.fake_empty_file, "/NO_OUTPUT_REQUIRED")
+ self.assertEqual(
+ jm.localedirs,
+ [
+ os.path.join(
+ os.path.abspath("/TOPSOURCEDIR"), "browser/locales", "en-US"
+ )
+ ],
+ )
+
+ def test_l10n_no_merge(self):
+ jm = self.jm
+ jm.l10nbase = "/L10N_BASE"
+ jm.makeJar(self.fake_empty_file, "/NO_OUTPUT_REQUIRED")
+ self.assertEqual(jm.localedirs, [os.path.join("/L10N_BASE", "browser")])
+
+ def test_l10n_merge(self):
+ jm = self.jm
+ jm.l10nbase = "/L10N_MERGE"
+ jm.makeJar(self.fake_empty_file, "/NO_OUTPUT_REQUIRED")
+ self.assertEqual(
+ jm.localedirs,
+ [
+ os.path.join("/L10N_MERGE", "browser"),
+ ],
+ )
+
+ def test_override(self):
+ jm = self.jm
+ jm.outputFormat = "flat" # doesn't touch chrome dir without files
+ jarcontents = StringIO(
+ """en-US.jar:
+relativesrcdir dom/locales:
+"""
+ )
+ jarcontents.name = "override.mn"
+ jm.makeJar(jarcontents, "/NO_OUTPUT_REQUIRED")
+ self.assertEqual(
+ jm.localedirs,
+ [os.path.join(os.path.abspath("/TOPSOURCEDIR"), "dom/locales", "en-US")],
+ )
+
+ def test_override_l10n(self):
+ jm = self.jm
+ jm.l10nbase = "/L10N_BASE"
+ jm.outputFormat = "flat" # doesn't touch chrome dir without files
+ jarcontents = StringIO(
+ """en-US.jar:
+relativesrcdir dom/locales:
+"""
+ )
+ jarcontents.name = "override.mn"
+ jm.makeJar(jarcontents, "/NO_OUTPUT_REQUIRED")
+ self.assertEqual(jm.localedirs, [os.path.join("/L10N_BASE", "dom")])
+
+
+class Test_fluent(unittest.TestCase):
+ """
+ Unit tests for JarMaker interaction with Fluent
+ """
+
+ debug = False # set to True to debug failing tests on disk
+
+ def setUp(self):
+ self.tmpdir = mkdtemp()
+ self.srcdir = os.path.join(self.tmpdir, "src")
+ os.mkdir(self.srcdir)
+ self.builddir = os.path.join(self.tmpdir, "build")
+ os.mkdir(self.builddir)
+ self.l10nbase = os.path.join(self.tmpdir, "l10n-base")
+ os.mkdir(self.l10nbase)
+ self.l10nmerge = os.path.join(self.tmpdir, "l10n-merge")
+ os.mkdir(self.l10nmerge)
+
+ def tearDown(self):
+ if self.debug:
+ print(self.tmpdir)
+ elif sys.platform != "win32":
+ # can't clean up on windows
+ rmtree(self.tmpdir)
+
+ def _create_fluent_setup(self):
+ # create src content
+ jarf = open(os.path.join(self.srcdir, "jar.mn"), "w")
+ jarf.write(
+ """[localization] test.jar:
+ app (%app/**/*.ftl)
+"""
+ )
+ jarf.close()
+ appdir = os.path.join(self.srcdir, "app", "locales", "en-US", "app")
+ os.makedirs(appdir)
+ open(os.path.join(appdir, "test.ftl"), "w").write("id = Value")
+ open(os.path.join(appdir, "test2.ftl"), "w").write("id2 = Value 2")
+
+ l10ndir = os.path.join(self.l10nbase, "app", "app")
+ os.makedirs(l10ndir)
+ open(os.path.join(l10ndir, "test.ftl"), "w").write("id = L10n Value")
+
+ def test_l10n_not_merge_ftl(self):
+ """Test that JarMaker doesn't merge source .ftl files"""
+ self._create_fluent_setup()
+ jm = JarMaker(outputFormat="symlink")
+ jm.sourcedirs = [self.srcdir]
+ jm.topsourcedir = self.srcdir
+ jm.l10nbase = self.l10nbase
+ jm.l10nmerge = self.l10nmerge
+ jm.relativesrcdir = "app/locales"
+ jm.makeJar(os.path.join(self.srcdir, "jar.mn"), self.builddir)
+
+ # test.ftl should be taken from the l10ndir, since it is present there
+ destpath = os.path.join(
+ self.builddir, "localization", "test", "app", "test.ftl"
+ )
+ srcpath = os.path.join(self.l10nbase, "app", "app", "test.ftl")
+ self.assertTrue(
+ is_symlink_to(destpath, srcpath),
+ "{0} should be a symlink to {1}".format(destpath, srcpath),
+ )
+
+ # test2.ftl on the other hand, is only present in en-US dir, and should
+ # not be linked from the build dir
+ destpath = os.path.join(
+ self.builddir, "localization", "test", "app", "test2.ftl"
+ )
+ self.assertFalse(
+ os.path.isfile(destpath), "test2.ftl should not be taken from en-US"
+ )
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_licenses.py b/python/mozbuild/mozbuild/test/test_licenses.py
new file mode 100644
index 0000000000..9f3f12d423
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_licenses.py
@@ -0,0 +1,33 @@
+import unittest
+
+import mozunit
+
+from mozbuild.vendor.vendor_rust import VendorRust
+
+
+class TestLicenses(unittest.TestCase):
+ """
+ Unit tests for the Rust Vendoring stuff
+ """
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def testLicense(self):
+ self.assertEqual(VendorRust.runtime_license("", "Apache-2.0"), True)
+ self.assertEqual(VendorRust.runtime_license("", "MIT"), True)
+ self.assertEqual(VendorRust.runtime_license("", "GPL"), False)
+ self.assertEqual(VendorRust.runtime_license("", "MIT /GPL"), True)
+ self.assertEqual(VendorRust.runtime_license("", "GPL/ Proprietary"), False)
+ self.assertEqual(VendorRust.runtime_license("", "GPL AND MIT"), False)
+ self.assertEqual(VendorRust.runtime_license("", "ISC\tAND\tMIT"), False)
+ self.assertEqual(VendorRust.runtime_license("", "GPL OR MIT"), True)
+ self.assertEqual(VendorRust.runtime_license("", "ALLIGATOR MIT"), False)
+ pass
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_line_endings.py b/python/mozbuild/mozbuild/test/test_line_endings.py
new file mode 100644
index 0000000000..f8cdd89174
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_line_endings.py
@@ -0,0 +1,45 @@
+import unittest
+
+import mozunit
+from mozfile import NamedTemporaryFile
+from six import StringIO
+
+from mozbuild.preprocessor import Preprocessor
+
+
+class TestLineEndings(unittest.TestCase):
+ """
+ Unit tests for the Context class
+ """
+
+ def setUp(self):
+ self.pp = Preprocessor()
+ self.pp.out = StringIO()
+ self.f = NamedTemporaryFile(mode="wb")
+
+ def tearDown(self):
+ self.f.close()
+
+ def createFile(self, lineendings):
+ for line, ending in zip([b"a", b"#literal b", b"c"], lineendings):
+ self.f.write(line + ending)
+ self.f.flush()
+
+ def testMac(self):
+ self.createFile([b"\x0D"] * 3)
+ self.pp.do_include(self.f.name)
+ self.assertEqual(self.pp.out.getvalue(), "a\nb\nc\n")
+
+ def testUnix(self):
+ self.createFile([b"\x0A"] * 3)
+ self.pp.do_include(self.f.name)
+ self.assertEqual(self.pp.out.getvalue(), "a\nb\nc\n")
+
+ def testWindows(self):
+ self.createFile([b"\x0D\x0A"] * 3)
+ self.pp.do_include(self.f.name)
+ self.assertEqual(self.pp.out.getvalue(), "a\nb\nc\n")
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_makeutil.py b/python/mozbuild/mozbuild/test/test_makeutil.py
new file mode 100644
index 0000000000..524851bfbd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_makeutil.py
@@ -0,0 +1,164 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+
+from mozunit import main
+from six import StringIO
+
+from mozbuild.makeutil import Makefile, Rule, read_dep_makefile, write_dep_makefile
+
+
+class TestMakefile(unittest.TestCase):
+ def test_rule(self):
+ out = StringIO()
+ rule = Rule()
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "")
+
+ out = StringIO()
+ rule.add_targets(["foo", "bar"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar:\n")
+
+ out = StringIO()
+ rule.add_targets(["baz"])
+ rule.add_dependencies(["qux", "hoge", "piyo"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar baz: qux hoge piyo\n")
+
+ out = StringIO()
+ rule = Rule(["foo", "bar"])
+ rule.add_dependencies(["baz"])
+ rule.add_commands(["echo $@"])
+ rule.add_commands(["$(BAZ) -o $@ $<", "$(TOUCH) $@"])
+ rule.dump(out)
+ self.assertEqual(
+ out.getvalue(),
+ "foo bar: baz\n"
+ + "\techo $@\n"
+ + "\t$(BAZ) -o $@ $<\n"
+ + "\t$(TOUCH) $@\n",
+ )
+
+ out = StringIO()
+ rule = Rule(["foo"])
+ rule.add_dependencies(["bar", "foo", "baz"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo: bar baz\n")
+
+ out = StringIO()
+ rule.add_targets(["bar"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar: baz\n")
+
+ out = StringIO()
+ rule.add_targets(["bar"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar: baz\n")
+
+ out = StringIO()
+ rule.add_dependencies(["bar"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar: baz\n")
+
+ out = StringIO()
+ rule.add_dependencies(["qux"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar: baz qux\n")
+
+ out = StringIO()
+ rule.add_dependencies(["qux"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar: baz qux\n")
+
+ out = StringIO()
+ rule.add_dependencies(["hoge", "hoge"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar: baz qux hoge\n")
+
+ out = StringIO()
+ rule.add_targets(["fuga", "fuga"])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), "foo bar fuga: baz qux hoge\n")
+
+ def test_makefile(self):
+ out = StringIO()
+ mk = Makefile()
+ rule = mk.create_rule(["foo"])
+ rule.add_dependencies(["bar", "baz", "qux"])
+ rule.add_commands(["echo foo"])
+ rule = mk.create_rule().add_targets(["bar", "baz"])
+ rule.add_dependencies(["hoge"])
+ rule.add_commands(["echo $@"])
+ mk.dump(out, removal_guard=False)
+ self.assertEqual(
+ out.getvalue(),
+ "foo: bar baz qux\n" + "\techo foo\n" + "bar baz: hoge\n" + "\techo $@\n",
+ )
+
+ out = StringIO()
+ mk.dump(out)
+ self.assertEqual(
+ out.getvalue(),
+ "foo: bar baz qux\n"
+ + "\techo foo\n"
+ + "bar baz: hoge\n"
+ + "\techo $@\n"
+ + "hoge qux:\n",
+ )
+
+ def test_statement(self):
+ out = StringIO()
+ mk = Makefile()
+ mk.create_rule(["foo"]).add_dependencies(["bar"]).add_commands(["echo foo"])
+ mk.add_statement("BAR = bar")
+ mk.create_rule(["$(BAR)"]).add_commands(["echo $@"])
+ mk.dump(out, removal_guard=False)
+ self.assertEqual(
+ out.getvalue(),
+ "foo: bar\n" + "\techo foo\n" + "BAR = bar\n" + "$(BAR):\n" + "\techo $@\n",
+ )
+
+ @unittest.skipIf(os.name != "nt", "Test only applicable on Windows.")
+ def test_path_normalization(self):
+ out = StringIO()
+ mk = Makefile()
+ rule = mk.create_rule(["c:\\foo"])
+ rule.add_dependencies(["c:\\bar", "c:\\baz\\qux"])
+ rule.add_commands(["echo c:\\foo"])
+ mk.dump(out)
+ self.assertEqual(
+ out.getvalue(),
+ "c:/foo: c:/bar c:/baz/qux\n" + "\techo c:\\foo\n" + "c:/bar c:/baz/qux:\n",
+ )
+
+ def test_read_dep_makefile(self):
+ input = StringIO(
+ os.path.abspath("foo")
+ + ": bar\n"
+ + "baz qux: \\ \n"
+ + "hoge \\\n"
+ + "piyo \\\n"
+ + "fuga\n"
+ + "fuga:\n"
+ )
+ result = list(read_dep_makefile(input))
+ self.assertEqual(len(result), 2)
+ self.assertEqual(
+ list(result[0].targets()), [os.path.abspath("foo").replace(os.sep, "/")]
+ )
+ self.assertEqual(list(result[0].dependencies()), ["bar"])
+ self.assertEqual(list(result[1].targets()), ["baz", "qux"])
+ self.assertEqual(list(result[1].dependencies()), ["hoge", "piyo", "fuga"])
+
+ def test_write_dep_makefile(self):
+ out = StringIO()
+ write_dep_makefile(out, "target", ["b", "c", "a"])
+ self.assertEqual(out.getvalue(), "target: b c a\n" + "a b c:\n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_manifest.py b/python/mozbuild/mozbuild/test/test_manifest.py
new file mode 100644
index 0000000000..e5675aba36
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_manifest.py
@@ -0,0 +1,2081 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import unittest
+
+import mozfile
+from mozunit import main
+
+from mozbuild.vendor.moz_yaml import MozYamlVerifyError, load_moz_yaml
+
+
+class TestManifest(unittest.TestCase):
+ def process_test_vectors(self, test_vectors):
+ index = 0
+ for vector in test_vectors:
+ print("Testing index", index)
+ expected, yaml = vector
+ with mozfile.NamedTemporaryFile() as tf:
+ tf.write(yaml)
+ tf.flush()
+ if expected == "exception":
+ with self.assertRaises(MozYamlVerifyError):
+ load_moz_yaml(tf.name, require_license_file=False)
+ else:
+ self.assertDictEqual(
+ load_moz_yaml(tf.name, require_license_file=False), expected
+ )
+ index += 1
+
+ # ===========================================================================================
+ def test_simple(self):
+ simple_dict = {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ }
+
+ self.process_test_vectors(
+ [
+ (
+ simple_dict,
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ (
+ simple_dict,
+ b"""
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ ]
+ )
+
+ # ===========================================================================================
+ def test_updatebot(self):
+ self.process_test_vectors(
+ [
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: 001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "updatebot": {
+ "try-preset": "foo",
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: 001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ try-preset: foo
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "fuzzy-query": "!linux64",
+ "tasks": [{"type": "commit-alert"}],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ fuzzy-query: "!linux64"
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ try-preset: foo
+ fuzzy-query: "!linux64"
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "fuzzy-paths": ["dir1/", "dir2"],
+ "tasks": [{"type": "commit-alert"}],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ fuzzy-paths:
+ - dir1/
+ - dir2
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "fuzzy-paths": ["dir1/"],
+ "tasks": [{"type": "commit-alert"}],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ fuzzy-paths: ['dir1/']
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ "tracking": "commit",
+ "flavor": "rust",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "tasks": [
+ {"type": "commit-alert", "frequency": "release"},
+ {
+ "type": "vendoring",
+ "enabled": False,
+ "cc": ["b@example.com"],
+ "needinfo": ["c@example.com"],
+ "frequency": "1 weeks",
+ "platform": "windows",
+ },
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ tracking: commit
+ source-hosting: gitlab
+ flavor: rust
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ frequency: release
+ - type: vendoring
+ enabled: False
+ cc: ["b@example.com"]
+ needinfo: ["c@example.com"]
+ frequency: 1 weeks
+ platform: windows
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ "tracking": "tag",
+ "flavor": "rust",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "tasks": [
+ {"type": "commit-alert", "frequency": "release"},
+ {
+ "type": "vendoring",
+ "enabled": False,
+ "cc": ["b@example.com"],
+ "needinfo": ["c@example.com"],
+ "frequency": "1 weeks, 4 commits",
+ "platform": "windows",
+ },
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ tracking: tag
+ source-hosting: gitlab
+ flavor: rust
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ frequency: release
+ - type: vendoring
+ enabled: False
+ cc: ["b@example.com"]
+ needinfo: ["c@example.com"]
+ frequency: 1 weeks, 4 commits
+ platform: windows
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception", # rust flavor cannot use update-actions
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ tracking: tag
+ source-hosting: gitlab
+ flavor: rust
+ update-actions:
+ - action: move-file
+ from: foo
+ to: bar
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ frequency: release
+ - type: vendoring
+ enabled: False
+ cc: ["b@example.com"]
+ needinfo: ["c@example.com"]
+ frequency: 1 weeks, 4 commits
+ platform: windows
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "tasks": [
+ {
+ "type": "vendoring",
+ "enabled": False,
+ "cc": ["b@example.com", "c@example.com"],
+ "needinfo": ["d@example.com", "e@example.com"],
+ "frequency": "every",
+ },
+ {
+ "type": "commit-alert",
+ "filter": "none",
+ "source-extensions": [".c", ".cpp"],
+ "frequency": "2 weeks",
+ "platform": "linux",
+ },
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 2 weeks
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "tasks": [
+ {
+ "type": "vendoring",
+ "enabled": False,
+ "cc": ["b@example.com", "c@example.com"],
+ "needinfo": ["d@example.com", "e@example.com"],
+ "frequency": "every",
+ },
+ {
+ "type": "commit-alert",
+ "filter": "none",
+ "source-extensions": [".c", ".cpp"],
+ "frequency": "2 commits",
+ "platform": "linux",
+ },
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 2 commits
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ },
+ "updatebot": {
+ "maintainer-phab": "tjr",
+ "maintainer-bz": "a@example.com",
+ "tasks": [
+ {
+ "type": "vendoring",
+ "enabled": False,
+ "cc": ["b@example.com", "c@example.com"],
+ "needinfo": ["d@example.com", "e@example.com"],
+ "frequency": "every",
+ "blocking": "1234",
+ },
+ {
+ "type": "commit-alert",
+ "filter": "none",
+ "source-extensions": [".c", ".cpp"],
+ "frequency": "2 commits",
+ "platform": "linux",
+ },
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ blocking: 1234
+ - type: commit-alert
+ filter: none
+ frequency: 2 commits
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ branch: foo
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ blocking: 1234
+ - type: commit-alert
+ filter: none
+ frequency: 2 commits
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "description": "2D Graphics Library",
+ "url": "https://www.cairographics.org/",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ "flavor": "individual-files",
+ "individual-files": [
+ {"upstream": "foo", "destination": "bar"}
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: individual-files
+ individual-files:
+ - upstream: foo
+ destination: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "description": "2D Graphics Library",
+ "url": "https://www.cairographics.org/",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ "flavor": "individual-files",
+ "individual-files": [
+ {"upstream": "foo", "destination": "bar"}
+ ],
+ "update-actions": [
+ {"action": "move-file", "from": "foo", "to": "bar"}
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: individual-files
+ individual-files:
+ - upstream: foo
+ destination: bar
+ update-actions:
+ - action: move-file
+ from: foo
+ to: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "description": "2D Graphics Library",
+ "url": "https://www.cairographics.org/",
+ "release": "version 1.6.4",
+ "revision": "AA001122334455",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ "vendoring": {
+ "url": "https://example.com",
+ "source-hosting": "gitlab",
+ "flavor": "individual-files",
+ "individual-files-default-destination": "bar",
+ "individual-files-default-upstream": "foo",
+ "individual-files-list": ["foo", "bar"],
+ "update-actions": [
+ {"action": "move-file", "from": "foo", "to": "bar"}
+ ],
+ },
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: individual-files
+ individual-files-default-upstream: foo
+ individual-files-default-destination: bar
+ individual-files-list:
+ - foo
+ - bar
+ update-actions:
+ - action: move-file
+ from: foo
+ to: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception", # can't have both types of indidivudal-files list
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: individual-files
+ individual-files-list:
+ - foo
+ individual-files:
+ - upstream: foo
+ destination: bar
+ update-actions:
+ - action: move-file
+ from: foo
+ to: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception", # can't have indidivudal-files-default-upstream
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: individual-files
+ indidivudal-files-default-upstream: foo
+ individual-files:
+ - upstream: foo
+ destination: bar
+ update-actions:
+ - action: move-file
+ from: foo
+ to: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception", # must have indidivudal-files-default-upstream
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: individual-files
+ indidivudal-files-default-destination: foo
+ individual-files-list:
+ - foo
+ - bar
+ update-actions:
+ - action: move-file
+ from: foo
+ to: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ tracking: tag
+ flavor: individual-files
+ individual-files:
+ - upstream-src: foo
+ dst: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: individual-files
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: rust
+ individual-files:
+ - upstream: foo
+ destination: bar
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: rust
+ include:
+ - foo
+bugzilla:
+ product: Core
+ component: Graphics
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ blocking: foo
+ - type: commit-alert
+ filter: none
+ frequency: 2 commits
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ fuzzy-paths: "must-be-array"
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 2 commits
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 2 commits, 4 weeks
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 4 weeks, 2 commits, 3 weeks
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: chocolate
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 2 weeks
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ flavor: chocolate
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 01 commits
+ platform: linux
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ needinfo:
+ - d@example.com
+ - e@example.com
+ frequency: every
+ - type: commit-alert
+ filter: none
+ frequency: 2 weeks
+ platform: mac
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ - type: commit-alert
+ filter: none
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+bugzilla:
+ product: Core
+ component: Graphics
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ - type: commit-alert
+ filter: none
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ filter: none
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: foo
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ source-extensions:
+ - .c
+ - .cpp
+ """.strip(),
+ ),
+ # -------------------------------------------------
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ filter: hogwash
+ """.strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ - type: commit-alert
+ - type: commit-alert
+ filter: none
+ source-extensions:
+ - .c
+ - .cpp""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ - type: vendoring
+ - type: commit-alert
+ filter: none
+ source-extensions:
+ - .c
+ - .cpp""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ - type: commit-alert
+ frequency: every-release
+ filter: none
+ source-extensions:
+ - .c
+ - .cpp""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: vendoring
+ enabled: False
+ cc:
+ - b@example.com
+ - c@example.com
+ frequency: 2 months
+ - type: commit-alert
+ filter: none
+ source-extensions:
+ - .c
+ - .cpp""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+bugzilla:
+ product: Core
+ component: Graphics
+updatebot:
+ maintainer-phab: tjr
+ maintainer-bz: a@example.com
+ tasks:
+ - type: commit-alert
+ frequency: 0 weeks
+ """.strip(),
+ ),
+ ]
+ )
+
+ # ===========================================================================================
+ def test_malformed(self):
+ with mozfile.NamedTemporaryFile() as tf:
+ tf.write(b"blah")
+ tf.flush()
+ with self.assertRaises(MozYamlVerifyError):
+ load_moz_yaml(tf.name, require_license_file=False)
+
+ def test_schema(self):
+ with mozfile.NamedTemporaryFile() as tf:
+ tf.write(b"schema: 99")
+ tf.flush()
+ with self.assertRaises(MozYamlVerifyError):
+ load_moz_yaml(tf.name, require_license_file=False)
+
+ def test_json(self):
+ with mozfile.NamedTemporaryFile() as tf:
+ tf.write(
+ b'{"origin": {"release": "version 1.6.4", "url": "https://w'
+ b'ww.cairographics.org/", "description": "2D Graphics Libra'
+ b'ry", "license": ["MPL-1.1", "LGPL-2.1"], "name": "cairo"}'
+ b', "bugzilla": {"product": "Core", "component": "Graphics"'
+ b'}, "schema": 1}'
+ )
+ tf.flush()
+ with self.assertRaises(MozYamlVerifyError):
+ load_moz_yaml(tf.name, require_license_file=False)
+
+ def test_revision(self):
+ self.process_test_vectors(
+ [
+ (
+ {
+ "schema": "1",
+ "origin": {
+ "description": "2D Graphics Library",
+ "license": ["MPL-1.1", "LGPL-2.1"],
+ "name": "cairo",
+ "release": "version 1.6.4",
+ "revision": "v1.6.37",
+ "url": "https://www.cairographics.org/",
+ },
+ "bugzilla": {"component": "Graphics", "product": "Core"},
+ },
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: v1.6.37
+bugzilla:
+ product: Core
+ component: Graphics""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: 4.0.0.
+bugzilla:
+ product: Core
+ component: Graphics""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: 4.^.0
+bugzilla:
+ product: Core
+ component: Graphics""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: " "
+bugzilla:
+ product: Core
+ component: Graphics""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: ???
+bugzilla:
+ product: Core
+ component: Graphics""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: ]
+bugzilla:
+ product: Core
+ component: Graphics""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ update-actions:
+ - action: run-script
+ cwd: '{cwd}'
+ script: 'script.py'
+ args: ['hi']
+ pattern: 'hi'
+""".strip(),
+ ),
+ (
+ "exception",
+ b"""
+---
+schema: 1
+origin:
+ name: cairo
+ description: 2D Graphics Library
+ url: https://www.cairographics.org/
+ release: version 1.6.4
+ license:
+ - MPL-1.1
+ - LGPL-2.1
+ revision: AA001122334455
+bugzilla:
+ product: Core
+ component: Graphics
+vendoring:
+ url: https://example.com
+ source-hosting: gitlab
+ update-actions:
+ - action: run-script
+ cwd: '{cwd}'
+ args: ['hi']
+""".strip(),
+ ),
+ ]
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_mozconfig.py b/python/mozbuild/mozbuild/test/test_mozconfig.py
new file mode 100644
index 0000000000..20827d7f29
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_mozconfig.py
@@ -0,0 +1,275 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import unittest
+from shutil import rmtree
+from tempfile import mkdtemp
+
+from mozfile.mozfile import NamedTemporaryFile
+from mozunit import main
+
+from mozbuild.mozconfig import MozconfigLoader, MozconfigLoadException
+
+
+class TestMozconfigLoader(unittest.TestCase):
+ def setUp(self):
+ self._old_env = dict(os.environ)
+ os.environ.pop("MOZCONFIG", None)
+ os.environ.pop("MOZ_OBJDIR", None)
+ os.environ.pop("CC", None)
+ os.environ.pop("CXX", None)
+ self._temp_dirs = set()
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ for d in self._temp_dirs:
+ rmtree(d)
+
+ def get_loader(self):
+ return MozconfigLoader(self.get_temp_dir())
+
+ def get_temp_dir(self):
+ d = mkdtemp()
+ self._temp_dirs.add(d)
+
+ return d
+
+ def test_read_no_mozconfig(self):
+ # This is basically to ensure changes to defaults incur a test failure.
+ result = self.get_loader().read_mozconfig()
+
+ self.assertEqual(
+ result,
+ {
+ "path": None,
+ "topobjdir": None,
+ "configure_args": None,
+ "make_flags": None,
+ "make_extra": None,
+ "env": None,
+ "vars": None,
+ },
+ )
+
+ def test_read_empty_mozconfig(self):
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(result["path"], mozconfig.name)
+ self.assertIsNone(result["topobjdir"])
+ self.assertEqual(result["configure_args"], [])
+ self.assertEqual(result["make_flags"], [])
+ self.assertEqual(result["make_extra"], [])
+
+ for f in ("added", "removed", "modified"):
+ self.assertEqual(len(result["vars"][f]), 0)
+ self.assertEqual(len(result["env"][f]), 0)
+
+ self.assertEqual(result["env"]["unmodified"], {})
+
+ def test_read_capture_ac_options(self):
+ """Ensures ac_add_options calls are captured."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("ac_add_options --enable-debug\n")
+ mozconfig.write("ac_add_options --disable-tests --enable-foo\n")
+ mozconfig.write('ac_add_options --foo="bar baz"\n')
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+ self.assertEqual(
+ result["configure_args"],
+ ["--enable-debug", "--disable-tests", "--enable-foo", "--foo=bar baz"],
+ )
+
+ def test_read_ac_options_substitution(self):
+ """Ensure ac_add_options values are substituted."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("ac_add_options --foo=@TOPSRCDIR@\n")
+ mozconfig.flush()
+
+ loader = self.get_loader()
+ result = loader.read_mozconfig(mozconfig.name)
+ self.assertEqual(result["configure_args"], ["--foo=%s" % loader.topsrcdir])
+
+ def test_read_capture_mk_options(self):
+ """Ensures mk_add_options calls are captured."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("mk_add_options MOZ_OBJDIR=/foo/bar\n")
+ mozconfig.write('mk_add_options MOZ_MAKE_FLAGS="-j8 -s"\n')
+ mozconfig.write('mk_add_options FOO="BAR BAZ"\n')
+ mozconfig.write("mk_add_options BIZ=1\n")
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+ self.assertEqual(result["topobjdir"], "/foo/bar")
+ self.assertEqual(result["make_flags"], ["-j8", "-s"])
+ self.assertEqual(result["make_extra"], ["FOO=BAR BAZ", "BIZ=1"])
+
+ def test_read_no_mozconfig_objdir_environ(self):
+ os.environ["MOZ_OBJDIR"] = "obj-firefox"
+ result = self.get_loader().read_mozconfig()
+ self.assertEqual(result["topobjdir"], "obj-firefox")
+
+ def test_read_empty_mozconfig_objdir_environ(self):
+ os.environ["MOZ_OBJDIR"] = "obj-firefox"
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+ self.assertEqual(result["topobjdir"], "obj-firefox")
+
+ def test_read_capture_mk_options_objdir_environ(self):
+ """Ensures mk_add_options calls are captured and override the environ."""
+ os.environ["MOZ_OBJDIR"] = "obj-firefox"
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("mk_add_options MOZ_OBJDIR=/foo/bar\n")
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+ self.assertEqual(result["topobjdir"], "/foo/bar")
+
+ def test_read_moz_objdir_substitution(self):
+ """Ensure @TOPSRCDIR@ substitution is recognized in MOZ_OBJDIR."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/some-objdir")
+ mozconfig.flush()
+
+ loader = self.get_loader()
+ result = loader.read_mozconfig(mozconfig.name)
+
+ self.assertEqual(result["topobjdir"], "%s/some-objdir" % loader.topsrcdir)
+
+ def test_read_new_variables(self):
+ """New variables declared in mozconfig file are detected."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("CC=/usr/local/bin/clang\n")
+ mozconfig.write("CXX=/usr/local/bin/clang++\n")
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(
+ result["vars"]["added"],
+ {"CC": "/usr/local/bin/clang", "CXX": "/usr/local/bin/clang++"},
+ )
+ self.assertEqual(result["env"]["added"], {})
+
+ def test_read_exported_variables(self):
+ """Exported variables are caught as new variables."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("export MY_EXPORTED=woot\n")
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(result["vars"]["added"], {})
+ self.assertEqual(result["env"]["added"], {"MY_EXPORTED": "woot"})
+
+ def test_read_modify_variables(self):
+ """Variables modified by mozconfig are detected."""
+ old_path = os.path.realpath("/usr/bin/gcc")
+ new_path = os.path.realpath("/usr/local/bin/clang")
+ os.environ["CC"] = old_path
+
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write('CC="%s"\n' % new_path)
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(result["vars"]["modified"], {})
+ self.assertEqual(result["env"]["modified"], {"CC": (old_path, new_path)})
+
+ def test_read_unmodified_variables(self):
+ """Variables modified by mozconfig are detected."""
+ cc_path = os.path.realpath("/usr/bin/gcc")
+ os.environ["CC"] = cc_path
+
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(result["vars"]["unmodified"], {})
+ self.assertEqual(result["env"]["unmodified"], {"CC": cc_path})
+
+ def test_read_removed_variables(self):
+ """Variables unset by the mozconfig are detected."""
+ cc_path = os.path.realpath("/usr/bin/clang")
+ os.environ["CC"] = cc_path
+
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("unset CC\n")
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(result["vars"]["removed"], {})
+ self.assertEqual(result["env"]["removed"], {"CC": cc_path})
+
+ def test_read_multiline_variables(self):
+ """Ensure multi-line variables are captured properly."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write('multi="foo\nbar"\n')
+ mozconfig.write("single=1\n")
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(
+ result["vars"]["added"], {"multi": "foo\nbar", "single": "1"}
+ )
+ self.assertEqual(result["env"]["added"], {})
+
+ def test_read_topsrcdir_defined(self):
+ """Ensure $topsrcdir references work as expected."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("TEST=$topsrcdir")
+ mozconfig.flush()
+
+ loader = self.get_loader()
+ result = loader.read_mozconfig(mozconfig.name)
+
+ self.assertEqual(
+ result["vars"]["added"]["TEST"], loader.topsrcdir.replace(os.sep, "/")
+ )
+ self.assertEqual(result["env"]["added"], {})
+
+ def test_read_empty_variable_value(self):
+ """Ensure empty variable values are parsed properly."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write("EMPTY=\n")
+ mozconfig.write("export EXPORT_EMPTY=\n")
+ mozconfig.flush()
+
+ result = self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertEqual(
+ result["vars"]["added"],
+ {
+ "EMPTY": "",
+ },
+ )
+ self.assertEqual(result["env"]["added"], {"EXPORT_EMPTY": ""})
+
+ def test_read_load_exception(self):
+ """Ensure non-0 exit codes in mozconfigs are handled properly."""
+ with NamedTemporaryFile(mode="w") as mozconfig:
+ mozconfig.write('echo "hello world"\n')
+ mozconfig.write("exit 1\n")
+ mozconfig.flush()
+
+ with self.assertRaises(MozconfigLoadException) as e:
+ self.get_loader().read_mozconfig(mozconfig.name)
+
+ self.assertIn(
+ "Evaluation of your mozconfig exited with an error", str(e.exception)
+ )
+ self.assertEqual(e.exception.path, mozconfig.name.replace(os.sep, "/"))
+ self.assertEqual(e.exception.output, ["hello world"])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_mozinfo.py b/python/mozbuild/mozbuild/test/test_mozinfo.py
new file mode 100755
index 0000000000..0d966b3dcc
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_mozinfo.py
@@ -0,0 +1,318 @@
+#!/usr/bin/env python
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+import os
+import tempfile
+import unittest
+
+import mozunit
+import six
+from mozfile.mozfile import NamedTemporaryFile
+from six import StringIO
+
+from mozbuild.backend.configenvironment import ConfigEnvironment
+from mozbuild.mozinfo import build_dict, write_mozinfo
+
+
+class Base(object):
+ def _config(self, substs={}):
+ d = os.path.dirname(__file__)
+ return ConfigEnvironment(d, d, substs=substs)
+
+
+class TestBuildDict(unittest.TestCase, Base):
+ def test_missing(self):
+ """
+ Test that missing required values raises.
+ """
+
+ with self.assertRaises(Exception):
+ build_dict(self._config(substs=dict(OS_TARGET="foo")))
+
+ with self.assertRaises(Exception):
+ build_dict(self._config(substs=dict(TARGET_CPU="foo")))
+
+ with self.assertRaises(Exception):
+ build_dict(self._config(substs=dict(MOZ_WIDGET_TOOLKIT="foo")))
+
+ def test_win(self):
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="WINNT",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="windows",
+ )
+ )
+ )
+ self.assertEqual("win", d["os"])
+ self.assertEqual("x86", d["processor"])
+ self.assertEqual("windows", d["toolkit"])
+ self.assertEqual(32, d["bits"])
+
+ def test_linux(self):
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ )
+ )
+ )
+ self.assertEqual("linux", d["os"])
+ self.assertEqual("x86", d["processor"])
+ self.assertEqual("gtk", d["toolkit"])
+ self.assertEqual(32, d["bits"])
+
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="x86_64",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ )
+ )
+ )
+ self.assertEqual("linux", d["os"])
+ self.assertEqual("x86_64", d["processor"])
+ self.assertEqual("gtk", d["toolkit"])
+ self.assertEqual(64, d["bits"])
+
+ def test_mac(self):
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Darwin",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="cocoa",
+ )
+ )
+ )
+ self.assertEqual("mac", d["os"])
+ self.assertEqual("x86", d["processor"])
+ self.assertEqual("cocoa", d["toolkit"])
+ self.assertEqual(32, d["bits"])
+
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Darwin",
+ TARGET_CPU="x86_64",
+ MOZ_WIDGET_TOOLKIT="cocoa",
+ )
+ )
+ )
+ self.assertEqual("mac", d["os"])
+ self.assertEqual("x86_64", d["processor"])
+ self.assertEqual("cocoa", d["toolkit"])
+ self.assertEqual(64, d["bits"])
+
+ def test_android(self):
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Android",
+ TARGET_CPU="arm",
+ MOZ_WIDGET_TOOLKIT="android",
+ )
+ )
+ )
+ self.assertEqual("android", d["os"])
+ self.assertEqual("arm", d["processor"])
+ self.assertEqual("android", d["toolkit"])
+ self.assertEqual(32, d["bits"])
+
+ def test_x86(self):
+ """
+ Test that various i?86 values => x86.
+ """
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="WINNT",
+ TARGET_CPU="i486",
+ MOZ_WIDGET_TOOLKIT="windows",
+ )
+ )
+ )
+ self.assertEqual("x86", d["processor"])
+
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="WINNT",
+ TARGET_CPU="i686",
+ MOZ_WIDGET_TOOLKIT="windows",
+ )
+ )
+ )
+ self.assertEqual("x86", d["processor"])
+
+ def test_arm(self):
+ """
+ Test that all arm CPU architectures => arm.
+ """
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="arm",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ )
+ )
+ )
+ self.assertEqual("arm", d["processor"])
+
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="armv7",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ )
+ )
+ )
+ self.assertEqual("arm", d["processor"])
+
+ def test_unknown(self):
+ """
+ Test that unknown values pass through okay.
+ """
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="RandOS",
+ TARGET_CPU="cptwo",
+ MOZ_WIDGET_TOOLKIT="foobar",
+ )
+ )
+ )
+ self.assertEqual("randos", d["os"])
+ self.assertEqual("cptwo", d["processor"])
+ self.assertEqual("foobar", d["toolkit"])
+ # unknown CPUs should not get a bits value
+ self.assertFalse("bits" in d)
+
+ def test_debug(self):
+ """
+ Test that debug values are properly detected.
+ """
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ )
+ )
+ )
+ self.assertEqual(False, d["debug"])
+
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ MOZ_DEBUG="1",
+ )
+ )
+ )
+ self.assertEqual(True, d["debug"])
+
+ def test_crashreporter(self):
+ """
+ Test that crashreporter values are properly detected.
+ """
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ )
+ )
+ )
+ self.assertEqual(False, d["crashreporter"])
+
+ d = build_dict(
+ self._config(
+ dict(
+ OS_TARGET="Linux",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="gtk",
+ MOZ_CRASHREPORTER="1",
+ )
+ )
+ )
+ self.assertEqual(True, d["crashreporter"])
+
+
+class TestWriteMozinfo(unittest.TestCase, Base):
+ """
+ Test the write_mozinfo function.
+ """
+
+ def setUp(self):
+ fd, f = tempfile.mkstemp()
+ self.f = six.ensure_text(f)
+ os.close(fd)
+
+ def tearDown(self):
+ os.unlink(self.f)
+
+ def test_basic(self):
+ """
+ Test that writing to a file produces correct output.
+ """
+ c = self._config(
+ dict(
+ OS_TARGET="WINNT",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="windows",
+ )
+ )
+ tempdir = tempfile.gettempdir()
+ c.topsrcdir = tempdir
+ with NamedTemporaryFile(
+ dir=os.path.normpath(c.topsrcdir), mode="wt"
+ ) as mozconfig:
+ mozconfig.write("unused contents")
+ mozconfig.flush()
+ c.mozconfig = mozconfig.name
+ write_mozinfo(self.f, c)
+ with open(self.f) as f:
+ d = json.load(f)
+ self.assertEqual("win", d["os"])
+ self.assertEqual("x86", d["processor"])
+ self.assertEqual("windows", d["toolkit"])
+ self.assertEqual(tempdir, d["topsrcdir"])
+ self.assertEqual(mozconfig.name, d["mozconfig"])
+ self.assertEqual(32, d["bits"])
+
+ def test_fileobj(self):
+ """
+ Test that writing to a file-like object produces correct output.
+ """
+ s = StringIO()
+ c = self._config(
+ dict(
+ OS_TARGET="WINNT",
+ TARGET_CPU="i386",
+ MOZ_WIDGET_TOOLKIT="windows",
+ )
+ )
+ write_mozinfo(s, c)
+ d = json.loads(s.getvalue())
+ self.assertEqual("win", d["os"])
+ self.assertEqual("x86", d["processor"])
+ self.assertEqual("windows", d["toolkit"])
+ self.assertEqual(32, d["bits"])
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_preprocessor.py b/python/mozbuild/mozbuild/test/test_preprocessor.py
new file mode 100644
index 0000000000..82039c2bd7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_preprocessor.py
@@ -0,0 +1,832 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import shutil
+import unittest
+from tempfile import mkdtemp
+
+from mozunit import MockedOpen, main
+from six import StringIO
+
+from mozbuild.preprocessor import Preprocessor
+
+
+class TestPreprocessor(unittest.TestCase):
+ """
+ Unit tests for the Context class
+ """
+
+ def setUp(self):
+ self.pp = Preprocessor()
+ self.pp.out = StringIO()
+
+ def do_include_compare(self, content_lines, expected_lines):
+ content = "%s" % "\n".join(content_lines)
+ expected = "%s".rstrip() % "\n".join(expected_lines)
+
+ with MockedOpen({"dummy": content}):
+ self.pp.do_include("dummy")
+ self.assertEqual(self.pp.out.getvalue().rstrip("\n"), expected)
+
+ def do_include_pass(self, content_lines):
+ self.do_include_compare(content_lines, ["PASS"])
+
+ def test_conditional_if_0(self):
+ self.do_include_pass(
+ [
+ "#if 0",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_no_marker(self):
+ lines = [
+ "#if 0",
+ "PASS",
+ "#endif",
+ ]
+ self.pp.setMarker(None)
+ self.do_include_compare(lines, lines)
+
+ def test_string_value(self):
+ self.do_include_compare(
+ [
+ "#define FOO STRING",
+ "#if FOO",
+ "string value is true",
+ "#else",
+ "string value is false",
+ "#endif",
+ ],
+ ["string value is false"],
+ )
+
+ def test_number_value(self):
+ self.do_include_compare(
+ [
+ "#define FOO 1",
+ "#if FOO",
+ "number value is true",
+ "#else",
+ "number value is false",
+ "#endif",
+ ],
+ ["number value is true"],
+ )
+
+ def test_conditional_if_0_elif_1(self):
+ self.do_include_pass(
+ [
+ "#if 0",
+ "#elif 1",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_if_1(self):
+ self.do_include_pass(
+ [
+ "#if 1",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_if_0_or_1(self):
+ self.do_include_pass(
+ [
+ "#if 0 || 1",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_if_1_elif_1_else(self):
+ self.do_include_pass(
+ [
+ "#if 1",
+ "PASS",
+ "#elif 1",
+ "FAIL",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_if_1_if_1(self):
+ self.do_include_pass(
+ [
+ "#if 1",
+ "#if 1",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_not_0(self):
+ self.do_include_pass(
+ [
+ "#if !0",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_not_0_and_1(self):
+ self.do_include_pass(
+ [
+ "#if !0 && !1",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_not_1(self):
+ self.do_include_pass(
+ [
+ "#if !1",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_conditional_not_emptyval(self):
+ self.do_include_compare(
+ [
+ "#define EMPTYVAL",
+ "#ifndef EMPTYVAL",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ "#ifdef EMPTYVAL",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ],
+ ["PASS", "PASS"],
+ )
+
+ def test_conditional_not_nullval(self):
+ self.do_include_pass(
+ [
+ "#define NULLVAL 0",
+ "#if !NULLVAL",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_indentation(self):
+ self.do_include_pass(
+ [
+ " #define NULLVAL 0",
+ " #if !NULLVAL",
+ "PASS",
+ " #else",
+ "FAIL",
+ " #endif",
+ ]
+ )
+
+ def test_expand(self):
+ self.do_include_pass(
+ [
+ "#define ASVAR AS",
+ "#expand P__ASVAR__S",
+ ]
+ )
+
+ def test_undef_defined(self):
+ self.do_include_compare(
+ [
+ "#define BAR",
+ "#undef BAR",
+ "BAR",
+ ],
+ ["BAR"],
+ )
+
+ def test_undef_undefined(self):
+ self.do_include_compare(
+ [
+ "#undef BAR",
+ ],
+ [],
+ )
+
+ def test_filter_attemptSubstitution(self):
+ self.do_include_compare(
+ [
+ "#filter attemptSubstitution",
+ "@PASS@",
+ "#unfilter attemptSubstitution",
+ ],
+ ["@PASS@"],
+ )
+
+ def test_filter_emptyLines(self):
+ self.do_include_compare(
+ [
+ "lines with a",
+ "",
+ "blank line",
+ "#filter emptyLines",
+ "lines with",
+ "",
+ "no blank lines",
+ "#unfilter emptyLines",
+ "yet more lines with",
+ "",
+ "blank lines",
+ ],
+ [
+ "lines with a",
+ "",
+ "blank line",
+ "lines with",
+ "no blank lines",
+ "yet more lines with",
+ "",
+ "blank lines",
+ ],
+ )
+
+ def test_filter_dumbComments(self):
+ self.do_include_compare(
+ [
+ "#filter dumbComments",
+ "PASS//PASS // PASS",
+ " //FAIL",
+ "// FAIL",
+ "PASS //",
+ "PASS // FAIL",
+ "//",
+ "",
+ "#unfilter dumbComments",
+ "// PASS",
+ ],
+ [
+ "PASS//PASS // PASS",
+ "",
+ "",
+ "PASS //",
+ "PASS // FAIL",
+ "",
+ "",
+ "// PASS",
+ ],
+ )
+
+ def test_filter_dumbComments_and_emptyLines(self):
+ self.do_include_compare(
+ [
+ "#filter dumbComments emptyLines",
+ "PASS//PASS // PASS",
+ " //FAIL",
+ "// FAIL",
+ "PASS //",
+ "PASS // FAIL",
+ "//",
+ "",
+ "#unfilter dumbComments emptyLines",
+ "",
+ "// PASS",
+ ],
+ [
+ "PASS//PASS // PASS",
+ "PASS //",
+ "PASS // FAIL",
+ "",
+ "// PASS",
+ ],
+ )
+
+ def test_filter_substitution(self):
+ self.do_include_pass(
+ [
+ "#define VAR ASS",
+ "#filter substitution",
+ "P@VAR@",
+ "#unfilter substitution",
+ ]
+ )
+
+ def test_error(self):
+ with MockedOpen({"f": "#error spit this message out\n"}):
+ with self.assertRaises(Preprocessor.Error) as e:
+ self.pp.do_include("f")
+ self.assertEqual(e.args[0][-1], "spit this message out")
+
+ def test_ambigous_command(self):
+ comment = "# if I tell you a joke\n"
+ with MockedOpen({"f": comment}):
+ with self.assertRaises(Preprocessor.Error) as e:
+ self.pp.do_include("f")
+ the_exception = e.exception
+ self.assertEqual(the_exception.args[0][-1], comment)
+
+ def test_javascript_line(self):
+ # The preprocessor is reading the filename from somewhere not caught
+ # by MockedOpen.
+ tmpdir = mkdtemp()
+ try:
+ full = os.path.join(tmpdir, "javascript_line.js.in")
+ with open(full, "w") as fh:
+ fh.write(
+ "\n".join(
+ [
+ "// Line 1",
+ "#if 0",
+ "// line 3",
+ "#endif",
+ "// line 5",
+ "# comment",
+ "// line 7",
+ "// line 8",
+ "// line 9",
+ "# another comment",
+ "// line 11",
+ "#define LINE 1",
+ "// line 13, given line number overwritten with 2",
+ "",
+ ]
+ )
+ )
+
+ self.pp.do_include(full)
+ out = "\n".join(
+ [
+ "// Line 1",
+ '//@line 5 "CWDjavascript_line.js.in"',
+ "// line 5",
+ '//@line 7 "CWDjavascript_line.js.in"',
+ "// line 7",
+ "// line 8",
+ "// line 9",
+ '//@line 11 "CWDjavascript_line.js.in"',
+ "// line 11",
+ '//@line 2 "CWDjavascript_line.js.in"',
+ "// line 13, given line number overwritten with 2",
+ "",
+ ]
+ )
+ out = out.replace("CWD", tmpdir + os.path.sep)
+ self.assertEqual(self.pp.out.getvalue(), out)
+ finally:
+ shutil.rmtree(tmpdir)
+
+ def test_literal(self):
+ self.do_include_pass(
+ [
+ "#literal PASS",
+ ]
+ )
+
+ def test_var_directory(self):
+ self.do_include_pass(
+ [
+ "#ifdef DIRECTORY",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_var_file(self):
+ self.do_include_pass(
+ [
+ "#ifdef FILE",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_var_if_0(self):
+ self.do_include_pass(
+ [
+ "#define VAR 0",
+ "#if VAR",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_var_if_0_elifdef(self):
+ self.do_include_pass(
+ [
+ "#if 0",
+ "#elifdef FILE",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_var_if_0_elifndef(self):
+ self.do_include_pass(
+ [
+ "#if 0",
+ "#elifndef VAR",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_var_ifdef_0(self):
+ self.do_include_pass(
+ [
+ "#define VAR 0",
+ "#ifdef VAR",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_var_ifdef_1_or_undef(self):
+ self.do_include_pass(
+ [
+ "#define FOO 1",
+ "#if defined(FOO) || defined(BAR)",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_var_ifdef_undef(self):
+ self.do_include_pass(
+ [
+ "#define VAR 0",
+ "#undef VAR",
+ "#ifdef VAR",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_var_ifndef_0(self):
+ self.do_include_pass(
+ [
+ "#define VAR 0",
+ "#ifndef VAR",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_var_ifndef_0_and_undef(self):
+ self.do_include_pass(
+ [
+ "#define FOO 0",
+ "#if !defined(FOO) && !defined(BAR)",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_var_ifndef_undef(self):
+ self.do_include_pass(
+ [
+ "#define VAR 0",
+ "#undef VAR",
+ "#ifndef VAR",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_var_line(self):
+ self.do_include_pass(
+ [
+ "#ifdef LINE",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_filterDefine(self):
+ self.do_include_pass(
+ [
+ "#filter substitution",
+ "#define VAR AS",
+ "#define VAR2 P@VAR@",
+ "@VAR2@S",
+ ]
+ )
+
+ def test_number_value_equals(self):
+ self.do_include_pass(
+ [
+ "#define FOO 1000",
+ "#if FOO == 1000",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_default_defines(self):
+ self.pp.handleCommandLine(["-DFOO"])
+ self.do_include_pass(
+ [
+ "#if FOO == 1",
+ "PASS",
+ "#else",
+ "FAIL",
+ ]
+ )
+
+ def test_number_value_equals_defines(self):
+ self.pp.handleCommandLine(["-DFOO=1000"])
+ self.do_include_pass(
+ [
+ "#if FOO == 1000",
+ "PASS",
+ "#else",
+ "FAIL",
+ ]
+ )
+
+ def test_octal_value_equals(self):
+ self.do_include_pass(
+ [
+ "#define FOO 0100",
+ "#if FOO == 0100",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_octal_value_equals_defines(self):
+ self.pp.handleCommandLine(["-DFOO=0100"])
+ self.do_include_pass(
+ [
+ "#if FOO == 0100",
+ "PASS",
+ "#else",
+ "FAIL",
+ "#endif",
+ ]
+ )
+
+ def test_value_quoted_expansion(self):
+ """
+ Quoted values on the commandline don't currently have quotes stripped.
+ Pike says this is for compat reasons.
+ """
+ self.pp.handleCommandLine(['-DFOO="ABCD"'])
+ self.do_include_compare(
+ [
+ "#filter substitution",
+ "@FOO@",
+ ],
+ ['"ABCD"'],
+ )
+
+ def test_octal_value_quoted_expansion(self):
+ self.pp.handleCommandLine(['-DFOO="0100"'])
+ self.do_include_compare(
+ [
+ "#filter substitution",
+ "@FOO@",
+ ],
+ ['"0100"'],
+ )
+
+ def test_number_value_not_equals_quoted_defines(self):
+ self.pp.handleCommandLine(['-DFOO="1000"'])
+ self.do_include_pass(
+ [
+ "#if FOO == 1000",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_octal_value_not_equals_quoted_defines(self):
+ self.pp.handleCommandLine(['-DFOO="0100"'])
+ self.do_include_pass(
+ [
+ "#if FOO == 0100",
+ "FAIL",
+ "#else",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+ def test_undefined_variable(self):
+ with MockedOpen({"f": "#filter substitution\n@foo@"}):
+ with self.assertRaises(Preprocessor.Error) as e:
+ self.pp.do_include("f")
+ self.assertEqual(e.key, "UNDEFINED_VAR")
+
+ def test_include(self):
+ files = {
+ "foo/test": "\n".join(
+ [
+ "#define foo foobarbaz",
+ "#include @inc@",
+ "@bar@",
+ "",
+ ]
+ ),
+ "bar": "\n".join(
+ [
+ "#define bar barfoobaz",
+ "@foo@",
+ "",
+ ]
+ ),
+ "f": "\n".join(
+ [
+ "#filter substitution",
+ "#define inc ../bar",
+ "#include foo/test",
+ "",
+ ]
+ ),
+ }
+
+ with MockedOpen(files):
+ self.pp.do_include("f")
+ self.assertEqual(self.pp.out.getvalue(), "foobarbaz\nbarfoobaz\n")
+
+ def test_include_line(self):
+ files = {
+ "srcdir/test.js": "\n".join(
+ [
+ "#define foo foobarbaz",
+ "#include @inc@",
+ "@bar@",
+ "",
+ ]
+ ),
+ "srcdir/bar.js": "\n".join(
+ [
+ "#define bar barfoobaz",
+ "@foo@",
+ "",
+ ]
+ ),
+ "srcdir/foo.js": "\n".join(
+ [
+ "bazfoobar",
+ "#include bar.js",
+ "bazbarfoo",
+ "",
+ ]
+ ),
+ "objdir/baz.js": "baz\n",
+ "srcdir/f.js": "\n".join(
+ [
+ "#include foo.js",
+ "#filter substitution",
+ "#define inc bar.js",
+ "#include test.js",
+ "#include ../objdir/baz.js",
+ "fin",
+ "",
+ ]
+ ),
+ }
+
+ preprocessed = (
+ '//@line 1 "$SRCDIR/foo.js"\n'
+ "bazfoobar\n"
+ '//@line 2 "$SRCDIR/bar.js"\n'
+ "@foo@\n"
+ '//@line 3 "$SRCDIR/foo.js"\n'
+ "bazbarfoo\n"
+ '//@line 2 "$SRCDIR/bar.js"\n'
+ "foobarbaz\n"
+ '//@line 3 "$SRCDIR/test.js"\n'
+ "barfoobaz\n"
+ '//@line 1 "$OBJDIR/baz.js"\n'
+ "baz\n"
+ '//@line 6 "$SRCDIR/f.js"\n'
+ "fin\n"
+ )
+
+ # Try with separate srcdir/objdir
+ with MockedOpen(files):
+ self.pp.topsrcdir = os.path.abspath("srcdir")
+ self.pp.topobjdir = os.path.abspath("objdir")
+ self.pp.do_include("srcdir/f.js")
+ self.assertEqual(self.pp.out.getvalue(), preprocessed)
+
+ # Try again with relative objdir
+ self.setUp()
+ files["srcdir/objdir/baz.js"] = files["objdir/baz.js"]
+ del files["objdir/baz.js"]
+ files["srcdir/f.js"] = files["srcdir/f.js"].replace("../", "")
+ with MockedOpen(files):
+ self.pp.topsrcdir = os.path.abspath("srcdir")
+ self.pp.topobjdir = os.path.abspath("srcdir/objdir")
+ self.pp.do_include("srcdir/f.js")
+ self.assertEqual(self.pp.out.getvalue(), preprocessed)
+
+ def test_include_missing_file(self):
+ with MockedOpen({"f": "#include foo\n"}):
+ with self.assertRaises(Preprocessor.Error) as e:
+ self.pp.do_include("f")
+ self.assertEqual(e.exception.key, "FILE_NOT_FOUND")
+
+ def test_include_undefined_variable(self):
+ with MockedOpen({"f": "#filter substitution\n#include @foo@\n"}):
+ with self.assertRaises(Preprocessor.Error) as e:
+ self.pp.do_include("f")
+ self.assertEqual(e.exception.key, "UNDEFINED_VAR")
+
+ def test_include_literal_at(self):
+ files = {
+ "@foo@": "#define foo foobarbaz\n",
+ "f": "#include @foo@\n#filter substitution\n@foo@\n",
+ }
+
+ with MockedOpen(files):
+ self.pp.do_include("f")
+ self.assertEqual(self.pp.out.getvalue(), "foobarbaz\n")
+
+ def test_command_line_literal_at(self):
+ with MockedOpen({"@foo@.in": "@foo@\n"}):
+ self.pp.handleCommandLine(["-Fsubstitution", "-Dfoo=foobarbaz", "@foo@.in"])
+ self.assertEqual(self.pp.out.getvalue(), "foobarbaz\n")
+
+ def test_invalid_ifdef(self):
+ with MockedOpen({"dummy": "#ifdef FOO == BAR\nPASS\n#endif"}):
+ with self.assertRaises(Preprocessor.Error) as e:
+ self.pp.do_include("dummy")
+ self.assertEqual(e.exception.key, "INVALID_VAR")
+
+ with MockedOpen({"dummy": "#ifndef FOO == BAR\nPASS\n#endif"}):
+ with self.assertRaises(Preprocessor.Error) as e:
+ self.pp.do_include("dummy")
+ self.assertEqual(e.exception.key, "INVALID_VAR")
+
+ # Trailing whitespaces, while not nice, shouldn't be an error.
+ self.do_include_pass(
+ [
+ "#ifndef FOO ",
+ "PASS",
+ "#endif",
+ ]
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_pythonutil.py b/python/mozbuild/mozbuild/test/test_pythonutil.py
new file mode 100644
index 0000000000..6ebb5cc46e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_pythonutil.py
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+from mozunit import main
+
+from mozbuild.pythonutil import iter_modules_in_path
+
+
+def test_iter_modules_in_path():
+ tests_path = os.path.normcase(os.path.dirname(__file__))
+ paths = list(iter_modules_in_path(tests_path))
+ assert set(paths) == set(
+ [
+ os.path.join(os.path.abspath(tests_path), "__init__.py"),
+ os.path.join(os.path.abspath(tests_path), "test_pythonutil.py"),
+ ]
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_rewrite_mozbuild.py b/python/mozbuild/mozbuild/test/test_rewrite_mozbuild.py
new file mode 100644
index 0000000000..467295c9e9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_rewrite_mozbuild.py
@@ -0,0 +1,515 @@
+# coding: utf-8
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import tempfile
+import unittest
+
+from mozunit import main
+
+import mozbuild.vendor.rewrite_mozbuild as mu
+
+SAMPLE_PIXMAN_MOZBUILD = """
+if CONFIG['OS_ARCH'] != 'Darwin' and CONFIG['CC_TYPE'] in ('clang', 'gcc'):
+ if CONFIG['HAVE_ARM_NEON']:
+ SOURCES += [
+ "pixman-arm-neon-asm-bilinear.S",
+ "pixman-arm-neon-asm.S",
+ ]
+ if CONFIG['HAVE_ARM_SIMD']:
+ SOURCES += [
+ 'pixman-arm-simd-asm-scaled.S',
+ 'pixman-arm-simd-asm.S']
+
+SOURCES += ['pixman-region32.c',
+ 'pixman-solid-fill.c',
+ 'pixman-trap.c',
+ 'pixman-utils.c',
+ 'pixman-x86.c',
+ 'pixman.c',
+]
+
+if use_sse2:
+ DEFINES['USE_SSE'] = True
+ DEFINES['USE_SSE2'] = True
+ SOURCES += ['pixman-sse2.c']
+ SOURCES['pixman-sse2.c'].flags += CONFIG['SSE_FLAGS'] + CONFIG['SSE2_FLAGS']
+ if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
+ SOURCES['pixman-sse2.c'].flags += ['-Winline']
+"""
+
+SAMPLE_DAV1D_MOZBUILD = """
+SOURCES += [
+ '../../third_party/dav1d/src/cdf.c',
+ '../../third_party/dav1d/src/cpu.c',
+ ]
+EXPORTS = [
+ '../../third_party/dav1d/src/header1.h',
+ '../../third_party/dav1d/src/header2.h',
+ ]
+"""
+
+
+SAMPLE_JPEGXL_MOZBUILD = """
+SOURCES += [
+ "/third_party/jpeg-xl/lib/jxl/ac_strategy.cc",
+ "/third_party/jpeg-xl/lib/jxl/alpha.cc",
+ "/third_party/jpeg-xl/lib/jxl/ans_common.cc",
+ "/third_party/jpeg-xl/lib/jxl/aux_out.cc",
+ ]
+EXPORTS.bob.carol = [
+ "/third_party/jpeg-xl/lib/jxl/header1.hpp",
+ "/third_party/jpeg-xl/lib/jxl/header2.h",
+]
+"""
+
+
+def _make_mozbuild_directory_structure(mozbuild_path, contents):
+ d = tempfile.TemporaryDirectory()
+ os.makedirs(os.path.join(d.name, os.path.split(mozbuild_path)[0]))
+
+ arcconfig = open(os.path.join(d.name, ".arcconfig"), mode="w")
+ arcconfig.close()
+
+ mozbuild = open(os.path.join(d.name, mozbuild_path), mode="w")
+ mozbuild.write(contents)
+ mozbuild.close()
+
+ return d
+
+
+class TestUtils(unittest.TestCase):
+ def test_normalize_filename(self):
+ self.assertEqual(mu.normalize_filename("foo/bar/moz.build", "/"), "/")
+ self.assertEqual(
+ mu.normalize_filename("foo/bar/moz.build", "a.c"), "foo/bar/a.c"
+ )
+ self.assertEqual(
+ mu.normalize_filename("foo/bar/moz.build", "baz/a.c"), "foo/bar/baz/a.c"
+ )
+ self.assertEqual(mu.normalize_filename("foo/bar/moz.build", "/a.c"), "/a.c")
+
+ def test_unnormalize_filename(self):
+ test_vectors = [
+ ("foo/bar/moz.build", "/"),
+ ("foo/bar/moz.build", "a.c"),
+ ("foo/bar/moz.build", "baz/a.c"),
+ ("foo/bar/moz.build", "/a.c"),
+ ]
+
+ for vector in test_vectors:
+ mozbuild, file = vector
+ self.assertEqual(
+ mu.unnormalize_filename(
+ mozbuild, mu.normalize_filename(mozbuild, file)
+ ),
+ file,
+ )
+
+ def test_find_all_posible_assignments_from_filename(self):
+ test_vectors = [
+ # (
+ # target_filename_normalized
+ # source_assignments
+ # expected
+ # )
+ (
+ "root/dir/asm/blah.S",
+ {
+ "> SOURCES": ["root/dir/main.c"],
+ "> if conditional > SOURCES": ["root/dir/asm/blah.S"],
+ },
+ {"> if conditional > SOURCES": ["root/dir/asm/blah.S"]},
+ ),
+ (
+ "root/dir/dostuff.c",
+ {
+ "> SOURCES": ["root/dir/main.c"],
+ "> if conditional > SOURCES": ["root/dir/asm/blah.S"],
+ },
+ {"> SOURCES": ["root/dir/main.c"]},
+ ),
+ ]
+
+ for vector in test_vectors:
+ target_filename_normalized, source_assignments, expected = vector
+ actual = mu.find_all_posible_assignments_from_filename(
+ source_assignments, target_filename_normalized
+ )
+ self.assertEqual(actual, expected)
+
+ def test_filenames_directory_is_in_filename_list(self):
+ test_vectors = [
+ # (
+ # normalized filename
+ # list of normalized_filenames
+ # expected
+ # )
+ ("foo/bar/a.c", ["foo/b.c"], False),
+ ("foo/bar/a.c", ["foo/b.c", "foo/bar/c.c"], True),
+ ("foo/bar/a.c", ["foo/b.c", "foo/bar/baz/d.c"], False),
+ ]
+ for vector in test_vectors:
+ normalized_filename, list_of_normalized_filesnames, expected = vector
+ actual = mu.filenames_directory_is_in_filename_list(
+ normalized_filename, list_of_normalized_filesnames
+ )
+ self.assertEqual(actual, expected)
+
+ def test_guess_best_assignment(self):
+ test_vectors = [
+ # (
+ # filename_normalized
+ # source_assignments
+ # expected
+ # )
+ (
+ "foo/asm_arm.c",
+ {
+ "> SOURCES": ["foo/main.c", "foo/all_utility.c"],
+ "> if ASM > SOURCES": ["foo/asm_x86.c"],
+ },
+ "> if ASM > SOURCES",
+ )
+ ]
+ for vector in test_vectors:
+ normalized_filename, source_assignments, expected = vector
+ actual, _ = mu.guess_best_assignment(
+ source_assignments, normalized_filename
+ )
+ self.assertEqual(actual, expected)
+
+ def test_mozbuild_removing(self):
+ test_vectors = [
+ (
+ "media/dav1d/moz.build",
+ SAMPLE_DAV1D_MOZBUILD,
+ "third_party/dav1d/src/cdf.c",
+ "media/dav1d/",
+ "third-party/dav1d/",
+ " '../../third_party/dav1d/src/cdf.c',\n",
+ ),
+ (
+ "media/dav1d/moz.build",
+ SAMPLE_DAV1D_MOZBUILD,
+ "third_party/dav1d/src/header1.h",
+ "media/dav1d/",
+ "third-party/dav1d/",
+ " '../../third_party/dav1d/src/header1.h',\n",
+ ),
+ (
+ "media/jxl/moz.build",
+ SAMPLE_JPEGXL_MOZBUILD,
+ "third_party/jpeg-xl/lib/jxl/alpha.cc",
+ "media/jxl/",
+ "third-party/jpeg-xl/",
+ ' "/third_party/jpeg-xl/lib/jxl/alpha.cc",\n',
+ ),
+ (
+ "media/jxl/moz.build",
+ SAMPLE_JPEGXL_MOZBUILD,
+ "third_party/jpeg-xl/lib/jxl/header1.hpp",
+ "media/jxl/",
+ "third-party/jpeg-xl/",
+ ' "/third_party/jpeg-xl/lib/jxl/header1.hpp",\n',
+ ),
+ ]
+
+ for vector in test_vectors:
+ (
+ mozbuild_path,
+ mozbuild_contents,
+ file_to_remove,
+ moz_yaml_dir,
+ vendoring_dir,
+ replace_str,
+ ) = vector
+
+ startdir = os.getcwd()
+ try:
+ mozbuild_dir = _make_mozbuild_directory_structure(
+ mozbuild_path, mozbuild_contents
+ )
+ os.chdir(mozbuild_dir.name)
+
+ mu.remove_file_from_moz_build_file(
+ file_to_remove,
+ moz_yaml_dir=moz_yaml_dir,
+ vendoring_dir=vendoring_dir,
+ )
+
+ with open(os.path.join(mozbuild_dir.name, mozbuild_path)) as file:
+ contents = file.read()
+
+ expected_output = mozbuild_contents.replace(replace_str, "")
+ if contents != expected_output:
+ print("File to remove:", file_to_remove)
+ print("Contents:")
+ print("-------------------")
+ print(contents)
+ print("-------------------")
+ print("Expected:")
+ print("-------------------")
+ print(expected_output)
+ print("-------------------")
+ self.assertEqual(contents, expected_output)
+ finally:
+ os.chdir(startdir)
+
+ def test_mozbuild_adding(self):
+ test_vectors = [
+ (
+ "media/dav1d/moz.build",
+ SAMPLE_DAV1D_MOZBUILD,
+ "third_party/dav1d/src/cdf2.c",
+ "media/dav1d/",
+ "third-party/dav1d/",
+ "cdf.c',\n",
+ "cdf.c',\n '../../third_party/dav1d/src/cdf2.c',\n",
+ ),
+ (
+ "media/dav1d/moz.build",
+ SAMPLE_DAV1D_MOZBUILD,
+ "third_party/dav1d/src/header3.h",
+ "media/dav1d/",
+ "third-party/dav1d/",
+ "header2.h',\n",
+ "header2.h',\n '../../third_party/dav1d/src/header3.h',\n",
+ ),
+ (
+ "media/jxl/moz.build",
+ SAMPLE_JPEGXL_MOZBUILD,
+ "third_party/jpeg-xl/lib/jxl/alpha2.cc",
+ "media/jxl/",
+ "third-party/jpeg-xl/",
+ 'alpha.cc",\n',
+ 'alpha.cc",\n "/third_party/jpeg-xl/lib/jxl/alpha2.cc",\n',
+ ),
+ (
+ "media/jxl/moz.build",
+ SAMPLE_JPEGXL_MOZBUILD,
+ "third_party/jpeg-xl/lib/jxl/header3.hpp",
+ "media/jxl/",
+ "third-party/jpeg-xl/",
+ 'header2.h",\n',
+ 'header2.h",\n "/third_party/jpeg-xl/lib/jxl/header3.hpp",\n',
+ ),
+ ]
+
+ for vector in test_vectors:
+ (
+ mozbuild_path,
+ mozbuild_contents,
+ file_to_add,
+ moz_yaml_dir,
+ vendoring_dir,
+ search_str,
+ replace_str,
+ ) = vector
+
+ startdir = os.getcwd()
+ try:
+ mozbuild_dir = _make_mozbuild_directory_structure(
+ mozbuild_path, mozbuild_contents
+ )
+ os.chdir(mozbuild_dir.name)
+
+ mu.add_file_to_moz_build_file(
+ file_to_add, moz_yaml_dir=moz_yaml_dir, vendoring_dir=vendoring_dir
+ )
+
+ with open(os.path.join(mozbuild_dir.name, mozbuild_path)) as file:
+ contents = file.read()
+
+ expected_output = mozbuild_contents.replace(search_str, replace_str)
+ if contents != expected_output:
+ print("File to add:", file_to_add)
+ print("Contents:")
+ print("-------------------")
+ print(contents)
+ print("-------------------")
+ print("Expected:")
+ print("-------------------")
+ print(expected_output)
+ print("-------------------")
+ self.assertEqual(contents, expected_output)
+ finally:
+ os.chdir(startdir)
+
+ # This test is legacy. I'm keeping it around, but new test vectors should be added to the
+ # non-internal test to exercise the public API.
+ def test_mozbuild_adding_internal(self):
+ test_vectors = [
+ # (
+ # mozbuild_contents
+ # unnormalized_filename_to_add,
+ # unnormalized_list_of_files
+ # expected_output
+ # )
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-sse2-more.c",
+ ["pixman-sse2.c"],
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ "SOURCES += ['pixman-sse2.c']",
+ "SOURCES += ['pixman-sse2-more.c','pixman-sse2.c']",
+ ),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-trap-more.c",
+ [
+ "pixman-region32.c",
+ "pixman-solid-fill.c",
+ "pixman-trap.c",
+ "pixman-utils.c",
+ "pixman-x86.c",
+ "pixman.c",
+ ],
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ "'pixman-trap.c',", "'pixman-trap-more.c',\n 'pixman-trap.c',"
+ ),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-arm-neon-asm-more.S",
+ ["pixman-arm-neon-asm-bilinear.S", "pixman-arm-neon-asm.S"],
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ '"pixman-arm-neon-asm.S"',
+ '"pixman-arm-neon-asm-more.S",\n "pixman-arm-neon-asm.S"',
+ ),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-arm-simd-asm-smore.S",
+ ["pixman-arm-simd-asm-scaled.S", "pixman-arm-simd-asm.S"],
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ "'pixman-arm-simd-asm.S'",
+ "'pixman-arm-simd-asm-smore.S',\n 'pixman-arm-simd-asm.S'",
+ ),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-arm-simd-asn.S",
+ ["pixman-arm-simd-asm-scaled.S", "pixman-arm-simd-asm.S"],
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ "'pixman-arm-simd-asm.S'",
+ "'pixman-arm-simd-asm.S',\n 'pixman-arm-simd-asn.S'",
+ ),
+ ),
+ ]
+
+ for vector in test_vectors:
+ (
+ mozbuild_contents,
+ unnormalized_filename_to_add,
+ unnormalized_list_of_files,
+ expected_output,
+ ) = vector
+
+ fd, filename = tempfile.mkstemp(text=True)
+ os.close(fd)
+ file = open(filename, mode="w")
+ file.write(mozbuild_contents)
+ file.close()
+
+ mu.edit_moz_build_file_to_add_file(
+ filename, unnormalized_filename_to_add, unnormalized_list_of_files
+ )
+
+ with open(filename) as file:
+ contents = file.read()
+ os.remove(filename)
+
+ if contents != expected_output:
+ print("File to add:", unnormalized_filename_to_add)
+ print("Contents:")
+ print("-------------------")
+ print(contents)
+ print("-------------------")
+ print("Expected:")
+ print("-------------------")
+ print(expected_output)
+ print("-------------------")
+ self.assertEqual(contents, expected_output)
+
+ # This test is legacy. I'm keeping it around, but new test vectors should be added to the
+ # non-internal test to exercise the public API.
+ def test_mozbuild_removing_internal(self):
+ test_vectors = [
+ # (
+ # mozbuild_contents
+ # unnormalized_filename_to_add
+ # expected_output
+ # )
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-sse2.c",
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ "SOURCES += ['pixman-sse2.c']", "SOURCES += []"
+ ),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-trap.c",
+ SAMPLE_PIXMAN_MOZBUILD.replace(" 'pixman-trap.c',\n", ""),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-arm-neon-asm.S",
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ ' "pixman-arm-neon-asm.S",\n', ""
+ ),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-arm-simd-asm.S",
+ SAMPLE_PIXMAN_MOZBUILD.replace(
+ " 'pixman-arm-simd-asm.S'", " "
+ ),
+ ),
+ (
+ SAMPLE_PIXMAN_MOZBUILD,
+ "pixman-region32.c",
+ SAMPLE_PIXMAN_MOZBUILD.replace("'pixman-region32.c',", ""),
+ ),
+ ]
+
+ for vector in test_vectors:
+ (
+ mozbuild_contents,
+ unnormalized_filename_to_remove,
+ expected_output,
+ ) = vector
+
+ fd, filename = tempfile.mkstemp(text=True)
+ os.close(fd)
+ file = open(filename, mode="w")
+ file.write(mozbuild_contents)
+ file.close()
+
+ mu.edit_moz_build_file_to_remove_file(
+ filename, unnormalized_filename_to_remove
+ )
+
+ with open(filename) as file:
+ contents = file.read()
+ os.remove(filename)
+
+ if contents != expected_output:
+ print("File to remove:", unnormalized_filename_to_remove)
+ print("Contents:")
+ print("-------------------")
+ print(contents)
+ print("-------------------")
+ print("Expected:")
+ print("-------------------")
+ print(expected_output)
+ print("-------------------")
+ self.assertEqual(contents, expected_output)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_telemetry.py b/python/mozbuild/mozbuild/test/test_telemetry.py
new file mode 100644
index 0000000000..894e32ee2d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_telemetry.py
@@ -0,0 +1,102 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import os
+
+import buildconfig
+import mozunit
+
+from mozbuild.telemetry import filter_args
+
+TELEMETRY_LOAD_ERROR = """
+Error loading telemetry. mach output:
+=========================================================
+%s
+=========================================================
+"""
+
+
+def test_path_filtering():
+ srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
+ srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
+ objdir_path = os.path.join(buildconfig.topobjdir, "x")
+ objdir_path_2 = os.path.join(buildconfig.topobjdir, "x/y/z")
+ home_path = os.path.join(os.path.expanduser("~"), "something_in_home")
+ other_path = "/other/path"
+ args = filter_args(
+ "pass",
+ [
+ "python",
+ "-c",
+ "pass",
+ srcdir_path,
+ srcdir_path_2,
+ objdir_path,
+ objdir_path_2,
+ home_path,
+ other_path,
+ ],
+ buildconfig.topsrcdir,
+ buildconfig.topobjdir,
+ cwd=buildconfig.topsrcdir,
+ )
+
+ expected = [
+ "a",
+ "a/b/c",
+ "$topobjdir/x",
+ "$topobjdir/x/y/z",
+ "$HOME/something_in_home",
+ "<path omitted>",
+ ]
+ assert args == expected
+
+
+def test_path_filtering_in_objdir():
+ srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
+ srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
+ objdir_path = os.path.join(buildconfig.topobjdir, "x")
+ objdir_path_2 = os.path.join(buildconfig.topobjdir, "x/y/z")
+ other_path = "/other/path"
+ args = filter_args(
+ "pass",
+ [
+ "python",
+ "-c",
+ "pass",
+ srcdir_path,
+ srcdir_path_2,
+ objdir_path,
+ objdir_path_2,
+ other_path,
+ ],
+ buildconfig.topsrcdir,
+ buildconfig.topobjdir,
+ cwd=buildconfig.topobjdir,
+ )
+ expected = ["$topsrcdir/a", "$topsrcdir/a/b/c", "x", "x/y/z", "<path omitted>"]
+ assert args == expected
+
+
+def test_path_filtering_other_cwd(tmpdir):
+ srcdir_path = os.path.join(buildconfig.topsrcdir, "a")
+ srcdir_path_2 = os.path.join(buildconfig.topsrcdir, "a/b/c")
+ other_path = str(tmpdir.join("other"))
+ args = filter_args(
+ "pass",
+ ["python", "-c", "pass", srcdir_path, srcdir_path_2, other_path],
+ buildconfig.topsrcdir,
+ buildconfig.topobjdir,
+ cwd=str(tmpdir),
+ )
+ expected = [
+ "$topsrcdir/a",
+ "$topsrcdir/a/b/c",
+ # cwd-relative paths should be relativized
+ "other",
+ ]
+ assert args == expected
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_telemetry_settings.py b/python/mozbuild/mozbuild/test/test_telemetry_settings.py
new file mode 100644
index 0000000000..2d50141a15
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_telemetry_settings.py
@@ -0,0 +1,174 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+from unittest import mock
+from unittest.mock import Mock
+
+import mozunit
+import pytest
+import requests
+from mach.config import ConfigSettings
+from mach.decorators import SettingsProvider
+from mach.telemetry import (
+ initialize_telemetry_setting,
+ record_telemetry_settings,
+ resolve_is_employee,
+)
+
+from mozbuild.settings import TelemetrySettings
+
+
+@SettingsProvider
+class OtherSettings:
+ config_settings = [("foo.bar", "int", "", 1), ("build.abc", "string", "", "")]
+
+
+def record_enabled_telemetry(mozbuild_path, settings):
+ record_telemetry_settings(settings, mozbuild_path, True)
+
+
+@pytest.fixture
+def settings():
+ s = ConfigSettings()
+ s.register_provider(TelemetrySettings)
+ s.register_provider(OtherSettings)
+ return s
+
+
+def load_settings_file(mozbuild_path, settings):
+ settings.load_file(os.path.join(mozbuild_path, "machrc"))
+
+
+def write_config(mozbuild_path, contents):
+ with open(os.path.join(mozbuild_path, "machrc"), "w") as f:
+ f.write(contents)
+
+
+def test_nonexistent(tmpdir, settings):
+ record_enabled_telemetry(tmpdir, settings)
+ load_settings_file(tmpdir, settings)
+ assert settings.mach_telemetry.is_enabled
+
+
+def test_file_exists_no_build_section(tmpdir, settings):
+ write_config(
+ tmpdir,
+ """[foo]
+bar = 2
+""",
+ )
+ record_enabled_telemetry(tmpdir, settings)
+ load_settings_file(tmpdir, settings)
+ assert settings.mach_telemetry.is_enabled
+ assert settings.foo.bar == 2
+
+
+def test_existing_build_section(tmpdir, settings):
+ write_config(
+ tmpdir,
+ """[foo]
+bar = 2
+
+[build]
+abc = xyz
+""",
+ )
+ record_enabled_telemetry(tmpdir, settings)
+ load_settings_file(tmpdir, settings)
+ assert settings.mach_telemetry.is_enabled
+ assert settings.build.abc == "xyz"
+ assert settings.foo.bar == 2
+
+
+def test_malformed_file(tmpdir, settings):
+ """Ensure that a malformed config file doesn't cause breakage."""
+ write_config(
+ tmpdir,
+ """[foo
+bar = 1
+""",
+ )
+ record_enabled_telemetry(tmpdir, settings)
+ # Can't load_settings config, it will not have been written!
+
+
+def _initialize_telemetry(settings, is_employee, contributor_prompt_response=None):
+ with mock.patch(
+ "mach.telemetry.resolve_is_employee", return_value=is_employee
+ ), mock.patch(
+ "mach.telemetry.prompt_telemetry_message_contributor",
+ return_value=contributor_prompt_response,
+ ) as prompt_mock, mock.patch(
+ "subprocess.run", return_value=Mock(returncode=0)
+ ), mock.patch(
+ "mach.config.ConfigSettings"
+ ):
+ initialize_telemetry_setting(settings, "", "")
+ return prompt_mock.call_count == 1
+
+
+def test_initialize_new_contributor_deny_telemetry(settings):
+ did_prompt = _initialize_telemetry(settings, False, False)
+ assert did_prompt
+ assert not settings.mach_telemetry.is_enabled
+ assert settings.mach_telemetry.is_set_up
+ assert settings.mach_telemetry.is_done_first_time_setup
+
+
+def test_initialize_new_contributor_allow_telemetry(settings):
+ did_prompt = _initialize_telemetry(settings, False, True)
+ assert did_prompt
+ assert settings.mach_telemetry.is_enabled
+ assert settings.mach_telemetry.is_set_up
+ assert settings.mach_telemetry.is_done_first_time_setup
+
+
+def test_initialize_new_employee(settings):
+ did_prompt = _initialize_telemetry(settings, True)
+ assert not did_prompt
+ assert settings.mach_telemetry.is_enabled
+ assert settings.mach_telemetry.is_set_up
+ assert settings.mach_telemetry.is_done_first_time_setup
+
+
+def test_initialize_noop_when_telemetry_disabled_env(monkeypatch):
+ monkeypatch.setenv("DISABLE_TELEMETRY", "1")
+ with mock.patch("mach.telemetry.record_telemetry_settings") as record_mock:
+ did_prompt = _initialize_telemetry(None, False)
+ assert record_mock.call_count == 0
+ assert not did_prompt
+
+
+def test_initialize_noop_when_request_error(settings):
+ with mock.patch(
+ "mach.telemetry.resolve_is_employee",
+ side_effect=requests.exceptions.RequestException("Unlucky"),
+ ), mock.patch("mach.telemetry.record_telemetry_settings") as record_mock:
+ initialize_telemetry_setting(None, None, None)
+ assert record_mock.call_count == 0
+
+
+def test_resolve_is_employee():
+ def mock_and_run(is_employee_bugzilla, is_employee_vcs):
+ with mock.patch(
+ "mach.telemetry.resolve_is_employee_by_credentials",
+ return_value=is_employee_bugzilla,
+ ), mock.patch(
+ "mach.telemetry.resolve_is_employee_by_vcs", return_value=is_employee_vcs
+ ):
+ return resolve_is_employee(None)
+
+ assert not mock_and_run(False, False)
+ assert not mock_and_run(False, True)
+ assert not mock_and_run(False, None)
+ assert mock_and_run(True, False)
+ assert mock_and_run(True, True)
+ assert mock_and_run(True, None)
+ assert not mock_and_run(None, False)
+ assert mock_and_run(None, True)
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_util.py b/python/mozbuild/mozbuild/test/test_util.py
new file mode 100644
index 0000000000..9931b338b9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_util.py
@@ -0,0 +1,889 @@
+# coding: utf-8
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import copy
+import hashlib
+import itertools
+import os
+import string
+import sys
+import unittest
+
+import pytest
+import six
+from mozfile.mozfile import NamedTemporaryFile
+from mozunit import main
+
+from mozbuild.util import (
+ EnumString,
+ EnumStringComparisonError,
+ HierarchicalStringList,
+ MozbuildDeletionError,
+ ReadOnlyDict,
+ StrictOrderingOnAppendList,
+ StrictOrderingOnAppendListWithAction,
+ StrictOrderingOnAppendListWithFlagsFactory,
+ TypedList,
+ TypedNamedTuple,
+ UnsortedError,
+ expand_variables,
+ group_unified_files,
+ hash_file,
+ hexdump,
+ memoize,
+ memoized_property,
+ pair,
+ resolve_target_to_make,
+)
+
+if sys.version_info[0] == 3:
+ str_type = "str"
+else:
+ str_type = "unicode"
+
+data_path = os.path.abspath(os.path.dirname(__file__))
+data_path = os.path.join(data_path, "data")
+
+
+class TestHashing(unittest.TestCase):
+ def test_hash_file_known_hash(self):
+ """Ensure a known hash value is recreated."""
+ data = b"The quick brown fox jumps over the lazy cog"
+ expected = "de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3"
+
+ temp = NamedTemporaryFile()
+ temp.write(data)
+ temp.flush()
+
+ actual = hash_file(temp.name)
+
+ self.assertEqual(actual, expected)
+
+ def test_hash_file_large(self):
+ """Ensure that hash_file seems to work with a large file."""
+ data = b"x" * 1048576
+
+ hasher = hashlib.sha1()
+ hasher.update(data)
+ expected = hasher.hexdigest()
+
+ temp = NamedTemporaryFile()
+ temp.write(data)
+ temp.flush()
+
+ actual = hash_file(temp.name)
+
+ self.assertEqual(actual, expected)
+
+
+class TestResolveTargetToMake(unittest.TestCase):
+ def setUp(self):
+ self.topobjdir = data_path
+
+ def assertResolve(self, path, expected):
+ # Handle Windows path separators.
+ (reldir, target) = resolve_target_to_make(self.topobjdir, path)
+ if reldir is not None:
+ reldir = reldir.replace(os.sep, "/")
+ if target is not None:
+ target = target.replace(os.sep, "/")
+ self.assertEqual((reldir, target), expected)
+
+ def test_root_path(self):
+ self.assertResolve("/test-dir", ("test-dir", None))
+ self.assertResolve("/test-dir/with", ("test-dir/with", None))
+ self.assertResolve("/test-dir/without", ("test-dir", None))
+ self.assertResolve("/test-dir/without/with", ("test-dir/without/with", None))
+
+ def test_dir(self):
+ self.assertResolve("test-dir", ("test-dir", None))
+ self.assertResolve("test-dir/with", ("test-dir/with", None))
+ self.assertResolve("test-dir/with", ("test-dir/with", None))
+ self.assertResolve("test-dir/without", ("test-dir", None))
+ self.assertResolve("test-dir/without/with", ("test-dir/without/with", None))
+
+ def test_top_level(self):
+ self.assertResolve("package", (None, "package"))
+ # Makefile handling shouldn't affect top-level targets.
+ self.assertResolve("Makefile", (None, "Makefile"))
+
+ def test_regular_file(self):
+ self.assertResolve("test-dir/with/file", ("test-dir/with", "file"))
+ self.assertResolve(
+ "test-dir/with/without/file", ("test-dir/with", "without/file")
+ )
+ self.assertResolve(
+ "test-dir/with/without/with/file", ("test-dir/with/without/with", "file")
+ )
+
+ self.assertResolve("test-dir/without/file", ("test-dir", "without/file"))
+ self.assertResolve(
+ "test-dir/without/with/file", ("test-dir/without/with", "file")
+ )
+ self.assertResolve(
+ "test-dir/without/with/without/file",
+ ("test-dir/without/with", "without/file"),
+ )
+
+ def test_Makefile(self):
+ self.assertResolve("test-dir/with/Makefile", ("test-dir", "with/Makefile"))
+ self.assertResolve(
+ "test-dir/with/without/Makefile", ("test-dir/with", "without/Makefile")
+ )
+ self.assertResolve(
+ "test-dir/with/without/with/Makefile",
+ ("test-dir/with", "without/with/Makefile"),
+ )
+
+ self.assertResolve(
+ "test-dir/without/Makefile", ("test-dir", "without/Makefile")
+ )
+ self.assertResolve(
+ "test-dir/without/with/Makefile", ("test-dir", "without/with/Makefile")
+ )
+ self.assertResolve(
+ "test-dir/without/with/without/Makefile",
+ ("test-dir/without/with", "without/Makefile"),
+ )
+
+
+class TestHierarchicalStringList(unittest.TestCase):
+ def setUp(self):
+ self.EXPORTS = HierarchicalStringList()
+
+ def test_exports_append(self):
+ self.assertEqual(self.EXPORTS._strings, [])
+ self.EXPORTS += ["foo.h"]
+ self.assertEqual(self.EXPORTS._strings, ["foo.h"])
+ self.EXPORTS += ["bar.h"]
+ self.assertEqual(self.EXPORTS._strings, ["foo.h", "bar.h"])
+
+ def test_exports_subdir(self):
+ self.assertEqual(self.EXPORTS._children, {})
+ self.EXPORTS.foo += ["foo.h"]
+ six.assertCountEqual(self, self.EXPORTS._children, {"foo": True})
+ self.assertEqual(self.EXPORTS.foo._strings, ["foo.h"])
+ self.EXPORTS.bar += ["bar.h"]
+ six.assertCountEqual(self, self.EXPORTS._children, {"foo": True, "bar": True})
+ self.assertEqual(self.EXPORTS.foo._strings, ["foo.h"])
+ self.assertEqual(self.EXPORTS.bar._strings, ["bar.h"])
+
+ def test_exports_multiple_subdir(self):
+ self.EXPORTS.foo.bar = ["foobar.h"]
+ six.assertCountEqual(self, self.EXPORTS._children, {"foo": True})
+ six.assertCountEqual(self, self.EXPORTS.foo._children, {"bar": True})
+ six.assertCountEqual(self, self.EXPORTS.foo.bar._children, {})
+ self.assertEqual(self.EXPORTS._strings, [])
+ self.assertEqual(self.EXPORTS.foo._strings, [])
+ self.assertEqual(self.EXPORTS.foo.bar._strings, ["foobar.h"])
+
+ def test_invalid_exports_append(self):
+ with self.assertRaises(ValueError) as ve:
+ self.EXPORTS += "foo.h"
+ six.assertRegex(
+ self,
+ str(ve.exception),
+ "Expected a list of strings, not <(?:type|class) '%s'>" % str_type,
+ )
+
+ def test_invalid_exports_set(self):
+ with self.assertRaises(ValueError) as ve:
+ self.EXPORTS.foo = "foo.h"
+
+ six.assertRegex(
+ self,
+ str(ve.exception),
+ "Expected a list of strings, not <(?:type|class) '%s'>" % str_type,
+ )
+
+ def test_invalid_exports_append_base(self):
+ with self.assertRaises(ValueError) as ve:
+ self.EXPORTS += "foo.h"
+
+ six.assertRegex(
+ self,
+ str(ve.exception),
+ "Expected a list of strings, not <(?:type|class) '%s'>" % str_type,
+ )
+
+ def test_invalid_exports_bool(self):
+ with self.assertRaises(ValueError) as ve:
+ self.EXPORTS += [True]
+
+ six.assertRegex(
+ self,
+ str(ve.exception),
+ "Expected a list of strings, not an element of " "<(?:type|class) 'bool'>",
+ )
+
+ def test_del_exports(self):
+ with self.assertRaises(MozbuildDeletionError):
+ self.EXPORTS.foo += ["bar.h"]
+ del self.EXPORTS.foo
+
+ def test_unsorted(self):
+ with self.assertRaises(UnsortedError):
+ self.EXPORTS += ["foo.h", "bar.h"]
+
+ with self.assertRaises(UnsortedError):
+ self.EXPORTS.foo = ["foo.h", "bar.h"]
+
+ with self.assertRaises(UnsortedError):
+ self.EXPORTS.foo += ["foo.h", "bar.h"]
+
+ def test_reassign(self):
+ self.EXPORTS.foo = ["foo.h"]
+
+ with self.assertRaises(KeyError):
+ self.EXPORTS.foo = ["bar.h"]
+
+ def test_walk(self):
+ l = HierarchicalStringList()
+ l += ["root1", "root2", "root3"]
+ l.child1 += ["child11", "child12", "child13"]
+ l.child1.grandchild1 += ["grandchild111", "grandchild112"]
+ l.child1.grandchild2 += ["grandchild121", "grandchild122"]
+ l.child2.grandchild1 += ["grandchild211", "grandchild212"]
+ l.child2.grandchild1 += ["grandchild213", "grandchild214"]
+
+ els = list((path, list(seq)) for path, seq in l.walk())
+ self.assertEqual(
+ els,
+ [
+ ("", ["root1", "root2", "root3"]),
+ ("child1", ["child11", "child12", "child13"]),
+ ("child1/grandchild1", ["grandchild111", "grandchild112"]),
+ ("child1/grandchild2", ["grandchild121", "grandchild122"]),
+ (
+ "child2/grandchild1",
+ [
+ "grandchild211",
+ "grandchild212",
+ "grandchild213",
+ "grandchild214",
+ ],
+ ),
+ ],
+ )
+
+ def test_merge(self):
+ l1 = HierarchicalStringList()
+ l1 += ["root1", "root2", "root3"]
+ l1.child1 += ["child11", "child12", "child13"]
+ l1.child1.grandchild1 += ["grandchild111", "grandchild112"]
+ l1.child1.grandchild2 += ["grandchild121", "grandchild122"]
+ l1.child2.grandchild1 += ["grandchild211", "grandchild212"]
+ l1.child2.grandchild1 += ["grandchild213", "grandchild214"]
+ l2 = HierarchicalStringList()
+ l2.child1 += ["child14", "child15"]
+ l2.child1.grandchild2 += ["grandchild123"]
+ l2.child3 += ["child31", "child32"]
+
+ l1 += l2
+ els = list((path, list(seq)) for path, seq in l1.walk())
+ self.assertEqual(
+ els,
+ [
+ ("", ["root1", "root2", "root3"]),
+ ("child1", ["child11", "child12", "child13", "child14", "child15"]),
+ ("child1/grandchild1", ["grandchild111", "grandchild112"]),
+ (
+ "child1/grandchild2",
+ ["grandchild121", "grandchild122", "grandchild123"],
+ ),
+ (
+ "child2/grandchild1",
+ [
+ "grandchild211",
+ "grandchild212",
+ "grandchild213",
+ "grandchild214",
+ ],
+ ),
+ ("child3", ["child31", "child32"]),
+ ],
+ )
+
+
+class TestStrictOrderingOnAppendList(unittest.TestCase):
+ def test_init(self):
+ l = StrictOrderingOnAppendList()
+ self.assertEqual(len(l), 0)
+
+ l = StrictOrderingOnAppendList(["a", "b", "c"])
+ self.assertEqual(len(l), 3)
+
+ with self.assertRaises(UnsortedError):
+ StrictOrderingOnAppendList(["c", "b", "a"])
+
+ self.assertEqual(len(l), 3)
+
+ def test_extend(self):
+ l = StrictOrderingOnAppendList()
+ l.extend(["a", "b"])
+ self.assertEqual(len(l), 2)
+ self.assertIsInstance(l, StrictOrderingOnAppendList)
+
+ with self.assertRaises(UnsortedError):
+ l.extend(["d", "c"])
+
+ self.assertEqual(len(l), 2)
+
+ def test_slicing(self):
+ l = StrictOrderingOnAppendList()
+ l[:] = ["a", "b"]
+ self.assertEqual(len(l), 2)
+ self.assertIsInstance(l, StrictOrderingOnAppendList)
+
+ with self.assertRaises(UnsortedError):
+ l[:] = ["b", "a"]
+
+ self.assertEqual(len(l), 2)
+
+ def test_add(self):
+ l = StrictOrderingOnAppendList()
+ l2 = l + ["a", "b"]
+ self.assertEqual(len(l), 0)
+ self.assertEqual(len(l2), 2)
+ self.assertIsInstance(l2, StrictOrderingOnAppendList)
+
+ with self.assertRaises(UnsortedError):
+ l2 = l + ["b", "a"]
+
+ self.assertEqual(len(l), 0)
+
+ def test_iadd(self):
+ l = StrictOrderingOnAppendList()
+ l += ["a", "b"]
+ self.assertEqual(len(l), 2)
+ self.assertIsInstance(l, StrictOrderingOnAppendList)
+
+ with self.assertRaises(UnsortedError):
+ l += ["b", "a"]
+
+ self.assertEqual(len(l), 2)
+
+ def test_add_after_iadd(self):
+ l = StrictOrderingOnAppendList(["b"])
+ l += ["a"]
+ l2 = l + ["c", "d"]
+ self.assertEqual(len(l), 2)
+ self.assertEqual(len(l2), 4)
+ self.assertIsInstance(l2, StrictOrderingOnAppendList)
+ with self.assertRaises(UnsortedError):
+ l2 = l + ["d", "c"]
+
+ self.assertEqual(len(l), 2)
+
+ def test_add_StrictOrderingOnAppendList(self):
+ l = StrictOrderingOnAppendList()
+ l += ["c", "d"]
+ l += ["a", "b"]
+ l2 = StrictOrderingOnAppendList()
+ with self.assertRaises(UnsortedError):
+ l2 += list(l)
+ # Adding a StrictOrderingOnAppendList to another shouldn't throw
+ l2 += l
+
+
+class TestStrictOrderingOnAppendListWithAction(unittest.TestCase):
+ def setUp(self):
+ self.action = lambda a: (a, id(a))
+
+ def assertSameList(self, expected, actual):
+ self.assertEqual(len(expected), len(actual))
+ for idx, item in enumerate(actual):
+ self.assertEqual(item, expected[idx])
+
+ def test_init(self):
+ l = StrictOrderingOnAppendListWithAction(action=self.action)
+ self.assertEqual(len(l), 0)
+ original = ["a", "b", "c"]
+ l = StrictOrderingOnAppendListWithAction(["a", "b", "c"], action=self.action)
+ expected = [self.action(i) for i in original]
+ self.assertSameList(expected, l)
+
+ with self.assertRaises(ValueError):
+ StrictOrderingOnAppendListWithAction("abc", action=self.action)
+
+ with self.assertRaises(ValueError):
+ StrictOrderingOnAppendListWithAction()
+
+ def test_extend(self):
+ l = StrictOrderingOnAppendListWithAction(action=self.action)
+ original = ["a", "b"]
+ l.extend(original)
+ expected = [self.action(i) for i in original]
+ self.assertSameList(expected, l)
+
+ with self.assertRaises(ValueError):
+ l.extend("ab")
+
+ def test_slicing(self):
+ l = StrictOrderingOnAppendListWithAction(action=self.action)
+ original = ["a", "b"]
+ l[:] = original
+ expected = [self.action(i) for i in original]
+ self.assertSameList(expected, l)
+
+ with self.assertRaises(ValueError):
+ l[:] = "ab"
+
+ def test_add(self):
+ l = StrictOrderingOnAppendListWithAction(action=self.action)
+ original = ["a", "b"]
+ l2 = l + original
+ expected = [self.action(i) for i in original]
+ self.assertSameList(expected, l2)
+
+ with self.assertRaises(ValueError):
+ l + "abc"
+
+ def test_iadd(self):
+ l = StrictOrderingOnAppendListWithAction(action=self.action)
+ original = ["a", "b"]
+ l += original
+ expected = [self.action(i) for i in original]
+ self.assertSameList(expected, l)
+
+ with self.assertRaises(ValueError):
+ l += "abc"
+
+
+class TestStrictOrderingOnAppendListWithFlagsFactory(unittest.TestCase):
+ def test_strict_ordering_on_append_list_with_flags_factory(self):
+ cls = StrictOrderingOnAppendListWithFlagsFactory(
+ {
+ "foo": bool,
+ "bar": int,
+ }
+ )
+
+ l = cls()
+ l += ["a", "b"]
+
+ with self.assertRaises(Exception):
+ l["a"] = "foo"
+
+ with self.assertRaises(Exception):
+ l["c"]
+
+ self.assertEqual(l["a"].foo, False)
+ l["a"].foo = True
+ self.assertEqual(l["a"].foo, True)
+
+ with self.assertRaises(TypeError):
+ l["a"].bar = "bar"
+
+ self.assertEqual(l["a"].bar, 0)
+ l["a"].bar = 42
+ self.assertEqual(l["a"].bar, 42)
+
+ l["b"].foo = True
+ self.assertEqual(l["b"].foo, True)
+
+ with self.assertRaises(AttributeError):
+ l["b"].baz = False
+
+ l["b"].update(foo=False, bar=12)
+ self.assertEqual(l["b"].foo, False)
+ self.assertEqual(l["b"].bar, 12)
+
+ with self.assertRaises(AttributeError):
+ l["b"].update(xyz=1)
+
+ def test_strict_ordering_on_append_list_with_flags_factory_extend(self):
+ FooList = StrictOrderingOnAppendListWithFlagsFactory(
+ {"foo": bool, "bar": six.text_type}
+ )
+ foo = FooList(["a", "b", "c"])
+ foo["a"].foo = True
+ foo["b"].bar = "bar"
+
+ # Don't allow extending lists with different flag definitions.
+ BarList = StrictOrderingOnAppendListWithFlagsFactory(
+ {"foo": six.text_type, "baz": bool}
+ )
+ bar = BarList(["d", "e", "f"])
+ bar["d"].foo = "foo"
+ bar["e"].baz = True
+ with self.assertRaises(ValueError):
+ foo + bar
+ with self.assertRaises(ValueError):
+ bar + foo
+
+ # It's not obvious what to do with duplicate list items with possibly
+ # different flag values, so don't allow that case.
+ with self.assertRaises(ValueError):
+ foo + foo
+
+ def assertExtended(l):
+ self.assertEqual(len(l), 6)
+ self.assertEqual(l["a"].foo, True)
+ self.assertEqual(l["b"].bar, "bar")
+ self.assertTrue("c" in l)
+ self.assertEqual(l["d"].foo, True)
+ self.assertEqual(l["e"].bar, "bar")
+ self.assertTrue("f" in l)
+
+ # Test extend.
+ zot = FooList(["d", "e", "f"])
+ zot["d"].foo = True
+ zot["e"].bar = "bar"
+ zot.extend(foo)
+ assertExtended(zot)
+
+ # Test __add__.
+ zot = FooList(["d", "e", "f"])
+ zot["d"].foo = True
+ zot["e"].bar = "bar"
+ assertExtended(foo + zot)
+ assertExtended(zot + foo)
+
+ # Test __iadd__.
+ foo += zot
+ assertExtended(foo)
+
+ # Test __setitem__.
+ foo[3:] = []
+ self.assertEqual(len(foo), 3)
+ foo[3:] = zot
+ assertExtended(foo)
+
+
+class TestMemoize(unittest.TestCase):
+ def test_memoize(self):
+ self._count = 0
+
+ @memoize
+ def wrapped(a, b):
+ self._count += 1
+ return a + b
+
+ self.assertEqual(self._count, 0)
+ self.assertEqual(wrapped(1, 1), 2)
+ self.assertEqual(self._count, 1)
+ self.assertEqual(wrapped(1, 1), 2)
+ self.assertEqual(self._count, 1)
+ self.assertEqual(wrapped(2, 1), 3)
+ self.assertEqual(self._count, 2)
+ self.assertEqual(wrapped(1, 2), 3)
+ self.assertEqual(self._count, 3)
+ self.assertEqual(wrapped(1, 2), 3)
+ self.assertEqual(self._count, 3)
+ self.assertEqual(wrapped(1, 1), 2)
+ self.assertEqual(self._count, 3)
+
+ def test_memoize_method(self):
+ class foo(object):
+ def __init__(self):
+ self._count = 0
+
+ @memoize
+ def wrapped(self, a, b):
+ self._count += 1
+ return a + b
+
+ instance = foo()
+ refcount = sys.getrefcount(instance)
+ self.assertEqual(instance._count, 0)
+ self.assertEqual(instance.wrapped(1, 1), 2)
+ self.assertEqual(instance._count, 1)
+ self.assertEqual(instance.wrapped(1, 1), 2)
+ self.assertEqual(instance._count, 1)
+ self.assertEqual(instance.wrapped(2, 1), 3)
+ self.assertEqual(instance._count, 2)
+ self.assertEqual(instance.wrapped(1, 2), 3)
+ self.assertEqual(instance._count, 3)
+ self.assertEqual(instance.wrapped(1, 2), 3)
+ self.assertEqual(instance._count, 3)
+ self.assertEqual(instance.wrapped(1, 1), 2)
+ self.assertEqual(instance._count, 3)
+
+ # Memoization of methods is expected to not keep references to
+ # instances, so the refcount shouldn't have changed after executing the
+ # memoized method.
+ self.assertEqual(refcount, sys.getrefcount(instance))
+
+ def test_memoized_property(self):
+ class foo(object):
+ def __init__(self):
+ self._count = 0
+
+ @memoized_property
+ def wrapped(self):
+ self._count += 1
+ return 42
+
+ instance = foo()
+ self.assertEqual(instance._count, 0)
+ self.assertEqual(instance.wrapped, 42)
+ self.assertEqual(instance._count, 1)
+ self.assertEqual(instance.wrapped, 42)
+ self.assertEqual(instance._count, 1)
+
+
+class TestTypedList(unittest.TestCase):
+ def test_init(self):
+ cls = TypedList(int)
+ l = cls()
+ self.assertEqual(len(l), 0)
+
+ l = cls([1, 2, 3])
+ self.assertEqual(len(l), 3)
+
+ with self.assertRaises(ValueError):
+ cls([1, 2, "c"])
+
+ def test_extend(self):
+ cls = TypedList(int)
+ l = cls()
+ l.extend([1, 2])
+ self.assertEqual(len(l), 2)
+ self.assertIsInstance(l, cls)
+
+ with self.assertRaises(ValueError):
+ l.extend([3, "c"])
+
+ self.assertEqual(len(l), 2)
+
+ def test_slicing(self):
+ cls = TypedList(int)
+ l = cls()
+ l[:] = [1, 2]
+ self.assertEqual(len(l), 2)
+ self.assertIsInstance(l, cls)
+
+ with self.assertRaises(ValueError):
+ l[:] = [3, "c"]
+
+ self.assertEqual(len(l), 2)
+
+ def test_add(self):
+ cls = TypedList(int)
+ l = cls()
+ l2 = l + [1, 2]
+ self.assertEqual(len(l), 0)
+ self.assertEqual(len(l2), 2)
+ self.assertIsInstance(l2, cls)
+
+ with self.assertRaises(ValueError):
+ l2 = l + [3, "c"]
+
+ self.assertEqual(len(l), 0)
+
+ def test_iadd(self):
+ cls = TypedList(int)
+ l = cls()
+ l += [1, 2]
+ self.assertEqual(len(l), 2)
+ self.assertIsInstance(l, cls)
+
+ with self.assertRaises(ValueError):
+ l += [3, "c"]
+
+ self.assertEqual(len(l), 2)
+
+ def test_add_coercion(self):
+ objs = []
+
+ class Foo(object):
+ def __init__(self, obj):
+ objs.append(obj)
+
+ cls = TypedList(Foo)
+ l = cls()
+ l += [1, 2]
+ self.assertEqual(len(objs), 2)
+ self.assertEqual(type(l[0]), Foo)
+ self.assertEqual(type(l[1]), Foo)
+
+ # Adding a TypedList to a TypedList shouldn't trigger coercion again
+ l2 = cls()
+ l2 += l
+ self.assertEqual(len(objs), 2)
+ self.assertEqual(type(l2[0]), Foo)
+ self.assertEqual(type(l2[1]), Foo)
+
+ # Adding a TypedList to a TypedList shouldn't even trigger the code
+ # that does coercion at all.
+ l2 = cls()
+ list.__setitem__(l, slice(0, -1), [1, 2])
+ l2 += l
+ self.assertEqual(len(objs), 2)
+ self.assertEqual(type(l2[0]), int)
+ self.assertEqual(type(l2[1]), int)
+
+ def test_memoized(self):
+ cls = TypedList(int)
+ cls2 = TypedList(str)
+ self.assertEqual(TypedList(int), cls)
+ self.assertNotEqual(cls, cls2)
+
+
+class TypedTestStrictOrderingOnAppendList(unittest.TestCase):
+ def test_init(self):
+ class Unicode(six.text_type):
+ def __new__(cls, other):
+ if not isinstance(other, six.text_type):
+ raise ValueError()
+ return six.text_type.__new__(cls, other)
+
+ cls = TypedList(Unicode, StrictOrderingOnAppendList)
+ l = cls()
+ self.assertEqual(len(l), 0)
+
+ l = cls(["a", "b", "c"])
+ self.assertEqual(len(l), 3)
+
+ with self.assertRaises(UnsortedError):
+ cls(["c", "b", "a"])
+
+ with self.assertRaises(ValueError):
+ cls(["a", "b", 3])
+
+ self.assertEqual(len(l), 3)
+
+
+class TestTypedNamedTuple(unittest.TestCase):
+ def test_simple(self):
+ FooBar = TypedNamedTuple("FooBar", [("foo", six.text_type), ("bar", int)])
+
+ t = FooBar(foo="foo", bar=2)
+ self.assertEqual(type(t), FooBar)
+ self.assertEqual(t.foo, "foo")
+ self.assertEqual(t.bar, 2)
+ self.assertEqual(t[0], "foo")
+ self.assertEqual(t[1], 2)
+
+ FooBar("foo", 2)
+
+ with self.assertRaises(TypeError):
+ FooBar("foo", "not integer")
+ with self.assertRaises(TypeError):
+ FooBar(2, 4)
+
+ # Passing a tuple as the first argument is the same as passing multiple
+ # arguments.
+ t1 = ("foo", 3)
+ t2 = FooBar(t1)
+ self.assertEqual(type(t2), FooBar)
+ self.assertEqual(FooBar(t1), FooBar("foo", 3))
+
+
+class TestGroupUnifiedFiles(unittest.TestCase):
+ FILES = ["%s.cpp" % letter for letter in string.ascii_lowercase]
+
+ def test_multiple_files(self):
+ mapping = list(group_unified_files(self.FILES, "Unified", "cpp", 5))
+
+ def check_mapping(index, expected_num_source_files):
+ (unified_file, source_files) = mapping[index]
+
+ self.assertEqual(unified_file, "Unified%d.cpp" % index)
+ self.assertEqual(len(source_files), expected_num_source_files)
+
+ all_files = list(itertools.chain(*[files for (_, files) in mapping]))
+ self.assertEqual(len(all_files), len(self.FILES))
+ self.assertEqual(set(all_files), set(self.FILES))
+
+ expected_amounts = [5, 5, 5, 5, 5, 1]
+ for i, amount in enumerate(expected_amounts):
+ check_mapping(i, amount)
+
+
+class TestMisc(unittest.TestCase):
+ def test_pair(self):
+ self.assertEqual(list(pair([1, 2, 3, 4, 5, 6])), [(1, 2), (3, 4), (5, 6)])
+
+ self.assertEqual(
+ list(pair([1, 2, 3, 4, 5, 6, 7])), [(1, 2), (3, 4), (5, 6), (7, None)]
+ )
+
+ def test_expand_variables(self):
+ self.assertEqual(expand_variables("$(var)", {"var": "value"}), "value")
+
+ self.assertEqual(
+ expand_variables("$(a) and $(b)", {"a": "1", "b": "2"}), "1 and 2"
+ )
+
+ self.assertEqual(
+ expand_variables("$(a) and $(undefined)", {"a": "1", "b": "2"}), "1 and "
+ )
+
+ self.assertEqual(
+ expand_variables(
+ "before $(string) between $(list) after",
+ {"string": "abc", "list": ["a", "b", "c"]},
+ ),
+ "before abc between a b c after",
+ )
+
+
+class TestEnumString(unittest.TestCase):
+ def test_string(self):
+ CompilerType = EnumString.subclass("gcc", "clang", "clang-cl")
+
+ type = CompilerType("gcc")
+ self.assertEqual(type, "gcc")
+ self.assertNotEqual(type, "clang")
+ self.assertNotEqual(type, "clang-cl")
+ self.assertIn(type, ("gcc", "clang-cl"))
+ self.assertNotIn(type, ("clang", "clang-cl"))
+
+ with self.assertRaises(EnumStringComparisonError):
+ self.assertEqual(type, "foo")
+
+ with self.assertRaises(EnumStringComparisonError):
+ self.assertNotEqual(type, "foo")
+
+ with self.assertRaises(EnumStringComparisonError):
+ self.assertIn(type, ("foo", "gcc"))
+
+ with self.assertRaises(ValueError):
+ type = CompilerType("foo")
+
+
+class TestHexDump(unittest.TestCase):
+ @unittest.skipUnless(six.PY3, "requires Python 3")
+ def test_hexdump(self):
+ self.assertEqual(
+ hexdump("abcdef123💩ZYXWVU".encode("utf-8")),
+ [
+ "00 61 62 63 64 65 66 31 32 33 f0 9f 92 a9 5a 59 58 |abcdef123....ZYX|\n",
+ "10 57 56 55 |WVU |\n",
+ ],
+ )
+
+
+def test_read_only_dict():
+ d = ReadOnlyDict(foo="bar")
+ with pytest.raises(Exception):
+ d["foo"] = "baz"
+
+ with pytest.raises(Exception):
+ d.update({"foo": "baz"})
+
+ with pytest.raises(Exception):
+ del d["foo"]
+
+ # ensure copy still works
+ d_copy = d.copy()
+ assert d == d_copy
+ # TODO Returning a dict here feels like a bug, but there are places in-tree
+ # relying on this behaviour.
+ assert isinstance(d_copy, dict)
+
+ d_copy = copy.copy(d)
+ assert d == d_copy
+ assert isinstance(d_copy, ReadOnlyDict)
+
+ d_copy = copy.deepcopy(d)
+ assert d == d_copy
+ assert isinstance(d_copy, ReadOnlyDict)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_util_fileavoidwrite.py b/python/mozbuild/mozbuild/test/test_util_fileavoidwrite.py
new file mode 100644
index 0000000000..38c8941562
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_util_fileavoidwrite.py
@@ -0,0 +1,110 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+"""Tests for the FileAvoidWrite object."""
+
+import locale
+import pathlib
+
+import pytest
+from mozunit import main
+
+from mozbuild.util import FileAvoidWrite
+
+
+@pytest.fixture
+def tmp_path(tmpdir):
+ """Backport of the tmp_path fixture from pytest 3.9.1."""
+ return pathlib.Path(str(tmpdir))
+
+
+def test_overwrite_contents(tmp_path):
+ file = tmp_path / "file.txt"
+ file.write_text("abc")
+
+ faw = FileAvoidWrite(str(file))
+ faw.write("bazqux")
+
+ assert faw.close() == (True, True)
+ assert file.read_text() == "bazqux"
+
+
+def test_store_new_contents(tmp_path):
+ file = tmp_path / "file.txt"
+
+ faw = FileAvoidWrite(str(file))
+ faw.write("content")
+
+ assert faw.close() == (False, True)
+ assert file.read_text() == "content"
+
+
+def test_change_binary_file_contents(tmp_path):
+ file = tmp_path / "file.dat"
+ file.write_bytes(b"\0")
+
+ faw = FileAvoidWrite(str(file), readmode="rb")
+ faw.write(b"\0\0\0")
+
+ assert faw.close() == (True, True)
+ assert file.read_bytes() == b"\0\0\0"
+
+
+def test_obj_as_context_manager(tmp_path):
+ file = tmp_path / "file.txt"
+
+ with FileAvoidWrite(str(file)) as fh:
+ fh.write("foobar")
+
+ assert file.read_text() == "foobar"
+
+
+def test_no_write_happens_if_file_contents_same(tmp_path):
+ file = tmp_path / "file.txt"
+ file.write_text("content")
+ original_write_time = file.stat().st_mtime
+
+ faw = FileAvoidWrite(str(file))
+ faw.write("content")
+
+ assert faw.close() == (True, False)
+ assert file.stat().st_mtime == original_write_time
+
+
+def test_diff_not_created_by_default(tmp_path):
+ file = tmp_path / "file.txt"
+ faw = FileAvoidWrite(str(file))
+ faw.write("dummy")
+ faw.close()
+ assert faw.diff is None
+
+
+def test_diff_update(tmp_path):
+ file = tmp_path / "diffable.txt"
+ file.write_text("old")
+
+ faw = FileAvoidWrite(str(file), capture_diff=True)
+ faw.write("new")
+ faw.close()
+
+ diff = "\n".join(faw.diff)
+ assert "-old" in diff
+ assert "+new" in diff
+
+
+@pytest.mark.skipif(
+ locale.getdefaultlocale()[1] == "cp1252",
+ reason="Fails on win32 terminals with cp1252 encoding",
+)
+def test_write_unicode(tmp_path):
+ # Unicode grinning face :D
+ binary_emoji = b"\xf0\x9f\x98\x80"
+
+ file = tmp_path / "file.dat"
+ faw = FileAvoidWrite(str(file))
+ faw.write(binary_emoji)
+ faw.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_vendor.py b/python/mozbuild/mozbuild/test/test_vendor.py
new file mode 100644
index 0000000000..07ba088337
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_vendor.py
@@ -0,0 +1,48 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import shutil
+import subprocess
+import tempfile
+from unittest.mock import Mock
+
+import mozunit
+from buildconfig import topsrcdir
+
+from mozbuild.vendor.vendor_python import VendorPython
+
+
+def test_up_to_date_vendor():
+ with tempfile.TemporaryDirectory() as work_dir:
+ subprocess.check_call(["hg", "init", work_dir])
+ os.makedirs(os.path.join(work_dir, "third_party"))
+ shutil.copytree(
+ os.path.join(topsrcdir, os.path.join("third_party", "python")),
+ os.path.join(work_dir, os.path.join("third_party", "python")),
+ )
+
+ # Run the vendoring process
+ vendor = VendorPython(
+ work_dir, None, Mock(), topobjdir=os.path.join(work_dir, "obj")
+ )
+ vendor.vendor()
+
+ # Verify that re-vendoring did not cause file changes.
+ # Note that we don't want hg-ignored generated files
+ # to bust the diff, so we exclude them (pycache, egg-info).
+ subprocess.check_call(
+ [
+ "diff",
+ "-r",
+ os.path.join(topsrcdir, os.path.join("third_party", "python")),
+ os.path.join(work_dir, os.path.join("third_party", "python")),
+ "--exclude=__pycache__",
+ "--strip-trailing-cr",
+ ]
+ )
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/test_vendor_tools.py b/python/mozbuild/mozbuild/test/test_vendor_tools.py
new file mode 100644
index 0000000000..271be6d7da
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_vendor_tools.py
@@ -0,0 +1,90 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import mozunit
+
+from mozbuild.vendor.vendor_manifest import list_of_paths_to_readable_string
+
+
+def test_list_of_paths_to_readable_string():
+ paths = ["/tmp/a", "/tmp/b"]
+ s = list_of_paths_to_readable_string(paths)
+ assert not s.endswith(", ]")
+ assert s.endswith("]")
+ assert "/tmp/a" in s
+ assert "/tmp/b" in s
+
+ paths = ["/tmp/a", "/tmp/b", "/tmp/c", "/tmp/d"]
+ s = list_of_paths_to_readable_string(paths)
+ assert not s.endswith(", ")
+ assert s.endswith("]")
+ assert "/tmp/a" not in s
+ assert "/tmp/b" not in s
+ assert "4 items in /tmp" in s
+
+ paths = [
+ "/tmp/a",
+ "/tmp/b",
+ "/tmp/c",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ ]
+ s = list_of_paths_to_readable_string(paths)
+ assert not s.endswith(", ")
+ assert s.endswith("]")
+ assert "/tmp/a" not in s
+ assert " a" not in s
+ assert "/tmp/b" not in s
+ assert "10 (omitted) items in /tmp" in s
+
+ paths = ["/tmp", "/foo"]
+ s = list_of_paths_to_readable_string(paths)
+ assert not s.endswith(", ")
+ assert s.endswith("]")
+ assert "/tmp" in s
+ assert "/foo" in s
+
+ paths = [
+ "/tmp/a",
+ "/tmp/b",
+ "/tmp/c",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ "/tmp/d",
+ ]
+ paths.extend(["/foo/w", "/foo/x", "/foo/y", "/foo/z"])
+ paths.extend(["/bar/m", "/bar/n"])
+ paths.extend(["/etc"])
+ s = list_of_paths_to_readable_string(paths)
+ assert not s.endswith(", ")
+ assert s.endswith("]")
+ assert "/tmp/a" not in s
+ assert " d" not in s
+ assert "/tmp/b" not in s
+ assert "10 (omitted) items in /tmp" in s
+
+ assert "/foo/w" not in s
+ assert "/foo/x" not in s
+ assert "4 items in /foo" in s
+ assert " w" in s
+
+ assert "/bar/m" in s
+ assert "/bar/n" in s
+
+ assert "/etc" in s
+
+ assert len(s) < len(str(paths))
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/vendor_requirements.in b/python/mozbuild/mozbuild/test/vendor_requirements.in
new file mode 100644
index 0000000000..852826fc1a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/vendor_requirements.in
@@ -0,0 +1,5 @@
+# Until bug 1724273 lands, python-testing code that uses a site is not possible. Work around
+# this by representing the "vendor" site's dependency as a separate "requirements.txt" file,
+# which can be used by python-test's "requirements" feature.
+poetry==1.4
+poetry-core==1.5.1
diff --git a/python/mozbuild/mozbuild/test/vendor_requirements.txt b/python/mozbuild/mozbuild/test/vendor_requirements.txt
new file mode 100644
index 0000000000..10a32a524c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/vendor_requirements.txt
@@ -0,0 +1,416 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# pip-compile --generate-hashes --output-file=python/mozbuild/mozbuild/test/vendor_requirements.txt python/mozbuild/mozbuild/test/vendor_requirements.in
+#
+appdirs==1.4.4 \
+ --hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 \
+ --hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128
+ # via virtualenv
+attrs==22.2.0 \
+ --hash=sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836 \
+ --hash=sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99
+ # via jsonschema
+build==0.10.0 \
+ --hash=sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171 \
+ --hash=sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269
+ # via poetry
+cachecontrol[filecache]==0.12.10 \
+ --hash=sha256:b0d43d8f71948ef5ebdee5fe236b86c6ffc7799370453dccb0e894c20dfa487c \
+ --hash=sha256:d8aca75b82eec92d84b5d6eb8c8f66ea16f09d2adb09dbca27fe2d5fc8d3732d
+ # via poetry
+certifi==2021.10.8 \
+ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
+ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
+ # via requests
+charset-normalizer==2.0.12 \
+ --hash=sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597 \
+ --hash=sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df
+ # via requests
+cleo==2.0.1 \
+ --hash=sha256:6eb133670a3ed1f3b052d53789017b6e50fca66d1287e6e6696285f4cb8ea448 \
+ --hash=sha256:eb4b2e1f3063c11085cebe489a6e9124163c226575a3c3be69b2e51af4a15ec5
+ # via poetry
+colorama==0.4.6 \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+ # via build
+crashtest==0.4.1 \
+ --hash=sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce \
+ --hash=sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5
+ # via
+ # cleo
+ # poetry
+distlib==0.3.4 \
+ --hash=sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b \
+ --hash=sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579
+ # via virtualenv
+dulwich==0.21.3 \
+ --hash=sha256:026427b5ef0f1fe138ed22078e49b00175b58b11e5c18e2be00f06ee0782603b \
+ --hash=sha256:03ed9448f2944166e28aa8d3f4c8feeceb5c6880e9ffe5ab274869d45abd9589 \
+ --hash=sha256:058aaba18aefe18fcd84b216fd34d032ad453967dcf3dee263278951cd43e2d4 \
+ --hash=sha256:075c8e9d2694ff16fc6e8a5ec0c771b7c33be12e4ebecc346fd74315d3d84605 \
+ --hash=sha256:08ee426b609dab552839b5c7394ae9af2112c164bb727b7f85a69980eced9251 \
+ --hash=sha256:092829f27a2c87cdf6b6523216822859ecf01d281ddfae0e58cad1f44adafff6 \
+ --hash=sha256:0b541bd58426a30753ab12cc024ba29b6699d197d9d0d9f130b9768ab20e0e6a \
+ --hash=sha256:0cd83f84e58aa59fb9d85cf15e74be83a5be876ac5876d5030f60fcce7ab36f1 \
+ --hash=sha256:1799c04bd53ec404ebd2c82c1d66197a31e5f0549c95348bb7d3f57a28c94241 \
+ --hash=sha256:1cf246530b8d574b33a9614da76881b96c190c0fe78f76ab016c88082c0da051 \
+ --hash=sha256:208d01a9cda1bae16c92e8c54e806701a16969346aba44b8d6921c6c227277a9 \
+ --hash=sha256:21ee962211839bb6e52d41f363ce9dbb0638d341a1c02263e163d69012f58b25 \
+ --hash=sha256:250ec581682af846cb85844f8032b7642dd278006b1c3abd5e8e718eba0b1b00 \
+ --hash=sha256:25376efc6ea2ee9daa868a120d4f9c905dcb7774f68931be921fba41a657f58a \
+ --hash=sha256:2bf2be68fddfc0adfe43be99ab31f6b0f16b9ef1e40464679ba831ff615ad4a3 \
+ --hash=sha256:33f73e8f902c6397cc73a727db1f6e75add8ce894bfbb1a15daa2f7a4138a744 \
+ --hash=sha256:3b048f84c94c3284f29bf228f1094ccc48763d76ede5c35632153bd7f697b846 \
+ --hash=sha256:40f8f461eba87ef2e8ce0005ca2c12f1b4fdbbafd3a717b8570060d7cd35ee0c \
+ --hash=sha256:512bb4b04e403a38860f7eb22abeeaefba3c4a9c08bc7beec8885494c5828034 \
+ --hash=sha256:5a1137177b62eec949c0f1564eef73920f842af5ebfc260c20d9cd47e8ecd519 \
+ --hash=sha256:6618e35268d116bffddd6dbec360a40c54b3164f8af0513d95d8698f36e2eacc \
+ --hash=sha256:67dbf4dd7586b2d437f539d5dc930ebceaf74a4150720644d6ea7e5ffc1cb2ff \
+ --hash=sha256:6f8d45f5fcdb52c60c902a951f549faad9979314e7e069f4fa3d14eb409b16a0 \
+ --hash=sha256:73f9feba3da1ae66f0b521d7c2727db7f5025a83facdc73f4f39abe2b6d4f00d \
+ --hash=sha256:7aaf5c4528e83e3176e7dbb01dcec34fb41c93279a8f8527cf33e5df88bfb910 \
+ --hash=sha256:7c69c95d5242171d07396761f759a8a4d566e9a01bf99612f9b9e309e70a80fc \
+ --hash=sha256:7ca3b453d767eb83b3ec58f0cfcdc934875a341cdfdb0dc55c1431c96608cf83 \
+ --hash=sha256:7f2cb11fe789b72feeae7cdf6e27375c33ed6915f8ca5ea7ce81b5e234c75a9e \
+ --hash=sha256:89af4ee347f361338bad5c27b023f9d19e7aed17aa75cb519f28e6cf1658a0ba \
+ --hash=sha256:8ad7de37c9ff817bc5d26f89100f87b7f1a5cc25e5eaaa54f11dc66cca9652e4 \
+ --hash=sha256:8ba1fe3fb415fd34cae5ca090fb82030b6e8423d6eb2c4c9c4fbf50b15c7664c \
+ --hash=sha256:9213a114dd19cfca19715088f12f143e918c5e1b4e26f7acf1a823d7da9e1413 \
+ --hash=sha256:9f08e5cc10143d3da2a2cf735d8b932ef4e4e1d74b0c74ce66c52eab02068be8 \
+ --hash=sha256:a275b3a579dfd923d6330f6e5c2886dbdb5da4e004c5abecb107eb347d301412 \
+ --hash=sha256:a2e6270923bf5ec0e9f720d689579a904f401c62193222d000d8cb8e880684e9 \
+ --hash=sha256:a98989ff1ed20825728495ffb859cd700a120850074184d2e1ec08a0b1ab8ab3 \
+ --hash=sha256:ae38c6d24d7aff003a241c8f1dd268eb1c6f7625d91e3435836ff5a5eed05ce5 \
+ --hash=sha256:af7a417e19068b1abeb9addd3c045a2d6e40d15365af6aa3cbe2d47305b5bb11 \
+ --hash=sha256:b09b6166876d2cba8f331a548932b09e11c9386db0525c9ca15c399b666746fc \
+ --hash=sha256:b9fc609a3d4009ee31212f435f5a75720ef24280f6d23edfd53f77b562a79c5b \
+ --hash=sha256:ba3d42cd83d7f89b9c1b2f76df971e8ab58815f8060da4dc67b9ae9dba1b34cc \
+ --hash=sha256:baf5b3b901272837bee2311ecbd28fdbe960d288a070dc72bdfdf48cfcbb8090 \
+ --hash=sha256:bb54fe45deb55e4caae4ea2c1dba93ee79fb5c377287b14056d4c30fb156920e \
+ --hash=sha256:be0801ae3f9017c6437bcd23a4bf2b2aa88e465f7efeed4b079944d07e3df994 \
+ --hash=sha256:c349431f5c8aa99b8744550d0bb4615f63e73450584202ac5db0e5d7da4d82ff \
+ --hash=sha256:c80ade5cdb0ea447e7f43b32abc2f4a628dcdfa64dc8ee5ab4262987e5e0814f \
+ --hash=sha256:c8d1837c3d2d8e56aacc13a91ec7540b3baadc1b254fbdf225a2d15b72b654c3 \
+ --hash=sha256:c97561c22fc05d0f6ba370d9bd67f86c313c38f31a1793e0ee9acb78ee28e4b8 \
+ --hash=sha256:cf1f6edc968619a4355481c29d5571726723bc12924e2b25bd3348919f9bc992 \
+ --hash=sha256:cf7af6458cf6343a2a0632ae2fc5f04821b2ffefc7b8a27f4eacb726ef89c682 \
+ --hash=sha256:d0ac29adf468a838884e1507d81e872096238c76fe7da7f3325507e4390b6867 \
+ --hash=sha256:d7ad871d044a96f794170f2434e832c6b42804d0b53721377d03f865245cd273 \
+ --hash=sha256:ddb790f2fdc22984fba643866b21d04733c5cf7c3ace2a1e99e0c1c1d2336aab \
+ --hash=sha256:e3b686b49adeb7fc45791dfae96ffcffeba1038e8b7603f369d6661f59e479fc \
+ --hash=sha256:e7b8cb38a93de87b980f882f0dcd19f2e3ad43216f34e06916315cb3a03e6964 \
+ --hash=sha256:f4f8ff776ca38ce272d9c164a7f77db8a54a8cad6d9468124317adf8732be07d
+ # via poetry
+filelock==3.10.0 \
+ --hash=sha256:3199fd0d3faea8b911be52b663dfccceb84c95949dd13179aa21436d1a79c4ce \
+ --hash=sha256:e90b34656470756edf8b19656785c5fea73afa1953f3e1b0d645cef11cab3182
+ # via
+ # poetry
+ # virtualenv
+html5lib==1.1 \
+ --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \
+ --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f
+ # via poetry
+idna==3.3 \
+ --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
+ --hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d
+ # via requests
+importlib-metadata==6.1.0 \
+ --hash=sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20 \
+ --hash=sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09
+ # via
+ # keyring
+ # poetry
+installer==0.6.0 \
+ --hash=sha256:ae7c62d1d6158b5c096419102ad0d01fdccebf857e784cee57f94165635fe038 \
+ --hash=sha256:f3bd36cd261b440a88a1190b1becca0578fee90b4b62decc796932fdd5ae8839
+ # via poetry
+jaraco-classes==3.2.3 \
+ --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \
+ --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a
+ # via keyring
+jsonschema==4.17.3 \
+ --hash=sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d \
+ --hash=sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6
+ # via poetry
+keyring==23.13.1 \
+ --hash=sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd \
+ --hash=sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678
+ # via poetry
+lockfile==0.12.2 \
+ --hash=sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799 \
+ --hash=sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa
+ # via
+ # cachecontrol
+ # poetry
+more-itertools==9.1.0 \
+ --hash=sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d \
+ --hash=sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3
+ # via jaraco-classes
+msgpack==1.0.3 \
+ --hash=sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc \
+ --hash=sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147 \
+ --hash=sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3 \
+ --hash=sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba \
+ --hash=sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39 \
+ --hash=sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85 \
+ --hash=sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9 \
+ --hash=sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a \
+ --hash=sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec \
+ --hash=sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88 \
+ --hash=sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e \
+ --hash=sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a \
+ --hash=sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b \
+ --hash=sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1 \
+ --hash=sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3 \
+ --hash=sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef \
+ --hash=sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079 \
+ --hash=sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52 \
+ --hash=sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a \
+ --hash=sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a \
+ --hash=sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4 \
+ --hash=sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996 \
+ --hash=sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73 \
+ --hash=sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a \
+ --hash=sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920 \
+ --hash=sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7 \
+ --hash=sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d \
+ --hash=sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770 \
+ --hash=sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50 \
+ --hash=sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2 \
+ --hash=sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2 \
+ --hash=sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d \
+ --hash=sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea \
+ --hash=sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611
+ # via cachecontrol
+packaging==20.9 \
+ --hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5 \
+ --hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a
+ # via
+ # build
+ # poetry
+pexpect==4.8.0 \
+ --hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \
+ --hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c
+ # via poetry
+pkginfo==1.9.6 \
+ --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \
+ --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046
+ # via poetry
+platformdirs==2.6.2 \
+ --hash=sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490 \
+ --hash=sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2
+ # via poetry
+poetry==1.4.0 \
+ --hash=sha256:151ad741e163a329c8b13ea602dde979b7616fc350cfcff74b604e93263934a8 \
+ --hash=sha256:f88a7a812a5d8c1f5a378e0924f898926b2ac10c3b5c03f7282f2182f90d8507
+ # via
+ # -r python/mozbuild/mozbuild/test/vendor_requirements.in
+ # poetry-plugin-export
+poetry-core==1.5.1 \
+ --hash=sha256:41887261358863f25831fa0ad1fe7e451fc32d1c81fcf7710ba5174cc0047c6d \
+ --hash=sha256:b1900dea81eb18feb7323d404e5f10430205541a4a683a912893f9d2b5807797
+ # via
+ # -r python/mozbuild/mozbuild/test/vendor_requirements.in
+ # poetry
+ # poetry-plugin-export
+poetry-plugin-export==1.3.0 \
+ --hash=sha256:61ae5ec1db233aba947a48e1ce54c6ff66afd0e1c87195d6bce64c73a5ae658c \
+ --hash=sha256:6e5919bf84afcb08cdd419a03f909f490d8671f00633a3c6df8ba09b0820dc2f
+ # via poetry
+ptyprocess==0.7.0 \
+ --hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \
+ --hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220
+ # via pexpect
+pyparsing==3.0.8 \
+ --hash=sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954 \
+ --hash=sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06
+ # via packaging
+pyproject-hooks==1.0.0 \
+ --hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \
+ --hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5
+ # via
+ # build
+ # poetry
+pyrsistent==0.19.3 \
+ --hash=sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8 \
+ --hash=sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440 \
+ --hash=sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a \
+ --hash=sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c \
+ --hash=sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3 \
+ --hash=sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393 \
+ --hash=sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9 \
+ --hash=sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da \
+ --hash=sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf \
+ --hash=sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64 \
+ --hash=sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a \
+ --hash=sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3 \
+ --hash=sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98 \
+ --hash=sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2 \
+ --hash=sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8 \
+ --hash=sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf \
+ --hash=sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc \
+ --hash=sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7 \
+ --hash=sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28 \
+ --hash=sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2 \
+ --hash=sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b \
+ --hash=sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a \
+ --hash=sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64 \
+ --hash=sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19 \
+ --hash=sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1 \
+ --hash=sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9 \
+ --hash=sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c
+ # via jsonschema
+pywin32-ctypes==0.2.0 \
+ --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \
+ --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98
+ # via keyring
+rapidfuzz==2.13.7 \
+ --hash=sha256:020858dd89b60ce38811cd6e37875c4c3c8d7fcd8bc20a0ad2ed1f464b34dc4e \
+ --hash=sha256:042644133244bfa7b20de635d500eb9f46af7097f3d90b1724f94866f17cb55e \
+ --hash=sha256:08590905a95ccfa43f4df353dcc5d28c15d70664299c64abcad8721d89adce4f \
+ --hash=sha256:114810491efb25464016fd554fdf1e20d390309cecef62587494fc474d4b926f \
+ --hash=sha256:1333fb3d603d6b1040e365dca4892ba72c7e896df77a54eae27dc07db90906e3 \
+ --hash=sha256:16080c05a63d6042643ae9b6cfec1aefd3e61cef53d0abe0df3069b9d4b72077 \
+ --hash=sha256:16ffad751f43ab61001187b3fb4a9447ec2d1aedeff7c5bac86d3b95f9980cc3 \
+ --hash=sha256:1f50d1227e6e2a0e3ae1fb1c9a2e1c59577d3051af72c7cab2bcc430cb5e18da \
+ --hash=sha256:1fbad8fb28d98980f5bff33c7842efef0315d42f0cd59082108482a7e6b61410 \
+ --hash=sha256:23524635840500ce6f4d25005c9529a97621689c85d2f727c52eed1782839a6a \
+ --hash=sha256:24d3fea10680d085fd0a4d76e581bfb2b1074e66e78fd5964d4559e1fcd2a2d4 \
+ --hash=sha256:24eb6b843492bdc63c79ee4b2f104059b7a2201fef17f25177f585d3be03405a \
+ --hash=sha256:25b4cedf2aa19fb7212894ce5f5219010cce611b60350e9a0a4d492122e7b351 \
+ --hash=sha256:27be9c63215d302ede7d654142a2e21f0d34ea6acba512a4ae4cfd52bbaa5b59 \
+ --hash=sha256:2c836f0f2d33d4614c3fbaf9a1eb5407c0fe23f8876f47fd15b90f78daa64c34 \
+ --hash=sha256:3a9bd02e1679c0fd2ecf69b72d0652dbe2a9844eaf04a36ddf4adfbd70010e95 \
+ --hash=sha256:3d8b081988d0a49c486e4e845a547565fee7c6e7ad8be57ff29c3d7c14c6894c \
+ --hash=sha256:3dcffe1f3cbda0dc32133a2ae2255526561ca594f15f9644384549037b355245 \
+ --hash=sha256:3f11a7eff7bc6301cd6a5d43f309e22a815af07e1f08eeb2182892fca04c86cb \
+ --hash=sha256:42085d4b154a8232767de8296ac39c8af5bccee6b823b0507de35f51c9cbc2d7 \
+ --hash=sha256:424f82c35dbe4f83bdc3b490d7d696a1dc6423b3d911460f5493b7ffae999fd2 \
+ --hash=sha256:43fb8cb030f888c3f076d40d428ed5eb4331f5dd6cf1796cfa39c67bf0f0fc1e \
+ --hash=sha256:460853983ab88f873173e27cc601c5276d469388e6ad6e08c4fd57b2a86f1064 \
+ --hash=sha256:467c1505362823a5af12b10234cb1c4771ccf124c00e3fc9a43696512bd52293 \
+ --hash=sha256:46b9b8aa09998bc48dd800854e8d9b74bc534d7922c1d6e1bbf783e7fa6ac29c \
+ --hash=sha256:53dcae85956853b787c27c1cb06f18bb450e22cf57a4ad3444cf03b8ff31724a \
+ --hash=sha256:585206112c294e335d84de5d5f179c0f932837752d7420e3de21db7fdc476278 \
+ --hash=sha256:5ada0a14c67452358c1ee52ad14b80517a87b944897aaec3e875279371a9cb96 \
+ --hash=sha256:5e2b3d020219baa75f82a4e24b7c8adcb598c62f0e54e763c39361a9e5bad510 \
+ --hash=sha256:6120f2995f5154057454c5de99d86b4ef3b38397899b5da1265467e8980b2f60 \
+ --hash=sha256:68a89bb06d5a331511961f4d3fa7606f8e21237467ba9997cae6f67a1c2c2b9e \
+ --hash=sha256:7496e8779905b02abc0ab4ba2a848e802ab99a6e20756ffc967a0de4900bd3da \
+ --hash=sha256:759a3361711586a29bc753d3d1bdb862983bd9b9f37fbd7f6216c24f7c972554 \
+ --hash=sha256:75c45dcd595f8178412367e302fd022860ea025dc4a78b197b35428081ed33d5 \
+ --hash=sha256:7d005e058d86f2a968a8d28ca6f2052fab1f124a39035aa0523261d6baf21e1f \
+ --hash=sha256:7f7930adf84301797c3f09c94b9c5a9ed90a9e8b8ed19b41d2384937e0f9f5bd \
+ --hash=sha256:8109e0324d21993d5b2d111742bf5958f3516bf8c59f297c5d1cc25a2342eb66 \
+ --hash=sha256:81642a24798851b118f82884205fc1bd9ff70b655c04018c467824b6ecc1fabc \
+ --hash=sha256:8450d15f7765482e86ef9be2ad1a05683cd826f59ad236ef7b9fb606464a56aa \
+ --hash=sha256:875d51b3497439a72e2d76183e1cb5468f3f979ab2ddfc1d1f7dde3b1ecfb42f \
+ --hash=sha256:8b477b43ced896301665183a5e0faec0f5aea2373005648da8bdcb3c4b73f280 \
+ --hash=sha256:8d3e252d4127c79b4d7c2ae47271636cbaca905c8bb46d80c7930ab906cf4b5c \
+ --hash=sha256:916bc2e6cf492c77ad6deb7bcd088f0ce9c607aaeabc543edeb703e1fbc43e31 \
+ --hash=sha256:988f8f6abfba7ee79449f8b50687c174733b079521c3cc121d65ad2d38831846 \
+ --hash=sha256:99a84ab9ac9a823e7e93b4414f86344052a5f3e23b23aa365cda01393ad895bd \
+ --hash=sha256:9be02162af0376d64b840f2fc8ee3366794fc149f1e06d095a6a1d42447d97c5 \
+ --hash=sha256:a5585189b3d90d81ccd62d4f18530d5ac8972021f0aaaa1ffc6af387ff1dce75 \
+ --hash=sha256:ae33a72336059213996fe4baca4e0e4860913905c2efb7c991eab33b95a98a0a \
+ --hash=sha256:af4f7c3c904ca709493eb66ca9080b44190c38e9ecb3b48b96d38825d5672559 \
+ --hash=sha256:b20141fa6cee041917801de0bab503447196d372d4c7ee9a03721b0a8edf5337 \
+ --hash=sha256:b3210869161a864f3831635bb13d24f4708c0aa7208ef5baac1ac4d46e9b4208 \
+ --hash=sha256:b34e8c0e492949ecdd5da46a1cfc856a342e2f0389b379b1a45a3cdcd3176a6e \
+ --hash=sha256:b52ac2626945cd21a2487aeefed794c14ee31514c8ae69b7599170418211e6f6 \
+ --hash=sha256:b5dd713a1734574c2850c566ac4286594bacbc2d60b9170b795bee4b68656625 \
+ --hash=sha256:b5f705652360d520c2de52bee11100c92f59b3e3daca308ebb150cbc58aecdad \
+ --hash=sha256:b6389c50d8d214c9cd11a77f6d501529cb23279a9c9cafe519a3a4b503b5f72a \
+ --hash=sha256:b6bad92de071cbffa2acd4239c1779f66851b60ffbbda0e4f4e8a2e9b17e7eef \
+ --hash=sha256:b75dd0928ce8e216f88660ab3d5c5ffe990f4dd682fd1709dba29d5dafdde6de \
+ --hash=sha256:c2523f8180ebd9796c18d809e9a19075a1060b1a170fde3799e83db940c1b6d5 \
+ --hash=sha256:c31022d9970177f6affc6d5dd757ed22e44a10890212032fabab903fdee3bfe7 \
+ --hash=sha256:c36fd260084bb636b9400bb92016c6bd81fd80e59ed47f2466f85eda1fc9f782 \
+ --hash=sha256:c3741cb0bf9794783028e8b0cf23dab917fa5e37a6093b94c4c2f805f8e36b9f \
+ --hash=sha256:c3fbe449d869ea4d0909fc9d862007fb39a584fb0b73349a6aab336f0d90eaed \
+ --hash=sha256:c66546e30addb04a16cd864f10f5821272a1bfe6462ee5605613b4f1cb6f7b48 \
+ --hash=sha256:c71d9d512b76f05fa00282227c2ae884abb60e09f08b5ca3132b7e7431ac7f0d \
+ --hash=sha256:c8601a66fbfc0052bb7860d2eacd303fcde3c14e87fdde409eceff516d659e77 \
+ --hash=sha256:c88adbcb933f6b8612f6c593384bf824e562bb35fc8a0f55fac690ab5b3486e5 \
+ --hash=sha256:ca00fafd2756bc9649bf80f1cf72c647dce38635f0695d7ce804bc0f759aa756 \
+ --hash=sha256:ca8a23097c1f50e0fdb4de9e427537ca122a18df2eead06ed39c3a0bef6d9d3a \
+ --hash=sha256:cda1e2f66bb4ba7261a0f4c2d052d5d909798fca557cbff68f8a79a87d66a18f \
+ --hash=sha256:cdfc04f7647c29fb48da7a04082c34cdb16f878d3c6d098d62d5715c0ad3000c \
+ --hash=sha256:cf62dacb3f9234f3fddd74e178e6d25c68f2067fde765f1d95f87b1381248f58 \
+ --hash=sha256:d00df2e4a81ffa56a6b1ec4d2bc29afdcb7f565e0b8cd3092fece2290c4c7a79 \
+ --hash=sha256:d248a109699ce9992304e79c1f8735c82cc4c1386cd8e27027329c0549f248a2 \
+ --hash=sha256:d63def9bbc6b35aef4d76dc740301a4185867e8870cbb8719ec9de672212fca8 \
+ --hash=sha256:d82f20c0060ffdaadaf642b88ab0aa52365b56dffae812e188e5bdb998043588 \
+ --hash=sha256:dbcf5371ea704759fcce772c66a07647751d1f5dbdec7818331c9b31ae996c77 \
+ --hash=sha256:e8914dad106dacb0775718e54bf15e528055c4e92fb2677842996f2d52da5069 \
+ --hash=sha256:ebe303cd9839af69dd1f7942acaa80b1ba90bacef2e7ded9347fbed4f1654672 \
+ --hash=sha256:ec55a81ac2b0f41b8d6fb29aad16e55417036c7563bad5568686931aa4ff08f7 \
+ --hash=sha256:effe182767d102cb65dfbbf74192237dbd22d4191928d59415aa7d7c861d8c88 \
+ --hash=sha256:f42b82f268689f429def9ecfb86fa65ceea0eaf3fed408b570fe113311bf5ce7 \
+ --hash=sha256:f6fe570e20e293eb50491ae14ddeef71a6a7e5f59d7e791393ffa99b13f1f8c2 \
+ --hash=sha256:f799d1d6c33d81e983d3682571cc7d993ae7ff772c19b3aabb767039c33f6d1e \
+ --hash=sha256:f891b98f8bc6c9d521785816085e9657212621e93f223917fb8e32f318b2957e \
+ --hash=sha256:fa263135b892686e11d5b84f6a1892523123a00b7e5882eff4fbdabb38667347 \
+ --hash=sha256:fa4c598ed77f74ec973247ca776341200b0f93ec3883e34c222907ce72cb92a4 \
+ --hash=sha256:fe56659ccadbee97908132135de4b875543353351e0c92e736b7c57aee298b5a \
+ --hash=sha256:fe59a0c21a032024edb0c8e43f5dee5623fef0b65a1e3c1281836d9ce199af3b
+ # via cleo
+requests==2.27.1 \
+ --hash=sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61 \
+ --hash=sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d
+ # via
+ # cachecontrol
+ # poetry
+ # requests-toolbelt
+requests-toolbelt==0.9.1 \
+ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \
+ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0
+ # via poetry
+shellingham==1.5.0.post1 \
+ --hash=sha256:368bf8c00754fd4f55afb7bbb86e272df77e4dc76ac29dbcbb81a59e9fc15744 \
+ --hash=sha256:823bc5fb5c34d60f285b624e7264f4dda254bc803a3774a147bf99c0e3004a28
+ # via poetry
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via
+ # html5lib
+ # virtualenv
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via
+ # build
+ # poetry
+ # pyproject-hooks
+tomlkit==0.11.6 \
+ --hash=sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b \
+ --hash=sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73
+ # via poetry
+trove-classifiers==2023.3.9 \
+ --hash=sha256:06fd10c95d285e7ddebd59e6a4ba299f03d7417d38d369248a4a40c9754a68fa \
+ --hash=sha256:ee42f2f8c1d4bcfe35f746e472f07633570d485fab45407effc0379270a3bb03
+ # via poetry
+urllib3==1.26.9 \
+ --hash=sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14 \
+ --hash=sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e
+ # via
+ # dulwich
+ # poetry
+ # requests
+virtualenv==20.4.4 \
+ --hash=sha256:09c61377ef072f43568207dc8e46ddeac6bcdcaf288d49011bda0e7f4d38c4a2 \
+ --hash=sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535
+ # via poetry
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via html5lib
+zipp==3.6.0 \
+ --hash=sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832 \
+ --hash=sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc
+ # via importlib-metadata