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/invalid/region.properties12
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/assets/asset.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/classes.dex1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1.ap_bin0 -> 503 bytes
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/res/res.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/resources.arsc1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2.apkbin0 -> 1649 bytes
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/asset.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/omni.ja1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/classes.dex1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/lib/lib.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/res/res.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/resources.arsc1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/root_file.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/lib/lib.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/omni.ja1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/package_fennec_apk/root_file.txt1
-rw-r--r--python/mozbuild/mozbuild/test/action/data/valid-zh-CN/region.properties37
-rw-r--r--python/mozbuild/mozbuild/test/action/test_buildlist.py89
-rw-r--r--python/mozbuild/mozbuild/test/action/test_generate_browsersearch.py55
-rw-r--r--python/mozbuild/mozbuild/test/action/test_package_fennec_apk.py70
-rw-r--r--python/mozbuild/mozbuild/test/backend/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/backend/common.py156
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/library1/resources/values/strings.xml1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main1/AndroidManifest.xml1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/AndroidManifest.xml1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/assets/dummy.txt1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/extra.jar1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/res/values/strings.xml1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/AndroidManifest.xml1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/a/A.java1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/b/B.java1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/c/C.java1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/main41
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/moz.build37
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/moz.build13
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/submain/AndroidManifest.xml1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/binary-components/bar/moz.build2
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/binary-components/foo/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/binary-components/moz.build10
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/branding-files/bar.ico0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/branding-files/foo.ico0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/branding-files/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/branding-files/sub/quux.png0
-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/defines/moz.build14
-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/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/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.build14
-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.build10
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build10
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/ipdl_sources/moz.build10
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/jar-manifests/moz.build8
-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/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/sdk-files/bar.ico0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sdk-files/foo.ico0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sdk-files/moz.build11
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sdk-files/sub/quux.png0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/bar.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/bar.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/bar.mm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/bar.s0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/baz.S0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/foo.S0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/foo.asm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/foo.c0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/foo.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/foo.mm0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/sources/moz.build21
-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.build5
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/stub0/dir2/moz.build4
-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.build4
-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-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_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/moz.build23
-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/config/makefiles/xpidl/Makefile.in0
-rw-r--r--python/mozbuild/mozbuild/test/backend/data/xpidl/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_android_eclipse.py153
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_build.py233
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_configenvironment.py63
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_recursivemake.py942
-rw-r--r--python/mozbuild/mozbuild/test/backend/test_visualstudio.py64
-rw-r--r--python/mozbuild/mozbuild/test/common.py50
-rw-r--r--python/mozbuild/mozbuild/test/compilation/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/compilation/test_warnings.py241
-rw-r--r--python/mozbuild/mozbuild/test/configure/common.py279
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/decorators.configure44
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/empty_mozconfig0
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/extra.configure13
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure32
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure24
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure31
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure34
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure24
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure24
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/included.configure53
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/moz.configure174
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/set_config.configure43
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/set_define.configure43
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/subprocess.configure23
-rw-r--r--python/mozbuild/mozbuild/test/configure/lint.py65
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_checks_configure.py940
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_compile_checks.py403
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_configure.py1273
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_lint.py132
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_moz_configure.py93
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_options.py852
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py1271
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py437
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py67
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_util.py558
-rw-r--r--python/mozbuild/mozbuild/test/controller/__init__.py0
-rw-r--r--python/mozbuild/mozbuild/test/controller/test_ccachestats.py208
-rw-r--r--python/mozbuild/mozbuild/test/controller/test_clobber.py213
-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/android-res-dirs/dir1/foo0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/android-res-dirs/moz.build9
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/binary-components/bar/moz.build2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/binary-components/foo/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/binary-components/moz.build10
-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.build13
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/branding-files/quux.icns0
-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.build18
-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.build14
-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/files-test-metadata/default/module.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/moz.build6
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest-stylo.list2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest.list1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1-ref.html0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1.html0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/test_default_mod.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/xpcshell.ini1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/base.cpp0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/browser.ini1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/test_mod.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/moz.build22
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/module.jsm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/moz.build3
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/mochitest.ini2
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/moz.build1
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_general.html0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_specific.html0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/moz.build15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/bar.jsm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/submodule/foo.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/mochitest.ini3
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_bar.js0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_simple.html0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_specific.html0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/xpcshell.ini1
-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-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.build5
-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.build37
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/generated_includes/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/host-defines/moz.build14
-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.build25
-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.build10
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build10
-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.build8
-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.build9
-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/missing-local-includes/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/moz.build27
-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/program/moz.build15
-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.build6
-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/rust-library-dash-folding/Cargo.toml15
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/moz.build18
-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.build18
-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.build18
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-no-cargo-toml/moz.build18
-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.build18
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-no-profile-section/Cargo.toml12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-no-profile-section/moz.build18
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-non-abort-panic/Cargo.toml14
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/rust-library-non-abort-panic/moz.build18
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sdk-files/bar.ico0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sdk-files/baz.png0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sdk-files/foo.xpm0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sdk-files/moz.build12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/sdk-files/quux.icns0
-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.build27
-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.build37
-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.build12
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build11
-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.build9
-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.build9
-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-stylo.list3
-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.ini4
-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-install-subdir/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/subdir.ini5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/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.ini4
-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/reftest-stylo.list2
-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.ini6
-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-python-unit-test-missing/moz.build4
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir-missing-generated/moz.build10
-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.build13
-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.build10
-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.build28
-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.build28
-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-yasm/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/bans.S0
-rw-r--r--python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build25
-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/xpidl-module-no-sources/moz.build5
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_context.py721
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_emitter.py1172
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_namespaces.py207
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_reader.py485
-rw-r--r--python/mozbuild/mozbuild/test/frontend/test_sandbox.py534
-rw-r--r--python/mozbuild/mozbuild/test/test_android_version_code.py63
-rw-r--r--python/mozbuild/mozbuild/test/test_base.py410
-rw-r--r--python/mozbuild/mozbuild/test/test_containers.py224
-rw-r--r--python/mozbuild/mozbuild/test/test_dotproperties.py178
-rw-r--r--python/mozbuild/mozbuild/test/test_expression.py82
-rw-r--r--python/mozbuild/mozbuild/test/test_jarmaker.py367
-rw-r--r--python/mozbuild/mozbuild/test/test_line_endings.py46
-rw-r--r--python/mozbuild/mozbuild/test/test_makeutil.py165
-rw-r--r--python/mozbuild/mozbuild/test/test_mozconfig.py489
-rwxr-xr-xpython/mozbuild/mozbuild/test/test_mozinfo.py278
-rw-r--r--python/mozbuild/mozbuild/test/test_preprocessor.py646
-rw-r--r--python/mozbuild/mozbuild/test/test_pythonutil.py23
-rw-r--r--python/mozbuild/mozbuild/test/test_testing.py332
-rw-r--r--python/mozbuild/mozbuild/test/test_util.py924
592 files changed, 18920 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/__init__.py b/python/mozbuild/mozbuild/test/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/__init__.py
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 000000000..d4d8109b6
--- /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/package_fennec_apk/assets/asset.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/assets/asset.txt
new file mode 100644
index 000000000..b01830602
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/assets/asset.txt
@@ -0,0 +1 @@
+assets/asset.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/classes.dex b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/classes.dex
new file mode 100644
index 000000000..dfc99f9c2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/classes.dex
@@ -0,0 +1 @@
+classes.dex \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1.ap_ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1.ap_
new file mode 100644
index 000000000..915be683b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1.ap_
Binary files differ
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/res/res.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/res/res.txt
new file mode 100644
index 000000000..01d2fb0a1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/res/res.txt
@@ -0,0 +1 @@
+input1/res/res.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/resources.arsc b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/resources.arsc
new file mode 100644
index 000000000..6274a181a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input1/resources.arsc
@@ -0,0 +1 @@
+input1/resources.arsc \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2.apk b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2.apk
new file mode 100644
index 000000000..3003f5ae9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2.apk
Binary files differ
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/asset.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/asset.txt
new file mode 100644
index 000000000..31a0e5129
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/asset.txt
@@ -0,0 +1 @@
+input2/assets/asset.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/omni.ja b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/omni.ja
new file mode 100644
index 000000000..36deb6725
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/assets/omni.ja
@@ -0,0 +1 @@
+input2/assets/omni.ja \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/classes.dex b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/classes.dex
new file mode 100644
index 000000000..99779eb45
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/classes.dex
@@ -0,0 +1 @@
+input2/classes.dex \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/lib/lib.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/lib/lib.txt
new file mode 100644
index 000000000..7a2594a02
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/lib/lib.txt
@@ -0,0 +1 @@
+input2/lib/lib.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/res/res.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/res/res.txt
new file mode 100644
index 000000000..2a52ab524
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/res/res.txt
@@ -0,0 +1 @@
+input2/res/res.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/resources.arsc b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/resources.arsc
new file mode 100644
index 000000000..64f4b77ad
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/resources.arsc
@@ -0,0 +1 @@
+input/resources.arsc \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/root_file.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/root_file.txt
new file mode 100644
index 000000000..9f2f53518
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/input2/root_file.txt
@@ -0,0 +1 @@
+input2/root_file.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/lib/lib.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/lib/lib.txt
new file mode 100644
index 000000000..acbcebb3d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/lib/lib.txt
@@ -0,0 +1 @@
+lib/lib.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/omni.ja b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/omni.ja
new file mode 100644
index 000000000..48c422a3a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/omni.ja
@@ -0,0 +1 @@
+omni.ja \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/root_file.txt b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/root_file.txt
new file mode 100644
index 000000000..89b006da4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/package_fennec_apk/root_file.txt
@@ -0,0 +1 @@
+root_file.txt
diff --git a/python/mozbuild/mozbuild/test/action/data/valid-zh-CN/region.properties b/python/mozbuild/mozbuild/test/action/data/valid-zh-CN/region.properties
new file mode 100644
index 000000000..d4d7fcfee
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/valid-zh-CN/region.properties
@@ -0,0 +1,37 @@
+# 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/.
+
+# Default search engine
+browser.search.defaultenginename=百度
+
+# Search engine order (order displayed in the search bar dropdown)s
+browser.search.order.1=百度
+browser.search.order.2=Google
+
+# This is the default set of web based feed handlers shown in the reader
+# selection UI
+browser.contentHandlers.types.0.title=Bloglines
+browser.contentHandlers.types.0.uri=http://www.bloglines.com/login?r=/sub/%s
+
+# increment this number when anything gets changed in the list below. This will
+# cause Firefox to re-read these prefs and inject any new handlers into the
+# profile database. Note that "new" is defined as "has a different URL"; this
+# means that it's not possible to update the name of existing handler, so
+# don't make any spelling errors here.
+gecko.handlerService.defaultHandlersVersion=3
+
+# The default set of protocol handlers for webcal:
+gecko.handlerService.schemes.webcal.0.name=30 Boxes
+gecko.handlerService.schemes.webcal.0.uriTemplate=https://30boxes.com/external/widget?refer=ff&url=%s
+
+# The default set of protocol handlers for mailto:
+gecko.handlerService.schemes.mailto.0.name=Yahoo! 邮件
+gecko.handlerService.schemes.mailto.0.uriTemplate=https://compose.mail.yahoo.com/?To=%s
+gecko.handlerService.schemes.mailto.1.name=Gmail
+gecko.handlerService.schemes.mailto.1.uriTemplate=https://mail.google.com/mail/?extsrc=mailto&url=%s
+
+# This is the default set of web based feed handlers shown in the reader
+# selection UI
+browser.contentHandlers.types.0.title=My Yahoo!
+browser.contentHandlers.types.0.uri=http://www.bloglines.com/login?r=/sub/%s
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 000000000..9c2631812
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_buildlist.py
@@ -0,0 +1,89 @@
+# 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 os, sys, os.path, time
+from tempfile import mkdtemp
+from shutil import rmtree
+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.assert_(len(l) > 0,
+ "ran out of expected lines! (expected '{0}', got '{1}')"
+ .format(l, lines))
+ self.assertEqual(line, l.pop(0))
+ self.assert_(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_generate_browsersearch.py b/python/mozbuild/mozbuild/test/action/test_generate_browsersearch.py
new file mode 100644
index 000000000..4c7f5635e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_generate_browsersearch.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import unicode_literals
+
+import json
+import os
+import unittest
+
+import mozunit
+
+import mozbuild.action.generate_browsersearch as generate_browsersearch
+
+from mozfile.mozfile import (
+ NamedTemporaryFile,
+ TemporaryDirectory,
+)
+
+import mozpack.path as mozpath
+
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, 'data')
+
+
+class TestGenerateBrowserSearch(unittest.TestCase):
+ """
+ Unit tests for generate_browsersearch.py.
+ """
+
+ def _test_one(self, name):
+ with TemporaryDirectory() as tmpdir:
+ with NamedTemporaryFile(mode='r+') as temp:
+ srcdir = os.path.join(test_data_path, name)
+
+ generate_browsersearch.main([
+ '--silent',
+ '--srcdir', srcdir,
+ temp.name])
+ return json.load(temp)
+
+ def test_valid_unicode(self):
+ o = self._test_one('valid-zh-CN')
+ self.assertEquals(o['default'], '百度')
+ self.assertEquals(o['engines'], ['百度', 'Google'])
+
+ def test_invalid_unicode(self):
+ with self.assertRaises(UnicodeDecodeError):
+ self._test_one('invalid')
+
+
+if __name__ == '__main__':
+ mozunit.main()
diff --git a/python/mozbuild/mozbuild/test/action/test_package_fennec_apk.py b/python/mozbuild/mozbuild/test/action/test_package_fennec_apk.py
new file mode 100644
index 000000000..5b7760836
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_package_fennec_apk.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import unicode_literals
+
+import os
+import unittest
+
+import mozunit
+
+from mozbuild.action.package_fennec_apk import (
+ package_fennec_apk as package,
+)
+from mozpack.mozjar import JarReader
+import mozpack.path as mozpath
+
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, 'data', 'package_fennec_apk')
+
+
+def data(name):
+ return os.path.join(test_data_path, name)
+
+
+class TestPackageFennecAPK(unittest.TestCase):
+ """
+ Unit tests for package_fennec_apk.py.
+ """
+
+ def test_arguments(self):
+ # Language repacks take updated resources from an ap_ and pack them
+ # into an apk. Make sure the second input overrides the first.
+ jarrer = package(inputs=[],
+ omni_ja=data('omni.ja'),
+ classes_dex=data('classes.dex'),
+ assets_dirs=[data('assets')],
+ lib_dirs=[data('lib')],
+ root_files=[data('root_file.txt')])
+
+ # omni.ja ends up in assets/omni.ja.
+ self.assertEquals(jarrer['assets/omni.ja'].open().read().strip(), 'omni.ja')
+
+ # Everything else is in place.
+ for name in ('classes.dex',
+ 'assets/asset.txt',
+ 'lib/lib.txt',
+ 'root_file.txt'):
+ self.assertEquals(jarrer[name].open().read().strip(), name)
+
+ def test_inputs(self):
+ # Language repacks take updated resources from an ap_ and pack them
+ # into an apk. In this case, the first input is the original package,
+ # the second input the update ap_. Make sure the second input
+ # overrides the first.
+ jarrer = package(inputs=[data('input2.apk'), data('input1.ap_')])
+
+ files1 = JarReader(data('input1.ap_')).entries.keys()
+ files2 = JarReader(data('input2.apk')).entries.keys()
+ for name in files2:
+ self.assertTrue(name in files1 or
+ jarrer[name].open().read().startswith('input2/'))
+ for name in files1:
+ self.assertTrue(jarrer[name].open().read().startswith('input1/'))
+
+
+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 000000000..e69de29bb
--- /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 000000000..85ccb1037
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/common.py
@@ -0,0 +1,156 @@
+# 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 __future__ import unicode_literals
+
+import os
+import unittest
+
+from collections import defaultdict
+from shutil import rmtree
+from tempfile import mkdtemp
+
+from mach.logging import LoggingManager
+
+from mozbuild.backend.configenvironment import ConfigEnvironment
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import BuildReader
+
+import mozpack.path as mozpath
+
+
+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': {},
+ 'non_global_defines': [],
+ 'substs': {'OS_TARGET': 'WINNT'},
+}, {
+ 'android_eclipse': {
+ 'defines': {
+ 'MOZ_ANDROID_MIN_SDK_VERSION': '15',
+ },
+ 'non_global_defines': [],
+ 'substs': {
+ 'ANDROID_TARGET_SDK': '16',
+ 'MOZ_WIDGET_TOOLKIT': 'android',
+ },
+ },
+ 'binary-components': {
+ 'defines': {},
+ 'non_global_defines': [],
+ 'substs': {
+ 'LIB_PREFIX': 'lib',
+ 'LIB_SUFFIX': 'a',
+ 'COMPILE_ENVIRONMENT': '1',
+ },
+ },
+ 'sources': {
+ 'defines': {},
+ 'non_global_defines': [],
+ 'substs': {
+ 'LIB_PREFIX': 'lib',
+ 'LIB_SUFFIX': 'a',
+ },
+ },
+ 'stub0': {
+ 'defines': {
+ 'MOZ_TRUE_1': '1',
+ 'MOZ_TRUE_2': '1',
+ },
+ 'non_global_defines': [
+ 'MOZ_NONGLOBAL_1',
+ 'MOZ_NONGLOBAL_2',
+ ],
+ 'substs': {
+ 'MOZ_FOO': 'foo',
+ 'MOZ_BAR': 'bar',
+ },
+ },
+ 'substitute_config_files': {
+ 'defines': {},
+ 'non_global_defines': [],
+ 'substs': {
+ 'MOZ_FOO': 'foo',
+ 'MOZ_BAR': 'bar',
+ },
+ },
+ 'test_config': {
+ 'defines': {
+ 'foo': 'baz qux',
+ 'baz': 1,
+ },
+ 'non_global_defines': [],
+ 'substs': {
+ 'foo': 'bar baz',
+ },
+ },
+ 'visual-studio': {
+ 'defines': {},
+ 'non_global_defines': [],
+ 'substs': {
+ 'MOZ_APP_NAME': 'my_app',
+ },
+ },
+})
+
+
+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]
+
+ objdir = mkdtemp()
+ self.addCleanup(rmtree, objdir)
+
+ srcdir = mozpath.join(test_data_path, name)
+ config['substs']['top_srcdir'] = srcdir
+ 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/android_eclipse/library1/resources/values/strings.xml b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/library1/resources/values/strings.xml
new file mode 100644
index 000000000..a7337c554
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/library1/resources/values/strings.xml
@@ -0,0 +1 @@
+<string name="label">library1</string>
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main1/AndroidManifest.xml b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main1/AndroidManifest.xml
new file mode 100644
index 000000000..7a906454d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main1/AndroidManifest.xml
@@ -0,0 +1 @@
+<!-- Placeholder. -->
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/AndroidManifest.xml b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/AndroidManifest.xml
new file mode 100644
index 000000000..7a906454d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/AndroidManifest.xml
@@ -0,0 +1 @@
+<!-- Placeholder. -->
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/assets/dummy.txt b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/assets/dummy.txt
new file mode 100644
index 000000000..c32a95993
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/assets/dummy.txt
@@ -0,0 +1 @@
+# Placeholder. \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/extra.jar b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/extra.jar
new file mode 100644
index 000000000..c32a95993
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/extra.jar
@@ -0,0 +1 @@
+# Placeholder. \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/res/values/strings.xml b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/res/values/strings.xml
new file mode 100644
index 000000000..0b28bf41e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main2/res/values/strings.xml
@@ -0,0 +1 @@
+<string name="label">main1</string>
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/AndroidManifest.xml b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/AndroidManifest.xml
new file mode 100644
index 000000000..7a906454d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/AndroidManifest.xml
@@ -0,0 +1 @@
+<!-- Placeholder. -->
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/a/A.java b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/a/A.java
new file mode 100644
index 000000000..0ab867d3d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/a/A.java
@@ -0,0 +1 @@
+package a.a;
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/b/B.java b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/b/B.java
new file mode 100644
index 000000000..66eb44c15
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/b/B.java
@@ -0,0 +1 @@
+package b;
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/c/C.java b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/c/C.java
new file mode 100644
index 000000000..ca474ff33
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main3/c/C.java
@@ -0,0 +1 @@
+package d.e;
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main4 b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main4
new file mode 100644
index 000000000..7a906454d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/main4
@@ -0,0 +1 @@
+<!-- Placeholder. -->
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/moz.build b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/moz.build
new file mode 100644
index 000000000..327284c88
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+p = add_android_eclipse_library_project('library1')
+p.package_name = 'org.mozilla.test.library1'
+p.res = 'library1/resources'
+
+p = add_android_eclipse_library_project('library2')
+p.package_name = 'org.mozilla.test.library2'
+
+p = add_android_eclipse_project('main1', 'main1/AndroidManifest.xml')
+p.package_name = 'org.mozilla.test.main1'
+p.recursive_make_targets += ['target1', 'target2']
+
+p = add_android_eclipse_project('main2', 'main2/AndroidManifest.xml')
+p.package_name = 'org.mozilla.test.main2'
+p.res = 'main2/res'
+p.assets = 'main2/assets'
+p.extra_jars = ['main2/extra.jar']
+
+p = add_android_eclipse_project('main3', 'main3/AndroidManifest.xml')
+p.package_name = 'org.mozilla.test.main3'
+cpe = p.add_classpathentry('a', 'main3/a', dstdir='a/a')
+cpe = p.add_classpathentry('b', 'main3/b', dstdir='b')
+cpe.exclude_patterns += ['b/Excludes.java', 'b/Excludes2.java']
+cpe = p.add_classpathentry('c', 'main3/c', dstdir='d/e')
+cpe.ignore_warnings = True
+
+p = add_android_eclipse_project('main4', 'main3/AndroidManifest.xml')
+p.package_name = 'org.mozilla.test.main3'
+p.referenced_projects += ['library1']
+p.included_projects += ['library2']
+p.recursive_make_targets += ['target3', 'target4']
+
+DIRS += ['subdir']
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/moz.build b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/moz.build
new file mode 100644
index 000000000..c75aec456
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/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/
+
+DEFINES['FOO'] = 'FOO'
+
+p = add_android_eclipse_library_project('sublibrary')
+p.package_name = 'org.mozilla.test.sublibrary'
+p.is_library = True
+
+p = add_android_eclipse_project('submain', 'submain/AndroidManifest.xml')
+p.package_name = 'org.mozilla.test.submain'
+p.recursive_make_targets += ['subtarget1', 'subtarget2']
diff --git a/python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/submain/AndroidManifest.xml b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/submain/AndroidManifest.xml
new file mode 100644
index 000000000..7a906454d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/android_eclipse/subdir/submain/AndroidManifest.xml
@@ -0,0 +1 @@
+<!-- Placeholder. -->
diff --git a/python/mozbuild/mozbuild/test/backend/data/binary-components/bar/moz.build b/python/mozbuild/mozbuild/test/backend/data/binary-components/bar/moz.build
new file mode 100644
index 000000000..2946e42aa
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/binary-components/bar/moz.build
@@ -0,0 +1,2 @@
+Component('bar')
+NO_COMPONENTS_MANIFEST = True
diff --git a/python/mozbuild/mozbuild/test/backend/data/binary-components/foo/moz.build b/python/mozbuild/mozbuild/test/backend/data/binary-components/foo/moz.build
new file mode 100644
index 000000000..8611a74be
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/binary-components/foo/moz.build
@@ -0,0 +1 @@
+Component('foo')
diff --git a/python/mozbuild/mozbuild/test/backend/data/binary-components/moz.build b/python/mozbuild/mozbuild/test/backend/data/binary-components/moz.build
new file mode 100644
index 000000000..1776d0514
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/binary-components/moz.build
@@ -0,0 +1,10 @@
+@template
+def Component(name):
+ LIBRARY_NAME = name
+ FORCE_SHARED_LIB = True
+ IS_COMPONENT = True
+
+DIRS += [
+ 'foo',
+ 'bar',
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/branding-files/bar.ico b/python/mozbuild/mozbuild/test/backend/data/branding-files/bar.ico
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/branding-files/bar.ico
diff --git a/python/mozbuild/mozbuild/test/backend/data/branding-files/foo.ico b/python/mozbuild/mozbuild/test/backend/data/branding-files/foo.ico
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/branding-files/foo.ico
diff --git a/python/mozbuild/mozbuild/test/backend/data/branding-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/branding-files/moz.build
new file mode 100644
index 000000000..083f0f82d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/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',
+ 'sub/quux.png',
+]
+
+BRANDING_FILES.icons += [
+ 'foo.ico',
+]
+
diff --git a/python/mozbuild/mozbuild/test/backend/data/branding-files/sub/quux.png b/python/mozbuild/mozbuild/test/backend/data/branding-files/sub/quux.png
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/branding-files/sub/quux.png
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 000000000..8d6218ea9
--- /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 000000000..91dcbe153
--- /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 000000000..1a608e8a5
--- /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 000000000..05db2e2f6
--- /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 000000000..975a1e437
--- /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 000000000..f39ed0208
--- /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 000000000..b5bb87254
--- /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 000000000..1803d6c57
--- /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 000000000..c93c9d765
--- /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 000000000..4fa71e2d2
--- /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 000000000..d58fd61c1
--- /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 000000000..393055c4e
--- /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 000000000..b0b0cabd1
--- /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 000000000..a030da9fd
--- /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 000000000..3ce157eb6
--- /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 000000000..9c5fe28d5
--- /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 000000000..91e75c679
--- /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 000000000..b7c270096
--- /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 000000000..80c887a84
--- /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/defines/moz.build b/python/mozbuild/mozbuild/test/backend/data/defines/moz.build
new file mode 100644
index 000000000..be4b31143
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/defines/moz.build
@@ -0,0 +1,14 @@
+# 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.update({
+ 'BAR': 7,
+ 'VALUE': value,
+ 'QUX': False,
+})
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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..cbd2c942b
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..b604ef1a0
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..725fa1fd4
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/exports/pprio.h
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 000000000..c926e3788
--- /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 000000000..8dcf066a4
--- /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 000000000..1d746eea5
--- /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 000000000..280299475
--- /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 000000000..54bc30fec
--- /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/foo-data b/python/mozbuild/mozbuild/test/backend/data/generated-files/foo-data
new file mode 100644
index 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..1fa389f51
--- /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.c', 'quux.c' ]
+
+bar = GENERATED_FILES['bar.c']
+bar.script = 'generate-bar.py:baz'
+
+foo = GENERATED_FILES['foo.c']
+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 000000000..14deaf8cf
--- /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 000000000..30f8c160f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/host-defines/moz.build
@@ -0,0 +1,14 @@
+# 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.update({
+ 'BAR': 7,
+ 'VALUE': value,
+ 'QUX': False,
+})
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 000000000..dbadef914
--- /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 000000000..da287dfca
--- /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 000000000..c2ef44079
--- /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 000000000..f189212fd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/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/.
+
+IPDL_SOURCES += [
+ 'bar.ipdl',
+ 'bar2.ipdlh',
+]
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 000000000..4e1554559
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/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/.
+
+IPDL_SOURCES += [
+ 'foo.ipdl',
+ 'foo2.ipdlh',
+]
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 000000000..03cf5e236
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/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/backend/data/jar-manifests/moz.build b/python/mozbuild/mozbuild/test/backend/data/jar-manifests/moz.build
new file mode 100644
index 000000000..7daa419f1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/jar-manifests/moz.build
@@ -0,0 +1,8 @@
+# -*- 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/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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..565c2bee6
--- /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/resources/bar.res.in b/python/mozbuild/mozbuild/test/backend/data/resources/bar.res.in
new file mode 100644
index 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..a5771c808
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/resources/test.manifest
diff --git a/python/mozbuild/mozbuild/test/backend/data/sdk-files/bar.ico b/python/mozbuild/mozbuild/test/backend/data/sdk-files/bar.ico
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sdk-files/bar.ico
diff --git a/python/mozbuild/mozbuild/test/backend/data/sdk-files/foo.ico b/python/mozbuild/mozbuild/test/backend/data/sdk-files/foo.ico
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sdk-files/foo.ico
diff --git a/python/mozbuild/mozbuild/test/backend/data/sdk-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/sdk-files/moz.build
new file mode 100644
index 000000000..342987741
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sdk-files/moz.build
@@ -0,0 +1,11 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SDK_FILES += [
+ 'bar.ico',
+ 'sub/quux.png',
+]
+
+SDK_FILES.icons += [
+ 'foo.ico',
+]
diff --git a/python/mozbuild/mozbuild/test/backend/data/sdk-files/sub/quux.png b/python/mozbuild/mozbuild/test/backend/data/sdk-files/sub/quux.png
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sdk-files/sub/quux.png
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/bar.c b/python/mozbuild/mozbuild/test/backend/data/sources/bar.c
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/bar.c
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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/bar.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/bar.mm b/python/mozbuild/mozbuild/test/backend/data/sources/bar.mm
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/bar.mm
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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/bar.s
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/baz.S b/python/mozbuild/mozbuild/test/backend/data/sources/baz.S
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/baz.S
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/foo.S b/python/mozbuild/mozbuild/test/backend/data/sources/foo.S
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/foo.S
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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/foo.asm
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/foo.c b/python/mozbuild/mozbuild/test/backend/data/sources/foo.c
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/foo.c
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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/foo.cpp
diff --git a/python/mozbuild/mozbuild/test/backend/data/sources/foo.mm b/python/mozbuild/mozbuild/test/backend/data/sources/foo.mm
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/foo.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 000000000..d31acae3d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/sources/moz.build
@@ -0,0 +1,21 @@
+# -*- 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 += ['bar.c', 'foo.c']
+
+SOURCES += ['bar.c', 'foo.c']
+
+SOURCES += ['bar.mm', 'foo.mm']
+
+SOURCES += ['baz.S', 'foo.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 000000000..02ff0a3f9
--- /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 000000000..17c147d97
--- /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 000000000..041381548
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir1/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/
+
+
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 000000000..32a37fe46
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir2/moz.build
@@ -0,0 +1,4 @@
+# -*- 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 000000000..17c147d97
--- /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 000000000..32a37fe46
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/stub0/dir3/moz.build
@@ -0,0 +1,4 @@
+# -*- 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 000000000..0d92bb7c3
--- /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 000000000..e69de29bb
--- /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 000000000..5331f1f05
--- /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 000000000..01545c250
--- /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 000000000..e69de29bb
--- /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 000000000..4f1335d6b
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..a9860f3de
--- /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 000000000..1c1d064ea
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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-duplicate-support-files/mochitest1.ini b/python/mozbuild/mozbuild/test/backend/data/test-manifests-duplicate-support-files/mochitest1.ini
new file mode 100644
index 000000000..1f9816a89
--- /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 000000000..e2a2fc96a
--- /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 000000000..d10500f8d
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..03d4f794e
--- /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 000000000..009b2b223
--- /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 000000000..e69de29bb
--- /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 000000000..82dba29dc
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..0cddad8ba
--- /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 000000000..81869e1fa
--- /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 000000000..e69de29bb
--- /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 000000000..d004cdd0f
--- /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 000000000..f6a5351e9
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js
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 000000000..07aa30deb
--- /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 000000000..f0c357aaf
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/Makefile.in
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 000000000..36a2603b1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+NO_VISIBILITY_FLAGS = True
+
+DELAYLOAD_DLLS = ['foo.dll', 'bar.dll']
+
+RCFILE = 'foo.rc'
+RESFILE = 'bar.res'
+RCINCLUDE = 'bar.rc'
+DEFFILE = 'baz.def'
+
+CFLAGS += ['-fno-exceptions', '-w']
+CXXFLAGS += ['-fcxx-exceptions', '-option with spaces']
+LDFLAGS += ['-ld flag with spaces', '-x']
+HOST_CFLAGS += ['-funroll-loops', '-wall']
+HOST_CXXFLAGS += ['-funroll-loops-harder', '-wall-day-everyday']
+WIN32_EXE_LDFLAGS += ['-subsystem:console']
+
+DISABLE_STL_WRAPPING = True
+
+ALLOW_COMPILER_WARNINGS = True
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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..b77e67ade
--- /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 000000000..d339b48c4
--- /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/config/makefiles/xpidl/Makefile.in b/python/mozbuild/mozbuild/test/backend/data/xpidl/config/makefiles/xpidl/Makefile.in
new file mode 100644
index 000000000..e69de29bb
--- /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/moz.build b/python/mozbuild/mozbuild/test/backend/data/xpidl/moz.build
new file mode 100644
index 000000000..d49efde26
--- /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_android_eclipse.py b/python/mozbuild/mozbuild/test/backend/test_android_eclipse.py
new file mode 100644
index 000000000..c4e9221c9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_android_eclipse.py
@@ -0,0 +1,153 @@
+# 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 __future__ import unicode_literals
+
+import json
+import os
+import unittest
+
+from mozbuild.backend.android_eclipse import AndroidEclipseBackend
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import BuildReader
+from mozbuild.test.backend.common import BackendTester
+from mozpack.manifests import InstallManifest
+from mozunit import main
+
+import mozpack.path as mozpath
+
+class TestAndroidEclipseBackend(BackendTester):
+ def __init__(self, *args, **kwargs):
+ BackendTester.__init__(self, *args, **kwargs)
+ self.env = None
+
+ def assertExists(self, *args):
+ p = mozpath.join(self.env.topobjdir, 'android_eclipse', *args)
+ self.assertTrue(os.path.exists(p), "Path %s exists" % p)
+
+ def assertNotExists(self, *args):
+ p = mozpath.join(self.env.topobjdir, 'android_eclipse', *args)
+ self.assertFalse(os.path.exists(p), "Path %s does not exist" % p)
+
+ def test_library_project_files(self):
+ """Ensure we generate reasonable files for library projects."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ for f in ['.classpath',
+ '.project',
+ '.settings',
+ 'AndroidManifest.xml',
+ 'project.properties']:
+ self.assertExists('library1', f)
+
+ def test_main_project_files(self):
+ """Ensure we generate reasonable files for main (non-library) projects."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ for f in ['.classpath',
+ '.project',
+ '.settings',
+ 'gen',
+ 'lint.xml',
+ 'project.properties']:
+ self.assertExists('main1', f)
+
+ def test_library_manifest(self):
+ """Ensure we generate manifest for library projects."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertExists('library1', 'AndroidManifest.xml')
+
+ def test_classpathentries(self):
+ """Ensure we produce reasonable classpathentries."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertExists('main3', '.classpath')
+ # This is brittle but simple.
+ with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main3', '.classpath'), 'rt') as fh:
+ lines = fh.readlines()
+ lines = [line.strip() for line in lines]
+ self.assertIn('<classpathentry including="**/*.java" kind="src" path="a" />', lines)
+ self.assertIn('<classpathentry excluding="b/Excludes.java|b/Excludes2.java" including="**/*.java" kind="src" path="b" />', lines)
+ self.assertIn('<classpathentry including="**/*.java" kind="src" path="c"><attributes><attribute name="ignore_optional_problems" value="true" /></attributes></classpathentry>', lines)
+
+ def test_library_project_setting(self):
+ """Ensure we declare a library project correctly."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+
+ self.assertExists('library1', 'project.properties')
+ with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'library1', 'project.properties'), 'rt') as fh:
+ lines = fh.readlines()
+ lines = [line.strip() for line in lines]
+ self.assertIn('android.library=true', lines)
+
+ self.assertExists('main1', 'project.properties')
+ with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main1', 'project.properties'), 'rt') as fh:
+ lines = fh.readlines()
+ lines = [line.strip() for line in lines]
+ self.assertNotIn('android.library=true', lines)
+
+ def test_referenced_projects(self):
+ """Ensure we reference another project correctly."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertExists('main4', '.classpath')
+ # This is brittle but simple.
+ with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main4', '.classpath'), 'rt') as fh:
+ lines = fh.readlines()
+ lines = [line.strip() for line in lines]
+ self.assertIn('<classpathentry combineaccessrules="false" kind="src" path="/library1" />', lines)
+
+ def test_extra_jars(self):
+ """Ensure we add class path entries to extra jars iff asked to."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertExists('main2', '.classpath')
+ # This is brittle but simple.
+ with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main2', '.classpath'), 'rt') as fh:
+ lines = fh.readlines()
+ lines = [line.strip() for line in lines]
+ self.assertIn('<classpathentry exported="true" kind="lib" path="%s/main2/extra.jar" />' % self.env.topsrcdir, lines)
+
+ def test_included_projects(self):
+ """Ensure we include another project correctly."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertExists('main4', 'project.properties')
+ # This is brittle but simple.
+ with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main4', 'project.properties'), 'rt') as fh:
+ lines = fh.readlines()
+ lines = [line.strip() for line in lines]
+ self.assertIn('android.library.reference.1=library2', lines)
+
+ def assertInManifest(self, project_name, *args):
+ manifest_path = mozpath.join(self.env.topobjdir, 'android_eclipse', '%s.manifest' % project_name)
+ manifest = InstallManifest(manifest_path)
+ for arg in args:
+ self.assertIn(arg, manifest, '%s in manifest for project %s' % (arg, project_name))
+
+ def assertNotInManifest(self, project_name, *args):
+ manifest_path = mozpath.join(self.env.topobjdir, 'android_eclipse', '%s.manifest' % project_name)
+ manifest = InstallManifest(manifest_path)
+ for arg in args:
+ self.assertNotIn(arg, manifest, '%s not in manifest for project %s' % (arg, project_name))
+
+ def test_manifest_main_manifest(self):
+ """Ensure we symlink manifest if asked to for main projects."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertInManifest('main1', 'AndroidManifest.xml')
+
+ def test_manifest_res(self):
+ """Ensure we symlink res/ iff asked to."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertInManifest('library1', 'res')
+ self.assertNotInManifest('library2', 'res')
+
+ def test_manifest_classpathentries(self):
+ """Ensure we symlink classpathentries correctly."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertInManifest('main3', 'a/a', 'b', 'd/e')
+
+ def test_manifest_assets(self):
+ """Ensure we symlink assets/ iff asked to."""
+ self.env = self._consume('android_eclipse', AndroidEclipseBackend)
+ self.assertNotInManifest('main1', 'assets')
+ self.assertInManifest('main2', 'assets')
+
+
+if __name__ == '__main__':
+ main()
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 000000000..d3f5fb6a9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_build.py
@@ -0,0 +1,233 @@
+# 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 __future__ import unicode_literals, print_function
+
+import buildconfig
+import os
+import shutil
+import sys
+import unittest
+import mozpack.path as mozpath
+from contextlib import contextmanager
+from mozunit import main
+from mozbuild.backend import get_backend_class
+from mozbuild.backend.configenvironment import ConfigEnvironment
+from mozbuild.backend.recursivemake import RecursiveMakeBackend
+from mozbuild.backend.fastermake import FasterMakeBackend
+from mozbuild.base import MozbuildObject
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import BuildReader
+from mozbuild.util import ensureParentDir
+from mozpack.files import FileFinder
+from tempfile import mkdtemp
+
+
+BASE_SUBSTS = [
+ ('PYTHON', mozpath.normsep(sys.executable)),
+]
+
+
+class TestBuild(unittest.TestCase):
+ def setUp(self):
+ self._old_env = dict(os.environ)
+ os.environ.pop('MOZCONFIG', None)
+ os.environ.pop('MOZ_OBJDIR', None)
+
+ def tearDown(self):
+ os.environ.clear()
+ os.environ.update(self._old_env)
+
+ @contextmanager
+ def do_test_backend(self, *backends, **kwargs):
+ topobjdir = mkdtemp()
+ 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:
+ 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:
+ 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)
+ 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)
+ 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)
+ 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 = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ 'data', 'build') + os.sep
+
+ # We want unicode instances out of the files, because having plain str
+ # makes assertEqual diff output in case of error extra verbose because
+ # of the difference in type.
+ result = {
+ p: f.open().read().decode('utf-8')
+ 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 "%sbaz.jsm"\nbaz.jsm: FOO is foo\n' % (test_path),
+ 'bin/chrome/foo/child/hoge.js':
+ '//@line 2 "%sbar.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 "%sbar.js"\nbar.js: FOO is foo\n' % (test_path),
+ 'bin/components/components.manifest':
+ 'component {foo} foo.js\ncomponent {bar} bar.js\n',
+ '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 "%sbaz.jsm"\nbaz.jsm: FOO is foo\n' % (test_path),
+ 'bin/modules/child/bar.jsm': 'bar.jsm\n',
+ 'bin/modules/child2/qux.jsm':
+ '//@line 4 "%squx.jsm"\nqux.jsm: BAR is not defined\n'
+ % (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 "%sbaz.jsm"\nbaz.jsm: FOO is bar\n' % (test_path),
+ 'bin/app/chrome/foo/child/hoge.js':
+ '//@line 2 "%sbar.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 "%sbar.js"\nbar.js: FOO is bar\n' % (test_path),
+ 'bin/app/components/components.manifest':
+ 'component {foo} foo.js\ncomponent {bar} bar.js\n',
+ '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 "%sbaz.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 "%squx.jsm"\nqux.jsm: BAR is defined\n'
+ % (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 000000000..95593e186
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_configenvironment.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, posixpath
+from StringIO import StringIO
+import unittest
+from mozunit import main, MockedOpen
+
+import mozbuild.backend.configenvironment as ConfigStatus
+
+from mozbuild.util import ReadOnlyDict
+
+import mozpack.path as mozpath
+
+
+class ConfigEnvironment(ConfigStatus.ConfigEnvironment):
+ def __init__(self, *args, **kwargs):
+ ConfigStatus.ConfigEnvironment.__init__(self, *args, **kwargs)
+ # Be helpful to unit tests
+ if not 'top_srcdir' 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)
+
+ d = dict(self.substs_unicode)
+ d[u'top_srcdir'] = top_srcdir.decode('utf-8')
+ self.substs_unicode = 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", 'extra': 'foobar' },
+ non_global_defines = ['extra', 'ignore'],
+ substs = { 'FOO': 'bar', 'FOOBAR': '', 'ABC': 'def',
+ 'bar': 'baz qux', 'zzz': '"abc def"',
+ 'qux': '' })
+ # non_global_defines should be filtered out in ACDEFINES.
+ # 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_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
new file mode 100644
index 000000000..87f50f497
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -0,0 +1,942 @@
+# 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 __future__ import unicode_literals
+
+import cPickle as pickle
+import json
+import os
+import unittest
+
+from mozpack.manifests import (
+ InstallManifest,
+)
+from mozunit import main
+
+from mozbuild.backend.recursivemake import (
+ RecursiveMakeBackend,
+ RecursiveMakeTraversal,
+)
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import BuildReader
+
+from mozbuild.test.backend.common import BackendTester
+
+import mozpack.path as mozpath
+
+
+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',),
+ })
+
+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,
+ 'VPATH := %s' % env.topsrcdir,
+ '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 = {
+ 'ALLOW_COMPILER_WARNINGS': [
+ 'ALLOW_COMPILER_WARNINGS := 1',
+ ],
+ 'DISABLE_STL_WRAPPING': [
+ 'DISABLE_STL_WRAPPING := 1',
+ ],
+ 'VISIBILITY_FLAGS': [
+ 'VISIBILITY_FLAGS :=',
+ ],
+ 'RCFILE': [
+ 'RCFILE := foo.rc',
+ ],
+ 'RESFILE': [
+ 'RESFILE := bar.res',
+ ],
+ 'RCINCLUDE': [
+ 'RCINCLUDE := bar.rc',
+ ],
+ 'DEFFILE': [
+ 'DEFFILE := baz.def',
+ ],
+ 'MOZBUILD_CFLAGS': [
+ 'MOZBUILD_CFLAGS += -fno-exceptions',
+ 'MOZBUILD_CFLAGS += -w',
+ ],
+ 'MOZBUILD_CXXFLAGS': [
+ 'MOZBUILD_CXXFLAGS += -fcxx-exceptions',
+ "MOZBUILD_CXXFLAGS += '-option with spaces'",
+ ],
+ 'MOZBUILD_LDFLAGS': [
+ "MOZBUILD_LDFLAGS += '-ld flag with spaces'",
+ 'MOZBUILD_LDFLAGS += -x',
+ 'MOZBUILD_LDFLAGS += -DELAYLOAD:foo.dll',
+ 'MOZBUILD_LDFLAGS += -DELAYLOAD:bar.dll',
+ ],
+ 'MOZBUILD_HOST_CFLAGS': [
+ 'MOZBUILD_HOST_CFLAGS += -funroll-loops',
+ 'MOZBUILD_HOST_CFLAGS += -wall',
+ ],
+ 'MOZBUILD_HOST_CXXFLAGS': [
+ 'MOZBUILD_HOST_CXXFLAGS += -funroll-loops-harder',
+ 'MOZBUILD_HOST_CXXFLAGS += -wall-day-everyday',
+ ],
+ '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 and HOST_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 += bar.s',
+ 'ASFILES += foo.asm',
+ ],
+ 'CMMSRCS': [
+ 'CMMSRCS += bar.mm',
+ 'CMMSRCS += foo.mm',
+ ],
+ 'CSRCS': [
+ 'CSRCS += bar.c',
+ 'CSRCS += foo.c',
+ ],
+ 'HOST_CPPSRCS': [
+ 'HOST_CPPSRCS += bar.cpp',
+ 'HOST_CPPSRCS += foo.cpp',
+ ],
+ 'HOST_CSRCS': [
+ 'HOST_CSRCS += bar.c',
+ 'HOST_CSRCS += foo.c',
+ ],
+ 'SSRCS': [
+ 'SSRCS += baz.S',
+ 'SSRCS += foo.S',
+ ],
+ }
+
+ 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 = [
+ 'export:: bar.c',
+ 'GARBAGE += bar.c',
+ 'EXTRA_MDDEPEND_FILES += bar.c.pp',
+ 'bar.c: %s/generate-bar.py' % env.topsrcdir,
+ '$(REPORT_BUILD)',
+ '$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp)' % env.topsrcdir,
+ '',
+ 'export:: foo.c',
+ 'GARBAGE += foo.c',
+ 'EXTRA_MDDEPEND_FILES += foo.c.pp',
+ 'foo.c: %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 $(srcdir)/foo-data)' % (env.topsrcdir),
+ '',
+ 'export:: quux.c',
+ 'GARBAGE += quux.c',
+ 'EXTRA_MDDEPEND_FILES += quux.c.pp',
+ ]
+
+ 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 = [
+ 'export:: bar.h',
+ 'GARBAGE += bar.h',
+ 'EXTRA_MDDEPEND_FILES += bar.h.pp',
+ 'export:: mozilla2.h',
+ 'GARBAGE += mozilla2.h',
+ 'EXTRA_MDDEPEND_FILES += mozilla2.h.pp',
+ 'export:: dom2.h',
+ 'GARBAGE += dom2.h',
+ 'EXTRA_MDDEPEND_FILES += dom2.h.pp',
+ 'export:: dom3.h',
+ 'GARBAGE += dom3.h',
+ 'EXTRA_MDDEPEND_FILES += dom3.h.pp',
+ '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_branding_files(self):
+ """Ensure BRANDING_FILES is handled properly."""
+ env = self._consume('branding-files', RecursiveMakeBackend)
+
+ #BRANDING_FILES should appear in the dist_branding install manifest.
+ m = InstallManifest(path=os.path.join(env.topobjdir,
+ '_build_manifests', 'install', 'dist_branding'))
+ self.assertEqual(len(m), 3)
+ self.assertIn('bar.ico', m)
+ self.assertIn('quux.png', m)
+ self.assertIn('icons/foo.ico', m)
+
+ def test_sdk_files(self):
+ """Ensure SDK_FILES is handled properly."""
+ env = self._consume('sdk-files', RecursiveMakeBackend)
+
+ #SDK_FILES should appear in the dist_sdk install manifest.
+ m = InstallManifest(path=os.path.join(env.topobjdir,
+ '_build_manifests', 'install', 'dist_sdk'))
+ self.assertEqual(len(m), 3)
+ self.assertIn('bar.ico', m)
+ self.assertIn('quux.png', m)
+ self.assertIn('icons/foo.ico', 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]',
+ ])
+
+ 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_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)
+ 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, 'r') 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)
+
+ test_files_manifest = mozpath.join(env.topobjdir,
+ '_build_manifests',
+ 'install',
+ '_test_files')
+
+ # First, read the generated for ini manifest contents.
+ 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.
+ synthesized_manifest = InstallManifest()
+ for item, installs in test_installs.items():
+ for install_info in installs:
+ if len(install_info) == 3:
+ synthesized_manifest.add_pattern_symlink(*install_info)
+ if len(install_info) == 2:
+ synthesized_manifest.add_symlink(*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, 'dist_idl')))
+ self.assertTrue(os.path.isfile(mozpath.join(install_dir, 'xpidl')))
+
+ m = InstallManifest(path=mozpath.join(install_dir, 'dist_idl'))
+ self.assertEqual(len(m), 2)
+ self.assertIn('bar.idl', m)
+ self.assertIn('foo.idl', m)
+
+ m = InstallManifest(path=mozpath.join(install_dir, 'xpidl'))
+ self.assertIn('.deps/my_module.pp', m)
+
+ m = InstallManifest(path=os.path.join(install_dir, 'dist_bin'))
+ self.assertIn('components/my_module.xpt', m)
+ self.assertIn('components/interfaces.manifest', 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_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_symlink(__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 IPDL_SOURCES are written to ipdlsrcs.mk correctly."""
+ env = self._consume('ipdl_sources', RecursiveMakeBackend)
+
+ manifest_path = mozpath.join(env.topobjdir,
+ 'ipc', 'ipdl', 'ipdlsrcs.mk')
+ lines = [l.strip() for l in open(manifest_path, 'rt').readlines()]
+
+ # Handle Windows paths correctly
+ topsrcdir = env.topsrcdir.replace(os.sep, '/')
+
+ expected = [
+ "ALL_IPDLSRCS := %s/bar/bar.ipdl %s/bar/bar2.ipdlh %s/foo/foo.ipdl %s/foo/foo2.ipdlh" % tuple([topsrcdir] * 4),
+ "CPPSRCS := UnifiedProtocols0.cpp",
+ "IPDLDIRS := %s/bar %s/foo" % (topsrcdir, topsrcdir),
+ ]
+
+ found = [str for str in lines if str.startswith(('ALL_IPDLSRCS',
+ 'CPPSRCS',
+ 'IPDLDIRS'))]
+ 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_host_defines(self):
+ """Test that HOST_DEFINES are written to backend.mk correctly."""
+ env = self._consume('host-defines', RecursiveMakeBackend)
+
+ backend_path = mozpath.join(env.topobjdir, 'backend.mk')
+ lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
+
+ var = 'HOST_DEFINES'
+ defines = [val for val in lines if val.startswith(var)]
+
+ expected = ['HOST_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:]]
+
+ topobjdir = env.topobjdir.replace('\\', '/')
+
+ 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_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:%=/%)"
+ 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 expected.iteritems():
+ 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_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_jar_manifests(self):
+ env = self._consume('jar-manifests', RecursiveMakeBackend)
+
+ with open(os.path.join(env.topobjdir, 'backend.mk'), 'rb') 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_android_eclipse(self):
+ env = self._consume('android_eclipse', RecursiveMakeBackend)
+
+ with open(mozpath.join(env.topobjdir, 'backend.mk'), 'rb') as fh:
+ lines = fh.readlines()
+
+ lines = [line.rstrip() for line in lines]
+
+ # Dependencies first.
+ self.assertIn('ANDROID_ECLIPSE_PROJECT_main1: target1 target2', lines)
+ self.assertIn('ANDROID_ECLIPSE_PROJECT_main4: target3 target4', lines)
+
+ command_template = '\t$(call py_action,process_install_manifest,' + \
+ '--no-remove --no-remove-all-directory-symlinks ' + \
+ '--no-remove-empty-directories %s %s.manifest)'
+ # Commands second.
+ for project_name in ['main1', 'main2', 'library1', 'library2']:
+ stem = '%s/android_eclipse/%s' % (env.topobjdir, project_name)
+ self.assertIn(command_template % (stem, stem), lines)
+
+ # Projects declared in subdirectories.
+ with open(mozpath.join(env.topobjdir, 'subdir', 'backend.mk'), 'rb') as fh:
+ lines = fh.readlines()
+
+ lines = [line.rstrip() for line in lines]
+
+ self.assertIn('ANDROID_ECLIPSE_PROJECT_submain: subtarget1 subtarget2', lines)
+
+ for project_name in ['submain', 'sublibrary']:
+ # Destination and install manifest are relative to topobjdir.
+ stem = '%s/android_eclipse/%s' % (env.topobjdir, project_name)
+ self.assertIn(command_template % (stem, stem), lines)
+
+ def test_install_manifests_package_tests(self):
+ """Ensure test suites honor package_tests=False."""
+ env = self._consume('test-manifests-package-tests', RecursiveMakeBackend)
+
+ 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('mochitest.js', o)
+ self.assertIn('not_packaged.java', o)
+
+ 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_binary_components(self):
+ """Ensure binary components are correctly handled."""
+ env = self._consume('binary-components', RecursiveMakeBackend)
+
+ with open(mozpath.join(env.topobjdir, 'foo', 'backend.mk')) as fh:
+ lines = fh.readlines()[2:]
+
+ self.assertEqual(lines, [
+ 'misc::\n',
+ '\t$(call py_action,buildlist,$(DEPTH)/dist/bin/chrome.manifest '
+ + "'manifest components/components.manifest')\n",
+ '\t$(call py_action,buildlist,'
+ + '$(DEPTH)/dist/bin/components/components.manifest '
+ + "'binary-component foo')\n",
+ 'LIBRARY_NAME := foo\n',
+ 'FORCE_SHARED_LIB := 1\n',
+ 'IMPORT_LIBRARY := foo\n',
+ 'SHARED_LIBRARY := foo\n',
+ 'IS_COMPONENT := 1\n',
+ 'DSO_SONAME := foo\n',
+ 'LIB_IS_C_ONLY := 1\n',
+ ])
+
+ with open(mozpath.join(env.topobjdir, 'bar', 'backend.mk')) as fh:
+ lines = fh.readlines()[2:]
+
+ self.assertEqual(lines, [
+ 'LIBRARY_NAME := bar\n',
+ 'FORCE_SHARED_LIB := 1\n',
+ 'IMPORT_LIBRARY := bar\n',
+ 'SHARED_LIBRARY := bar\n',
+ 'IS_COMPONENT := 1\n',
+ 'DSO_SONAME := bar\n',
+ 'LIB_IS_C_ONLY := 1\n',
+ ])
+
+ self.assertTrue(os.path.exists(mozpath.join(env.topobjdir, 'binaries.json')))
+ with open(mozpath.join(env.topobjdir, 'binaries.json'), 'rb') as fh:
+ binaries = json.load(fh)
+
+ self.assertEqual(binaries, {
+ 'programs': [],
+ 'shared_libraries': [
+ {
+ 'basename': 'foo',
+ 'import_name': 'foo',
+ 'install_target': 'dist/bin',
+ 'lib_name': 'foo',
+ 'relobjdir': 'foo',
+ 'soname': 'foo',
+ },
+ {
+ 'basename': 'bar',
+ 'import_name': 'bar',
+ 'install_target': 'dist/bin',
+ 'lib_name': 'bar',
+ 'relobjdir': 'bar',
+ 'soname': 'bar',
+ }
+ ],
+ })
+
+
+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 000000000..bfc95e552
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_visualstudio.py
@@ -0,0 +1,64 @@
+# 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 __future__ import unicode_literals
+
+from xml.dom.minidom import parse
+import os
+import unittest
+
+from mozbuild.backend.visualstudio import VisualStudioBackend
+from mozbuild.test.backend.common import BackendTester
+
+from mozunit import main
+
+
+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/common.py b/python/mozbuild/mozbuild/test/common.py
new file mode 100644
index 000000000..76a39b313
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/common.py
@@ -0,0 +1,50 @@
+# 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 __future__ import unicode_literals
+
+from mach.logging import LoggingManager
+
+from mozbuild.util import ReadOnlyDict
+
+import mozpack.path as mozpath
+
+
+# By including this module, tests get structured logging.
+log_manager = LoggingManager()
+log_manager.add_terminal_logging()
+
+# 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.substs_unicode = ReadOnlyDict({k.decode('utf-8'): v.decode('utf-8',
+ 'replace') for k, v in self.substs.items()})
+
+ self.defines = self.substs
+
+ self.external_source_dir = None
+ 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 000000000..e69de29bb
--- /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 000000000..cd2406dfc
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/compilation/test_warnings.py
@@ -0,0 +1,241 @@
+# 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 mozbuild.compilation.warnings import CompilerWarning
+from mozbuild.compilation.warnings import WarningsCollector
+from mozbuild.compilation.warnings import WarningsDatabase
+
+from mozunit import main
+
+CLANG_TESTS = [
+ ('foobar.cpp:123:10: warning: you messed up [-Wfoo]',
+ 'foobar.cpp', 123, 10, 'you messed up', '-Wfoo'),
+ ("c_locale_dummy.c:457:1: warning: (near initialization for "
+ "'full_wmonthname[0]') [-Wpointer-sign]",
+ 'c_locale_dummy.c', 457, 1,
+ "(near initialization for 'full_wmonthname[0]')", '-Wpointer-sign')
+]
+
+MSVC_TESTS = [
+ ("C:/mozilla-central/test/foo.cpp(793) : warning C4244: 'return' : "
+ "conversion from 'double' to 'uint32_t', possible loss of data",
+ 'C:/mozilla-central/test/foo.cpp', 793, 'C4244',
+ "'return' : conversion from 'double' to 'uint32_t', possible loss of "
+ 'data')
+]
+
+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 TestWarningsParsing(unittest.TestCase):
+ def test_clang_parsing(self):
+ for source, filename, line, column, message, flag in CLANG_TESTS:
+ collector = WarningsCollector(resolve_files=False)
+ 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['message'], message)
+ self.assertEqual(warning['flag'], flag)
+
+ def test_msvc_parsing(self):
+ for source, filename, line, flag, message in MSVC_TESTS:
+ collector = WarningsCollector(resolve_files=False)
+ warning = collector.process_line(source)
+
+ self.assertIsNotNone(warning)
+
+ self.assertEqual(warning['filename'], os.path.normpath(filename))
+ self.assertEqual(warning['line'], line)
+ self.assertEqual(warning['flag'], flag)
+ self.assertEqual(warning['message'], message)
+
+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 000000000..089d61a0d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/common.py
@@ -0,0 +1,279 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import copy
+import errno
+import os
+import subprocess
+import sys
+import tempfile
+import unittest
+
+from mozbuild.configure import ConfigureSandbox
+from mozbuild.util import ReadOnlyNamespace
+from mozpack import path as mozpath
+
+from StringIO import StringIO
+from which import WhichError
+
+from buildconfig import (
+ topobjdir,
+ topsrcdir,
+)
+
+
+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 exists(self, path):
+ path = mozpath.abspath(path)
+ if path in self._paths:
+ return True
+ if mozpath.basedir(path, [topsrcdir, topobjdir]):
+ return os.path.exists(path)
+ return False
+
+ def isfile(self, path):
+ path = mozpath.abspath(path)
+ if path in self._paths:
+ return True
+ if mozpath.basedir(path, [topsrcdir, topobjdir]):
+ return os.path.isfile(path)
+ return False
+
+
+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 paths.iteritems() if v
+ }
+
+ paths = paths.keys()
+
+ environ = dict(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._environ = copy.copy(environ)
+
+ 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__)
+
+ self.imported_os = ReadOnlyNamespace(path=ReadOnlyNamespace(**os_path))
+
+ super(ConfigureTestSandbox, self).__init__(config, environ, *args,
+ **kwargs)
+
+ def _get_one_import(self, what):
+ if what == 'which.which':
+ return self.which
+
+ if what == 'which':
+ return ReadOnlyNamespace(
+ which=self.which,
+ WhichError=WhichError,
+ )
+
+ if what == 'subprocess.Popen':
+ return self.Popen
+
+ if what == 'subprocess':
+ return ReadOnlyNamespace(
+ CalledProcessError=subprocess.CalledProcessError,
+ check_output=self.check_output,
+ PIPE=subprocess.PIPE,
+ STDOUT=subprocess.STDOUT,
+ Popen=self.Popen,
+ )
+
+ if what == 'os.environ':
+ return self._environ
+
+ if what == 'ctypes.wintypes':
+ return ReadOnlyNamespace(
+ LPCWSTR=0,
+ LPWSTR=1,
+ DWORD=2,
+ )
+
+ if what == 'ctypes':
+ 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),
+ )
+ ),
+ )
+
+ if what == '_winreg':
+ def OpenKey(*args, **kwargs):
+ raise WindowsError()
+
+ return ReadOnlyNamespace(
+ HKEY_LOCAL_MACHINE=0,
+ OpenKey=OpenKey,
+ )
+
+ return super(ConfigureTestSandbox, self)._get_one_import(what)
+
+ 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, path=None):
+ 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
+ raise WhichError()
+
+ def Popen(self, args, stdin=None, stdout=None, stderr=None, **kargs):
+ try:
+ program = self.which(args[0])
+ except WhichError:
+ 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'
+
+
+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):
+ 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()
+ os.write(fh, 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 = ConfigureTestSandbox(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 000000000..e5e41c68a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/decorators.configure
@@ -0,0 +1,44 @@
+# -*- 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 000000000..e69de29bb
--- /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 000000000..43fbf7c5d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/extra.configure
@@ -0,0 +1,13 @@
+# -*- 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 000000000..ad05e383c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure
@@ -0,0 +1,32 @@
+# -*- 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 000000000..2ad1506ef
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.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/.
+
+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 000000000..72b88d7b5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure
@@ -0,0 +1,31 @@
+# -*- 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 000000000..ca8e9df3a
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure
@@ -0,0 +1,34 @@
+# -*- 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 000000000..6d905ebbb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.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/.
+
+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 000000000..6af4b1eda
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/values.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/.
+
+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 000000000..5c056764d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/included.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/.
+
+# 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)
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 000000000..32c4b8535
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/moz.configure
@@ -0,0 +1,174 @@
+# -*- 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. The function must also depend
+# on --help.
+@depends('--enable-simple', '--help')
+def simple(simple, help):
+ 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', '--help')
+def choices(values, help):
+ 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. That function needs to depend on '--help' like for option
+# defaults and choices.
+option('--enable-include', nargs=1, help='Include')
+@depends('--enable-include', '--help')
+def include_path(path, help):
+ 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)
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 000000000..cf5743963
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_config.configure
@@ -0,0 +1,43 @@
+# -*- 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 000000000..422263427
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_define.configure
@@ -0,0 +1,43 @@
+# -*- 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 000000000..de6be9cec
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/subprocess.configure
@@ -0,0 +1,23 @@
+# -*- 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'.encode('utf-8')
+ 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 000000000..9965a60e9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/lint.py
@@ -0,0 +1,65 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import os
+import unittest
+from StringIO import StringIO
+from mozunit import main
+from buildconfig import (
+ topobjdir,
+ topsrcdir,
+)
+
+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 (
+ 'b2g',
+ 'b2g/dev',
+ 'b2g/graphene',
+ 'browser',
+ 'embedding/ios',
+ 'extensions',
+ 'js',
+ 'mobile/android',
+ ):
+ attrs['test_%s' % project.replace('/', '_')] = create_test(
+ project, attrs['lint'])
+
+ return type.__new__(mcs, name, bases, attrs)
+
+
+class Lint(unittest.TestCase):
+ __metaclass__ = LintMeta
+
+ 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'),
+ }, ['--enable-project=%s' % project])
+ sandbox.run(os.path.join(topsrcdir, 'moz.configure'))
+
+
+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 000000000..181c7acbd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
@@ -0,0 +1,940 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+from StringIO import StringIO
+import os
+import sys
+import textwrap
+import unittest
+
+from mozunit import (
+ main,
+ MockedOpen,
+)
+
+from mozbuild.configure import (
+ ConfigureError,
+ ConfigureSandbox,
+)
+from mozbuild.util import exec_
+from mozpack import path as mozpath
+
+from buildconfig import topsrcdir
+from common import (
+ ConfigureTestSandbox,
+ ensure_exe_extension,
+ fake_short_path,
+)
+
+
+class TestChecksConfigure(unittest.TestCase):
+ def test_checking(self):
+ 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_(textwrap.dedent('''
+ @checking('for a thing')
+ def foo(value):
+ return value
+ '''), sandbox)
+
+ foo = sandbox['foo']
+
+ foo(True)
+ self.assertEqual(out.getvalue(), 'checking for a thing... yes\n')
+
+ out.truncate(0)
+ foo(False)
+ self.assertEqual(out.getvalue(), 'checking for a thing... no\n')
+
+ out.truncate(0)
+ foo(42)
+ self.assertEqual(out.getvalue(), 'checking for a thing... 42\n')
+
+ out.truncate(0)
+ foo('foo')
+ self.assertEqual(out.getvalue(), 'checking for a thing... foo\n')
+
+ out.truncate(0)
+ data = ['foo', 'bar']
+ foo(data)
+ self.assertEqual(out.getvalue(), 'checking for a thing... %r\n' % data)
+
+ # When the function given to checking does nothing interesting, the
+ # behavior is not altered
+ exec_(textwrap.dedent('''
+ @checking('for a thing', lambda x: x)
+ def foo(value):
+ return value
+ '''), sandbox)
+
+ foo = sandbox['foo']
+
+ out.truncate(0)
+ foo(True)
+ self.assertEqual(out.getvalue(), 'checking for a thing... yes\n')
+
+ out.truncate(0)
+ foo(False)
+ self.assertEqual(out.getvalue(), 'checking for a thing... no\n')
+
+ out.truncate(0)
+ foo(42)
+ self.assertEqual(out.getvalue(), 'checking for a thing... 42\n')
+
+ out.truncate(0)
+ foo('foo')
+ self.assertEqual(out.getvalue(), 'checking for a thing... foo\n')
+
+ out.truncate(0)
+ data = ['foo', 'bar']
+ foo(data)
+ self.assertEqual(out.getvalue(), 'checking for a thing... %r\n' % data)
+
+ exec_(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
+ '''), sandbox)
+
+ foo = sandbox['foo']
+
+ out.truncate(0)
+ foo(True)
+ self.assertEqual(out.getvalue(), 'checking for a thing... yes\n')
+
+ out.truncate(0)
+ foo(False)
+ self.assertEqual(out.getvalue(), 'checking for a thing... not found\n')
+
+ out.truncate(0)
+ foo(42)
+ self.assertEqual(out.getvalue(), 'checking for a thing... 42\n')
+
+ out.truncate(0)
+ foo('foo')
+ self.assertEqual(out.getvalue(), 'checking for a thing... foo\n')
+
+ out.truncate(0)
+ foo(['foo', 'bar'])
+ self.assertEqual(out.getvalue(), '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"
+ % 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: Trying 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: Trying unknown
+ DEBUG: foo: Trying unknown-2
+ DEBUG: foo: Trying '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, {'FOO': ':'})
+ 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: Trying %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"
+ % 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: Trying 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: Trying unknown
+ DEBUG: cc: Trying unknown-2
+ DEBUG: cc: Trying '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: Trying gcc
+ DEBUG: cc: Trying 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: Trying gcc
+ DEBUG: cc: Trying 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: Trying cl
+ DEBUG: cc: Trying 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(e.exception.message,
+ '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(e.exception.message,
+ '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(e.exception.message,
+ '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: Trying 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...
+ DEBUG: a: Trying known-a
+ ERROR: Paths provided to find_program must be a list of strings, not %r
+ ''' % mozpath.dirname(self.OTHER_A)))
+
+ def test_java_tool_checks(self):
+ includes = ('util.configure', 'checks.configure', 'java.configure')
+
+ def mock_valid_javac(_, args):
+ if len(args) == 1 and args[0] == '-version':
+ return 0, '1.7', ''
+ self.fail("Unexpected arguments to mock_valid_javac: %s" % args)
+
+ # A valid set of tools in a standard location.
+ java = mozpath.abspath('/usr/bin/java')
+ javah = mozpath.abspath('/usr/bin/javah')
+ javac = mozpath.abspath('/usr/bin/javac')
+ jar = mozpath.abspath('/usr/bin/jar')
+ jarsigner = mozpath.abspath('/usr/bin/jarsigner')
+ keytool = mozpath.abspath('/usr/bin/keytool')
+
+ paths = {
+ java: None,
+ javah: None,
+ javac: mock_valid_javac,
+ jar: None,
+ jarsigner: None,
+ keytool: None,
+ }
+
+ config, out, status = self.get_result(includes=includes, extra_paths=paths)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {
+ 'JAVA': java,
+ 'JAVAH': javah,
+ 'JAVAC': javac,
+ 'JAR': jar,
+ 'JARSIGNER': jarsigner,
+ 'KEYTOOL': keytool,
+ })
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for java... %s
+ checking for javah... %s
+ checking for jar... %s
+ checking for jarsigner... %s
+ checking for keytool... %s
+ checking for javac... %s
+ checking for javac version... 1.7
+ ''' % (java, javah, jar, jarsigner, keytool, javac)))
+
+ # An alternative valid set of tools referred to by JAVA_HOME.
+ alt_java = mozpath.abspath('/usr/local/bin/java')
+ alt_javah = mozpath.abspath('/usr/local/bin/javah')
+ alt_javac = mozpath.abspath('/usr/local/bin/javac')
+ alt_jar = mozpath.abspath('/usr/local/bin/jar')
+ alt_jarsigner = mozpath.abspath('/usr/local/bin/jarsigner')
+ alt_keytool = mozpath.abspath('/usr/local/bin/keytool')
+ alt_java_home = mozpath.dirname(mozpath.dirname(alt_java))
+
+ paths.update({
+ alt_java: None,
+ alt_javah: None,
+ alt_javac: mock_valid_javac,
+ alt_jar: None,
+ alt_jarsigner: None,
+ alt_keytool: None,
+ })
+
+ config, out, status = self.get_result(includes=includes,
+ extra_paths=paths,
+ environ={
+ 'JAVA_HOME': alt_java_home,
+ 'PATH': mozpath.dirname(java)
+ })
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {
+ 'JAVA': alt_java,
+ 'JAVAH': alt_javah,
+ 'JAVAC': alt_javac,
+ 'JAR': alt_jar,
+ 'JARSIGNER': alt_jarsigner,
+ 'KEYTOOL': alt_keytool,
+ })
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for java... %s
+ checking for javah... %s
+ checking for jar... %s
+ checking for jarsigner... %s
+ checking for keytool... %s
+ checking for javac... %s
+ checking for javac version... 1.7
+ ''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
+ alt_keytool, alt_javac)))
+
+ # We can use --with-java-bin-path instead of JAVA_HOME to similar
+ # effect.
+ config, out, status = self.get_result(
+ args=['--with-java-bin-path=%s' % mozpath.dirname(alt_java)],
+ includes=includes,
+ extra_paths=paths,
+ environ={
+ 'PATH': mozpath.dirname(java)
+ })
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {
+ 'JAVA': alt_java,
+ 'JAVAH': alt_javah,
+ 'JAVAC': alt_javac,
+ 'JAR': alt_jar,
+ 'JARSIGNER': alt_jarsigner,
+ 'KEYTOOL': alt_keytool,
+ })
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for java... %s
+ checking for javah... %s
+ checking for jar... %s
+ checking for jarsigner... %s
+ checking for keytool... %s
+ checking for javac... %s
+ checking for javac version... 1.7
+ ''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
+ alt_keytool, alt_javac)))
+
+ # If --with-java-bin-path and JAVA_HOME are both set,
+ # --with-java-bin-path takes precedence.
+ config, out, status = self.get_result(
+ args=['--with-java-bin-path=%s' % mozpath.dirname(alt_java)],
+ includes=includes,
+ extra_paths=paths,
+ environ={
+ 'PATH': mozpath.dirname(java),
+ 'JAVA_HOME': mozpath.dirname(mozpath.dirname(java)),
+ })
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {
+ 'JAVA': alt_java,
+ 'JAVAH': alt_javah,
+ 'JAVAC': alt_javac,
+ 'JAR': alt_jar,
+ 'JARSIGNER': alt_jarsigner,
+ 'KEYTOOL': alt_keytool,
+ })
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for java... %s
+ checking for javah... %s
+ checking for jar... %s
+ checking for jarsigner... %s
+ checking for keytool... %s
+ checking for javac... %s
+ checking for javac version... 1.7
+ ''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
+ alt_keytool, alt_javac)))
+
+ def mock_old_javac(_, args):
+ if len(args) == 1 and args[0] == '-version':
+ return 0, '1.6.9', ''
+ self.fail("Unexpected arguments to mock_old_javac: %s" % args)
+
+ # An old javac is fatal.
+ paths[javac] = mock_old_javac
+ config, out, status = self.get_result(includes=includes,
+ extra_paths=paths,
+ environ={
+ 'PATH': mozpath.dirname(java)
+ })
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {
+ 'JAVA': java,
+ 'JAVAH': javah,
+ 'JAVAC': javac,
+ 'JAR': jar,
+ 'JARSIGNER': jarsigner,
+ 'KEYTOOL': keytool,
+ })
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for java... %s
+ checking for javah... %s
+ checking for jar... %s
+ checking for jarsigner... %s
+ checking for keytool... %s
+ checking for javac... %s
+ checking for javac version...
+ ERROR: javac 1.7 or higher is required (found 1.6.9)
+ ''' % (java, javah, jar, jarsigner, keytool, javac)))
+
+ # Any missing tool is fatal when these checks run.
+ del paths[jarsigner]
+ config, out, status = self.get_result(includes=includes,
+ extra_paths=paths,
+ environ={
+ 'PATH': mozpath.dirname(java)
+ })
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {
+ 'JAVA': java,
+ 'JAVAH': javah,
+ 'JAR': jar,
+ 'JARSIGNER': ':',
+ })
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for java... %s
+ checking for javah... %s
+ checking for jar... %s
+ checking for jarsigner... not found
+ ERROR: The program jarsigner was not found. Set $JAVA_HOME to your Java SDK directory or use '--with-java-bin-path={java-bin-dir}'
+ ''' % (java, javah, jar)))
+
+ def test_pkg_check_modules(self):
+ mock_pkg_config_version = '0.10.0'
+ mock_pkg_config_path = mozpath.abspath('/usr/bin/pkg-config')
+
+ def mock_pkg_config(_, 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, ''
+ self.fail("Unexpected arguments to mock_pkg_config: %s" % args)
+
+ def get_result(cmd, args=[], extra_paths=None):
+ return self.get_result(textwrap.dedent('''\
+ option('--disable-compile-environment', help='compile env')
+ include('%(topsrcdir)s/build/moz.configure/util.configure')
+ include('%(topsrcdir)s/build/moz.configure/checks.configure')
+ include('%(topsrcdir)s/build/moz.configure/pkg.configure')
+ ''' % {'topsrcdir': topsrcdir}) + cmd, args=args, extra_paths=extra_paths,
+ includes=())
+
+ extra_paths = {
+ mock_pkg_config_path: mock_pkg_config,
+ }
+ includes = ('util.configure', 'checks.configure', 'pkg.configure')
+
+ 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.
+ '''))
+
+
+ config, output, status = get_result("pkg_check_modules('MOZ_VALID', 'valid')",
+ 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 for valid... yes
+ checking MOZ_VALID_CFLAGS... -I/usr/include/valid
+ checking MOZ_VALID_LIBS... -lvalid
+ ''' % (mock_pkg_config_path, mock_pkg_config_version)))
+ self.assertEqual(config, {
+ 'PKG_CONFIG': mock_pkg_config_path,
+ 'MOZ_VALID_CFLAGS': ('-I/usr/include/valid',),
+ 'MOZ_VALID_LIBS': ('-lvalid',),
+ })
+
+ 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 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 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 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', ''
+ 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
+ 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',
+ })
+
+ 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, {})
+
+
+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 000000000..5913dbe3d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_compile_checks.py
@@ -0,0 +1,403 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import os
+import textwrap
+import unittest
+import mozpack.path as mozpath
+
+from StringIO import StringIO
+
+from buildconfig import topsrcdir
+from common import ConfigureTestSandbox
+from mozbuild.util import exec_
+from mozunit import main
+from test_toolchain_helpers import FakeCompiler
+
+
+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):
+ args, test_file = args[:-1], args[-1]
+ 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 []
+
+ include('%s/compilers-util.configure')
+
+ @compiler_class
+ @depends(when=True)
+ def c_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C',
+ )
+
+ @compiler_class
+ @depends(when=True)
+ def 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_cflags)
+ set_config('_WARNINGS_CXXFLAGS', warnings_cxxflags)
+ ''')
+
+ def test_check_and_add_gcc_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_gcc_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_gcc_warning_one(self):
+ cmd = textwrap.dedent('''\
+ check_and_add_gcc_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_gcc_warning_when(self):
+ cmd = textwrap.dedent('''\
+ @depends(when=True)
+ def never():
+ return False
+ check_and_add_gcc_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_gcc_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_gcc_warning(self):
+ cmd = textwrap.dedent('''\
+ add_gcc_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_gcc_warning_one(self):
+ cmd = textwrap.dedent('''\
+ add_gcc_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_gcc_warning_when(self):
+ cmd = textwrap.dedent('''\
+ @depends(when=True)
+ def never():
+ return False
+ add_gcc_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_gcc_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 000000000..df97ba70d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -0,0 +1,1273 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+from StringIO import StringIO
+import os
+import sys
+import textwrap
+import unittest
+
+from mozunit import (
+ main,
+ MockedOpen,
+)
+
+from mozbuild.configure.options import (
+ InvalidOptionError,
+ NegativeOptionValue,
+ PositiveOptionValue,
+)
+from mozbuild.configure import (
+ ConfigureError,
+ ConfigureSandbox,
+)
+from mozbuild.util import exec_
+
+import mozpack.path as mozpath
+
+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 out.getvalue(), config
+ self.assertEquals('', 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.assertEquals({
+ '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.assertEquals({}, config)
+ self.maxDiff = None
+ self.assertEquals(
+ 'Usage: configure [options]\n'
+ '\n'
+ 'Options: [defaults in brackets after descriptions]\n'
+ ' --help print this message\n'
+ ' --enable-simple Enable simple\n'
+ ' --enable-with-env Enable with env\n'
+ ' --enable-values Enable values\n'
+ ' --without-thing Build without thing\n'
+ ' --with-stuff Build with stuff\n'
+ ' --option Option\n'
+ ' --with-returned-default Returned default [not-simple]\n'
+ ' --returned-choices Choices\n'
+ ' --enable-imports-in-template\n'
+ ' Imports in template\n'
+ ' --enable-include Include\n'
+ ' --with-imports Imports\n'
+ '\n'
+ 'Environment variables:\n'
+ ' CC C Compiler\n',
+ help
+ )
+
+ 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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(NegativeOptionValue(), config[name])
+
+ for config in (
+ self.get_config(['--enable-values']),
+ self.get_config(['--disable-values', '--enable-values']),
+ ):
+ self.assertIn(name, config)
+ self.assertEquals(PositiveOptionValue(), config[name])
+
+ config = self.get_config(['--enable-values=foo'])
+ self.assertIn(name, config)
+ self.assertEquals(PositiveOptionValue(('foo',)), config[name])
+
+ config = self.get_config(['--enable-values=foo,bar'])
+ self.assertIn(name, config)
+ self.assertTrue(config[name])
+ self.assertEquals(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.assertEquals(
+ PositiveOptionValue(('simple',)), config['DEFAULTED'])
+
+ config = self.get_config(['--disable-simple'])
+ self.assertIn('DEFAULTED', config)
+ self.assertEquals(
+ 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.assertEquals(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.assertEquals(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.assertEquals(config['IS_GCC'], True)
+
+ config = self.get_config(
+ ['--enable-include=extra.configure', '--extra'])
+ self.assertIn('EXTRA', config)
+ self.assertEquals(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.assertEquals(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, {}, [], 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)
+
+ exec_(textwrap.dedent('''
+ @template
+ @imports(_from='os', _import='path')
+ def foo():
+ return path'''),
+ sandbox
+ )
+
+ self.assertIs(sandbox['foo'](), os.path)
+
+ exec_(textwrap.dedent('''
+ @template
+ @imports(_from='os', _import='path', _as='os_path')
+ def foo():
+ return os_path'''),
+ sandbox
+ )
+
+ self.assertIs(sandbox['foo'](), os.path)
+
+ exec_(textwrap.dedent('''
+ @template
+ @imports('__builtin__')
+ def foo():
+ return __builtin__'''),
+ sandbox
+ )
+
+ import __builtin__
+ self.assertIs(sandbox['foo'](), __builtin__)
+
+ exec_(textwrap.dedent('''
+ @template
+ @imports(_from='__builtin__', _import='open')
+ def foo():
+ return open('%s')''' % os.devnull),
+ sandbox
+ )
+
+ f = sandbox['foo']()
+ self.assertEquals(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.assertEquals(sandbox.keys(), ['__builtins__', 'foo'])
+ self.assertEquals(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.assertEquals(e.exception.message,
+ "global name 'sys' is not defined")
+
+ 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, {}, [], out, out)
+
+ exec_(textwrap.dedent('''
+ @template
+ @imports('sys')
+ def foo():
+ return sys
+ foo()
+ foo()'''),
+ sandbox
+ )
+
+ self.assertEquals(len(imports), 1)
+
+ def test_os_path(self):
+ config = self.get_config(['--with-imports=%s' % __file__])
+ self.assertIn('HAS_ABSPATH', config)
+ self.assertEquals(config['HAS_ABSPATH'], True)
+ self.assertIn('HAS_GETATIME', config)
+ self.assertEquals(config['HAS_GETATIME'], True)
+ self.assertIn('HAS_GETATIME2', config)
+ self.assertEquals(config['HAS_GETATIME2'], False)
+
+ def test_template_call(self):
+ config = self.get_config(env={'CC': 'gcc'})
+ self.assertIn('TEMPLATE_VALUE', config)
+ self.assertEquals(config['TEMPLATE_VALUE'], 42)
+ self.assertIn('TEMPLATE_VALUE_2', config)
+ self.assertEquals(config['TEMPLATE_VALUE_2'], 21)
+
+ def test_template_imports(self):
+ config = self.get_config(['--enable-imports-in-template'])
+ self.assertIn('PLATFORM', config)
+ self.assertEquals(config['PLATFORM'], sys.platform)
+
+ def test_decorators(self):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, {}, [], 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.assertEquals(config, {})
+
+ config = get_config(['--set-foo'])
+ self.assertIn('FOO', config)
+ self.assertEquals(config['FOO'], True)
+
+ config = get_config(['--set-bar'])
+ self.assertNotIn('FOO', config)
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], True)
+
+ config = get_config(['--set-value=qux'])
+ self.assertIn('VALUE', config)
+ self.assertEquals(config['VALUE'], 'qux')
+
+ config = get_config(['--set-name=hoge'])
+ self.assertIn('hoge', config)
+ self.assertEquals(config['hoge'], True)
+
+ config = get_config([])
+ self.assertEquals(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.assertEquals(config, {
+ 'FOO': 'foo',
+ })
+ config = self.get_config(['--with-qux'])
+ self.assertEquals(config, {
+ 'FOO': 'foo',
+ 'QUX': 'qux',
+ })
+
+ def test_set_define(self):
+ def get_config(*args):
+ return self.get_config(*args, configure='set_define.configure')
+
+ help, config = get_config(['--help'])
+ self.assertEquals(config, {'DEFINES': {}})
+
+ config = get_config(['--set-foo'])
+ self.assertIn('FOO', config['DEFINES'])
+ self.assertEquals(config['DEFINES']['FOO'], True)
+
+ config = get_config(['--set-bar'])
+ self.assertNotIn('FOO', config['DEFINES'])
+ self.assertIn('BAR', config['DEFINES'])
+ self.assertEquals(config['DEFINES']['BAR'], True)
+
+ config = get_config(['--set-value=qux'])
+ self.assertIn('VALUE', config['DEFINES'])
+ self.assertEquals(config['DEFINES']['VALUE'], 'qux')
+
+ config = get_config(['--set-name=hoge'])
+ self.assertIn('hoge', config['DEFINES'])
+ self.assertEquals(config['DEFINES']['hoge'], True)
+
+ config = get_config([])
+ self.assertEquals(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.assertEquals(config['DEFINES'], {
+ 'FOO': 'foo',
+ })
+ config = self.get_config(['--with-qux'])
+ self.assertEquals(config['DEFINES'], {
+ 'FOO': 'foo',
+ 'QUX': 'qux',
+ })
+
+ 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.assertEquals(config, {})
+
+ config = get_config([])
+ self.assertEquals(config, {})
+
+ config = get_config(['--enable-foo'])
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], PositiveOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(['--enable-foo', '--disable-bar'])
+
+ self.assertEquals(
+ e.exception.message,
+ "'--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.assertEquals(config, {})
+
+ config = get_config([])
+ self.assertEquals(config, {})
+
+ config = get_config(['--enable-foo'])
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(['--enable-foo', '--enable-bar'])
+
+ self.assertEquals(
+ e.exception.message,
+ "'--disable-bar' implied by '--enable-foo' conflicts with "
+ "'--enable-bar' from the command-line")
+
+ config = get_config(['--disable-hoge'])
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(['--disable-hoge', '--enable-bar'])
+
+ self.assertEquals(
+ e.exception.message,
+ "'--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.assertEquals(config, {})
+
+ config = get_config([])
+ self.assertEquals(config, {})
+
+ config = get_config(['--enable-foo=a'])
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], PositiveOptionValue(('a',)))
+
+ config = get_config(['--enable-foo=a,b'])
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], PositiveOptionValue(('a','b')))
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(['--enable-foo=a,b', '--disable-bar'])
+
+ self.assertEquals(
+ e.exception.message,
+ "'--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.assertEquals(config, {})
+
+ config = get_config([])
+ self.assertEquals(config, {})
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(['--enable-foo', '--disable-bar'])
+
+ self.assertEquals(
+ e.exception.message,
+ "'--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.assertEquals(
+ e.exception.message,
+ "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.assertEquals(config, {})
+
+ config = get_config([])
+ self.assertEquals(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:16' 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:25' 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.assertEquals(e.exception.message,
+ "`--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.assertEquals(e.exception.message,
+ "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.assertEquals(config, {
+ 'QUX': NegativeOptionValue(),
+ })
+
+ config = self.get_config(['--with-foo'])
+ self.assertEquals(config, {
+ 'QUX': PositiveOptionValue(),
+ })
+
+ def test_option_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('option("--with-foo", help="foo")'):
+ self.get_config()
+
+ self.assertEquals(
+ e.exception.message,
+ '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.assertEquals(
+ e.exception.message,
+ '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.assertEquals(
+ e.exception.message,
+ '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.assertEquals(
+ e.exception.message,
+ '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.assertEquals(
+ e.exception.message,
+ '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.assertEquals(
+ e.exception.message,
+ '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.assertEquals(config, {
+ 'FOO': NegativeOptionValue(),
+ })
+
+ config = self.get_config(['--with-foo'])
+ self.assertEquals(config, {
+ 'FOO': PositiveOptionValue(),
+ 'QUX': NegativeOptionValue(),
+ })
+
+ config = self.get_config(['--with-foo', '--with-qux'])
+ self.assertEquals(config, {
+ 'FOO': PositiveOptionValue(),
+ 'QUX': PositiveOptionValue(),
+ })
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(['--with-bar'])
+
+ self.assertEquals(
+ e.exception.message,
+ '--with-bar is not available in this configuration'
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(['--with-qux'])
+
+ self.assertEquals(
+ e.exception.message,
+ '--with-qux is not available in this configuration'
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(['QUX=1'])
+
+ self.assertEquals(
+ e.exception.message,
+ 'QUX is not available in this configuration'
+ )
+
+ config = self.get_config(env={'QUX': '1'})
+ self.assertEquals(config, {
+ 'FOO': NegativeOptionValue(),
+ })
+
+ help, config = self.get_config(['--help'])
+ self.assertEquals(help, textwrap.dedent('''\
+ Usage: configure [options]
+
+ Options: [defaults in brackets after descriptions]
+ --help print this message
+ --with-foo foo
+
+ Environment variables:
+ '''))
+
+ help, config = self.get_config(['--help', '--with-foo'])
+ self.assertEquals(help, textwrap.dedent('''\
+ Usage: configure [options]
+
+ Options: [defaults in brackets after descriptions]
+ --help print this message
+ --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.assertEquals(e.exception.message,
+ '@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.assertEquals(e.exception.message,
+ '@depends function needs the same `when` as '
+ 'options it depends on')
+
+ def test_include_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('include("../foo.configure")'):
+ self.get_config()
+
+ self.assertEquals(
+ e.exception.message,
+ '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.assertEquals(
+ e.exception.message,
+ '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.assertEquals(e.exception.message, "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.assertEquals(config, {})
+
+ config = self.get_config(['--with-foo'])
+ self.assertEquals(config, {})
+
+ config = self.get_config(['--with-bar'])
+ self.assertEquals(config, {
+ 'BAR': 'bar',
+ })
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(['--with-qux'])
+
+ self.assertEquals(
+ e.exception.message,
+ '--with-qux is not available in this configuration'
+ )
+
+ config = self.get_config(['--with-foo', '--with-foo-really'])
+ self.assertEquals(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.assertEquals(e.exception.message, 'Cannot reassign builtins')
+
+ with self.assertRaises(KeyError) as e:
+ with self.moz_configure('''
+ foo = 42
+ '''):
+ self.get_config()
+
+ self.assertEquals(e.exception.message,
+ 'Cannot assign `foo` because it is neither a '
+ '@depends nor a @template')
+
+ def test_depends_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('''
+ @depends()
+ def foo():
+ return
+ '''):
+ self.get_config()
+
+ self.assertEquals(e.exception.message,
+ "@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.assertEquals(e.exception.message,
+ "'--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.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "Cannot decorate generator functions with @depends")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure('''
+ depends('--help')(42)
+ '''):
+ self.get_config()
+
+ self.assertEquals(e.exception.message,
+ "Unexpected type: 'int'")
+
+ 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.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "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.assertEquals(config, {
+ 'FOO': 'foo',
+ })
+
+ config = self.get_config(['--with-qux'])
+ self.assertEquals(config, {
+ 'FOO': 'foo',
+ 'QUX': 'qux',
+ })
+
+ 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.assertEquals(e.exception.message,
+ '@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.assertEquals(e.exception.message,
+ '@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.assertEquals(e.exception.message, "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.assertEquals(e.exception.message, "Unexpected type: 'int'")
+
+ with self.assertRaises(ValueError) as e:
+ with self.moz_configure('''
+ @imports('os*')
+ def foo(value):
+ return value
+ '''):
+ self.get_config()
+
+ self.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ '--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.assertEquals(e.exception.message,
+ '@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.assertEquals(e.exception.message,
+ '--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'])
+
+
+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 000000000..6ac2bb356
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_lint.py
@@ -0,0 +1,132 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+from StringIO import StringIO
+import os
+import textwrap
+import unittest
+
+from mozunit import (
+ main,
+ MockedOpen,
+)
+
+from mozbuild.configure import ConfigureError
+from mozbuild.configure.lint import LintSandbox
+
+import mozpack.path as mozpath
+
+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)
+ })
+
+ def test_depends_failures(self):
+ with self.moz_configure('''
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ def bar(help, foo):
+ return
+ '''):
+ self.lint_test()
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('''
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ def bar(help, foo):
+ return
+ '''):
+ self.lint_test()
+
+ self.assertEquals(e.exception.message,
+ "`bar` depends on '--help' and `foo`. "
+ "`foo` must depend on '--help'")
+
+ with self.assertRaises(ConfigureError) 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)
+ def bar(help, foo):
+ return
+ tmpl()
+ '''):
+ self.lint_test()
+
+ self.assertEquals(e.exception.message,
+ "`bar` depends on '--help' and `foo`. "
+ "`foo` must depend on '--help'")
+
+ with self.moz_configure('''
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ include(foo)
+ '''):
+ self.lint_test()
+
+ with self.assertRaises(ConfigureError) 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.assertEquals(e.exception.message,
+ "Missing @depends for `foo`: '--help'")
+
+ # 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()
+
+
+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 000000000..7c318adef
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_moz_configure.py
@@ -0,0 +1,93 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+from mozunit import main
+from mozpack import path as mozpath
+
+from common import BaseConfigureTest
+
+
+class TestMozConfigure(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')
+
+ result = sandbox._value_for(sandbox['all_configure_options'])
+ shell = mozpath.abspath('/bin/sh')
+ return result.replace('CONFIG_SHELL=%s ' % shell, '')
+
+ self.assertEquals('--enable-application=browser',
+ get_value_for(['--enable-application=browser']))
+
+ self.assertEquals('--enable-application=browser '
+ 'MOZ_PROFILING=1',
+ get_value_for(['--enable-application=browser',
+ 'MOZ_PROFILING=1']))
+
+ value = get_value_for(
+ environ={'MOZ_PROFILING': '1'},
+ mozconfig='ac_add_options --enable-project=js')
+
+ self.assertEquals('--enable-project=js MOZ_PROFILING=1',
+ value)
+
+ # --disable-js-shell is the default, so it's filtered out.
+ self.assertEquals('--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.assertEquals('--enable-application=browser --without-foo',
+ get_value_for(['--enable-application=browser',
+ '--without-foo']))
+ self.assertEquals('--enable-application=browser --with-foo',
+ get_value_for(['--enable-application=browser',
+ '--with-foo']))
+
+ self.assertEquals("--enable-application=browser '--with-foo=foo bar'",
+ get_value_for(['--enable-application=browser',
+ '--with-foo=foo bar']))
+
+ def test_nsis_version(self):
+ this = self
+
+ class FakeNSIS(object):
+ def __init__(self, version):
+ self.version = version
+
+ def __call__(self, stdin, args):
+ this.assertEquals(args, ('-version',))
+ return 0, self.version, ''
+
+ def check_nsis_version(version):
+ sandbox = self.get_sandbox(
+ {'/usr/bin/makensis': FakeNSIS(version)}, {}, [],
+ {'PATH': '/usr/bin', 'MAKENSISU': '/usr/bin/makensis'})
+ return sandbox._value_for(sandbox['nsis_version'])
+
+ with self.assertRaises(SystemExit) as e:
+ check_nsis_version('v2.5')
+
+ with self.assertRaises(SystemExit) as e:
+ check_nsis_version('v3.0a2')
+
+ self.assertEquals(check_nsis_version('v3.0b1'), '3.0b1')
+ self.assertEquals(check_nsis_version('v3.0b2'), '3.0b2')
+ self.assertEquals(check_nsis_version('v3.0rc1'), '3.0rc1')
+ self.assertEquals(check_nsis_version('v3.0'), '3.0')
+ self.assertEquals(check_nsis_version('v3.0-2'), '3.0')
+ self.assertEquals(check_nsis_version('v3.0.1'), '3.0')
+ self.assertEquals(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 000000000..e504f9e05
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_options.py
@@ -0,0 +1,852 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import unittest
+
+from mozunit import main
+
+from mozbuild.configure.options import (
+ CommandLineHelper,
+ ConflictingOptionError,
+ InvalidOptionError,
+ NegativeOptionValue,
+ Option,
+ 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.assertEquals(option.prefix, '')
+ self.assertEquals(option.name, 'option')
+ self.assertEquals(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option('--enable-option')
+ self.assertEquals(option.prefix, 'enable')
+ self.assertEquals(option.name, 'option')
+ self.assertEquals(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option('--disable-option')
+ self.assertEquals(option.prefix, 'disable')
+ self.assertEquals(option.name, 'option')
+ self.assertEquals(option.env, None)
+ self.assertTrue(option.default)
+
+ option = Option('--with-option')
+ self.assertEquals(option.prefix, 'with')
+ self.assertEquals(option.name, 'option')
+ self.assertEquals(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option('--without-option')
+ self.assertEquals(option.prefix, 'without')
+ self.assertEquals(option.name, 'option')
+ self.assertEquals(option.env, None)
+ self.assertTrue(option.default)
+
+ option = Option('--without-option-foo', env='MOZ_OPTION')
+ self.assertEquals(option.env, 'MOZ_OPTION')
+
+ option = Option(env='MOZ_OPTION')
+ self.assertEquals(option.prefix, '')
+ self.assertEquals(option.name, None)
+ self.assertEquals(option.env, 'MOZ_OPTION')
+ self.assertFalse(option.default)
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs=0, default=('a',))
+ self.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs=1, default=())
+ self.assertEquals(
+ e.exception.message,
+ '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.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs=1, default=('a', 'b'))
+ self.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs=2, default=())
+ self.assertEquals(
+ e.exception.message,
+ '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.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs=2, default=('a',))
+ self.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs='?', default=('a', 'b'))
+ self.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs='+', default=())
+ self.assertEquals(
+ e.exception.message,
+ 'default must be a bool, a string or a tuple of strings')
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs='+', default=True)
+ self.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--disable-option', nargs='+')
+ self.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+ # Test nargs inference from default value
+ option = Option('--with-foo', default=True)
+ self.assertEquals(option.nargs, 0)
+
+ option = Option('--with-foo', default=False)
+ self.assertEquals(option.nargs, 0)
+
+ option = Option('--with-foo', default='a')
+ self.assertEquals(option.nargs, '?')
+
+ option = Option('--with-foo', default=('a',))
+ self.assertEquals(option.nargs, '?')
+
+ option = Option('--with-foo', default=('a', 'b'))
+ self.assertEquals(option.nargs, '*')
+
+ option = Option(env='FOO', default=True)
+ self.assertEquals(option.nargs, 0)
+
+ option = Option(env='FOO', default=False)
+ self.assertEquals(option.nargs, 0)
+
+ option = Option(env='FOO', default='a')
+ self.assertEquals(option.nargs, '?')
+
+ option = Option(env='FOO', default=('a',))
+ self.assertEquals(option.nargs, '?')
+
+ option = Option(env='FOO', default=('a', 'b'))
+ self.assertEquals(option.nargs, '*')
+
+ def test_option_option(self):
+ for option in (
+ '--option',
+ '--enable-option',
+ '--disable-option',
+ '--with-option',
+ '--without-option',
+ ):
+ self.assertEquals(Option(option).option, option)
+ self.assertEquals(Option(option, env='FOO').option, option)
+
+ opt = Option(option, default=False)
+ self.assertEquals(opt.option,
+ option.replace('-disable-', '-enable-')
+ .replace('-without-', '-with-'))
+
+ opt = Option(option, default=True)
+ self.assertEquals(opt.option,
+ option.replace('-enable-', '-disable-')
+ .replace('-with-', '-without-'))
+
+ self.assertEquals(Option(env='FOO').option, 'FOO')
+
+ def test_option_choices(self):
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs=3, choices=('a', 'b'))
+ self.assertEquals(e.exception.message,
+ 'Not enough `choices` for `nargs`')
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--without-option', nargs=1, choices=('a', 'b'))
+ self.assertEquals(e.exception.message,
+ 'A `default` must be given along with `choices`')
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--without-option', nargs='+', choices=('a', 'b'))
+ self.assertEquals(e.exception.message,
+ 'A `default` must be given along with `choices`')
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--without-option', default='c', choices=('a', 'b'))
+ self.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "The `default` value must be one of 'a', 'b'")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--without-option', default=('c',), choices=('a', 'b'))
+ self.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message, "'c' is not one of 'a', 'b'")
+
+ value = option.get_value('--with-option=b,a')
+ self.assertTrue(value)
+ self.assertEquals(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.assertEquals(e.exception.message, "'c' is not one of 'a', 'b'")
+
+ value = option.get_value('--with-option=b,a')
+ self.assertTrue(value)
+ self.assertEquals(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.assertEquals(PositiveOptionValue(('b', 'c', 'd')), value)
+
+ value = option.get_value('--with-option=-b')
+ self.assertEquals(PositiveOptionValue(('c',)), value)
+
+ value = option.get_value('--with-option=-b,+d')
+ self.assertEquals(PositiveOptionValue(('c','d')), value)
+
+ # Adding something that is in the default is fine
+ value = option.get_value('--with-option=+b')
+ self.assertEquals(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.assertEquals(PositiveOptionValue(('b', 'c')), value)
+
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--with-option=-e')
+ self.assertEquals(e.exception.message,
+ "'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.assertEquals(e.exception.message,
+ "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--with-option=e')
+ self.assertEquals(e.exception.message,
+ "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ def test_option_value_format(self):
+ val = PositiveOptionValue()
+ self.assertEquals('--with-value', val.format('--with-value'))
+ self.assertEquals('--with-value', val.format('--without-value'))
+ self.assertEquals('--enable-value', val.format('--enable-value'))
+ self.assertEquals('--enable-value', val.format('--disable-value'))
+ self.assertEquals('--value', val.format('--value'))
+ self.assertEquals('VALUE=1', val.format('VALUE'))
+
+ val = PositiveOptionValue(('a',))
+ self.assertEquals('--with-value=a', val.format('--with-value'))
+ self.assertEquals('--with-value=a', val.format('--without-value'))
+ self.assertEquals('--enable-value=a', val.format('--enable-value'))
+ self.assertEquals('--enable-value=a', val.format('--disable-value'))
+ self.assertEquals('--value=a', val.format('--value'))
+ self.assertEquals('VALUE=a', val.format('VALUE'))
+
+ val = PositiveOptionValue(('a', 'b'))
+ self.assertEquals('--with-value=a,b', val.format('--with-value'))
+ self.assertEquals('--with-value=a,b', val.format('--without-value'))
+ self.assertEquals('--enable-value=a,b', val.format('--enable-value'))
+ self.assertEquals('--enable-value=a,b', val.format('--disable-value'))
+ self.assertEquals('--value=a,b', val.format('--value'))
+ self.assertEquals('VALUE=a,b', val.format('VALUE'))
+
+ val = NegativeOptionValue()
+ self.assertEquals('--without-value', val.format('--with-value'))
+ self.assertEquals('--without-value', val.format('--without-value'))
+ self.assertEquals('--disable-value', val.format('--enable-value'))
+ self.assertEquals('--disable-value', val.format('--disable-value'))
+ self.assertEquals('', val.format('--value'))
+ self.assertEquals('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.assertEquals(value, posOptionValue())
+ self.assertEquals(value.origin, 'option')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--%s' % name)
+ if nargs == 1:
+ self.assertEquals(e.exception.message,
+ '--%s takes 1 value' % name)
+ elif nargs == '+':
+ self.assertEquals(e.exception.message,
+ '--%s takes 1 or more values' % name)
+ else:
+ self.assertEquals(e.exception.message,
+ '--%s takes 2 values' % name)
+
+ value = option.get_value('')
+ self.assertEquals(value, defaultValue)
+ self.assertEquals(value.origin, 'default')
+
+ value = option.get_value(None)
+ self.assertEquals(value, defaultValue)
+ self.assertEquals(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.assertEquals(value, PositiveOptionValue(('',)))
+ self.assertEquals(value.origin, 'option')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--%s=' % name)
+ if disabled:
+ self.assertEquals(e.exception.message,
+ 'Cannot pass a value to --%s' % name)
+ else:
+ self.assertEquals(e.exception.message,
+ '--%s takes %d values' % (name, nargs))
+
+ if nargs in (1, '?', '*', '+') and not disabled:
+ value = option.get_value('--%s=foo' % name, 'option')
+ self.assertEquals(value, PositiveOptionValue(('foo',)))
+ self.assertEquals(value.origin, 'option')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--%s=foo' % name)
+ if disabled:
+ self.assertEquals(e.exception.message,
+ 'Cannot pass a value to --%s' % name)
+ else:
+ self.assertEquals(e.exception.message,
+ '--%s takes %d values' % (name, nargs))
+
+ if nargs in (2, '*', '+') and not disabled:
+ value = option.get_value('--%s=foo,bar' % name, 'option')
+ self.assertEquals(value, PositiveOptionValue(('foo', 'bar')))
+ self.assertEquals(value.origin, 'option')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--%s=foo,bar' % name, 'option')
+ if disabled:
+ self.assertEquals(e.exception.message,
+ 'Cannot pass a value to --%s' % name)
+ elif nargs == '?':
+ self.assertEquals(e.exception.message,
+ '--%s takes 0 or 1 values' % name)
+ else:
+ self.assertEquals(e.exception.message,
+ '--%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.assertEquals(value, posOptionValue())
+ self.assertEquals(value.origin, 'option')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--%s' % name)
+ if disabled:
+ self.assertEquals(e.exception.message,
+ 'Cannot pass a value to --%s' % name)
+ elif nargs == '+':
+ self.assertEquals(e.exception.message,
+ '--%s takes 1 or more values' % name)
+ else:
+ self.assertEquals(e.exception.message,
+ '--%s takes %d value%s'
+ % (name, nargs, 's' if nargs != 1 else ''))
+
+ value = option.get_value('')
+ self.assertEquals(value, defaultValue)
+ self.assertEquals(value.origin, 'default')
+
+ value = option.get_value(None)
+ self.assertEquals(value, defaultValue)
+ self.assertEquals(value.origin, 'default')
+
+ value = option.get_value('MOZ_OPTION=', 'environment')
+ self.assertEquals(value, NegativeOptionValue())
+ self.assertEquals(value.origin, 'environment')
+
+ if nargs in (0, '?', '*'):
+ value = option.get_value('MOZ_OPTION=1', 'environment')
+ self.assertEquals(value, PositiveOptionValue())
+ self.assertEquals(value.origin, 'environment')
+ elif nargs in (1, '+'):
+ value = option.get_value('MOZ_OPTION=1', 'environment')
+ self.assertEquals(value, PositiveOptionValue(('1',)))
+ self.assertEquals(value.origin, 'environment')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('MOZ_OPTION=1', 'environment')
+ self.assertEquals(e.exception.message, 'MOZ_OPTION takes 2 values')
+
+ if nargs in (1, '?', '*', '+') and not disabled:
+ value = option.get_value('--%s=' % name, 'option')
+ self.assertEquals(value, PositiveOptionValue(('',)))
+ self.assertEquals(value.origin, 'option')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--%s=' % name, 'option')
+ if disabled:
+ self.assertEquals(e.exception.message,
+ 'Cannot pass a value to --%s' % name)
+ else:
+ self.assertEquals(e.exception.message,
+ '--%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.assertEquals(value, PositiveOptionValue(('foo',)))
+ self.assertEquals(value.origin, 'environment')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('MOZ_OPTION=foo', 'environment')
+ self.assertEquals(e.exception.message,
+ 'MOZ_OPTION takes %d values' % nargs)
+
+ if nargs in (2, '*', '+'):
+ value = option.get_value('MOZ_OPTION=foo,bar', 'environment')
+ self.assertEquals(value, PositiveOptionValue(('foo', 'bar')))
+ self.assertEquals(value.origin, 'environment')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('MOZ_OPTION=foo,bar', 'environment')
+ if nargs == '?':
+ self.assertEquals(e.exception.message,
+ 'MOZ_OPTION takes 0 or 1 values')
+ else:
+ self.assertEquals(e.exception.message,
+ '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.assertEquals(value, defaultValue)
+ self.assertEquals(value.origin, 'default')
+
+ value = env_option.get_value('MOZ_OPTION=', 'environment')
+ self.assertEquals(value, negOptionValue())
+ self.assertEquals(value.origin, 'environment')
+
+ if nargs in (0, '?', '*'):
+ value = env_option.get_value('MOZ_OPTION=1', 'environment')
+ self.assertEquals(value, posOptionValue())
+ self.assertTrue(value)
+ self.assertEquals(value.origin, 'environment')
+ elif nargs in (1, '+'):
+ value = env_option.get_value('MOZ_OPTION=1', 'environment')
+ self.assertEquals(value, PositiveOptionValue(('1',)))
+ self.assertEquals(value.origin, 'environment')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value('MOZ_OPTION=1', 'environment')
+ self.assertEquals(e.exception.message, '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.assertEquals(value, PositiveOptionValue(('foo',)))
+ self.assertEquals(value.origin, 'environment')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value('MOZ_OPTION=foo', 'environment')
+ self.assertEquals(e.exception.message,
+ 'MOZ_OPTION takes %d values' % nargs)
+
+ if nargs in (2, '*', '+'):
+ value = env_option.get_value('MOZ_OPTION=foo,bar', 'environment')
+ self.assertEquals(value, PositiveOptionValue(('foo', 'bar')))
+ self.assertEquals(value.origin, 'environment')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value('MOZ_OPTION=foo,bar', 'environment')
+ if nargs == '?':
+ self.assertEquals(e.exception.message,
+ 'MOZ_OPTION takes 0 or 1 values')
+ else:
+ self.assertEquals(e.exception.message,
+ '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.assertEquals(value, NegativeOptionValue())
+ self.assertEquals(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.assertEquals(value, PositiveOptionValue())
+ self.assertEquals(value.origin, 'option')
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value('--%s-option' % enable, 'option')
+ if nargs == 1:
+ self.assertEquals(e.exception.message,
+ '--%s-option takes 1 value' % enable)
+ elif nargs == '+':
+ self.assertEquals(e.exception.message,
+ '--%s-option takes 1 or more values'
+ % enable)
+ else:
+ self.assertEquals(e.exception.message,
+ '--%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.assertEquals(e.exception.message,
+ "nargs must be a positive integer, '?', '*' or '+'")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option('--option', nargs=-2)
+ self.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "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.assertEquals(e.exception.message,
+ "The given `default` doesn't satisfy `nargs`")
+
+
+class TestCommandLineHelper(unittest.TestCase):
+ def test_basic(self):
+ helper = CommandLineHelper({}, ['cmd', '--foo', '--bar'])
+
+ self.assertEquals(['--foo', '--bar'], list(helper))
+
+ helper.add('--enable-qux')
+
+ self.assertEquals(['--foo', '--bar', '--enable-qux'], list(helper))
+
+ value, option = helper.handle(Option('--bar'))
+ self.assertEquals(['--foo', '--enable-qux'], list(helper))
+ self.assertEquals(PositiveOptionValue(), value)
+ self.assertEquals('--bar', option)
+
+ value, option = helper.handle(Option('--baz'))
+ self.assertEquals(['--foo', '--enable-qux'], list(helper))
+ self.assertEquals(NegativeOptionValue(), value)
+ self.assertEquals(None, option)
+
+ def test_precedence(self):
+ foo = Option('--with-foo', nargs='*')
+ helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b'])
+ value, option = helper.handle(foo)
+ self.assertEquals(PositiveOptionValue(('a', 'b')), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--with-foo=a,b', option)
+
+ helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b',
+ '--without-foo'])
+ value, option = helper.handle(foo)
+ self.assertEquals(NegativeOptionValue(), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--without-foo', option)
+
+ helper = CommandLineHelper({}, ['cmd', '--without-foo',
+ '--with-foo=a,b'])
+ value, option = helper.handle(foo)
+ self.assertEquals(PositiveOptionValue(('a', 'b')), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--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.assertEquals(PositiveOptionValue(('a', 'b')), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--with-foo=a,b', option)
+
+ helper = CommandLineHelper({'FOO': 'a,b'}, ['cmd', '--without-foo'])
+ value, option = helper.handle(foo)
+ self.assertEquals(NegativeOptionValue(), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--without-foo', option)
+
+ helper = CommandLineHelper({'FOO': ''}, ['cmd', '--with-bar=a,b'])
+ value, option = helper.handle(foo)
+ self.assertEquals(NegativeOptionValue(), value)
+ self.assertEquals('environment', value.origin)
+ self.assertEquals('FOO=', option)
+
+ helper = CommandLineHelper({'FOO': 'a,b'}, ['cmd', '--without-bar'])
+ value, option = helper.handle(foo)
+ self.assertEquals(PositiveOptionValue(('a', 'b')), value)
+ self.assertEquals('environment', value.origin)
+ self.assertEquals('FOO=a,b', option)
+
+ helper = CommandLineHelper({}, ['cmd', '--with-foo=a,b', 'FOO='])
+ value, option = helper.handle(foo)
+ self.assertEquals(NegativeOptionValue(), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('FOO=', option)
+
+ helper = CommandLineHelper({}, ['cmd', '--without-foo', 'FOO=a,b'])
+ value, option = helper.handle(foo)
+ self.assertEquals(PositiveOptionValue(('a', 'b')), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('FOO=a,b', option)
+
+ helper = CommandLineHelper({}, ['cmd', 'FOO=', '--with-foo=a,b'])
+ value, option = helper.handle(foo)
+ self.assertEquals(PositiveOptionValue(('a', 'b')), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--with-foo=a,b', option)
+
+ helper = CommandLineHelper({}, ['cmd', 'FOO=a,b', '--without-foo'])
+ value, option = helper.handle(foo)
+ self.assertEquals(NegativeOptionValue(), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--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.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
+ self.assertEquals('other-origin', value.origin)
+ self.assertEquals('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.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
+ self.assertEquals('other-origin', value.origin)
+ self.assertEquals('--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.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
+ self.assertEquals('other-origin', value.origin)
+ self.assertEquals('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.assertEquals(PositiveOptionValue(('a', 'b', 'c')), value)
+ self.assertEquals('other-origin', value.origin)
+ self.assertEquals('--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.assertEquals(PositiveOptionValue(), value)
+ self.assertEquals('command-line', value.origin)
+ self.assertEquals('--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 000000000..2ef93792b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
@@ -0,0 +1,1271 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import logging
+import os
+
+from StringIO import StringIO
+
+from mozunit import main
+
+from common import BaseConfigureTest
+from mozbuild.configure.util import Version
+from mozbuild.util import memoize
+from mozpack import path as mozpath
+from test_toolchain_helpers import (
+ FakeCompiler,
+ CompilerResult,
+)
+
+
+DEFAULT_C99 = {
+ '__STDC_VERSION__': '199901L',
+}
+
+DEFAULT_C11 = {
+ '__STDC_VERSION__': '201112L',
+}
+
+DEFAULT_CXX_97 = {
+ '__cplusplus': '199711L',
+}
+
+DEFAULT_CXX_11 = {
+ '__cplusplus': '201103L',
+}
+
+DEFAULT_CXX_14 = {
+ '__cplusplus': '201402L',
+}
+
+SUPPORTS_GNU99 = {
+ '-std=gnu99': DEFAULT_C99,
+}
+
+SUPPORTS_GNUXX11 = {
+ '-std=gnu++11': DEFAULT_CXX_11,
+}
+
+SUPPORTS_CXX14 = {
+ '-std=c++14': DEFAULT_CXX_14,
+}
+
+
+@memoize
+def GCC_BASE(version):
+ version = Version(version)
+ return FakeCompiler({
+ '__GNUC__': version.major,
+ '__GNUC_MINOR__': version.minor,
+ '__GNUC_PATCHLEVEL__': version.patch,
+ '__STDC__': 1,
+ '__ORDER_LITTLE_ENDIAN__': 1234,
+ '__ORDER_BIG_ENDIAN__': 4321,
+ })
+
+
+@memoize
+def GCC(version):
+ return GCC_BASE(version) + SUPPORTS_GNU99
+
+
+@memoize
+def GXX(version):
+ return GCC_BASE(version) + DEFAULT_CXX_97 + SUPPORTS_GNUXX11
+
+
+GCC_4_7 = GCC('4.7.3')
+GXX_4_7 = GXX('4.7.3')
+GCC_4_9 = GCC('4.9.3')
+GXX_4_9 = GXX('4.9.3')
+GCC_5 = GCC('5.2.1') + DEFAULT_C11
+GXX_5 = GXX('5.2.1')
+
+GCC_PLATFORM_LITTLE_ENDIAN = {
+ '__BYTE_ORDER__': 1234,
+}
+
+GCC_PLATFORM_BIG_ENDIAN = {
+ '__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_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)
+
+
+CLANG_3_3 = CLANG('3.3.0') + DEFAULT_C99
+CLANGXX_3_3 = CLANGXX('3.3.0')
+CLANG_3_6 = CLANG('3.6.2') + DEFAULT_C11
+CLANGXX_3_6 = CLANGXX('3.6.2') + {
+ '-std=gnu++11': {
+ '__has_feature(cxx_alignof)': '1',
+ },
+}
+
+
+def CLANG_PLATFORM(gcc_platform):
+ base = {
+ '--target=x86_64-linux-gnu': GCC_PLATFORM_X86_64_LINUX[None],
+ '--target=x86_64-darwin11.2.0': GCC_PLATFORM_X86_64_OSX[None],
+ '--target=i686-linux-gnu': GCC_PLATFORM_X86_LINUX[None],
+ '--target=i686-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),
+ },
+ '*.cpp': DEFAULT_CXX_97,
+ })
+
+
+VS_2013u2 = VS('18.00.30501')
+VS_2013u3 = VS('18.00.30723')
+VS_2015 = VS('19.00.23026')
+VS_2015u1 = VS('19.00.23506')
+VS_2015u2 = VS('19.00.23918')
+VS_2015u3 = VS('19.00.24213')
+
+VS_PLATFORM_X86 = {
+ '_M_IX86': 600,
+ '_WIN32': 1,
+}
+
+VS_PLATFORM_X86_64 = {
+ '_M_X64': 100,
+ '_WIN32': 1,
+ '_WIN64': 1,
+}
+
+# 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',
+ },
+ '-fms-compatibility-version=19.00.24213': VS('19.00.24213')[None],
+}
+
+CLANG_CL_PLATFORM_X86 = FakeCompiler(VS_PLATFORM_X86, GCC_PLATFORM_X86[None])
+CLANG_CL_PLATFORM_X86_64 = FakeCompiler(VS_PLATFORM_X86_64, GCC_PLATFORM_X86_64[None])
+
+
+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'))
+
+ 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)
+ 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.assertEquals((var, compiler), (var, result))
+ except SystemExit:
+ self.assertEquals((var, result),
+ (var, self.out.getvalue().strip()))
+ return
+
+
+class LinuxToolchainTest(BaseToolchainTest):
+ PATHS = {
+ '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86_64_LINUX,
+ '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86_64_LINUX,
+ '/usr/bin/gcc-4.7': GCC_4_7 + GCC_PLATFORM_X86_64_LINUX,
+ '/usr/bin/g++-4.7': GXX_4_7 + 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/clang': CLANG_3_6 + CLANG_PLATFORM_X86_64_LINUX,
+ '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86_64_LINUX,
+ '/usr/bin/clang-3.6': CLANG_3_6 + CLANG_PLATFORM_X86_64_LINUX,
+ '/usr/bin/clang++-3.6': CLANGXX_3_6 + 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 = ('Only GCC 4.8 or newer is supported '
+ '(found version 4.7.3).')
+ GXX_4_7_RESULT = GCC_4_7_RESULT
+ GCC_4_9_RESULT = CompilerResult(
+ flags=['-std=gnu99'],
+ version='4.9.3',
+ type='gcc',
+ compiler='/usr/bin/gcc',
+ language='C',
+ )
+ GXX_4_9_RESULT = CompilerResult(
+ flags=['-std=gnu++11'],
+ version='4.9.3',
+ type='gcc',
+ compiler='/usr/bin/g++',
+ language='C++',
+ )
+ GCC_5_RESULT = CompilerResult(
+ flags=['-std=gnu99'],
+ version='5.2.1',
+ type='gcc',
+ compiler='/usr/bin/gcc-5',
+ language='C',
+ )
+ GXX_5_RESULT = CompilerResult(
+ flags=['-std=gnu++11'],
+ version='5.2.1',
+ type='gcc',
+ compiler='/usr/bin/g++-5',
+ language='C++',
+ )
+ CLANG_3_3_RESULT = CompilerResult(
+ flags=[],
+ version='3.3.0',
+ type='clang',
+ compiler='/usr/bin/clang-3.3',
+ language='C',
+ )
+ CLANGXX_3_3_RESULT = 'Only clang/llvm 3.6 or newer is supported.'
+ CLANG_3_6_RESULT = CompilerResult(
+ flags=['-std=gnu99'],
+ version='3.6.2',
+ type='clang',
+ compiler='/usr/bin/clang',
+ language='C',
+ )
+ CLANGXX_3_6_RESULT = CompilerResult(
+ flags=['-std=gnu++11'],
+ version='3.6.2',
+ type='clang',
+ compiler='/usr/bin/clang++',
+ language='C++',
+ )
+
+ def test_gcc(self):
+ # We'll try gcc and clang, and find gcc first.
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT,
+ 'cxx_compiler': self.GXX_4_9_RESULT,
+ })
+
+ def test_unsupported_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_7_RESULT,
+ }, environ={
+ 'CC': 'gcc-4.7',
+ 'CXX': 'g++-4.7',
+ })
+
+ # Maybe this should be reporting the mismatched version instead.
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT,
+ 'cxx_compiler': self.GXX_4_7_RESULT,
+ }, environ={
+ 'CXX': 'g++-4.7',
+ })
+
+ def test_overridden_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_5_RESULT,
+ 'cxx_compiler': self.GXX_5_RESULT,
+ }, environ={
+ 'CC': 'gcc-5',
+ 'CXX': 'g++-5',
+ })
+
+ 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_5_RESULT,
+ 'cxx_compiler': self.GXX_5_RESULT,
+ }, environ={
+ 'CC': 'gcc-5',
+ })
+
+ def test_mismatched_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT,
+ 'cxx_compiler': (
+ 'The target C compiler is version 4.9.3, while the target '
+ 'C++ compiler is version 5.2.1. Need to use the same compiler '
+ 'version.'),
+ }, environ={
+ 'CXX': 'g++-5',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT,
+ 'cxx_compiler': self.GXX_4_9_RESULT,
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': (
+ 'The host C compiler is version 4.9.3, while the host '
+ 'C++ compiler is version 5.2.1. Need to use the same compiler '
+ 'version.'),
+ }, environ={
+ 'HOST_CXX': 'g++-5',
+ })
+
+ def test_mismatched_compiler(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT,
+ 'cxx_compiler': (
+ 'The target C compiler is gcc, while the target C++ compiler '
+ 'is clang. Need to use the same compiler suite.'),
+ }, environ={
+ 'CXX': 'clang++',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT,
+ 'cxx_compiler': self.GXX_4_9_RESULT,
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': (
+ 'The host C compiler is gcc, while the host C++ compiler '
+ 'is clang. Need to use the same compiler suite.'),
+ }, environ={
+ 'HOST_CXX': 'clang++',
+ })
+
+ 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.GCC_4_9_RESULT,
+ 'cxx_compiler': '`%s` is not a C++ compiler.'
+ % mozpath.abspath('/usr/bin/gcc'),
+ }, environ={
+ 'CXX': 'gcc',
+ })
+
+ 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 self.PATHS.iteritems()
+ if os.path.basename(k) not in ('gcc', 'g++')
+ }
+ self.do_toolchain_test(paths, {
+ 'c_compiler': self.CLANG_3_6_RESULT,
+ 'cxx_compiler': self.CLANGXX_3_6_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_3_6_RESULT + {
+ 'compiler': '/usr/bin/clang-3.6',
+ },
+ 'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+ 'compiler': '/usr/bin/clang++-3.6',
+ },
+ }, environ={
+ 'CC': 'clang-3.6',
+ })
+
+ def test_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',
+ })
+
+ 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 self.PATHS.iteritems()
+ 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.CLANG_3_6_RESULT + {
+ 'compiler': '/opt/clang/bin/clang',
+ },
+ 'cxx_compiler': self.CLANGXX_3_6_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.CLANG_3_6_RESULT + {
+ 'compiler': '/usr/bin/afl-clang-fast',
+ },
+ 'cxx_compiler': self.CLANGXX_3_6_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.CLANG_3_6_RESULT,
+ 'cxx_compiler': self.CLANGXX_3_6_RESULT,
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_RESULT,
+ }, environ={
+ 'CC': 'clang',
+ 'HOST_CC': 'gcc',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.CLANG_3_6_RESULT,
+ 'cxx_compiler': self.CLANGXX_3_6_RESULT,
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_RESULT,
+ }, environ={
+ 'CC': 'clang',
+ 'CXX': 'clang++',
+ 'HOST_CC': 'gcc',
+ })
+
+
+class LinuxSimpleCrossToolchainTest(BaseToolchainTest):
+ TARGET = 'i686-pc-linux-gnu'
+ PATHS = LinuxToolchainTest.PATHS
+ GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+ GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
+ CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+ CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+
+ def test_cross_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT + {
+ 'flags': ['-m32']
+ },
+ 'cxx_compiler': self.GXX_4_9_RESULT + {
+ 'flags': ['-m32']
+ },
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_RESULT,
+ })
+
+ def test_cross_clang(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.CLANG_3_6_RESULT + {
+ 'flags': ['--target=i686-linux-gnu'],
+ },
+ 'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+ 'flags': ['--target=i686-linux-gnu'],
+ },
+ 'host_c_compiler': self.CLANG_3_6_RESULT,
+ 'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+ }, environ={
+ 'CC': 'clang',
+ })
+
+
+class LinuxX86_64CrossToolchainTest(BaseToolchainTest):
+ HOST = 'i686-pc-linux-gnu'
+ TARGET = 'x86_64-pc-linux-gnu'
+ PATHS = {
+ '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86_LINUX,
+ '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86_LINUX,
+ '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86_LINUX,
+ '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86_LINUX,
+ }
+ GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+ GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
+ CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+ CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+
+ def test_cross_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_9_RESULT + {
+ 'flags': ['-m64']
+ },
+ 'cxx_compiler': self.GXX_4_9_RESULT + {
+ 'flags': ['-m64']
+ },
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_RESULT,
+ })
+
+ def test_cross_clang(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.CLANG_3_6_RESULT + {
+ 'flags': ['--target=x86_64-linux-gnu'],
+ },
+ 'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+ 'flags': ['--target=x86_64-linux-gnu'],
+ },
+ 'host_c_compiler': self.CLANG_3_6_RESULT,
+ 'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+ }, environ={
+ 'CC': 'clang',
+ })
+
+
+class OSXToolchainTest(BaseToolchainTest):
+ HOST = 'x86_64-apple-darwin11.2.0'
+ PATHS = {
+ '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86_64_OSX,
+ '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86_64_OSX,
+ '/usr/bin/gcc-4.7': GCC_4_7 + GCC_PLATFORM_X86_64_OSX,
+ '/usr/bin/g++-4.7': GXX_4_7 + GCC_PLATFORM_X86_64_OSX,
+ '/usr/bin/gcc-5': GCC_5 + GCC_PLATFORM_X86_64_OSX,
+ '/usr/bin/g++-5': GXX_5 + GCC_PLATFORM_X86_64_OSX,
+ '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86_64_OSX,
+ '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86_64_OSX,
+ '/usr/bin/clang-3.6': CLANG_3_6 + CLANG_PLATFORM_X86_64_OSX,
+ '/usr/bin/clang++-3.6': CLANGXX_3_6 + CLANG_PLATFORM_X86_64_OSX,
+ '/usr/bin/clang-3.3': CLANG_3_3 + CLANG_PLATFORM_X86_64_OSX,
+ '/usr/bin/clang++-3.3': CLANGXX_3_3 + CLANG_PLATFORM_X86_64_OSX,
+ }
+ CLANG_3_3_RESULT = LinuxToolchainTest.CLANG_3_3_RESULT
+ CLANGXX_3_3_RESULT = LinuxToolchainTest.CLANGXX_3_3_RESULT
+ CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+ CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+ GCC_4_7_RESULT = LinuxToolchainTest.GCC_4_7_RESULT
+ GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT
+ GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT
+
+ def test_clang(self):
+ # We only try clang because gcc is known not to work.
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.CLANG_3_6_RESULT,
+ 'cxx_compiler': self.CLANGXX_3_6_RESULT,
+ })
+
+ def test_not_gcc(self):
+ # We won't pick GCC if it's the only thing available.
+ paths = {
+ k: v for k, v in self.PATHS.iteritems()
+ 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):
+ # 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',
+ })
+
+ 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_5_RESULT,
+ 'cxx_compiler': self.GXX_5_RESULT,
+ }, environ={
+ 'CC': 'gcc-5',
+ 'CXX': 'g++-5',
+ })
+
+ def test_forced_unsupported_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_7_RESULT,
+ }, environ={
+ 'CC': 'gcc-4.7',
+ 'CXX': 'g++-4.7',
+ })
+
+
+class WindowsToolchainTest(BaseToolchainTest):
+ HOST = 'i686-pc-mingw32'
+
+ # For the purpose of this test, it doesn't matter that the paths are not
+ # real Windows paths.
+ PATHS = {
+ '/opt/VS_2013u2/bin/cl': VS_2013u2 + VS_PLATFORM_X86,
+ '/opt/VS_2013u3/bin/cl': VS_2013u3 + VS_PLATFORM_X86,
+ '/opt/VS_2015/bin/cl': VS_2015 + VS_PLATFORM_X86,
+ '/opt/VS_2015u1/bin/cl': VS_2015u1 + VS_PLATFORM_X86,
+ '/opt/VS_2015u2/bin/cl': VS_2015u2 + VS_PLATFORM_X86,
+ '/usr/bin/cl': VS_2015u3 + VS_PLATFORM_X86,
+ '/usr/bin/clang-cl': CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86,
+ '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86_WIN,
+ '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86_WIN,
+ '/usr/bin/gcc-4.7': GCC_4_7 + GCC_PLATFORM_X86_WIN,
+ '/usr/bin/g++-4.7': GXX_4_7 + GCC_PLATFORM_X86_WIN,
+ '/usr/bin/gcc-5': GCC_5 + GCC_PLATFORM_X86_WIN,
+ '/usr/bin/g++-5': GXX_5 + GCC_PLATFORM_X86_WIN,
+ '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86_WIN,
+ '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86_WIN,
+ '/usr/bin/clang-3.6': CLANG_3_6 + CLANG_PLATFORM_X86_WIN,
+ '/usr/bin/clang++-3.6': CLANGXX_3_6 + 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,
+ }
+
+ VS_2013u2_RESULT = (
+ 'This version (18.00.30501) of the MSVC compiler is not supported.\n'
+ 'You must install Visual C++ 2015 Update 3 or newer in order to build.\n'
+ 'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
+ VS_2013u3_RESULT = (
+ 'This version (18.00.30723) of the MSVC compiler is not supported.\n'
+ 'You must install Visual C++ 2015 Update 3 or newer in order to build.\n'
+ 'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
+ VS_2015_RESULT = (
+ 'This version (19.00.23026) of the MSVC compiler is not supported.\n'
+ 'You must install Visual C++ 2015 Update 3 or newer in order to build.\n'
+ 'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
+ VS_2015u1_RESULT = (
+ 'This version (19.00.23506) of the MSVC compiler is not supported.\n'
+ 'You must install Visual C++ 2015 Update 3 or newer in order to build.\n'
+ 'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
+ VS_2015u2_RESULT = (
+ 'This version (19.00.23918) of the MSVC compiler is not supported.\n'
+ 'You must install Visual C++ 2015 Update 3 or newer in order to build.\n'
+ 'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
+ VS_2015u3_RESULT = CompilerResult(
+ flags=[],
+ version='19.00.24213',
+ type='msvc',
+ compiler='/usr/bin/cl',
+ language='C',
+ )
+ VSXX_2015u3_RESULT = CompilerResult(
+ flags=[],
+ version='19.00.24213',
+ type='msvc',
+ compiler='/usr/bin/cl',
+ language='C++',
+ )
+ CLANG_CL_3_9_RESULT = CompilerResult(
+ flags=['-Xclang', '-std=gnu99',
+ '-fms-compatibility-version=19.00.24213', '-fallback'],
+ version='19.00.24213',
+ type='clang-cl',
+ compiler='/usr/bin/clang-cl',
+ language='C',
+ )
+ CLANGXX_CL_3_9_RESULT = CompilerResult(
+ flags=['-Xclang', '-std=c++14',
+ '-fms-compatibility-version=19.00.24213', '-fallback'],
+ version='19.00.24213',
+ 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_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+ CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+ GCC_4_7_RESULT = LinuxToolchainTest.GCC_4_7_RESULT
+ GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+ GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
+ GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT
+ GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT
+
+ # VS2015u3 or greater is required.
+ def test_msvc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.VS_2015u3_RESULT,
+ 'cxx_compiler': self.VSXX_2015u3_RESULT,
+ })
+
+ def test_unsupported_msvc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.VS_2015u2_RESULT,
+ }, environ={
+ 'CC': '/opt/VS_2015u2/bin/cl',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.VS_2015u1_RESULT,
+ }, environ={
+ 'CC': '/opt/VS_2015u1/bin/cl',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.VS_2015_RESULT,
+ }, environ={
+ 'CC': '/opt/VS_2015/bin/cl',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.VS_2013u3_RESULT,
+ }, environ={
+ 'CC': '/opt/VS_2013u3/bin/cl',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.VS_2013u2_RESULT,
+ }, environ={
+ 'CC': '/opt/VS_2013u2/bin/cl',
+ })
+
+ def test_clang_cl(self):
+ # We'll pick clang-cl if msvc can't be found.
+ paths = {
+ k: v for k, v in self.PATHS.iteritems()
+ if os.path.basename(k) != 'cl'
+ }
+ self.do_toolchain_test(paths, {
+ 'c_compiler': self.CLANG_CL_3_9_RESULT,
+ 'cxx_compiler': self.CLANGXX_CL_3_9_RESULT,
+ })
+
+ def test_gcc(self):
+ # We'll pick GCC if msvc and clang-cl can't be found.
+ paths = {
+ k: v for k, v in self.PATHS.iteritems()
+ if os.path.basename(k) not in ('cl', 'clang-cl')
+ }
+ self.do_toolchain_test(paths, {
+ 'c_compiler': self.GCC_4_9_RESULT,
+ 'cxx_compiler': self.GXX_4_9_RESULT,
+ })
+
+ def test_overridden_unsupported_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.GCC_4_7_RESULT,
+ }, environ={
+ 'CC': 'gcc-4.7',
+ 'CXX': 'g++-4.7',
+ })
+
+ def test_clang(self):
+ # We'll pick clang if nothing else is found.
+ paths = {
+ k: v for k, v in self.PATHS.iteritems()
+ if os.path.basename(k) not in ('cl', 'clang-cl', 'gcc')
+ }
+ self.do_toolchain_test(paths, {
+ 'c_compiler': self.CLANG_3_6_RESULT,
+ 'cxx_compiler': self.CLANGXX_3_6_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',
+ })
+
+ def test_cannot_cross(self):
+ paths = {
+ '/usr/bin/cl': VS_2015u3 + VS_PLATFORM_X86_64,
+ }
+ self.do_toolchain_test(paths, {
+ 'c_compiler': ('Target C compiler target CPU (x86_64) '
+ 'does not match --target CPU (i686)'),
+ })
+
+
+class Windows64ToolchainTest(WindowsToolchainTest):
+ HOST = 'x86_64-pc-mingw32'
+
+ # For the purpose of this test, it doesn't matter that the paths are not
+ # real Windows paths.
+ PATHS = {
+ '/opt/VS_2013u2/bin/cl': VS_2013u2 + VS_PLATFORM_X86_64,
+ '/opt/VS_2013u3/bin/cl': VS_2013u3 + VS_PLATFORM_X86_64,
+ '/opt/VS_2015/bin/cl': VS_2015 + VS_PLATFORM_X86_64,
+ '/opt/VS_2015u1/bin/cl': VS_2015u1 + VS_PLATFORM_X86_64,
+ '/opt/VS_2015u2/bin/cl': VS_2015u2 + VS_PLATFORM_X86_64,
+ '/usr/bin/cl': VS_2015u3 + VS_PLATFORM_X86_64,
+ '/usr/bin/clang-cl': CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86_64,
+ '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86_64_WIN,
+ '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86_64_WIN,
+ '/usr/bin/gcc-4.7': GCC_4_7 + GCC_PLATFORM_X86_64_WIN,
+ '/usr/bin/g++-4.7': GXX_4_7 + GCC_PLATFORM_X86_64_WIN,
+ '/usr/bin/gcc-5': GCC_5 + GCC_PLATFORM_X86_64_WIN,
+ '/usr/bin/g++-5': GXX_5 + GCC_PLATFORM_X86_64_WIN,
+ '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86_64_WIN,
+ '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86_64_WIN,
+ '/usr/bin/clang-3.6': CLANG_3_6 + CLANG_PLATFORM_X86_64_WIN,
+ '/usr/bin/clang++-3.6': CLANGXX_3_6 + 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,
+ }
+
+ def test_cannot_cross(self):
+ paths = {
+ '/usr/bin/cl': VS_2015u3 + VS_PLATFORM_X86,
+ }
+ self.do_toolchain_test(paths, {
+ 'c_compiler': ('Target C compiler target CPU (x86) '
+ 'does not match --target CPU (x86_64)'),
+ })
+
+
+class LinuxCrossCompileToolchainTest(BaseToolchainTest):
+ TARGET = 'arm-unknown-linux-gnu'
+ PATHS = {
+ '/usr/bin/arm-linux-gnu-gcc': GCC_4_9 + GCC_PLATFORM_ARM_LINUX,
+ '/usr/bin/arm-linux-gnu-g++': GXX_4_9 + GCC_PLATFORM_ARM_LINUX,
+ '/usr/bin/arm-linux-gnu-gcc-4.7': GCC_4_7 + GCC_PLATFORM_ARM_LINUX,
+ '/usr/bin/arm-linux-gnu-g++-4.7': GXX_4_7 + 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,
+ }
+ PATHS.update(LinuxToolchainTest.PATHS)
+ ARM_GCC_4_7_RESULT = LinuxToolchainTest.GXX_4_7_RESULT
+ ARM_GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT + {
+ 'compiler': '/usr/bin/arm-linux-gnu-gcc-5',
+ }
+ ARM_GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT + {
+ 'compiler': '/usr/bin/arm-linux-gnu-g++-5',
+ }
+ CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+ CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+ GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+ GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_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,
+ },
+ },
+ 'mips64-unknown-linux-gnuabi64': big_endian + {
+ '__mips64': 1,
+ '__mips__': 1,
+ },
+ 'mips-unknown-linux-gnu': big_endian + {
+ '__mips__': 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': GCC_4_9 + self.PLATFORMS[host],
+ '/usr/bin/g++': GXX_4_9 + self.PLATFORMS[host],
+ }
+ cross_flags = {
+ 'flags': ['-m64' if '64' in target else '-m32']
+ }
+ self.do_toolchain_test(paths, {
+ 'c_compiler': self.GCC_4_9_RESULT + cross_flags,
+ 'cxx_compiler': self.GXX_4_9_RESULT + cross_flags,
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_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': GCC_4_9 + self.PLATFORMS[host],
+ '/usr/bin/g++': GXX_4_9 + 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: GCC_4_9 + self.PLATFORMS[target],
+ '%s-g++' % toolchain_prefix: GXX_4_9 + self.PLATFORMS[target],
+ })
+ self.do_toolchain_test(paths, {
+ 'c_compiler': self.GCC_4_9_RESULT + {
+ 'compiler': '%s-gcc' % toolchain_prefix,
+ },
+ 'cxx_compiler': self.GXX_4_9_RESULT + {
+ 'compiler': '%s-g++' % toolchain_prefix,
+ },
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_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': GCC_4_9 + self.PLATFORMS['mips-unknown-linux-gnu'],
+ '/usr/bin/g++': GXX_4_9 + 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_5_RESULT,
+ 'cxx_compiler': self.ARM_GXX_5_RESULT,
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_RESULT,
+ }, environ={
+ 'CC': 'arm-linux-gnu-gcc-5',
+ 'CXX': 'arm-linux-gnu-g++-5',
+ })
+
+ def test_overridden_unsupported_cross_gcc(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.ARM_GCC_4_7_RESULT,
+ }, environ={
+ 'CC': 'arm-linux-gnu-gcc-4.7',
+ 'CXX': 'arm-linux-gnu-g++-4.7',
+ })
+
+ 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_5_RESULT,
+ 'cxx_compiler': self.ARM_GXX_5_RESULT,
+ 'host_c_compiler': self.GCC_4_9_RESULT,
+ 'host_cxx_compiler': self.GXX_4_9_RESULT,
+ }, environ={
+ 'CC': 'arm-linux-gnu-gcc-5',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.ARM_GCC_5_RESULT,
+ 'cxx_compiler': self.ARM_GXX_5_RESULT,
+ 'host_c_compiler': self.CLANG_3_6_RESULT,
+ 'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+ }, environ={
+ 'CC': 'arm-linux-gnu-gcc-5',
+ 'HOST_CC': 'clang',
+ })
+
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.ARM_GCC_5_RESULT,
+ 'cxx_compiler': self.ARM_GXX_5_RESULT,
+ 'host_c_compiler': self.CLANG_3_6_RESULT,
+ 'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+ }, environ={
+ 'CC': 'arm-linux-gnu-gcc-5',
+ 'CXX': 'arm-linux-gnu-g++-5',
+ 'HOST_CC': 'clang',
+ })
+
+ def test_cross_clang(self):
+ cross_clang_result = self.CLANG_3_6_RESULT + {
+ 'flags': ['--target=arm-linux-gnu'],
+ }
+ cross_clangxx_result = self.CLANGXX_3_6_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.CLANG_3_6_RESULT,
+ 'host_cxx_compiler': self.CLANGXX_3_6_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.CLANG_3_6_RESULT,
+ 'host_cxx_compiler': self.CLANGXX_3_6_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.CLANG_3_6_RESULT + {
+ 'compiler': '/usr/bin/afl-clang-fast',
+ }
+ afl_clangxx_result = self.CLANGXX_3_6_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 = LinuxToolchainTest.PATHS
+ CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+ CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+
+ def test_osx_cross(self):
+ self.do_toolchain_test(self.PATHS, {
+ 'c_compiler': self.CLANG_3_6_RESULT + {
+ 'flags': ['--target=i686-darwin11.2.0'],
+ },
+ 'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+ 'flags': ['--target=i686-darwin11.2.0'],
+ },
+ 'host_c_compiler': self.CLANG_3_6_RESULT,
+ 'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+ }, environ={
+ 'CC': 'clang',
+ })
+
+ 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',
+ })
+
+
+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 000000000..8ec33a8b7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
@@ -0,0 +1,437 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import copy
+import re
+import types
+import unittest
+
+from fnmatch import fnmatch
+from StringIO import StringIO
+from textwrap import dedent
+
+from mozunit import (
+ main,
+ MockedOpen,
+)
+
+from mozbuild.preprocessor import Preprocessor
+from mozbuild.util import ReadOnlyNamespace
+from mozpack import path as mozpath
+
+
+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 = re.compile('(__has_feature)\(([^\)]*)\)')
+
+ 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, types.StringTypes):
+ 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(expr):
+ return self.HAS_FEATURE.sub(r'\1\2', expr)
+ self.context = self.Context(
+ (normalize_has_feature(k), normalize_numbers(v))
+ for k, v in context.iteritems()
+ )
+ try:
+ return Preprocessor.do_if(self, normalize_has_feature(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 = str(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.assertEquals(pp.out.getvalue(), '1 . 2 . c "D"')
+
+ 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.assertEquals('IFDEF_A\nIF_A\nIF_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 definition.itervalues()):
+ definition = {None: definition}
+ for key, value in definition.iteritems():
+ self.setdefault(key, {}).update(value)
+
+ def __call__(self, stdin, args):
+ files = [arg for arg in args if not arg.startswith('-')]
+ flags = [arg for arg in args if arg.startswith('-')]
+ if '-E' in flags:
+ assert len(files) == 1
+ file = files[0]
+ pp = CompilerPreprocessor(self[None])
+
+ def apply_defn(defn):
+ for k, v in defn.iteritems():
+ if v is False:
+ if k in pp.context:
+ del pp.context[k]
+ else:
+ pp.context[k] = v
+
+ for glob, defn in self.iteritems():
+ 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.assertEquals(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.assertEquals(compiler(None, ['-E', 'file']),
+ (0, '1 2 C', ''))
+ self.assertEquals(compiler(None, ['-E', '-foo', 'file']),
+ (0, '1 2 foo', ''))
+ self.assertEquals(compiler(None, ['-E', '-bar', 'file']),
+ (0, '1 bar bar', ''))
+ self.assertEquals(compiler(None, ['-E', '-qux', 'file']),
+ (0, '1 B C', ''))
+ self.assertEquals(compiler(None, ['-E', '-foo', '-bar', 'file']),
+ (0, '1 bar bar', ''))
+ self.assertEquals(compiler(None, ['-E', '-bar', '-foo', 'file']),
+ (0, '1 bar foo', ''))
+ self.assertEquals(compiler(None, ['-E', '-bar', '-qux', 'file']),
+ (0, '1 B bar', ''))
+ self.assertEquals(compiler(None, ['-E', '-qux', '-bar', 'file']),
+ (0, '1 bar bar', ''))
+ self.assertEquals(compiler(None, ['-E', 'file.c']),
+ (0, '1 42 C', ''))
+ self.assertEquals(compiler(None, ['-E', '-bar', 'file.c']),
+ (0, '1 bar bar', ''))
+
+ def test_multiple_definitions(self):
+ compiler = FakeCompiler({
+ 'A': 1,
+ 'B': 2,
+ }, {
+ 'C': 3,
+ })
+
+ self.assertEquals(compiler, {
+ None: {
+ 'A': 1,
+ 'B': 2,
+ 'C': 3,
+ },
+ })
+ compiler = FakeCompiler({
+ 'A': 1,
+ 'B': 2,
+ }, {
+ 'B': 4,
+ 'C': 3,
+ })
+
+ self.assertEquals(compiler, {
+ None: {
+ 'A': 1,
+ 'B': 4,
+ 'C': 3,
+ },
+ })
+ compiler = FakeCompiler({
+ 'A': 1,
+ 'B': 2,
+ }, {
+ None: {
+ 'B': 4,
+ 'C': 3,
+ },
+ '-foo': {
+ 'D': 5,
+ },
+ })
+
+ self.assertEquals(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.assertEquals(compiler, {
+ None: {
+ 'A': 1,
+ 'B': 2,
+ },
+ '-foo': {
+ 'D': 5,
+ },
+ '-bar': {
+ 'E': 6,
+ },
+ })
+
+
+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 other.iteritems():
+ if k == 'flags':
+ result.setdefault(k, []).extend(v)
+ else:
+ result[k] = v
+ return CompilerResult(**result)
+
+
+class TestCompilerResult(unittest.TestCase):
+ def test_compiler_result(self):
+ result = CompilerResult()
+ self.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(result.flags, ['-std=gnu99'])
+
+ result3 = result + {
+ 'compiler': '/usr/bin/gcc-4.7',
+ 'version': '4.7.3',
+ 'flags': ['-m32'],
+ }
+ self.assertEquals(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 000000000..30dc022b7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py
@@ -0,0 +1,67 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import os
+
+from buildconfig import topsrcdir
+from common import BaseConfigureTest
+from mozunit import main
+
+
+class TestToolkitMozConfigure(BaseConfigureTest):
+ def test_necko_protocols(self):
+ def get_value(arg):
+ sandbox = self.get_sandbox({}, {}, [arg])
+ return sandbox._value_for(sandbox['necko_protocols'])
+
+ default_protocols = get_value('')
+ self.assertNotEqual(default_protocols, ())
+
+ # Backwards compatibility
+ self.assertEqual(get_value('--enable-necko-protocols'),
+ default_protocols)
+
+ self.assertEqual(get_value('--enable-necko-protocols=yes'),
+ default_protocols)
+
+ self.assertEqual(get_value('--enable-necko-protocols=all'),
+ default_protocols)
+
+ self.assertEqual(get_value('--enable-necko-protocols=default'),
+ default_protocols)
+
+ self.assertEqual(get_value('--enable-necko-protocols='), ())
+
+ self.assertEqual(get_value('--enable-necko-protocols=no'), ())
+
+ self.assertEqual(get_value('--enable-necko-protocols=none'), ())
+
+ self.assertEqual(get_value('--disable-necko-protocols'), ())
+
+ self.assertEqual(get_value('--enable-necko-protocols=http'),
+ ('http',))
+
+ self.assertEqual(get_value('--enable-necko-protocols=http,about'),
+ ('about', 'http'))
+
+ self.assertEqual(get_value('--enable-necko-protocols=http,none'), ())
+
+ self.assertEqual(get_value('--enable-necko-protocols=-http'), ())
+
+ self.assertEqual(get_value('--enable-necko-protocols=none,http'),
+ ('http',))
+
+ self.assertEqual(
+ get_value('--enable-necko-protocols=all,-http,-about'),
+ tuple(p for p in default_protocols if p not in ('http', 'about')))
+
+ self.assertEqual(
+ get_value('--enable-necko-protocols=default,-http,-about'),
+ tuple(p for p in default_protocols if p not in ('http', 'about')))
+
+
+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 000000000..38b3c636e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_util.py
@@ -0,0 +1,558 @@
+# 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 __future__ import absolute_import, print_function, unicode_literals
+
+import logging
+import os
+import tempfile
+import textwrap
+import unittest
+import sys
+
+from StringIO import StringIO
+
+from mozunit import main
+from mozpack import path as mozpath
+
+from mozbuild.configure.util import (
+ ConfigureOutputHandler,
+ getpreferredencoding,
+ LineIO,
+ Version,
+)
+
+from mozbuild.configure import (
+ ConfigureSandbox,
+)
+
+from mozbuild.util import exec_
+
+from buildconfig import topsrcdir
+from common import ConfigureTestSandbox
+
+
+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({}, {}, [], 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.assertEquals(status, 0)
+ quote_char = "'"
+ if getpreferredencoding().lower() == 'utf-8':
+ quote_char = '\u00B4'.encode('utf-8')
+ self.assertEquals(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 000000000..e69de29bb
--- /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 000000000..7a6608ec8
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/controller/test_ccachestats.py
@@ -0,0 +1,208 @@
+# 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 __future__ import unicode_literals
+
+import unittest
+
+from mozunit import main
+
+from mozbuild.controller.building import CCacheStats
+
+
+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
+ """
+
+ 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.cache_dir, "/home/tlin/.ccache")
+ 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.assertTrue(stat3)
+ self.assertTrue(stats_diff)
+
+ def test_cache_size_shrinking(self):
+ stat4 = CCacheStats(self.STAT4)
+ stat5 = CCacheStats(self.STAT5)
+ stats_diff = stat5 - stat4
+ self.assertTrue(stat4)
+ self.assertTrue(stat5)
+ self.assertTrue(stats_diff)
+
+ def test_stats_version33(self):
+ stat3 = CCacheStats(self.STAT3)
+ stat6 = CCacheStats(self.STAT6)
+ stats_diff = stat6 - stat3
+ self.assertTrue(stat6)
+ self.assertTrue(stat3)
+ self.assertTrue(stats_diff)
+
+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 000000000..997f467ec
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/controller/test_clobber.py
@@ -0,0 +1,213 @@
+# 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 __future__ import unicode_literals
+
+import os
+import shutil
+import tempfile
+import unittest
+
+from StringIO import StringIO
+
+from mozunit import main
+
+from mozbuild.controller.clobber import Clobberer
+from mozbuild.controller.clobber import main as clobber
+
+
+class TestClobberer(unittest.TestCase):
+ def setUp(self):
+ self._temp_dirs = []
+
+ return unittest.TestCase.setUp(self)
+
+ def tearDown(self):
+ 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()
+ 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())
+
+ # Side-effect is topobjdir is created with CLOBBER file touched.
+ required, performed, reason = c.maybe_do_clobber(os.getcwd(), True)
+ self.assertFalse(required)
+ self.assertFalse(performed)
+ self.assertIsNone(reason)
+
+ self.assertTrue(os.path.isdir(tmp))
+ self.assertTrue(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.assertTrue(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.assertTrue(os.path.exists(c.obj_clobber))
+ self.assertGreaterEqual(os.path.getmtime(c.obj_clobber),
+ os.path.getmtime(c.src_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']
+
+ s = StringIO()
+ status = clobber([topsrcdir, topobjdir], env, os.getcwd(), s)
+ self.assertEqual(status, 1)
+ self.assertIn('Automatic clobbering is not enabled', s.getvalue())
+ self.assertTrue(os.path.exists(dummy_file))
+
+ # Check auto clobber opt-in works
+ env['AUTOCLOBBER'] = '1'
+
+ s = StringIO()
+ status = clobber([topsrcdir, topobjdir], env, os.getcwd(), s)
+ self.assertEqual(status, 0)
+ self.assertIn('Successfully completed auto clobber', s.getvalue())
+ 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 000000000..e69de29bb
--- /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 000000000..d4d8109b6
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..db64bf2ee
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/__init__.py
diff --git a/python/mozbuild/mozbuild/test/frontend/data/android-res-dirs/dir1/foo b/python/mozbuild/mozbuild/test/frontend/data/android-res-dirs/dir1/foo
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/android-res-dirs/dir1/foo
diff --git a/python/mozbuild/mozbuild/test/frontend/data/android-res-dirs/moz.build b/python/mozbuild/mozbuild/test/frontend/data/android-res-dirs/moz.build
new file mode 100644
index 000000000..242a3628d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/android-res-dirs/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/
+
+ANDROID_RES_DIRS += [
+ '/dir1',
+ '!/dir2',
+ '%/dir3',
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/binary-components/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/binary-components/bar/moz.build
new file mode 100644
index 000000000..2946e42aa
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/binary-components/bar/moz.build
@@ -0,0 +1,2 @@
+Component('bar')
+NO_COMPONENTS_MANIFEST = True
diff --git a/python/mozbuild/mozbuild/test/frontend/data/binary-components/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/binary-components/foo/moz.build
new file mode 100644
index 000000000..8611a74be
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/binary-components/foo/moz.build
@@ -0,0 +1 @@
+Component('foo')
diff --git a/python/mozbuild/mozbuild/test/frontend/data/binary-components/moz.build b/python/mozbuild/mozbuild/test/frontend/data/binary-components/moz.build
new file mode 100644
index 000000000..1776d0514
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/binary-components/moz.build
@@ -0,0 +1,10 @@
+@template
+def Component(name):
+ LIBRARY_NAME = name
+ FORCE_SHARED_LIB = True
+ IS_COMPONENT = True
+
+DIRS += [
+ 'foo',
+ 'bar',
+]
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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..251bc53ea
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/branding-files/moz.build
@@ -0,0 +1,13 @@
+# 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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/branding-files/quux.icns
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 000000000..f53dd9454
--- /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 000000000..99d10b1a6
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..01b3a35a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/crate-dependency-path-resolution/moz.build
@@ -0,0 +1,18 @@
+# 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 000000000..c347f8c08
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
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 000000000..10a4ded0a
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..ccb0d5e36
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/defines/moz.build
@@ -0,0 +1,14 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+value = 'xyz'
+DEFINES = {
+ 'FOO': True,
+}
+
+DEFINES['BAZ'] = '"abcd"'
+DEFINES.update({
+ 'BAR': 7,
+ 'VALUE': value,
+ 'QUX': False,
+})
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 000000000..e69de29bb
--- /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 000000000..cbd2c942b
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..cbd2c942b
--- /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 000000000..e69de29bb
--- /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 000000000..259d96fcd
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e0dfce264
--- /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 000000000..e69de29bb
--- /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 000000000..e1f93aab5
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..666fbeb81
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..d6a9799b8
--- /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 000000000..990453f7c
--- /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 000000000..cee286445
--- /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 = ('Core', 'Build Config')
+ 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 000000000..206bf661b
--- /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 000000000..4ecb1112c
--- /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 000000000..7994d4a38
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/bug_component/simple/moz.build
@@ -0,0 +1,2 @@
+with Files('*'):
+ BUG_COMPONENT = ('Core', 'Build Config')
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 000000000..0a88e09e7
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-info/moz.build
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/module.js b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/module.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/module.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/moz.build
new file mode 100644
index 000000000..8915edc12
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/moz.build
@@ -0,0 +1,6 @@
+XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
+REFTEST_MANIFESTS += ['tests/reftests/reftest.list']
+
+EXTRA_JS_MODULES += [
+ 'module.js',
+] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest-stylo.list b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest-stylo.list
new file mode 100644
index 000000000..252a5b986
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest-stylo.list
@@ -0,0 +1,2 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+== test1.html test1.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest.list b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest.list
new file mode 100644
index 000000000..504d45973
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/reftest.list
@@ -0,0 +1 @@
+== test1.html test1-ref.html \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1-ref.html b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1-ref.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1-ref.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1.html b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/reftests/test1.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/test_default_mod.js b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/test_default_mod.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/test_default_mod.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/xpcshell.ini b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..55c18a250
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/default/tests/xpcshell/xpcshell.ini
@@ -0,0 +1 @@
+[test_default_mod.js] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/moz.build
new file mode 100644
index 000000000..faff2a173
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/moz.build
@@ -0,0 +1,4 @@
+DIRS += [
+ 'default',
+ 'simple',
+] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/base.cpp b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/base.cpp
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/base.cpp
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/browser.ini b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/browser.ini
new file mode 100644
index 000000000..f284de043
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/browser.ini
@@ -0,0 +1 @@
+[test_mod.js] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/test_mod.js b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/test_mod.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/browser/test_mod.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/moz.build
new file mode 100644
index 000000000..cbce16e1d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/moz.build
@@ -0,0 +1,22 @@
+with Files('src/*'):
+ IMPACTED_TESTS.files += [
+ 'tests/test_general.html',
+ ]
+
+with Files('src/module.jsm'):
+ IMPACTED_TESTS.files += [
+ 'browser/**.js',
+ ]
+
+with Files('base.cpp'):
+ IMPACTED_TESTS.files += [
+ '/default/tests/xpcshell/test_default_mod.js',
+ 'tests/*',
+ ]
+
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
+
+UNIFIED_SOURCES += ['base.cpp']
+DIRS += ['src']
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/module.jsm b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/module.jsm
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/module.jsm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/moz.build
new file mode 100644
index 000000000..e0c49f129
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/src/moz.build
@@ -0,0 +1,3 @@
+EXTRA_JS_MODULES += [
+ 'module.jsm',
+] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/mochitest.ini
new file mode 100644
index 000000000..662566abd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/mochitest.ini
@@ -0,0 +1,2 @@
+[test_general.html]
+[test_specific.html] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/moz.build
new file mode 100644
index 000000000..8ef3a9fd8
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/moz.build
@@ -0,0 +1 @@
+MOCHITEST_MANIFESTS += ['mochitest.ini'] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_general.html b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_general.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_general.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_specific.html b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_specific.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/simple/tests/test_specific.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/moz.build b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/moz.build
new file mode 100644
index 000000000..0b7ca5a2b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/moz.build
@@ -0,0 +1,15 @@
+with Files('src/submodule/**'):
+ IMPACTED_TESTS.tags += [
+ 'submodule',
+ ]
+
+with Files('src/bar.jsm'):
+ IMPACTED_TESTS.flavors += [
+ 'browser-chrome',
+ ]
+ IMPACTED_TESTS.files += [
+ '**.js',
+ ]
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/bar.jsm b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/bar.jsm
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/bar.jsm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/submodule/foo.js b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/submodule/foo.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/src/submodule/foo.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/mochitest.ini b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/mochitest.ini
new file mode 100644
index 000000000..d40ca4d06
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/mochitest.ini
@@ -0,0 +1,3 @@
+[test_simple.html]
+[test_specific.html]
+tags = submodule \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_bar.js b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_bar.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_bar.js
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_simple.html b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_simple.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_simple.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_specific.html b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_specific.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/test_specific.html
diff --git a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/xpcshell.ini b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/xpcshell.ini
new file mode 100644
index 000000000..1275764c4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/tests/xpcshell.ini
@@ -0,0 +1 @@
+[test_bar.js] \ No newline at end of file
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 000000000..73132b0cf
--- /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 000000000..0b694ed84
--- /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 000000000..e69de29bb
--- /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-method-names/moz.build b/python/mozbuild/mozbuild/test/frontend/data/generated-files-method-names/moz.build
new file mode 100644
index 000000000..e080b47f9
--- /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 000000000..e69de29bb
--- /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 000000000..da96c5fbc
--- /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 000000000..e69de29bb
--- /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 000000000..080cb2a4e
--- /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 000000000..e69de29bb
--- /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 000000000..90fa17666
--- /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 000000000..1c24113f3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/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/
+
+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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..12d90b15c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/generated-sources/moz.build
@@ -0,0 +1,37 @@
+# -*- 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 000000000..14deaf8cf
--- /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-defines/moz.build b/python/mozbuild/mozbuild/test/frontend/data/host-defines/moz.build
new file mode 100644
index 000000000..37628fede
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-defines/moz.build
@@ -0,0 +1,14 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+value = 'xyz'
+HOST_DEFINES = {
+ 'FOO': True,
+}
+
+HOST_DEFINES['BAZ'] = '"abcd"'
+HOST_DEFINES.update({
+ 'BAR': 7,
+ 'VALUE': value,
+ 'QUX': False,
+})
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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..5a6f0acb6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/host-sources/moz.build
@@ -0,0 +1,25 @@
+# -*- 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 000000000..bb492a242
--- /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 000000000..8e6a0f338
--- /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 000000000..a6a0fd8ea
--- /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 000000000..9bfc65481
--- /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 000000000..7ba111d1f
--- /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 000000000..d72d47c46
--- /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 000000000..f8084f0dd
--- /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 000000000..446207081
--- /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 000000000..618a75ed0
--- /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 000000000..4d721fde4
--- /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 000000000..a2ed3fa49
--- /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 000000000..f9194c00e
--- /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 000000000..a2ed3fa49
--- /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 000000000..568f361a5
--- /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 000000000..a1b892e2d
--- /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 000000000..a06f6d12d
--- /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 000000000..2801f105d
--- /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 000000000..f189212fd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/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/.
+
+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 000000000..4e1554559
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/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/.
+
+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 000000000..03cf5e236
--- /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 000000000..43789914e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/jar-manifests-multiple-files/moz.build
@@ -0,0 +1,8 @@
+# -*- 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 000000000..aac3a838c
--- /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 000000000..5d5e78eed
--- /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 000000000..add45f6c1
--- /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 000000000..cf25e2c44
--- /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 000000000..dd057c3d7
--- /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 000000000..5f05fcef7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/library-defines/moz.build
@@ -0,0 +1,9 @@
+# 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/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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..565c2bee6
--- /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/missing-local-includes/moz.build b/python/mozbuild/mozbuild/test/frontend/data/missing-local-includes/moz.build
new file mode 100644
index 000000000..565c2bee6
--- /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/multiple-rust-libraries/moz.build b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/moz.build
new file mode 100644
index 000000000..b493ec5b5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/multiple-rust-libraries/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
+
+
+@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 000000000..9037d8f65
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..7418cca65
--- /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 000000000..f2001895e
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..abd34e7db
--- /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/program/moz.build b/python/mozbuild/mozbuild/test/frontend/data/program/moz.build
new file mode 100644
index 000000000..4c19b90cd
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/program/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
+
+
+@template
+def SimplePrograms(names):
+ SIMPLE_PROGRAMS += names
+
+Program('test_program')
+
+SimplePrograms([ 'test_program1', 'test_program2' ])
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 000000000..5fac39736
--- /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 000000000..0a91c4692
--- /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 000000000..4dfba1c60
--- /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 000000000..84b2cdea4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-error-error-func/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/
+
+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 000000000..9bfc65481
--- /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 000000000..4a29cae11
--- /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 000000000..d72d47c46
--- /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 000000000..149972edf
--- /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 000000000..6fc10f766
--- /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 000000000..847f95167
--- /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 000000000..a91d38b41
--- /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 000000000..70a0d2c06
--- /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 000000000..e3d0e656a
--- /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 000000000..34579849d
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/moz.build
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 000000000..fa122b7ce
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..01b3a35a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-dash-folding/moz.build
@@ -0,0 +1,18 @@
+# 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-invalid-crate-type/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/Cargo.toml
new file mode 100644
index 000000000..26c653fde
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..01b3a35a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-invalid-crate-type/moz.build
@@ -0,0 +1,18 @@
+# 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 000000000..41a9a7c8f
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..01b3a35a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-name-mismatch/moz.build
@@ -0,0 +1,18 @@
+# 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 000000000..01b3a35a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-cargo-toml/moz.build
@@ -0,0 +1,18 @@
+# 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 000000000..a20b19c62
--- /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 = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[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 000000000..01b3a35a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-lib-section/moz.build
@@ -0,0 +1,18 @@
+# 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-profile-section/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-profile-section/Cargo.toml
new file mode 100644
index 000000000..2700849db
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-profile-section/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.release]
+panic = "abort"
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-profile-section/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-profile-section/moz.build
new file mode 100644
index 000000000..01b3a35a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-no-profile-section/moz.build
@@ -0,0 +1,18 @@
+# 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-non-abort-panic/Cargo.toml b/python/mozbuild/mozbuild/test/frontend/data/rust-library-non-abort-panic/Cargo.toml
new file mode 100644
index 000000000..ccdd06243
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-non-abort-panic/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "random-crate"
+version = "0.1.0"
+authors = [
+ "Nobody <nobody@mozilla.org>",
+]
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "unwind"
+
+[profile.release]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/rust-library-non-abort-panic/moz.build b/python/mozbuild/mozbuild/test/frontend/data/rust-library-non-abort-panic/moz.build
new file mode 100644
index 000000000..d3896decc
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/rust-library-non-abort-panic/moz.build
@@ -0,0 +1,18 @@
+# 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') \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sdk-files/bar.ico b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/bar.ico
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/bar.ico
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sdk-files/baz.png b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/baz.png
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/baz.png
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sdk-files/foo.xpm b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/foo.xpm
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/foo.xpm
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sdk-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/moz.build
new file mode 100644
index 000000000..a2f8ddf9b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/moz.build
@@ -0,0 +1,12 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+SDK_FILES += [
+ 'bar.ico',
+ 'baz.png',
+ 'foo.xpm',
+]
+
+SDK_FILES.icons += [
+ 'quux.icns',
+]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/sdk-files/quux.icns b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/quux.icns
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sdk-files/quux.icns
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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..8937fc245
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources-just-c/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 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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..f9b453238
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/sources/moz.build
@@ -0,0 +1,37 @@
+# -*- 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 000000000..290104bc7
--- /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 000000000..d7f6377d0
--- /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 000000000..d87114ac7
--- /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 000000000..d87114ac7
--- /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 000000000..ff3fed0ee
--- /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 000000000..d87114ac7
--- /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 000000000..d87114ac7
--- /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 000000000..bdb209074
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-install-shared-lib/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
+
+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 000000000..b153dd085
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/moz.build
@@ -0,0 +1,11 @@
+# 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 000000000..e69de29bb
--- /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 000000000..f66270818
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/one/moz.build
@@ -0,0 +1,9 @@
+# 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 000000000..7b3497be6
--- /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 000000000..e69de29bb
--- /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 000000000..256642fea
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-linkables-cxx-link/two/moz.build
@@ -0,0 +1,9 @@
+# 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 000000000..900f42158
--- /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 000000000..ce0136250
--- /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 000000000..87b20c6b1
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..2f1fc406a
--- /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 000000000..4e7e9ff4e
--- /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 000000000..e69de29bb
--- /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 000000000..1caf9cc39
--- /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 000000000..39ad44c28
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/moz.build
@@ -0,0 +1 @@
+REFTEST_MANIFESTS += ['reftest.list'] \ No newline at end of file
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/reftest-stylo.list b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/reftest-stylo.list
new file mode 100644
index 000000000..237aea0e0
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-emitted-includes/reftest-stylo.list
@@ -0,0 +1,3 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+== reftest1.html reftest1.html
+include included-reftest-stylo.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 000000000..80caf8ffa
--- /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 000000000..83a0cec0c
--- /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 000000000..edfaf435f
--- /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 000000000..e69de29bb
--- /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 000000000..753cd0ec0
--- /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 000000000..b8d4e123d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-includes/mochitest.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+install-to-subdir = subdir
+
+[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 000000000..4e7e9ff4e
--- /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 000000000..18ecdcb79
--- /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-install-subdir/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/moz.build
new file mode 100644
index 000000000..9e4d7b21c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+MOCHITEST_MANIFESTS += ['subdir.ini']
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/subdir.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/subdir.ini
new file mode 100644
index 000000000..6b320c2d5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/subdir.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+install-to-subdir = subdir
+support-files = support.txt
+
+[test_foo.html]
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/test_foo.html b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/test_foo.html
new file mode 100644
index 000000000..18ecdcb79
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-install-subdir/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 000000000..ce0136250
--- /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 000000000..efa2d4bc0
--- /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 000000000..80a038d42
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..9cf798918
--- /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 000000000..a81ee3acb
--- /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 000000000..1070c7853
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/chrome.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+skip-if = buildapp == 'b2g'
+
+[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 000000000..b9d7f2685
--- /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 000000000..a7eb6def4
--- /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 000000000..69fd71de0
--- /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 000000000..33839d9e3
--- /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_UNIT_TESTS += ['test_foo.py']
diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest-stylo.list b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest-stylo.list
new file mode 100644
index 000000000..bd7b4f9cb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest-stylo.list
@@ -0,0 +1,2 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+== reftest1.html reftest1.html
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 000000000..3fc25b296
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..fb3005434
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+head = head1 head2
+tail = tail1 tail2
+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 000000000..45edcc027
--- /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 000000000..09c51cbb8
--- /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 000000000..9ab85c0ce
--- /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 000000000..e3ef6216b
--- /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 000000000..4e7e9ff4e
--- /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 000000000..c78822429
--- /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 000000000..e69de29bb
--- /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 000000000..a40e25625
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..4f1335d6b
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..ada59d387
--- /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 000000000..1c1d064ea
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..4f1335d6b
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..a9860f3de
--- /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 000000000..1c1d064ea
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..281dee610
--- /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 000000000..caf391186
--- /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 000000000..e69de29bb
--- /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-python-unit-test-missing/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-python-unit-test-missing/moz.build
new file mode 100644
index 000000000..c9d769802
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-python-unit-test-missing/moz.build
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+PYTHON_UNIT_TESTS += ['test_foo.py']
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 000000000..9d35a8ccc
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir-missing-generated/moz.build
@@ -0,0 +1,10 @@
+# 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 000000000..e69de29bb
--- /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 000000000..fe227224d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file-objdir/moz.build
@@ -0,0 +1,13 @@
+# 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 000000000..257cc5642
--- /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 000000000..d69333ea4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-symbols-file/moz.build
@@ -0,0 +1,10 @@
+# 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 000000000..73045dd43
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..92ceb7f3b
--- /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 000000000..e69de29bb
--- /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 000000000..ca1a429d9
--- /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 000000000..5fac39736
--- /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 000000000..f06edcd36
--- /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 000000000..ca1a429d9
--- /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 000000000..924f667d9
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..182541efd
--- /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 000000000..924f667d9
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..a3660222d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources-non-unified/moz.build
@@ -0,0 +1,28 @@
+# -*- 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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..5d1d89fb4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/moz.build
@@ -0,0 +1,28 @@
+# -*- 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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/unified-sources/quux.cc
diff --git a/python/mozbuild/mozbuild/test/frontend/data/use-yasm/moz.build b/python/mozbuild/mozbuild/test/frontend/data/use-yasm/moz.build
new file mode 100644
index 000000000..11f45953d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/use-yasm/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/
+
+USE_YASM = True
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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/bans.S
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 000000000..e85e6ff5d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
@@ -0,0 +1,25 @@
+# -*- 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
+
+NO_VISIBILITY_FLAGS = True
+
+DELAYLOAD_DLLS = ['foo.dll', 'bar.dll']
+
+RCFILE = 'foo.rc'
+RESFILE = 'bar.res'
+RCINCLUDE = 'bar.rc'
+DEFFILE = 'baz.def'
+
+CFLAGS += ['-fno-exceptions', '-w']
+CXXFLAGS += ['-fcxx-exceptions', '-include foo.h']
+LDFLAGS += ['-framework Foo', '-x']
+HOST_CFLAGS += ['-funroll-loops', '-wall']
+HOST_CXXFLAGS += ['-funroll-loops-harder', '-wall-day-everyday']
+WIN32_EXE_LDFLAGS += ['-subsystem:console']
+
+DISABLE_STL_WRAPPING = True
+
+ALLOW_COMPILER_WARNINGS = True
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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/test2.mm
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 000000000..60f061d5c
--- /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 000000000..070cfad67
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_context.py
@@ -0,0 +1,721 @@
+# 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 mozbuild.frontend.context import (
+ AbsolutePath,
+ Context,
+ ContextDerivedTypedHierarchicalStringList,
+ ContextDerivedTypedList,
+ ContextDerivedTypedListWithItems,
+ ContextDerivedTypedRecord,
+ Files,
+ FUNCTIONS,
+ ObjDirPath,
+ Path,
+ SourcePath,
+ SPECIAL_VARIABLES,
+ SUBCONTEXTS,
+ VARIABLES,
+)
+
+from mozbuild.util import StrictOrderingOnAppendListWithFlagsFactory
+from mozpack import path as mozpath
+
+
+class TestContext(unittest.TestCase):
+ def test_defaults(self):
+ test = Context({
+ 'foo': (int, int, ''),
+ 'bar': (bool, bool, ''),
+ 'baz': (dict, dict, ''),
+ })
+
+ self.assertEqual(test.keys(), [])
+
+ 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(test.keys(), [])
+
+ with self.assertRaises(ValueError):
+ test.update(bar=True, foo={})
+
+ self.assertEqual(test.keys(), [])
+
+ 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')
+ config.external_source_dir = None
+
+ 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', unicode),
+ ('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', unicode),
+ ('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, pattern='**')}
+
+ self.assertEqual(Files.aggregate(files), {
+ 'bug_component_counts': [],
+ 'recommended_bug_component': None,
+ })
+
+ def test_single_bug_component(self):
+ c = Context({})
+ f = Files(c, pattern='**')
+ f['BUG_COMPONENT'] = (u'Product1', u'Component1')
+
+ files = {'moz.build': f}
+ self.assertEqual(Files.aggregate(files), {
+ 'bug_component_counts': [((u'Product1', u'Component1'), 1)],
+ 'recommended_bug_component': (u'Product1', u'Component1'),
+ })
+
+ def test_multiple_bug_components(self):
+ c = Context({})
+ f1 = Files(c, pattern='**')
+ f1['BUG_COMPONENT'] = (u'Product1', u'Component1')
+
+ f2 = Files(c, pattern='**')
+ f2['BUG_COMPONENT'] = (u'Product2', u'Component2')
+
+ files = {'a': f1, 'b': f2, 'c': f1}
+ self.assertEqual(Files.aggregate(files), {
+ 'bug_component_counts': [
+ ((u'Product1', u'Component1'), 2),
+ ((u'Product2', u'Component2'), 1),
+ ],
+ 'recommended_bug_component': (u'Product1', u'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, pattern='**')
+ f1['BUG_COMPONENT'] = (u'Product1', u'Component1')
+
+ f2 = Files(c, pattern='**')
+ f2['BUG_COMPONENT'] = (u'Product2', u'Component2')
+
+ files = {'a': f1, 'b': f2}
+ self.assertEqual(Files.aggregate(files), {
+ 'bug_component_counts': [
+ ((u'Product1', u'Component1'), 1),
+ ((u'Product2', u'Component2'), 1),
+ ],
+ 'recommended_bug_component': None,
+ })
+
+
+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 000000000..6ac4e0aac
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -0,0 +1,1172 @@
+# 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 __future__ import unicode_literals
+
+import os
+import unittest
+
+from mozunit import main
+
+from mozbuild.frontend.context import (
+ ObjDirPath,
+ Path,
+)
+from mozbuild.frontend.data import (
+ AndroidResDirs,
+ BrandingFiles,
+ ChromeManifestEntry,
+ ConfigFileSubstitution,
+ Defines,
+ DirectoryTraversal,
+ Exports,
+ FinalTargetPreprocessedFiles,
+ GeneratedFile,
+ GeneratedSources,
+ HostDefines,
+ HostSources,
+ IPDLFile,
+ JARManifest,
+ LinkageMultipleRustLibrariesError,
+ LocalInclude,
+ Program,
+ RustLibrary,
+ SdkFiles,
+ SharedLibrary,
+ SimpleProgram,
+ Sources,
+ StaticLibrary,
+ TestHarnessFiles,
+ TestManifest,
+ UnifiedSources,
+ VariablePassthru,
+)
+from mozbuild.frontend.emitter import TreeMetadataEmitter
+from mozbuild.frontend.reader import (
+ BuildReader,
+ BuildReaderError,
+ SandboxValidationError,
+)
+from mozpack.chrome import manifest
+
+from mozbuild.test.common import MockConfig
+
+import mozpack.path as mozpath
+
+
+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',
+ OS_TARGET='WINNT',
+ COMPILE_ENVIRONMENT='1',
+ )
+ 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.relativedir 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.relativedir for o in objs])
+ self.assertEqual(reldirs, set(['', 'regular']))
+
+ for o in objs:
+ reldir = o.relativedir
+
+ 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.relativedir for o in objs])
+ self.assertEqual(reldirs, set(['', 'regular', 'test']))
+
+ for o in objs:
+ reldir = o.relativedir
+
+ 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 = {
+ 'ALLOW_COMPILER_WARNINGS': True,
+ 'DISABLE_STL_WRAPPING': True,
+ 'NO_DIST_INSTALL': True,
+ 'VISIBILITY_FLAGS': '',
+ 'RCFILE': 'foo.rc',
+ 'RESFILE': 'bar.res',
+ 'RCINCLUDE': 'bar.rc',
+ 'DEFFILE': 'baz.def',
+ 'MOZBUILD_CFLAGS': ['-fno-exceptions', '-w'],
+ 'MOZBUILD_CXXFLAGS': ['-fcxx-exceptions', '-include foo.h'],
+ 'MOZBUILD_LDFLAGS': ['-framework Foo', '-x', '-DELAYLOAD:foo.dll',
+ '-DELAYLOAD:bar.dll'],
+ 'MOZBUILD_HOST_CFLAGS': ['-funroll-loops', '-wall'],
+ 'MOZBUILD_HOST_CXXFLAGS': ['-funroll-loops-harder',
+ '-wall-day-everyday'],
+ 'WIN32_EXE_LDFLAGS': ['-subsystem:console'],
+ }
+
+ variables = objs[0].variables
+ maxDiff = self.maxDiff
+ self.maxDiff = None
+ self.assertEqual(wanted, variables)
+ self.maxDiff = maxDiff
+
+ def test_use_yasm(self):
+ # When yasm is not available, this should raise.
+ reader = self.reader('use-yasm')
+ with self.assertRaisesRegexp(SandboxValidationError,
+ 'yasm is not available'):
+ self.read_topsrcdir(reader)
+
+ # When yasm is available, this should work.
+ reader = self.reader('use-yasm',
+ extra_substs=dict(
+ YASM='yasm',
+ YASM_ASFLAGS='-foo',
+ ))
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], VariablePassthru)
+ maxDiff = self.maxDiff
+ self.maxDiff = None
+ self.assertEqual(objs[0].variables,
+ {'AS': 'yasm',
+ 'ASFLAGS': '-foo',
+ 'AS_DASH_C_FLAG': ''})
+ 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)
+
+ 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_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.assertRegexpMatches(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(SandboxValidationError,
+ 'Cannot install files to the root of TEST_HARNESS_FILES'):
+ self.read_topsrcdir(reader)
+
+ def test_branding_files(self):
+ reader = self.reader('branding-files')
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], BrandingFiles)
+
+ files = objs[0].files
+
+ self.assertEqual(files._strings, ['bar.ico', 'baz.png', 'foo.xpm'])
+
+ self.assertIn('icons', files._children)
+ icons = files._children['icons']
+
+ self.assertEqual(icons._strings, ['quux.icns'])
+
+ def test_sdk_files(self):
+ reader = self.reader('sdk-files')
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], SdkFiles)
+
+ files = objs[0].files
+
+ self.assertEqual(files._strings, ['bar.ico', 'baz.png', 'foo.xpm'])
+
+ self.assertIn('icons', files._children)
+ icons = files._children['icons']
+
+ self.assertEqual(icons._strings, ['quux.icns'])
+
+ def test_program(self):
+ reader = self.reader('program')
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 3)
+ self.assertIsInstance(objs[0], Program)
+ self.assertIsInstance(objs[1], SimpleProgram)
+ self.assertIsInstance(objs[2], SimpleProgram)
+
+ self.assertEqual(objs[0].program, 'test_program.prog')
+ self.assertEqual(objs[1].program, 'test_program1.prog')
+ self.assertEqual(objs[2].program, 'test_program2.prog')
+
+ def test_test_manifest_missing_manifest(self):
+ """A missing manifest file should result in an error."""
+ reader = self.reader('test-manifest-missing-manifest')
+
+ with self.assertRaisesRegexp(BuildReaderError, 'IOError: 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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(SandboxValidationError,
+ 'entry in support-files not present in the srcdir'):
+ self.read_topsrcdir(reader)
+
+ def test_test_manifest_install_to_subdir(self):
+ """ """
+ reader = self.reader('test-manifest-install-subdir')
+
+ objs = self.read_topsrcdir(reader)
+ self.assertEqual(len(objs), 1)
+ o = objs[0]
+ self.assertEqual(len(o.installs), 3)
+ self.assertEqual(o.manifest_relpath, "subdir.ini")
+ self.assertEqual(o.manifest_obj_relpath, "subdir/subdir.ini")
+ expected = [
+ mozpath.normpath(mozpath.join(o.install_prefix, "subdir/subdir.ini")),
+ mozpath.normpath(mozpath.join(o.install_prefix, "subdir/support.txt")),
+ mozpath.normpath(mozpath.join(o.install_prefix, "subdir/test_foo.html")),
+ ]
+ paths = sorted([v[0] for v in o.installs.values()])
+ self.assertEqual(paths, expected)
+
+ 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, "subdir/mochitest.ini")
+ expected = [
+ mozpath.normpath(mozpath.join(o.install_prefix, "subdir/common.ini")),
+ mozpath.normpath(mozpath.join(o.install_prefix, "subdir/mochitest.ini")),
+ mozpath.normpath(mozpath.join(o.install_prefix, "subdir/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_python_unit_test_missing(self):
+ """Missing files in PYTHON_UNIT_TESTS should raise."""
+ reader = self.reader('test-python-unit-test-missing')
+ with self.assertRaisesRegexp(SandboxValidationError,
+ 'Path specified in PYTHON_UNIT_TESTS does not exist:'):
+ self.read_topsrcdir(reader)
+
+ 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), 9)
+
+ 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,
+ },
+ },
+ 'metro.ini': {
+ 'flavor': 'metro-chrome',
+ 'installs': {
+ 'metro.ini': False,
+ 'test_metro.js': True,
+ },
+ },
+ '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,
+ 'tail1': False,
+ 'tail2': False,
+ },
+ },
+ 'reftest.list': {
+ 'flavor': 'reftest',
+ 'installs': {},
+ },
+ 'crashtest.list': {
+ 'flavor': 'crashtest',
+ 'installs': {},
+ },
+ 'moz.build': {
+ 'flavor': 'python',
+ 'installs': {},
+ }
+ }
+
+ 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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(SandboxValidationError,
+ 'lists test that does not exist: missing.js'):
+ self.read_topsrcdir(reader)
+
+ def test_ipdl_sources(self):
+ reader = self.reader('ipdl_sources')
+ objs = self.read_topsrcdir(reader)
+
+ ipdls = []
+ for o in objs:
+ if isinstance(o, IPDLFile):
+ ipdls.append('%s/%s' % (o.relativedir, o.basename))
+
+ expected = [
+ 'bar/bar.ipdl',
+ 'bar/bar2.ipdlh',
+ 'foo/foo.ipdl',
+ 'foo/foo2.ipdlh',
+ ]
+
+ self.assertEqual(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_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_host_defines(self):
+ reader = self.reader('host-defines')
+ objs = self.read_topsrcdir(reader)
+
+ defines = {}
+ for o in objs:
+ if isinstance(o, HostDefines):
+ 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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(SandboxValidationError, 'XPIDL_MODULE '
+ 'cannot be defined'):
+ reader = self.reader('xpidl-module-no-sources')
+ self.read_topsrcdir(reader)
+
+ def test_missing_local_includes(self):
+ """LOCAL_INCLUDES containing non-existent directories should be rejected."""
+ with self.assertRaisesRegexp(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)]
+ expected = {
+ 'liba': '-DIN_LIBA',
+ 'libb': '-DIN_LIBA -DIN_LIBB',
+ 'libc': '-DIN_LIBA -DIN_LIBB',
+ 'libd': ''
+ }
+ defines = {}
+ for lib in libraries:
+ defines[lib.basename] = ' '.join(lib.lib_defines.get_defines())
+ self.assertEqual(expected, defines)
+
+ def test_sources(self):
+ """Test that SOURCES works properly."""
+ reader = self.reader('sources')
+ objs = self.read_topsrcdir(reader)
+
+ # The last object is a Linkable.
+ linkable = objs.pop()
+ self.assertTrue(linkable.cxx_link)
+ 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])
+
+ 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)
+
+ # The 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.assertTrue(obj.cxx_link)
+ got_results += 1
+ elif obj.basename == 'just_c_shared':
+ 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)
+
+ # The last object is a Linkable.
+ linkable = objs.pop()
+ self.assertTrue(linkable.cxx_link)
+ self.assertEqual(len(objs), 6)
+
+ generated_sources = [o for o in objs if isinstance(o, GeneratedSources)]
+ 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.files,
+ [mozpath.join(reader.config.topobjdir, f) for f in files])
+
+ def test_host_sources(self):
+ """Test that HOST_SOURCES works properly."""
+ reader = self.reader('host-sources')
+ objs = self.read_topsrcdir(reader)
+
+ # The last object is a Linkable
+ linkable = objs.pop()
+ self.assertTrue(linkable.cxx_link)
+ 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])
+
+ 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 Linkable, ignore it
+ objs = objs[:-1]
+ 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.assertTrue(sources.have_unified_mapping)
+
+ 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, ignore it
+ objs = objs[:-1]
+ 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_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(unicode(f) in expected)
+
+ def test_missing_final_target_pp_files(self):
+ """Test that FINAL_TARGET_PP_FILES with missing files throws errors."""
+ with self.assertRaisesRegexp(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 self.assertRaisesRegexp(SandboxValidationError,
+ 'Only source directory paths allowed in FINAL_TARGET_PP_FILES:'):
+ self.read_topsrcdir(reader)
+
+ 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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(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 self.assertRaisesRegexp(SandboxValidationError,
+ 'Cargo.toml for.* has no \\[lib\\] section'):
+ self.read_topsrcdir(reader)
+
+ def test_rust_library_no_profile_section(self):
+ '''Test that a RustLibrary Cargo.toml with no [profile] section fails.'''
+ reader = self.reader('rust-library-no-profile-section')
+ with self.assertRaisesRegexp(SandboxValidationError,
+ 'Cargo.toml for.* has no \\[profile\\.dev\\] 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 self.assertRaisesRegexp(SandboxValidationError,
+ 'crate-type.* is not permitted'):
+ self.read_topsrcdir(reader)
+
+ def test_rust_library_non_abort_panic(self):
+ '''Test that a RustLibrary Cargo.toml has `panic = "abort" set'''
+ reader = self.reader('rust-library-non-abort-panic')
+ with self.assertRaisesRegexp(SandboxValidationError,
+ 'does not specify `panic = "abort"`'):
+ 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), 1)
+ lib = objs[0]
+ self.assertIsInstance(lib, RustLibrary)
+ self.assertRegexpMatches(lib.lib_name, "random_crate")
+ self.assertRegexpMatches(lib.import_name, "random_crate")
+ self.assertRegexpMatches(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 self.assertRaisesRegexp(LinkageMultipleRustLibrariesError,
+ 'Cannot link multiple Rust libraries'):
+ self.read_topsrcdir(reader)
+
+ 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), 1)
+ self.assertIsInstance(objs[0], RustLibrary)
+
+ def test_android_res_dirs(self):
+ """Test that ANDROID_RES_DIRS works properly."""
+ reader = self.reader('android-res-dirs')
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 1)
+ self.assertIsInstance(objs[0], AndroidResDirs)
+
+ # Android resource directories are ordered.
+ expected = [
+ mozpath.join(reader.config.topsrcdir, 'dir1'),
+ mozpath.join(reader.config.topobjdir, 'dir2'),
+ '/dir3',
+ ]
+ self.assertEquals([p.full_path for p in objs[0].paths], expected)
+
+ def test_binary_components(self):
+ """Test that IS_COMPONENT/NO_COMPONENTS_MANIFEST work properly."""
+ reader = self.reader('binary-components')
+ objs = self.read_topsrcdir(reader)
+
+ self.assertEqual(len(objs), 3)
+ self.assertIsInstance(objs[0], ChromeManifestEntry)
+ self.assertEqual(objs[0].path,
+ 'dist/bin/components/components.manifest')
+ self.assertIsInstance(objs[0].entry, manifest.ManifestBinaryComponent)
+ self.assertEqual(objs[0].entry.base, 'dist/bin/components')
+ self.assertEqual(objs[0].entry.relpath, objs[1].lib_name)
+ self.assertIsInstance(objs[1], SharedLibrary)
+ self.assertEqual(objs[1].basename, 'foo')
+ self.assertIsInstance(objs[2], SharedLibrary)
+ self.assertEqual(objs[2].basename, 'bar')
+
+ 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], SharedLibrary)
+ 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, shlib = self.read_topsrcdir(reader)
+ self.assertIsInstance(genfile, GeneratedFile)
+ 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, shlib = self.read_topsrcdir(reader)
+ self.assertIsInstance(genfile, GeneratedFile)
+ self.assertEqual(genfile.script,
+ mozpath.join(reader.config.topsrcdir, 'foo.py'))
+ 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 self.assertRaisesRegexp(SandboxValidationError,
+ 'Objdir file specified in SYMBOLS_FILE not in GENERATED_FILES:'):
+ self.read_topsrcdir(reader)
+
+
+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 000000000..71cc634e1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_namespaces.py
@@ -0,0 +1,207 @@
+# 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 __future__ import unicode_literals
+
+import unittest
+
+from mozunit import main
+
+from mozbuild.frontend.context import (
+ Context,
+ ContextDerivedValue,
+ ContextDerivedTypedList,
+ ContextDerivedTypedListWithItems,
+)
+
+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, unicode):
+ raise ValueError
+ self.context = context
+ self.value = value
+
+ def lower(self):
+ return self.value.lower()
+
+ def __str__(self):
+ return self.value
+
+ def __cmp__(self, other):
+ return cmp(self.value, str(other))
+
+ def __hash__(self):
+ return hash(self.value)
+
+
+VARIABLES = {
+ 'HOGE': (unicode, unicode, None),
+ 'FUGA': (Fuga, unicode, None),
+ 'PIYO': (Piyo, unicode, 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], unicode)
+
+ 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], unicode)
+
+ 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], unicode)
+
+ 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 000000000..7c2aed9df
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_reader.py
@@ -0,0 +1,485 @@
+# 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 __future__ import unicode_literals
+
+import os
+import sys
+import unittest
+
+from mozunit import main
+
+from mozbuild.frontend.context import BugzillaComponent
+from mozbuild.frontend.reader import (
+ BuildReaderError,
+ BuildReader,
+)
+
+from mozbuild.test.common import MockConfig
+
+import mozpack.path as mozpath
+
+
+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)
+
+ contexts = 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_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, 'Core')
+ self.assertEqual(flags['BUG_COMPONENT'].component, 'Build Config')
+
+ 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('Core', 'Build Config'))
+ self.assertEqual(v['bug_component/final/subcomponent/Makefile.in']['BUG_COMPONENT'],
+ BugzillaComponent('Core', 'Build Config'))
+ self.assertEqual(v['bug_component/final/subcomponent/bar']['BUG_COMPONENT'],
+ BugzillaComponent('Another', 'Component'))
+
+ def test_file_test_deps(self):
+ reader = self.reader('files-test-metadata')
+
+ expected = {
+ 'simple/src/module.jsm': set(['simple/tests/test_general.html',
+ 'simple/browser/**.js']),
+ 'simple/base.cpp': set(['simple/tests/*',
+ 'default/tests/xpcshell/test_default_mod.js']),
+ }
+
+ v = reader.files_info([
+ 'simple/src/module.jsm',
+ 'simple/base.cpp',
+ ])
+
+ for path, pattern_set in expected.items():
+ self.assertEqual(v[path].test_files,
+ expected[path])
+
+ def test_file_test_deps_default(self):
+ reader = self.reader('files-test-metadata')
+ v = reader.files_info([
+ 'default/module.js',
+ ])
+
+ expected = {
+ 'default/module.js': set(['default/tests/xpcshell/**',
+ 'default/tests/reftests/**']),
+ }
+
+ for path, pattern_set in expected.items():
+ self.assertEqual(v[path].test_files,
+ expected[path])
+
+ def test_file_test_deps_tags(self):
+ reader = self.reader('files-test-metadata')
+ v = reader.files_info([
+ 'tagged/src/bar.jsm',
+ 'tagged/src/submodule/foo.js',
+ ])
+
+ expected_patterns = {
+ 'tagged/src/submodule/foo.js': set([]),
+ 'tagged/src/bar.jsm': set(['tagged/**.js']),
+ }
+
+ for path, pattern_set in expected_patterns.items():
+ self.assertEqual(v[path].test_files,
+ expected_patterns[path])
+
+ expected_tags = {
+ 'tagged/src/submodule/foo.js': set(['submodule']),
+ 'tagged/src/bar.jsm': set([]),
+ }
+ for path, pattern_set in expected_tags.items():
+ self.assertEqual(v[path].test_tags,
+ expected_tags[path])
+
+ expected_flavors = {
+ 'tagged/src/bar.jsm': set(['browser-chrome']),
+ 'tagged/src/submodule/foo.js': set([]),
+ }
+ for path, pattern_set in expected_flavors.items():
+ self.assertEqual(v[path].test_flavors,
+ expected_flavors[path])
+
+ def test_invalid_flavor(self):
+ reader = self.reader('invalid-files-flavor')
+
+ with self.assertRaises(BuildReaderError):
+ reader.files_info(['foo.js'])
+
+
+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 000000000..d24c5d9ea
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/test_sandbox.py
@@ -0,0 +1,534 @@
+# 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 __future__ import unicode_literals
+
+import os
+import shutil
+import unittest
+
+from mozunit import main
+
+from mozbuild.frontend.reader import (
+ MozbuildSandbox,
+ SandboxCalledError,
+)
+
+from mozbuild.frontend.sandbox import (
+ Sandbox,
+ SandboxExecutionError,
+ SandboxLoadError,
+)
+
+from mozbuild.frontend.context import (
+ Context,
+ FUNCTIONS,
+ SourcePath,
+ SPECIAL_VARIABLES,
+ VARIABLES,
+)
+
+from mozbuild.test.common import MockConfig
+from types import StringTypes
+
+import mozpack.path as mozpath
+
+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('True = 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
+ self.assertEqual(e.message, 'This is an error.')
+
+ 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_utf8_substs(self):
+ """Ensure invalid UTF-8 in substs is converted with an error."""
+
+ # This is really mbcs. It's a bunch of invalid UTF-8.
+ config = MockConfig(extra_substs={'BAD_UTF8': b'\x83\x81\x83\x82\x3A'})
+
+ sandbox = MozbuildSandbox(Context(VARIABLES, config))
+
+ self.assertEqual(sandbox['CONFIG']['BAD_UTF8'],
+ u'\ufffd\ufffd\ufffd\ufffd:')
+
+ 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.assertEqual(e.message,
+ 'Template function names must be CamelCase.')
+
+ # 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.assertEqual(e.message,
+ 'A template named "Template" was already declared in %s.' %
+ sandbox.normalize_path('templates.mozbuild'))
+
+ 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.assertEquals(sandbox['a'], (Foo, int))
+ finally:
+ del FUNCTIONS['foo']
+
+
+if __name__ == '__main__':
+ 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 000000000..059f4588c
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_android_version_code.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/.
+
+from mozunit import main
+import unittest
+
+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_api15 = 0b01111000001000000001001001110001
+ x86_api9 = 0b01111000001000000001001001110100
+ self.assertEqual(android_version_code_v1(buildid, cpu_arch='armeabi-v7a', min_sdk=15, max_sdk=None), arm_api15)
+ 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_base.py b/python/mozbuild/mozbuild/test/test_base.py
new file mode 100644
index 000000000..87f0db85b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_base.py
@@ -0,0 +1,410 @@
+# 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 __future__ import unicode_literals
+
+import json
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import unittest
+
+from cStringIO import StringIO
+from mozfile.mozfile import NamedTemporaryFile
+
+from mozunit import main
+
+from mach.logging import LoggingManager
+
+from mozbuild.base import (
+ BadEnvironmentException,
+ MachCommandBase,
+ MozbuildObject,
+ ObjdirMismatchException,
+ PathArgument,
+)
+
+from mozbuild.backend.configenvironment import ConfigEnvironment
+from buildconfig import topsrcdir, topobjdir
+import mozpack.path as mozpath
+
+
+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)
+ os.environ.pop('MOZ_CURRENT_PROJECT', 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() as mozconfig:
+ os.environ[b'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() as mozconfig:
+ mozconfig.write('mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/foo/')
+ mozconfig.flush()
+ os.environ[b'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()
+ cmd = base._normalize_command(
+ [os.path.join(topsrcdir, 'build', 'autoconf', 'config.guess')],
+ True)
+ guess = subprocess.check_output(cmd, cwd=topsrcdir).strip()
+
+ # 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.
+ guess_path = os.path.join(d, 'build', 'autoconf', 'config.guess')
+ os.makedirs(os.path.dirname(guess_path))
+ shutil.copy(os.path.join(topsrcdir, 'build', 'autoconf',
+ 'config.guess',), guess_path)
+
+ mozinfo = os.path.join(topobjdir, 'mozinfo.json')
+ with open(mozinfo, 'wt') as fh:
+ json.dump(dict(
+ topsrcdir=d,
+ mozconfig=mozconfig,
+ ), fh)
+
+ os.environ[b'MOZCONFIG'] = mozconfig.encode('utf-8')
+ 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[b'MOZCONFIG'] = mozconfig.encode('utf-8')
+ 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'), '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')
+ os.makedirs(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)
+
+ 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')
+ os.makedirs(topsrcdir)
+
+ mozconfig = os.path.join(d, 'mozconfig')
+ with open(mozconfig, 'wt') as fh:
+ fh.write('mk_add_options MOZ_OBJDIR=%s' % real_topobjdir)
+
+ 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)
+
+ 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):
+ base = self.get_base(topobjdir=topobjdir)
+
+ ce = base.config_environment
+ self.assertIsInstance(ce, ConfigEnvironment)
+
+ self.assertEqual(base.defines, ce.defines)
+ self.assertEqual(base.substs, ce.substs)
+
+ self.assertIsInstance(base.defines, dict)
+ self.assertIsInstance(base.substs, dict)
+
+ 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 000000000..3d46f86a9
--- /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 mozunit import main
+
+from mozbuild.util import (
+ KeyedDefaultDict,
+ List,
+ OrderedDefaultDict,
+ ReadOnlyNamespace,
+ ReadOnlyDefaultDict,
+ ReadOnlyDict,
+ ReadOnlyKeyedDefaultDict,
+)
+
+from collections import OrderedDict
+
+
+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):
+ value = 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):
+ value = 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(test.keys(), ['foo', 'bar' ])
+
+ def test_defaults(self):
+ test = OrderedDefaultDict(bool, {'foo': 1 })
+
+ self.assertEqual(test['foo'], 1)
+
+ self.assertEqual(test['qux'], False)
+
+ self.assertEqual(test.keys(), ['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 000000000..a03f85b0d
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_dotproperties.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import unicode_literals
+
+import os
+import unittest
+
+from StringIO import StringIO
+
+import mozpack.path as mozpath
+
+from mozbuild.dotproperties import (
+ DotProperties,
+)
+
+from mozunit import (
+ main,
+)
+
+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 000000000..fb3c45894
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_expression.py
@@ -0,0 +1,82 @@
+import unittest
+
+import sys
+import os.path
+import mozunit
+
+from mozbuild.preprocessor import Expression, Context
+
+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.assert_('FAIL' in self.c)
+ self.assert_('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.assert_(Expression('!0').evaluate(self.c))
+ self.assert_(not Expression('!1').evaluate(self.c))
+
+ def test_equals(self):
+ """ Test for the == operator"""
+ self.assert_(Expression('FAIL == PASS').evaluate(self.c))
+
+ def test_notequals(self):
+ """ Test for the != operator"""
+ self.assert_(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 000000000..a4d4156a7
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_jarmaker.py
@@ -0,0 +1,367 @@
+# 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 __future__ import print_function
+import unittest
+
+import os, sys, os.path, time, inspect
+from filecmp import dircmp
+from tempfile import mkdtemp
+from shutil import rmtree, copy2
+from StringIO import StringIO
+from zipfile import ZipFile
+import mozunit
+
+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(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(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 dc.subdirs.iteritems():
+ 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 expected_symlinks.iteritems():
+ 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.assertEquals(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.assertEquals(jm.localedirs, [os.path.join('/L10N_BASE', 'browser')])
+ def test_l10n_merge(self):
+ jm = self.jm
+ jm.l10nbase = '/L10N_BASE'
+ jm.l10nmerge = '/L10N_MERGE'
+ jm.makeJar(self.fake_empty_file, '/NO_OUTPUT_REQUIRED')
+ self.assertEquals(jm.localedirs,
+ [os.path.join('/L10N_MERGE', 'browser'),
+ os.path.join('/L10N_BASE', 'browser'),
+ os.path.join(os.path.abspath('/TOPSOURCEDIR'),
+ 'browser/locales', 'en-US')
+ ])
+ 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.assertEquals(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.assertEquals(jm.localedirs, [os.path.join('/L10N_BASE', 'dom')])
+
+
+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 000000000..565abc8c9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_line_endings.py
@@ -0,0 +1,46 @@
+import unittest
+
+from StringIO import StringIO
+import os
+import sys
+import os.path
+import mozunit
+
+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.tempnam = os.tempnam('.')
+
+ def tearDown(self):
+ os.remove(self.tempnam)
+
+ def createFile(self, lineendings):
+ f = open(self.tempnam, 'wb')
+ for line, ending in zip(['a', '#literal b', 'c'], lineendings):
+ f.write(line+ending)
+ f.close()
+
+ def testMac(self):
+ self.createFile(['\x0D']*3)
+ self.pp.do_include(self.tempnam)
+ self.assertEquals(self.pp.out.getvalue(), 'a\nb\nc\n')
+
+ def testUnix(self):
+ self.createFile(['\x0A']*3)
+ self.pp.do_include(self.tempnam)
+ self.assertEquals(self.pp.out.getvalue(), 'a\nb\nc\n')
+
+ def testWindows(self):
+ self.createFile(['\x0D\x0A']*3)
+ self.pp.do_include(self.tempnam)
+ self.assertEquals(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 000000000..6fffa0e0e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_makeutil.py
@@ -0,0 +1,165 @@
+# 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 mozbuild.makeutil import (
+ Makefile,
+ read_dep_makefile,
+ Rule,
+ write_dep_makefile,
+)
+from mozunit import main
+import os
+import unittest
+from StringIO import StringIO
+
+
+class TestMakefile(unittest.TestCase):
+ def test_rule(self):
+ out = StringIO()
+ rule = Rule()
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), '')
+ out.truncate(0)
+
+ rule.add_targets(['foo', 'bar'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo bar:\n')
+ out.truncate(0)
+
+ 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.truncate(0)
+
+ 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.truncate(0)
+
+ rule = Rule(['foo'])
+ rule.add_dependencies(['bar', 'foo', 'baz'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo: bar baz\n')
+ out.truncate(0)
+
+ rule.add_targets(['bar'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo bar: baz\n')
+ out.truncate(0)
+
+ rule.add_targets(['bar'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo bar: baz\n')
+ out.truncate(0)
+
+ rule.add_dependencies(['bar'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo bar: baz\n')
+ out.truncate(0)
+
+ rule.add_dependencies(['qux'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo bar: baz qux\n')
+ out.truncate(0)
+
+ rule.add_dependencies(['qux'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo bar: baz qux\n')
+ out.truncate(0)
+
+ rule.add_dependencies(['hoge', 'hoge'])
+ rule.dump(out)
+ self.assertEqual(out.getvalue(), 'foo bar: baz qux hoge\n')
+ out.truncate(0)
+
+ 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.truncate(0)
+
+ 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_mozconfig.py b/python/mozbuild/mozbuild/test/test_mozconfig.py
new file mode 100644
index 000000000..0cd125912
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_mozconfig.py
@@ -0,0 +1,489 @@
+# 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 __future__ import unicode_literals
+
+import os
+import unittest
+
+from shutil import rmtree
+
+from tempfile import (
+ gettempdir,
+ mkdtemp,
+)
+
+from mozfile.mozfile import NamedTemporaryFile
+
+from mozunit import main
+
+from mozbuild.mozconfig import (
+ MozconfigFindException,
+ MozconfigLoadException,
+ MozconfigLoader,
+)
+
+
+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_find_legacy_env(self):
+ """Ensure legacy mozconfig path definitions result in error."""
+
+ os.environ[b'MOZ_MYCONFIG'] = '/foo'
+
+ with self.assertRaises(MozconfigFindException) as e:
+ self.get_loader().find_mozconfig()
+
+ self.assertTrue(e.exception.message.startswith('The MOZ_MYCONFIG'))
+
+ def test_find_multiple_configs(self):
+ """Ensure multiple relative-path MOZCONFIGs result in error."""
+ relative_mozconfig = '.mconfig'
+ os.environ[b'MOZCONFIG'] = relative_mozconfig
+
+ srcdir = self.get_temp_dir()
+ curdir = self.get_temp_dir()
+ dirs = [srcdir, curdir]
+ loader = MozconfigLoader(srcdir)
+ for d in dirs:
+ path = os.path.join(d, relative_mozconfig)
+ with open(path, 'wb') as f:
+ f.write(path)
+
+ orig_dir = os.getcwd()
+ try:
+ os.chdir(curdir)
+ with self.assertRaises(MozconfigFindException) as e:
+ loader.find_mozconfig()
+ finally:
+ os.chdir(orig_dir)
+
+ self.assertIn('exists in more than one of', e.exception.message)
+ for d in dirs:
+ self.assertIn(d, e.exception.message)
+
+ def test_find_multiple_but_identical_configs(self):
+ """Ensure multiple relative-path MOZCONFIGs pointing at the same file are OK."""
+ relative_mozconfig = '../src/.mconfig'
+ os.environ[b'MOZCONFIG'] = relative_mozconfig
+
+ topdir = self.get_temp_dir()
+ srcdir = os.path.join(topdir, 'src')
+ os.mkdir(srcdir)
+ curdir = os.path.join(topdir, 'obj')
+ os.mkdir(curdir)
+
+ loader = MozconfigLoader(srcdir)
+ path = os.path.join(srcdir, relative_mozconfig)
+ with open(path, 'w'):
+ pass
+
+ orig_dir = os.getcwd()
+ try:
+ os.chdir(curdir)
+ self.assertEqual(os.path.realpath(loader.find_mozconfig()),
+ os.path.realpath(path))
+ finally:
+ os.chdir(orig_dir)
+
+ def test_find_no_relative_configs(self):
+ """Ensure a missing relative-path MOZCONFIG is detected."""
+ relative_mozconfig = '.mconfig'
+ os.environ[b'MOZCONFIG'] = relative_mozconfig
+
+ srcdir = self.get_temp_dir()
+ curdir = self.get_temp_dir()
+ dirs = [srcdir, curdir]
+ loader = MozconfigLoader(srcdir)
+
+ orig_dir = os.getcwd()
+ try:
+ os.chdir(curdir)
+ with self.assertRaises(MozconfigFindException) as e:
+ loader.find_mozconfig()
+ finally:
+ os.chdir(orig_dir)
+
+ self.assertIn('does not exist in any of', e.exception.message)
+ for d in dirs:
+ self.assertIn(d, e.exception.message)
+
+ def test_find_relative_mozconfig(self):
+ """Ensure a relative MOZCONFIG can be found in the srcdir."""
+ relative_mozconfig = '.mconfig'
+ os.environ[b'MOZCONFIG'] = relative_mozconfig
+
+ srcdir = self.get_temp_dir()
+ curdir = self.get_temp_dir()
+ dirs = [srcdir, curdir]
+ loader = MozconfigLoader(srcdir)
+
+ path = os.path.join(srcdir, relative_mozconfig)
+ with open(path, 'w'):
+ pass
+
+ orig_dir = os.getcwd()
+ try:
+ os.chdir(curdir)
+ self.assertEqual(os.path.normpath(loader.find_mozconfig()),
+ os.path.normpath(path))
+ finally:
+ os.chdir(orig_dir)
+
+ def test_find_abs_path_not_exist(self):
+ """Ensure a missing absolute path is detected."""
+ os.environ[b'MOZCONFIG'] = '/foo/bar/does/not/exist'
+
+ with self.assertRaises(MozconfigFindException) as e:
+ self.get_loader().find_mozconfig()
+
+ self.assertIn('path that does not exist', e.exception.message)
+ self.assertTrue(e.exception.message.endswith('/foo/bar/does/not/exist'))
+
+ def test_find_path_not_file(self):
+ """Ensure non-file paths are detected."""
+
+ os.environ[b'MOZCONFIG'] = gettempdir()
+
+ with self.assertRaises(MozconfigFindException) as e:
+ self.get_loader().find_mozconfig()
+
+ self.assertIn('refers to a non-file', e.exception.message)
+ self.assertTrue(e.exception.message.endswith(gettempdir()))
+
+ def test_find_default_files(self):
+ """Ensure default paths are used when present."""
+ for p in MozconfigLoader.DEFAULT_TOPSRCDIR_PATHS:
+ d = self.get_temp_dir()
+ path = os.path.join(d, p)
+
+ with open(path, 'w'):
+ pass
+
+ self.assertEqual(MozconfigLoader(d).find_mozconfig(), path)
+
+ def test_find_multiple_defaults(self):
+ """Ensure we error when multiple default files are present."""
+ self.assertGreater(len(MozconfigLoader.DEFAULT_TOPSRCDIR_PATHS), 1)
+
+ d = self.get_temp_dir()
+ for p in MozconfigLoader.DEFAULT_TOPSRCDIR_PATHS:
+ with open(os.path.join(d, p), 'w'):
+ pass
+
+ with self.assertRaises(MozconfigFindException) as e:
+ MozconfigLoader(d).find_mozconfig()
+
+ self.assertIn('Multiple default mozconfig files present',
+ e.exception.message)
+
+ def test_find_deprecated_path_srcdir(self):
+ """Ensure we error when deprecated path locations are present."""
+ for p in MozconfigLoader.DEPRECATED_TOPSRCDIR_PATHS:
+ d = self.get_temp_dir()
+ with open(os.path.join(d, p), 'w'):
+ pass
+
+ with self.assertRaises(MozconfigFindException) as e:
+ MozconfigLoader(d).find_mozconfig()
+
+ self.assertIn('This implicit location is no longer',
+ e.exception.message)
+ self.assertIn(d, e.exception.message)
+
+ def test_find_deprecated_home_paths(self):
+ """Ensure we error when deprecated home directory paths are present."""
+
+ for p in MozconfigLoader.DEPRECATED_HOME_PATHS:
+ home = self.get_temp_dir()
+ os.environ[b'HOME'] = home
+ path = os.path.join(home, p)
+
+ with open(path, 'w'):
+ pass
+
+ with self.assertRaises(MozconfigFindException) as e:
+ self.get_loader().find_mozconfig()
+
+ self.assertIn('This implicit location is no longer',
+ e.exception.message)
+ self.assertIn(path, e.exception.message)
+
+ 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_ac_app_options(self):
+ with NamedTemporaryFile(mode='w') as mozconfig:
+ mozconfig.write('ac_add_options --foo=@TOPSRCDIR@\n')
+ mozconfig.write('ac_add_app_options app1 --bar=@TOPSRCDIR@\n')
+ mozconfig.write('ac_add_app_options app2 --bar=x\n')
+ mozconfig.flush()
+
+ loader = self.get_loader()
+ result = loader.read_mozconfig(mozconfig.name, moz_build_app='app1')
+ self.assertEqual(result['configure_args'], [
+ '--foo=%s' % loader.topsrcdir,
+ '--bar=%s' % loader.topsrcdir])
+
+ result = loader.read_mozconfig(mozconfig.name, moz_build_app='app2')
+ self.assertEqual(result['configure_args'], [
+ '--foo=%s' % loader.topsrcdir,
+ '--bar=x'])
+
+ 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'])
+
+ vars = result['vars']['added']
+ for var in ('MOZ_OBJDIR', 'MOZ_MAKE_FLAGS', 'FOO', 'BIZ'):
+ self.assertEqual(vars.get('%s_IS_SET' % var), '1')
+
+ def test_read_empty_mozconfig_objdir_environ(self):
+ os.environ[b'MOZ_OBJDIR'] = b'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[b'MOZ_OBJDIR'] = b'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(b'/usr/bin/gcc')
+ new_path = os.path.realpath(b'/usr/local/bin/clang')
+ os.environ[b'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(b'/usr/bin/gcc')
+ os.environ[b'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(b'/usr/bin/clang')
+ os.environ[b'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.assertTrue(e.exception.message.startswith(
+ 'Evaluation of your mozconfig exited with an error'))
+ self.assertEquals(e.exception.path,
+ mozconfig.name.replace(os.sep, '/'))
+ self.assertEquals(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 000000000..1a4194cb5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_mozinfo.py
@@ -0,0 +1,278 @@
+#!/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
+
+from StringIO import StringIO
+
+import mozunit
+
+from mozbuild.backend.configenvironment import ConfigEnvironment
+
+from mozbuild.mozinfo import (
+ build_dict,
+ write_mozinfo,
+)
+
+from mozfile.mozfile import NamedTemporaryFile
+
+
+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='gtk2',
+ )))
+ self.assertEqual('linux', d['os'])
+ self.assertEqual('x86', d['processor'])
+ self.assertEqual('gtk2', d['toolkit'])
+ self.assertEqual(32, d['bits'])
+
+ d = build_dict(self._config(dict(
+ OS_TARGET='Linux',
+ TARGET_CPU='x86_64',
+ MOZ_WIDGET_TOOLKIT='gtk2',
+ )))
+ self.assertEqual('linux', d['os'])
+ self.assertEqual('x86_64', d['processor'])
+ self.assertEqual('gtk2', 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_mac_universal(self):
+ d = build_dict(self._config(dict(
+ OS_TARGET='Darwin',
+ TARGET_CPU='i386',
+ MOZ_WIDGET_TOOLKIT='cocoa',
+ UNIVERSAL_BINARY='1',
+ )))
+ self.assertEqual('mac', d['os'])
+ self.assertEqual('universal-x86-x86_64', d['processor'])
+ self.assertEqual('cocoa', d['toolkit'])
+ self.assertFalse('bits' in d)
+
+ d = build_dict(self._config(dict(
+ OS_TARGET='Darwin',
+ TARGET_CPU='x86_64',
+ MOZ_WIDGET_TOOLKIT='cocoa',
+ UNIVERSAL_BINARY='1',
+ )))
+ self.assertEqual('mac', d['os'])
+ self.assertEqual('universal-x86-x86_64', d['processor'])
+ self.assertEqual('cocoa', d['toolkit'])
+ self.assertFalse('bits' in d)
+
+ 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='gtk2',
+ )))
+ self.assertEqual('arm', d['processor'])
+
+ d = build_dict(self._config(dict(
+ OS_TARGET='Linux',
+ TARGET_CPU='armv7',
+ MOZ_WIDGET_TOOLKIT='gtk2',
+ )))
+ 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='gtk2',
+ )))
+ self.assertEqual(False, d['debug'])
+
+ d = build_dict(self._config(dict(
+ OS_TARGET='Linux',
+ TARGET_CPU='i386',
+ MOZ_WIDGET_TOOLKIT='gtk2',
+ 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='gtk2',
+ )))
+ self.assertEqual(False, d['crashreporter'])
+
+ d = build_dict(self._config(dict(
+ OS_TARGET='Linux',
+ TARGET_CPU='i386',
+ MOZ_WIDGET_TOOLKIT='gtk2',
+ MOZ_CRASHREPORTER='1',
+ )))
+ self.assertEqual(True, d['crashreporter'])
+
+
+class TestWriteMozinfo(unittest.TestCase, Base):
+ """
+ Test the write_mozinfo function.
+ """
+ def setUp(self):
+ fd, self.f = tempfile.mkstemp()
+ 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.tempdir
+ c.topsrcdir = tempdir
+ with NamedTemporaryFile(dir=os.path.normpath(c.topsrcdir)) 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 000000000..9aba94853
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_preprocessor.py
@@ -0,0 +1,646 @@
+# 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 StringIO import StringIO
+import os
+import shutil
+
+from tempfile import mkdtemp
+
+from mozunit import main, MockedOpen
+
+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_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_slashslash(self):
+ self.do_include_compare([
+ '#filter slashslash',
+ 'PASS//FAIL // FAIL',
+ '#unfilter slashslash',
+ 'PASS // PASS',
+ ], [
+ 'PASS',
+ 'PASS // PASS',
+ ])
+
+ def test_filter_spaces(self):
+ self.do_include_compare([
+ '#filter spaces',
+ 'You should see two nice ascii tables',
+ ' +-+-+-+',
+ ' | | | |',
+ ' +-+-+-+',
+ '#unfilter spaces',
+ '+-+---+',
+ '| | |',
+ '+-+---+',
+ ], [
+ 'You should see two nice ascii tables',
+ '+-+-+-+',
+ '| | | |',
+ '+-+-+-+',
+ '+-+---+',
+ '| | |',
+ '+-+---+',
+ ])
+
+ 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_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 = {
+ 'test.js': '\n'.join([
+ '#define foo foobarbaz',
+ '#include @inc@',
+ '@bar@',
+ '',
+ ]),
+ 'bar.js': '\n'.join([
+ '#define bar barfoobaz',
+ '@foo@',
+ '',
+ ]),
+ 'foo.js': '\n'.join([
+ 'bazfoobar',
+ '#include bar.js',
+ 'bazbarfoo',
+ '',
+ ]),
+ 'baz.js': 'baz\n',
+ 'f.js': '\n'.join([
+ '#include foo.js',
+ '#filter substitution',
+ '#define inc bar.js',
+ '#include test.js',
+ '#include baz.js',
+ 'fin',
+ '',
+ ]),
+ }
+
+ with MockedOpen(files):
+ self.pp.do_include('f.js')
+ self.assertEqual(self.pp.out.getvalue(),
+ ('//@line 1 "CWD/foo.js"\n'
+ 'bazfoobar\n'
+ '//@line 2 "CWD/bar.js"\n'
+ '@foo@\n'
+ '//@line 3 "CWD/foo.js"\n'
+ 'bazbarfoo\n'
+ '//@line 2 "CWD/bar.js"\n'
+ 'foobarbaz\n'
+ '//@line 3 "CWD/test.js"\n'
+ 'barfoobaz\n'
+ '//@line 1 "CWD/baz.js"\n'
+ 'baz\n'
+ '//@line 6 "CWD/f.js"\n'
+ 'fin\n').replace('CWD/',
+ os.getcwd() + os.path.sep))
+
+ 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')
+
+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 000000000..87399b3f5
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_pythonutil.py
@@ -0,0 +1,23 @@
+# 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 mozbuild.pythonutil import iter_modules_in_path
+from mozunit import main
+import os
+import unittest
+
+
+class TestIterModules(unittest.TestCase):
+ def test_iter_modules_in_path(self):
+ mozbuild_path = os.path.normcase(os.path.dirname(os.path.dirname(__file__)))
+ paths = list(iter_modules_in_path(mozbuild_path))
+ self.assertEquals(sorted(paths), [
+ os.path.join(os.path.abspath(mozbuild_path), '__init__.py'),
+ os.path.join(os.path.abspath(mozbuild_path), 'pythonutil.py'),
+ os.path.join(os.path.abspath(mozbuild_path), 'test', 'test_pythonutil.py'),
+ ])
+
+
+if __name__ == '__main__':
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_testing.py b/python/mozbuild/mozbuild/test/test_testing.py
new file mode 100644
index 000000000..e71892e24
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_testing.py
@@ -0,0 +1,332 @@
+# 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 __future__ import unicode_literals
+
+import cPickle as pickle
+import os
+import shutil
+import tempfile
+import unittest
+
+import mozpack.path as mozpath
+
+from mozfile import NamedTemporaryFile
+from mozunit import main
+
+from mozbuild.base import MozbuildObject
+from mozbuild.testing import (
+ TestMetadata,
+ TestResolver,
+)
+
+
+ALL_TESTS = {
+ "accessible/tests/mochitest/actions/test_anchors.html": [
+ {
+ "dir_relpath": "accessible/tests/mochitest/actions",
+ "expected": "pass",
+ "file_relpath": "accessible/tests/mochitest/actions/test_anchors.html",
+ "flavor": "a11y",
+ "here": "/Users/gps/src/firefox/accessible/tests/mochitest/actions",
+ "manifest": "/Users/gps/src/firefox/accessible/tests/mochitest/actions/a11y.ini",
+ "name": "test_anchors.html",
+ "path": "/Users/gps/src/firefox/accessible/tests/mochitest/actions/test_anchors.html",
+ "relpath": "test_anchors.html"
+ }
+ ],
+ "services/common/tests/unit/test_async_chain.js": [
+ {
+ "dir_relpath": "services/common/tests/unit",
+ "file_relpath": "services/common/tests/unit/test_async_chain.js",
+ "firefox-appdir": "browser",
+ "flavor": "xpcshell",
+ "head": "head_global.js head_helpers.js head_http.js",
+ "here": "/Users/gps/src/firefox/services/common/tests/unit",
+ "manifest": "/Users/gps/src/firefox/services/common/tests/unit/xpcshell.ini",
+ "name": "test_async_chain.js",
+ "path": "/Users/gps/src/firefox/services/common/tests/unit/test_async_chain.js",
+ "relpath": "test_async_chain.js",
+ "tail": ""
+ }
+ ],
+ "services/common/tests/unit/test_async_querySpinningly.js": [
+ {
+ "dir_relpath": "services/common/tests/unit",
+ "file_relpath": "services/common/tests/unit/test_async_querySpinningly.js",
+ "firefox-appdir": "browser",
+ "flavor": "xpcshell",
+ "head": "head_global.js head_helpers.js head_http.js",
+ "here": "/Users/gps/src/firefox/services/common/tests/unit",
+ "manifest": "/Users/gps/src/firefox/services/common/tests/unit/xpcshell.ini",
+ "name": "test_async_querySpinningly.js",
+ "path": "/Users/gps/src/firefox/services/common/tests/unit/test_async_querySpinningly.js",
+ "relpath": "test_async_querySpinningly.js",
+ "tail": ""
+ }
+ ],
+ "toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js": [
+ {
+ "dir_relpath": "toolkit/mozapps/update/test/unit",
+ "file_relpath": "toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js",
+ "flavor": "xpcshell",
+ "generated-files": "head_update.js",
+ "head": "head_update.js",
+ "here": "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit",
+ "manifest": "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/xpcshell_updater.ini",
+ "name": "test_0201_app_launch_apply_update.js",
+ "path": "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js",
+ "reason": "bug 820380",
+ "relpath": "test_0201_app_launch_apply_update.js",
+ "run-sequentially": "Launches application.",
+ "skip-if": "toolkit == 'gonk' || os == 'android'",
+ "tail": ""
+ },
+ {
+ "dir_relpath": "toolkit/mozapps/update/test/unit",
+ "file_relpath": "toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js",
+ "flavor": "xpcshell",
+ "generated-files": "head_update.js",
+ "head": "head_update.js head2.js",
+ "here": "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit",
+ "manifest": "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/xpcshell_updater.ini",
+ "name": "test_0201_app_launch_apply_update.js",
+ "path": "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js",
+ "reason": "bug 820380",
+ "relpath": "test_0201_app_launch_apply_update.js",
+ "run-sequentially": "Launches application.",
+ "skip-if": "toolkit == 'gonk' || os == 'android'",
+ "tail": ""
+ }
+ ],
+ "mobile/android/tests/background/junit3/src/common/TestAndroidLogWriters.java": [
+ {
+ "dir_relpath": "mobile/android/tests/background/junit3/src/common",
+ "file_relpath": "mobile/android/tests/background/junit3/src/common/TestAndroidLogWriters.java",
+ "flavor": "instrumentation",
+ "here": "/Users/nalexander/Mozilla/gecko-dev/mobile/android/tests/background/junit3",
+ "manifest": "/Users/nalexander/Mozilla/gecko-dev/mobile/android/tests/background/junit3/instrumentation.ini",
+ "name": "src/common/TestAndroidLogWriters.java",
+ "path": "/Users/nalexander/Mozilla/gecko-dev/mobile/android/tests/background/junit3/src/common/TestAndroidLogWriters.java",
+ "relpath": "src/common/TestAndroidLogWriters.java",
+ "subsuite": "background"
+ }
+ ],
+ "mobile/android/tests/browser/junit3/src/TestDistribution.java": [
+ {
+ "dir_relpath": "mobile/android/tests/browser/junit3/src",
+ "file_relpath": "mobile/android/tests/browser/junit3/src/TestDistribution.java",
+ "flavor": "instrumentation",
+ "here": "/Users/nalexander/Mozilla/gecko-dev/mobile/android/tests/browser/junit3",
+ "manifest": "/Users/nalexander/Mozilla/gecko-dev/mobile/android/tests/browser/junit3/instrumentation.ini",
+ "name": "src/TestDistribution.java",
+ "path": "/Users/nalexander/Mozilla/gecko-dev/mobile/android/tests/browser/junit3/src/TestDistribution.java",
+ "relpath": "src/TestDistribution.java",
+ "subsuite": "browser"
+ }
+ ],
+ "image/test/browser/browser_bug666317.js": [
+ {
+ "dir_relpath": "image/test/browser",
+ "file_relpath": "image/test/browser/browser_bug666317.js",
+ "flavor": "browser-chrome",
+ "here": "/home/chris/m-c/obj-dbg/_tests/testing/mochitest/browser/image/test/browser",
+ "manifest": "/home/chris/m-c/image/test/browser/browser.ini",
+ "name": "browser_bug666317.js",
+ "path": "/home/chris/m-c/obj-dbg/_tests/testing/mochitest/browser/image/test/browser/browser_bug666317.js",
+ "relpath": "image/test/browser/browser_bug666317.js",
+ "skip-if": "e10s # Bug 948194 - Decoded Images seem to not be discarded on memory-pressure notification with e10s enabled",
+ "subsuite": ""
+ }
+ ],
+ "devtools/client/markupview/test/browser_markupview_copy_image_data.js": [
+ {
+ "dir_relpath": "devtools/client/markupview/test",
+ "file_relpath": "devtools/client/markupview/test/browser_markupview_copy_image_data.js",
+ "flavor": "browser-chrome",
+ "here": "/home/chris/m-c/obj-dbg/_tests/testing/mochitest/browser/devtools/client/markupview/test",
+ "manifest": "/home/chris/m-c/devtools/client/markupview/test/browser.ini",
+ "name": "browser_markupview_copy_image_data.js",
+ "path": "/home/chris/m-c/obj-dbg/_tests/testing/mochitest/browser/devtools/client/markupview/test/browser_markupview_copy_image_data.js",
+ "relpath": "devtools/client/markupview/test/browser_markupview_copy_image_data.js",
+ "subsuite": "devtools",
+ "tags": "devtools"
+ }
+ ]
+}
+
+TEST_DEFAULTS = {
+ "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/xpcshell_updater.ini": {"support-files": "\ndata/**\nxpcshell_updater.ini"}
+}
+
+
+class Base(unittest.TestCase):
+ def setUp(self):
+ self._temp_files = []
+
+ def tearDown(self):
+ for f in self._temp_files:
+ del f
+
+ self._temp_files = []
+
+ def _get_test_metadata(self):
+ all_tests = NamedTemporaryFile(mode='wb')
+ pickle.dump(ALL_TESTS, all_tests)
+ all_tests.flush()
+ self._temp_files.append(all_tests)
+
+ test_defaults = NamedTemporaryFile(mode='wb')
+ pickle.dump(TEST_DEFAULTS, test_defaults)
+ test_defaults.flush()
+ self._temp_files.append(test_defaults)
+
+ return TestMetadata(all_tests.name, test_defaults=test_defaults.name)
+
+
+class TestTestMetadata(Base):
+ def test_load(self):
+ t = self._get_test_metadata()
+ self.assertEqual(len(t._tests_by_path), 8)
+
+ self.assertEqual(len(list(t.tests_with_flavor('xpcshell'))), 3)
+ self.assertEqual(len(list(t.tests_with_flavor('mochitest-plain'))), 0)
+
+ def test_resolve_all(self):
+ t = self._get_test_metadata()
+ self.assertEqual(len(list(t.resolve_tests())), 9)
+
+ def test_resolve_filter_flavor(self):
+ t = self._get_test_metadata()
+ self.assertEqual(len(list(t.resolve_tests(flavor='xpcshell'))), 4)
+
+ def test_resolve_by_dir(self):
+ t = self._get_test_metadata()
+ self.assertEqual(len(list(t.resolve_tests(paths=['services/common']))), 2)
+
+ def test_resolve_under_path(self):
+ t = self._get_test_metadata()
+ self.assertEqual(len(list(t.resolve_tests(under_path='services'))), 2)
+
+ self.assertEqual(len(list(t.resolve_tests(flavor='xpcshell',
+ under_path='services'))), 2)
+
+ def test_resolve_multiple_paths(self):
+ t = self._get_test_metadata()
+ result = list(t.resolve_tests(paths=['services', 'toolkit']))
+ self.assertEqual(len(result), 4)
+
+ def test_resolve_support_files(self):
+ expected_support_files = "\ndata/**\nxpcshell_updater.ini"
+ t = self._get_test_metadata()
+ result = list(t.resolve_tests(paths=['toolkit']))
+ self.assertEqual(len(result), 2)
+
+ for test in result:
+ self.assertEqual(test['support-files'],
+ expected_support_files)
+
+ def test_resolve_path_prefix(self):
+ t = self._get_test_metadata()
+ result = list(t.resolve_tests(paths=['image']))
+ self.assertEqual(len(result), 1)
+
+
+class TestTestResolver(Base):
+ FAKE_TOPSRCDIR = '/Users/gps/src/firefox'
+
+ def setUp(self):
+ Base.setUp(self)
+
+ self._temp_dirs = []
+
+ def tearDown(self):
+ Base.tearDown(self)
+
+ for d in self._temp_dirs:
+ shutil.rmtree(d)
+
+ def _get_resolver(self):
+ topobjdir = tempfile.mkdtemp()
+ self._temp_dirs.append(topobjdir)
+
+ with open(os.path.join(topobjdir, 'all-tests.pkl'), 'wb') as fh:
+ pickle.dump(ALL_TESTS, fh)
+ with open(os.path.join(topobjdir, 'test-defaults.pkl'), 'wb') as fh:
+ pickle.dump(TEST_DEFAULTS, fh)
+
+ o = MozbuildObject(self.FAKE_TOPSRCDIR, None, None, topobjdir=topobjdir)
+
+ # Monkey patch the test resolver to avoid tests failing to find make
+ # due to our fake topscrdir.
+ TestResolver._run_make = lambda *a, **b: None
+
+ return o._spawn(TestResolver)
+
+ def test_cwd_children_only(self):
+ """If cwd is defined, only resolve tests under the specified cwd."""
+ r = self._get_resolver()
+
+ # Pretend we're under '/services' and ask for 'common'. This should
+ # pick up all tests from '/services/common'
+ tests = list(r.resolve_tests(paths=['common'], cwd=os.path.join(r.topsrcdir,
+ 'services')))
+
+ self.assertEqual(len(tests), 2)
+
+ # Tests should be rewritten to objdir.
+ for t in tests:
+ self.assertEqual(t['here'], mozpath.join(r.topobjdir,
+ '_tests/xpcshell/services/common/tests/unit'))
+
+ def test_various_cwd(self):
+ """Test various cwd conditions are all equal."""
+
+ r = self._get_resolver()
+
+ expected = list(r.resolve_tests(paths=['services']))
+ actual = list(r.resolve_tests(paths=['services'], cwd='/'))
+ self.assertEqual(actual, expected)
+
+ actual = list(r.resolve_tests(paths=['services'], cwd=r.topsrcdir))
+ self.assertEqual(actual, expected)
+
+ actual = list(r.resolve_tests(paths=['services'], cwd=r.topobjdir))
+ self.assertEqual(actual, expected)
+
+ def test_subsuites(self):
+ """Test filtering by subsuite."""
+
+ r = self._get_resolver()
+
+ tests = list(r.resolve_tests(paths=['mobile']))
+ self.assertEqual(len(tests), 2)
+
+ tests = list(r.resolve_tests(paths=['mobile'], subsuite='browser'))
+ self.assertEqual(len(tests), 1)
+ self.assertEqual(tests[0]['name'], 'src/TestDistribution.java')
+
+ tests = list(r.resolve_tests(paths=['mobile'], subsuite='background'))
+ self.assertEqual(len(tests), 1)
+ self.assertEqual(tests[0]['name'], 'src/common/TestAndroidLogWriters.java')
+
+ def test_wildcard_patterns(self):
+ """Test matching paths by wildcard."""
+
+ r = self._get_resolver()
+
+ tests = list(r.resolve_tests(paths=['mobile/**']))
+ self.assertEqual(len(tests), 2)
+ for t in tests:
+ self.assertTrue(t['file_relpath'].startswith('mobile'))
+
+ tests = list(r.resolve_tests(paths=['**/**.js', 'accessible/**']))
+ self.assertEqual(len(tests), 7)
+ for t in tests:
+ path = t['file_relpath']
+ self.assertTrue(path.startswith('accessible') or path.endswith('.js'))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/python/mozbuild/mozbuild/test/test_util.py b/python/mozbuild/mozbuild/test/test_util.py
new file mode 100644
index 000000000..6c3b39b1e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_util.py
@@ -0,0 +1,924 @@
+# 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/.
+
+from __future__ import unicode_literals
+
+import itertools
+import hashlib
+import os
+import unittest
+import shutil
+import string
+import sys
+import tempfile
+import textwrap
+
+from mozfile.mozfile import NamedTemporaryFile
+from mozunit import (
+ main,
+ MockedOpen,
+)
+
+from mozbuild.util import (
+ expand_variables,
+ FileAvoidWrite,
+ group_unified_files,
+ hash_file,
+ indented_repr,
+ memoize,
+ memoized_property,
+ pair,
+ resolve_target_to_make,
+ MozbuildDeletionError,
+ HierarchicalStringList,
+ EnumString,
+ EnumStringComparisonError,
+ ListWithAction,
+ StrictOrderingOnAppendList,
+ StrictOrderingOnAppendListWithFlagsFactory,
+ TypedList,
+ TypedNamedTuple,
+ UnsortedError,
+)
+
+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 TestFileAvoidWrite(unittest.TestCase):
+ def test_file_avoid_write(self):
+ with MockedOpen({'file': 'content'}):
+ # Overwriting an existing file replaces its content
+ faw = FileAvoidWrite('file')
+ faw.write('bazqux')
+ self.assertEqual(faw.close(), (True, True))
+ self.assertEqual(open('file', 'r').read(), 'bazqux')
+
+ # Creating a new file (obviously) stores its content
+ faw = FileAvoidWrite('file2')
+ faw.write('content')
+ self.assertEqual(faw.close(), (False, True))
+ self.assertEqual(open('file2').read(), 'content')
+
+ with MockedOpen({'file': 'content'}):
+ with FileAvoidWrite('file') as file:
+ file.write('foobar')
+
+ self.assertEqual(open('file', 'r').read(), 'foobar')
+
+ class MyMockedOpen(MockedOpen):
+ '''MockedOpen extension to raise an exception if something
+ attempts to write in an opened file.
+ '''
+ def __call__(self, name, mode):
+ if 'w' in mode:
+ raise Exception, 'Unexpected open with write mode'
+ return MockedOpen.__call__(self, name, mode)
+
+ with MyMockedOpen({'file': 'content'}):
+ # Validate that MyMockedOpen works as intended
+ file = FileAvoidWrite('file')
+ file.write('foobar')
+ self.assertRaises(Exception, file.close)
+
+ # Check that no write actually happens when writing the
+ # same content as what already is in the file
+ faw = FileAvoidWrite('file')
+ faw.write('content')
+ self.assertEqual(faw.close(), (True, False))
+
+ def test_diff_not_default(self):
+ """Diffs are not produced by default."""
+
+ with MockedOpen({'file': 'old'}):
+ faw = FileAvoidWrite('file')
+ faw.write('dummy')
+ faw.close()
+ self.assertIsNone(faw.diff)
+
+ def test_diff_update(self):
+ """Diffs are produced on file update."""
+
+ with MockedOpen({'file': 'old'}):
+ faw = FileAvoidWrite('file', capture_diff=True)
+ faw.write('new')
+ faw.close()
+
+ diff = '\n'.join(faw.diff)
+ self.assertIn('-old', diff)
+ self.assertIn('+new', diff)
+
+ def test_diff_create(self):
+ """Diffs are produced when files are created."""
+
+ tmpdir = tempfile.mkdtemp()
+ try:
+ path = os.path.join(tmpdir, 'file')
+ faw = FileAvoidWrite(path, capture_diff=True)
+ faw.write('new')
+ faw.close()
+
+ diff = '\n'.join(faw.diff)
+ self.assertIn('+new', diff)
+ finally:
+ shutil.rmtree(tmpdir)
+
+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"]
+ self.assertItemsEqual(self.EXPORTS._children, {"foo" : True})
+ self.assertEqual(self.EXPORTS.foo._strings, ["foo.h"])
+ self.EXPORTS.bar += ["bar.h"]
+ self.assertItemsEqual(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"]
+ self.assertItemsEqual(self.EXPORTS._children, {"foo" : True})
+ self.assertItemsEqual(self.EXPORTS.foo._children, {"bar" : True})
+ self.assertItemsEqual(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"
+ self.assertEqual(str(ve.exception),
+ "Expected a list of strings, not <type '%s'>" % str_type)
+
+ def test_invalid_exports_set(self):
+ with self.assertRaises(ValueError) as ve:
+ self.EXPORTS.foo = "foo.h"
+
+ self.assertEqual(str(ve.exception),
+ "Expected a list of strings, not <type '%s'>" % str_type)
+
+ def test_invalid_exports_append_base(self):
+ with self.assertRaises(ValueError) as ve:
+ self.EXPORTS += "foo.h"
+
+ self.assertEqual(str(ve.exception),
+ "Expected a list of strings, not <type '%s'>" % str_type)
+
+ def test_invalid_exports_bool(self):
+ with self.assertRaises(ValueError) as ve:
+ self.EXPORTS += [True]
+
+ self.assertEqual(str(ve.exception),
+ "Expected a list of strings, not an element of "
+ "<type 'bool'>")
+
+ def test_del_exports(self):
+ with self.assertRaises(MozbuildDeletionError) as mde:
+ self.EXPORTS.foo += ['bar.h']
+ del self.EXPORTS.foo
+
+ def test_unsorted(self):
+ with self.assertRaises(UnsortedError) as ee:
+ self.EXPORTS += ['foo.h', 'bar.h']
+
+ with self.assertRaises(UnsortedError) as ee:
+ self.EXPORTS.foo = ['foo.h', 'bar.h']
+
+ with self.assertRaises(UnsortedError) as ee:
+ self.EXPORTS.foo += ['foo.h', 'bar.h']
+
+ def test_reassign(self):
+ self.EXPORTS.foo = ['foo.h']
+
+ with self.assertRaises(KeyError) as ee:
+ 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 TestListWithAction(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 = ListWithAction(action=self.action)
+ self.assertEqual(len(l), 0)
+ original = ['a', 'b', 'c']
+ l = ListWithAction(['a', 'b', 'c'], action=self.action)
+ expected = map(self.action, original)
+ self.assertSameList(expected, l)
+
+ with self.assertRaises(ValueError):
+ ListWithAction('abc', action=self.action)
+
+ with self.assertRaises(ValueError):
+ ListWithAction()
+
+ def test_extend(self):
+ l = ListWithAction(action=self.action)
+ original = ['a', 'b']
+ l.extend(original)
+ expected = map(self.action, original)
+ self.assertSameList(expected, l)
+
+ with self.assertRaises(ValueError):
+ l.extend('ab')
+
+ def test_slicing(self):
+ l = ListWithAction(action=self.action)
+ original = ['a', 'b']
+ l[:] = original
+ expected = map(self.action, original)
+ self.assertSameList(expected, l)
+
+ with self.assertRaises(ValueError):
+ l[:] = 'ab'
+
+ def test_add(self):
+ l = ListWithAction(action=self.action)
+ original = ['a', 'b']
+ l2 = l + original
+ expected = map(self.action, original)
+ self.assertSameList(expected, l2)
+
+ with self.assertRaises(ValueError):
+ l + 'abc'
+
+ def test_iadd(self):
+ l = ListWithAction(action=self.action)
+ original = ['a', 'b']
+ l += original
+ expected = map(self.action, 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):
+ c = 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': unicode
+ })
+ 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': unicode, '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 __setslice__.
+ 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.__setslice__(l, 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(unicode):
+ def __init__(self, other):
+ if not isinstance(other, unicode):
+ raise ValueError()
+ super(Unicode, self).__init__(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', unicode), ('bar', int)])
+
+ t = FooBar(foo='foo', bar=2)
+ self.assertEquals(type(t), FooBar)
+ self.assertEquals(t.foo, 'foo')
+ self.assertEquals(t.bar, 2)
+ self.assertEquals(t[0], 'foo')
+ self.assertEquals(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.assertEquals(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)
+
+ def test_unsorted_files(self):
+ unsorted_files = ['a%d.cpp' % i for i in range(11)]
+ sorted_files = sorted(unsorted_files)
+ mapping = list(group_unified_files(unsorted_files, 'Unified', 'cpp', 5))
+
+ self.assertEqual(mapping[0][1], sorted_files[0:5])
+ self.assertEqual(mapping[1][1], sorted_files[5:10])
+ self.assertEqual(mapping[2][1], sorted_files[10:])
+
+
+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('msvc', 'gcc', 'clang', 'clang-cl')
+
+ type = CompilerType('msvc')
+ self.assertEquals(type, 'msvc')
+ self.assertNotEquals(type, 'gcc')
+ self.assertNotEquals(type, 'clang')
+ self.assertNotEquals(type, 'clang-cl')
+ self.assertIn(type, ('msvc', 'clang-cl'))
+ self.assertNotIn(type, ('gcc', 'clang'))
+
+ with self.assertRaises(EnumStringComparisonError):
+ self.assertEquals(type, 'foo')
+
+ with self.assertRaises(EnumStringComparisonError):
+ self.assertNotEquals(type, 'foo')
+
+ with self.assertRaises(EnumStringComparisonError):
+ self.assertIn(type, ('foo', 'gcc'))
+
+ with self.assertRaises(ValueError):
+ type = CompilerType('foo')
+
+
+class TestIndentedRepr(unittest.TestCase):
+ def test_indented_repr(self):
+ data = textwrap.dedent(r'''
+ {
+ 'a': 1,
+ 'b': b'abc',
+ b'c': 'xyz',
+ 'd': False,
+ 'e': {
+ 'a': 1,
+ 'b': b'2',
+ 'c': '3',
+ },
+ 'f': [
+ 1,
+ b'2',
+ '3',
+ ],
+ 'pile_of_bytes': b'\xf0\x9f\x92\xa9',
+ 'pile_of_poo': '💩',
+ 'special_chars': '\\\'"\x08\n\t',
+ 'with_accents': 'éàñ',
+ }''').lstrip()
+
+ obj = eval(data)
+
+ self.assertEqual(indented_repr(obj), data)
+
+
+if __name__ == '__main__':
+ main()