# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import os import sys import textwrap import unittest import mozpack.path as mozpath import six from mozunit import MockedOpen, main from six import StringIO from mozbuild.configure import ConfigureError, ConfigureSandbox from mozbuild.configure.options import ( InvalidOptionError, NegativeOptionValue, PositiveOptionValue, ) from mozbuild.util import ReadOnlyNamespace, exec_, memoized_property test_data_path = mozpath.abspath(mozpath.dirname(__file__)) test_data_path = mozpath.join(test_data_path, "data") class TestConfigure(unittest.TestCase): def get_config( self, options=[], env={}, configure="moz.configure", prog="/bin/configure" ): config = {} out = StringIO() sandbox = ConfigureSandbox(config, env, [prog] + options, out, out) sandbox.run(mozpath.join(test_data_path, configure)) if "--help" in options: return six.ensure_text(out.getvalue()), config self.assertEqual("", out.getvalue()) return config def moz_configure(self, source): return MockedOpen( {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)} ) def test_defaults(self): config = self.get_config() self.maxDiff = None self.assertEqual( { "CHOICES": NegativeOptionValue(), "DEFAULTED": PositiveOptionValue(("not-simple",)), "IS_GCC": NegativeOptionValue(), "REMAINDER": ( PositiveOptionValue(), NegativeOptionValue(), NegativeOptionValue(), NegativeOptionValue(), ), "SIMPLE": NegativeOptionValue(), "VALUES": NegativeOptionValue(), "VALUES2": NegativeOptionValue(), "VALUES3": NegativeOptionValue(), "WITH_ENV": NegativeOptionValue(), }, config, ) def test_help(self): help, config = self.get_config(["--help"], prog="configure") self.assertEqual({}, config) self.maxDiff = None self.assertEqual( "Usage: configure [options]\n" "\n" "Options: [defaults in brackets after descriptions]\n" " Help options:\n" " --help print this message\n" "\n" " Options from python/mozbuild/mozbuild/test/configure/data/included.configure:\n" " --enable-imports-in-template\n Imports in template\n" "\n" " Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n" " --enable-include Include\n" " --enable-simple Enable simple\n" " --enable-values Enable values\n" " --enable-with-env Enable with env\n" " --indirect-option Indirectly defined option\n" " --option Option\n" " --returned-choices Choices\n" " --with-imports Imports\n" " --with-returned-default Returned default [not-simple]\n" " --with-stuff Build with stuff\n" " --without-thing Build without thing\n" "\n" "\n" "Environment variables:\n" " Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n" " CC C Compiler\n" "\n", help.replace("\\", "/"), ) def test_unknown(self): with self.assertRaises(InvalidOptionError): self.get_config(["--unknown"]) def test_simple(self): for config in ( self.get_config(), self.get_config(["--disable-simple"]), # Last option wins. self.get_config(["--enable-simple", "--disable-simple"]), ): self.assertNotIn("ENABLED_SIMPLE", config) self.assertIn("SIMPLE", config) self.assertEqual(NegativeOptionValue(), config["SIMPLE"]) for config in ( self.get_config(["--enable-simple"]), self.get_config(["--disable-simple", "--enable-simple"]), ): self.assertIn("ENABLED_SIMPLE", config) self.assertIn("SIMPLE", config) self.assertEqual(PositiveOptionValue(), config["SIMPLE"]) self.assertIs(config["SIMPLE"], config["ENABLED_SIMPLE"]) # --enable-simple doesn't take values. with self.assertRaises(InvalidOptionError): self.get_config(["--enable-simple=value"]) def test_with_env(self): for config in ( self.get_config(), self.get_config(["--disable-with-env"]), self.get_config(["--enable-with-env", "--disable-with-env"]), self.get_config(env={"MOZ_WITH_ENV": ""}), # Options win over environment self.get_config(["--disable-with-env"], env={"MOZ_WITH_ENV": "1"}), ): self.assertIn("WITH_ENV", config) self.assertEqual(NegativeOptionValue(), config["WITH_ENV"]) for config in ( self.get_config(["--enable-with-env"]), self.get_config(["--disable-with-env", "--enable-with-env"]), self.get_config(env={"MOZ_WITH_ENV": "1"}), self.get_config(["--enable-with-env"], env={"MOZ_WITH_ENV": ""}), ): self.assertIn("WITH_ENV", config) self.assertEqual(PositiveOptionValue(), config["WITH_ENV"]) with self.assertRaises(InvalidOptionError): self.get_config(["--enable-with-env=value"]) with self.assertRaises(InvalidOptionError): self.get_config(env={"MOZ_WITH_ENV": "value"}) def test_values(self, name="VALUES"): for config in ( self.get_config(), self.get_config(["--disable-values"]), self.get_config(["--enable-values", "--disable-values"]), ): self.assertIn(name, config) self.assertEqual(NegativeOptionValue(), config[name]) for config in ( self.get_config(["--enable-values"]), self.get_config(["--disable-values", "--enable-values"]), ): self.assertIn(name, config) self.assertEqual(PositiveOptionValue(), config[name]) config = self.get_config(["--enable-values=foo"]) self.assertIn(name, config) self.assertEqual(PositiveOptionValue(("foo",)), config[name]) config = self.get_config(["--enable-values=foo,bar"]) self.assertIn(name, config) self.assertTrue(config[name]) self.assertEqual(PositiveOptionValue(("foo", "bar")), config[name]) def test_values2(self): self.test_values("VALUES2") def test_values3(self): self.test_values("VALUES3") def test_returned_default(self): config = self.get_config(["--enable-simple"]) self.assertIn("DEFAULTED", config) self.assertEqual(PositiveOptionValue(("simple",)), config["DEFAULTED"]) config = self.get_config(["--disable-simple"]) self.assertIn("DEFAULTED", config) self.assertEqual(PositiveOptionValue(("not-simple",)), config["DEFAULTED"]) def test_returned_choices(self): for val in ("a", "b", "c"): config = self.get_config( ["--enable-values=alpha", "--returned-choices=%s" % val] ) self.assertIn("CHOICES", config) self.assertEqual(PositiveOptionValue((val,)), config["CHOICES"]) for val in ("0", "1", "2"): config = self.get_config( ["--enable-values=numeric", "--returned-choices=%s" % val] ) self.assertIn("CHOICES", config) self.assertEqual(PositiveOptionValue((val,)), config["CHOICES"]) with self.assertRaises(InvalidOptionError): self.get_config(["--enable-values=numeric", "--returned-choices=a"]) with self.assertRaises(InvalidOptionError): self.get_config(["--enable-values=alpha", "--returned-choices=0"]) def test_included(self): config = self.get_config(env={"CC": "gcc"}) self.assertIn("IS_GCC", config) self.assertEqual(config["IS_GCC"], True) config = self.get_config(["--enable-include=extra.configure", "--extra"]) self.assertIn("EXTRA", config) self.assertEqual(PositiveOptionValue(), config["EXTRA"]) with self.assertRaises(InvalidOptionError): self.get_config(["--extra"]) def test_template(self): config = self.get_config(env={"CC": "gcc"}) self.assertIn("CFLAGS", config) self.assertEqual(config["CFLAGS"], ["-Werror=foobar"]) config = self.get_config(env={"CC": "clang"}) self.assertNotIn("CFLAGS", config) def test_imports(self): config = {} out = StringIO() sandbox = ConfigureSandbox(config, {}, ["configure"], out, out) with self.assertRaises(ImportError): exec_( textwrap.dedent( """ @template def foo(): import sys foo()""" ), sandbox, ) exec_( textwrap.dedent( """ @template @imports('sys') def foo(): return sys""" ), sandbox, ) self.assertIs(sandbox["foo"](), sys) # os.path after an import is a mix of vanilla os.path and sandbox os.path. os_path = {} exec_("from os.path import *", {}, os_path) os_path.update(sandbox.OS.path.__dict__) os_path = ReadOnlyNamespace(**os_path) exec_( textwrap.dedent( """ @template @imports(_from='os', _import='path') def foo(): return path""" ), sandbox, ) self.assertEqual(sandbox["foo"](), os_path) exec_( textwrap.dedent( """ @template @imports(_from='os', _import='path', _as='os_path') def foo(): return os_path""" ), sandbox, ) self.assertEqual(sandbox["foo"](), os_path) exec_( textwrap.dedent( """ @template @imports('__builtin__') def foo(): return __builtin__""" ), sandbox, ) self.assertIs(sandbox["foo"](), six.moves.builtins) exec_( textwrap.dedent( """ @template @imports(_from='__builtin__', _import='open') def foo(): return open('%s')""" % os.devnull ), sandbox, ) f = sandbox["foo"]() self.assertEqual(f.name, os.devnull) f.close() # This unlocks the sandbox exec_( textwrap.dedent( """ @template @imports(_import='__builtin__', _as='__builtins__') def foo(): import sys return sys""" ), sandbox, ) self.assertIs(sandbox["foo"](), sys) exec_( textwrap.dedent( """ @template @imports('__sandbox__') def foo(): return __sandbox__""" ), sandbox, ) self.assertIs(sandbox["foo"](), sandbox) exec_( textwrap.dedent( """ @template @imports(_import='__sandbox__', _as='s') def foo(): return s""" ), sandbox, ) self.assertIs(sandbox["foo"](), sandbox) # Nothing leaked from the function being executed self.assertEqual(list(sandbox), ["__builtins__", "foo"]) self.assertEqual(sandbox["__builtins__"], ConfigureSandbox.BUILTINS) exec_( textwrap.dedent( """ @template @imports('sys') def foo(): @depends(when=True) def bar(): return sys return bar bar = foo()""" ), sandbox, ) with self.assertRaises(NameError) as e: sandbox._depends[sandbox["bar"]].result() self.assertIn("name 'sys' is not defined", str(e.exception)) def test_apply_imports(self): imports = [] class CountApplyImportsSandbox(ConfigureSandbox): def _apply_imports(self, *args, **kwargs): imports.append((args, kwargs)) super(CountApplyImportsSandbox, self)._apply_imports(*args, **kwargs) config = {} out = StringIO() sandbox = CountApplyImportsSandbox(config, {}, ["configure"], out, out) exec_( textwrap.dedent( """ @template @imports('sys') def foo(): return sys foo() foo()""" ), sandbox, ) self.assertEqual(len(imports), 1) def test_import_wrapping(self): bar = object() foo = ReadOnlyNamespace(bar=bar) class BasicWrappingSandbox(ConfigureSandbox): @memoized_property def _wrapped_foo(self): return foo config = {} out = StringIO() sandbox = BasicWrappingSandbox(config, {}, ["configure"], out, out) exec_( textwrap.dedent( """ @template @imports('foo') def toplevel(): return foo @template @imports('foo.bar') def bar(): return foo.bar @template @imports('foo.bar') def bar_upper(): return foo @template @imports(_from='foo', _import='bar') def from_import(): return bar @template @imports(_from='foo', _import='bar', _as='custom_name') def from_import_as(): return custom_name @template @imports(_import='foo', _as='custom_name') def import_as(): return custom_name """ ), sandbox, ) self.assertIs(sandbox["toplevel"](), foo) self.assertIs(sandbox["bar"](), bar) self.assertIs(sandbox["bar_upper"](), foo) self.assertIs(sandbox["from_import"](), bar) self.assertIs(sandbox["from_import_as"](), bar) self.assertIs(sandbox["import_as"](), foo) def test_os_path(self): config = self.get_config(["--with-imports=%s" % __file__]) self.assertIn("HAS_ABSPATH", config) self.assertEqual(config["HAS_ABSPATH"], True) self.assertIn("HAS_GETATIME", config) self.assertEqual(config["HAS_GETATIME"], True) self.assertIn("HAS_GETATIME2", config) self.assertEqual(config["HAS_GETATIME2"], False) def test_template_call(self): config = self.get_config(env={"CC": "gcc"}) self.assertIn("TEMPLATE_VALUE", config) self.assertEqual(config["TEMPLATE_VALUE"], 42) self.assertIn("TEMPLATE_VALUE_2", config) self.assertEqual(config["TEMPLATE_VALUE_2"], 21) def test_template_imports(self): config = self.get_config(["--enable-imports-in-template"]) self.assertIn("PLATFORM", config) self.assertEqual(config["PLATFORM"], sys.platform) def test_decorators(self): config = {} out = StringIO() sandbox = ConfigureSandbox(config, {}, ["configure"], out, out) sandbox.include_file(mozpath.join(test_data_path, "decorators.configure")) self.assertNotIn("FOO", sandbox) self.assertNotIn("BAR", sandbox) self.assertNotIn("QUX", sandbox) def test_set_config(self): def get_config(*args): return self.get_config(*args, configure="set_config.configure") help, config = get_config(["--help"]) self.assertEqual(config, {}) config = get_config(["--set-foo"]) self.assertIn("FOO", config) self.assertEqual(config["FOO"], True) config = get_config(["--set-bar"]) self.assertNotIn("FOO", config) self.assertIn("BAR", config) self.assertEqual(config["BAR"], True) config = get_config(["--set-value=qux"]) self.assertIn("VALUE", config) self.assertEqual(config["VALUE"], "qux") config = get_config(["--set-name=hoge"]) self.assertIn("hoge", config) self.assertEqual(config["hoge"], True) config = get_config([]) self.assertEqual(config, {"BAR": False}) with self.assertRaises(ConfigureError): # Both --set-foo and --set-name=FOO are going to try to # set_config('FOO'...) get_config(["--set-foo", "--set-name=FOO"]) def test_set_config_when(self): with self.moz_configure( """ option('--with-qux', help='qux') set_config('FOO', 'foo', when=True) set_config('BAR', 'bar', when=False) set_config('QUX', 'qux', when='--with-qux') """ ): config = self.get_config() self.assertEqual( config, { "FOO": "foo", }, ) config = self.get_config(["--with-qux"]) self.assertEqual( config, { "FOO": "foo", "QUX": "qux", }, ) def test_set_config_when_disable(self): with self.moz_configure( """ option('--disable-baz', help='Disable baz') set_config('BAZ', True, when='--enable-baz') """ ): config = self.get_config() self.assertEqual(config["BAZ"], True) config = self.get_config(["--enable-baz"]) self.assertEqual(config["BAZ"], True) config = self.get_config(["--disable-baz"]) self.assertEqual(config, {}) def test_set_define(self): def get_config(*args): return self.get_config(*args, configure="set_define.configure") help, config = get_config(["--help"]) self.assertEqual(config, {"DEFINES": {}}) config = get_config(["--set-foo"]) self.assertIn("FOO", config["DEFINES"]) self.assertEqual(config["DEFINES"]["FOO"], True) config = get_config(["--set-bar"]) self.assertNotIn("FOO", config["DEFINES"]) self.assertIn("BAR", config["DEFINES"]) self.assertEqual(config["DEFINES"]["BAR"], True) config = get_config(["--set-value=qux"]) self.assertIn("VALUE", config["DEFINES"]) self.assertEqual(config["DEFINES"]["VALUE"], "qux") config = get_config(["--set-name=hoge"]) self.assertIn("hoge", config["DEFINES"]) self.assertEqual(config["DEFINES"]["hoge"], True) config = get_config([]) self.assertEqual(config["DEFINES"], {"BAR": False}) with self.assertRaises(ConfigureError): # Both --set-foo and --set-name=FOO are going to try to # set_define('FOO'...) get_config(["--set-foo", "--set-name=FOO"]) def test_set_define_when(self): with self.moz_configure( """ option('--with-qux', help='qux') set_define('FOO', 'foo', when=True) set_define('BAR', 'bar', when=False) set_define('QUX', 'qux', when='--with-qux') """ ): config = self.get_config() self.assertEqual( config["DEFINES"], { "FOO": "foo", }, ) config = self.get_config(["--with-qux"]) self.assertEqual( config["DEFINES"], { "FOO": "foo", "QUX": "qux", }, ) def test_set_define_when_disable(self): with self.moz_configure( """ option('--disable-baz', help='Disable baz') set_define('BAZ', True, when='--enable-baz') """ ): config = self.get_config() self.assertEqual(config["DEFINES"]["BAZ"], True) config = self.get_config(["--enable-baz"]) self.assertEqual(config["DEFINES"]["BAZ"], True) config = self.get_config(["--disable-baz"]) self.assertEqual(config["DEFINES"], {}) def test_imply_option_simple(self): def get_config(*args): return self.get_config(*args, configure="imply_option/simple.configure") help, config = get_config(["--help"]) self.assertEqual(config, {}) config = get_config([]) self.assertEqual(config, {}) config = get_config(["--enable-foo"]) self.assertIn("BAR", config) self.assertEqual(config["BAR"], PositiveOptionValue()) with self.assertRaises(InvalidOptionError) as e: get_config(["--enable-foo", "--disable-bar"]) self.assertEqual( str(e.exception), "'--enable-bar' implied by '--enable-foo' conflicts with " "'--disable-bar' from the command-line", ) def test_imply_option_negative(self): def get_config(*args): return self.get_config(*args, configure="imply_option/negative.configure") help, config = get_config(["--help"]) self.assertEqual(config, {}) config = get_config([]) self.assertEqual(config, {}) config = get_config(["--enable-foo"]) self.assertIn("BAR", config) self.assertEqual(config["BAR"], NegativeOptionValue()) with self.assertRaises(InvalidOptionError) as e: get_config(["--enable-foo", "--enable-bar"]) self.assertEqual( str(e.exception), "'--disable-bar' implied by '--enable-foo' conflicts with " "'--enable-bar' from the command-line", ) config = get_config(["--disable-hoge"]) self.assertIn("BAR", config) self.assertEqual(config["BAR"], NegativeOptionValue()) with self.assertRaises(InvalidOptionError) as e: get_config(["--disable-hoge", "--enable-bar"]) self.assertEqual( str(e.exception), "'--disable-bar' implied by '--disable-hoge' conflicts with " "'--enable-bar' from the command-line", ) def test_imply_option_values(self): def get_config(*args): return self.get_config(*args, configure="imply_option/values.configure") help, config = get_config(["--help"]) self.assertEqual(config, {}) config = get_config([]) self.assertEqual(config, {}) config = get_config(["--enable-foo=a"]) self.assertIn("BAR", config) self.assertEqual(config["BAR"], PositiveOptionValue(("a",))) config = get_config(["--enable-foo=a,b"]) self.assertIn("BAR", config) self.assertEqual(config["BAR"], PositiveOptionValue(("a", "b"))) with self.assertRaises(InvalidOptionError) as e: get_config(["--enable-foo=a,b", "--disable-bar"]) self.assertEqual( str(e.exception), "'--enable-bar=a,b' implied by '--enable-foo' conflicts with " "'--disable-bar' from the command-line", ) def test_imply_option_infer(self): def get_config(*args): return self.get_config(*args, configure="imply_option/infer.configure") help, config = get_config(["--help"]) self.assertEqual(config, {}) config = get_config([]) self.assertEqual(config, {}) with self.assertRaises(InvalidOptionError) as e: get_config(["--enable-foo", "--disable-bar"]) self.assertEqual( str(e.exception), "'--enable-bar' implied by '--enable-foo' conflicts with " "'--disable-bar' from the command-line", ) with self.assertRaises(ConfigureError) as e: self.get_config([], configure="imply_option/infer_ko.configure") self.assertEqual( str(e.exception), "Cannot infer what implies '--enable-bar'. Please add a `reason` " "to the `imply_option` call.", ) def test_imply_option_immediate_value(self): def get_config(*args): return self.get_config(*args, configure="imply_option/imm.configure") help, config = get_config(["--help"]) self.assertEqual(config, {}) config = get_config([]) self.assertEqual(config, {}) config_path = mozpath.abspath( mozpath.join(test_data_path, "imply_option", "imm.configure") ) with self.assertRaisesRegexp( InvalidOptionError, "--enable-foo' implied by 'imply_option at %s:7' conflicts " "with '--disable-foo' from the command-line" % config_path, ): get_config(["--disable-foo"]) with self.assertRaisesRegexp( InvalidOptionError, "--enable-bar=foo,bar' implied by 'imply_option at %s:18' " "conflicts with '--enable-bar=a,b,c' from the command-line" % config_path, ): get_config(["--enable-bar=a,b,c"]) with self.assertRaisesRegexp( InvalidOptionError, "--enable-baz=BAZ' implied by 'imply_option at %s:29' " "conflicts with '--enable-baz=QUUX' from the command-line" % config_path, ): get_config(["--enable-baz=QUUX"]) def test_imply_option_failures(self): with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ imply_option('--with-foo', ('a',), 'bar') """ ): self.get_config() self.assertEqual( str(e.exception), "`--with-foo`, emitted from `%s` line 2, is unknown." % mozpath.join(test_data_path, "moz.configure"), ) with self.assertRaises(TypeError) as e: with self.moz_configure( """ imply_option('--with-foo', 42, 'bar') option('--with-foo', help='foo') @depends('--with-foo') def foo(value): return value """ ): self.get_config() self.assertEqual(str(e.exception), "Unexpected type: 'int'") def test_imply_option_when(self): with self.moz_configure( """ option('--with-foo', help='foo') imply_option('--with-qux', True, when='--with-foo') option('--with-qux', help='qux') set_config('QUX', depends('--with-qux')(lambda x: x)) """ ): config = self.get_config() self.assertEqual( config, { "QUX": NegativeOptionValue(), }, ) config = self.get_config(["--with-foo"]) self.assertEqual( config, { "QUX": PositiveOptionValue(), }, ) def test_imply_option_dependency_loop(self): with self.moz_configure( """ option('--without-foo', help='foo') @depends('--with-foo') def qux_default(foo): return bool(foo) option('--with-qux', default=qux_default, help='qux') imply_option('--with-foo', depends('--with-qux')(lambda x: x or None)) set_config('FOO', depends('--with-foo')(lambda x: x)) set_config('QUX', depends('--with-qux')(lambda x: x)) """ ): config = self.get_config() self.assertEqual( config, { "FOO": PositiveOptionValue(), "QUX": PositiveOptionValue(), }, ) config = self.get_config(["--without-foo"]) self.assertEqual( config, { "FOO": NegativeOptionValue(), "QUX": NegativeOptionValue(), }, ) config = self.get_config(["--with-qux"]) self.assertEqual( config, { "FOO": PositiveOptionValue(), "QUX": PositiveOptionValue(), }, ) with self.assertRaises(InvalidOptionError) as e: config = self.get_config(["--without-foo", "--with-qux"]) self.assertEqual( str(e.exception), "'--with-foo' implied by '--with-qux' conflicts " "with '--without-foo' from the command-line", ) config = self.get_config(["--without-qux"]) self.assertEqual( config, { "FOO": PositiveOptionValue(), "QUX": NegativeOptionValue(), }, ) with self.moz_configure( """ option('--with-foo', help='foo') @depends('--with-foo') def qux_default(foo): return bool(foo) option('--with-qux', default=qux_default, help='qux') imply_option('--with-foo', depends('--with-qux')(lambda x: x or None)) set_config('FOO', depends('--with-foo')(lambda x: x)) set_config('QUX', depends('--with-qux')(lambda x: x)) """ ): config = self.get_config() self.assertEqual( config, { "FOO": NegativeOptionValue(), "QUX": NegativeOptionValue(), }, ) config = self.get_config(["--with-foo"]) self.assertEqual( config, { "FOO": PositiveOptionValue(), "QUX": PositiveOptionValue(), }, ) with self.assertRaises(InvalidOptionError) as e: config = self.get_config(["--with-qux"]) self.assertEqual( str(e.exception), "'--with-foo' implied by '--with-qux' conflicts " "with '--without-foo' from the default", ) with self.assertRaises(InvalidOptionError) as e: config = self.get_config(["--without-foo", "--with-qux"]) self.assertEqual( str(e.exception), "'--with-foo' implied by '--with-qux' conflicts " "with '--without-foo' from the command-line", ) config = self.get_config(["--without-qux"]) self.assertEqual( config, { "FOO": NegativeOptionValue(), "QUX": NegativeOptionValue(), }, ) config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure")) # Same test as above, but using `when` in the `imply_option`. with self.moz_configure( """ option('--with-foo', help='foo') @depends('--with-foo') def qux_default(foo): return bool(foo) option('--with-qux', default=qux_default, help='qux') imply_option('--with-foo', True, when='--with-qux') set_config('FOO', depends('--with-foo')(lambda x: x)) set_config('QUX', depends('--with-qux')(lambda x: x)) """ ): config = self.get_config() self.assertEqual( config, { "FOO": NegativeOptionValue(), "QUX": NegativeOptionValue(), }, ) config = self.get_config(["--with-foo"]) self.assertEqual( config, { "FOO": PositiveOptionValue(), "QUX": PositiveOptionValue(), }, ) with self.assertRaises(InvalidOptionError) as e: config = self.get_config(["--with-qux"]) self.assertEqual( str(e.exception), "'--with-foo' implied by 'imply_option at %s:10' conflicts " "with '--without-foo' from the default" % config_path, ) with self.assertRaises(InvalidOptionError) as e: config = self.get_config(["--without-foo", "--with-qux"]) self.assertEqual( str(e.exception), "'--with-foo' implied by 'imply_option at %s:10' conflicts " "with '--without-foo' from the command-line" % config_path, ) config = self.get_config(["--without-qux"]) self.assertEqual( config, { "FOO": NegativeOptionValue(), "QUX": NegativeOptionValue(), }, ) def test_imply_option_recursion(self): config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure")) message = ( "'--without-foo' appears somewhere in the direct or indirect dependencies " "when resolving imply_option at %s:8" % config_path ) with self.moz_configure( """ option('--without-foo', help='foo') imply_option('--with-qux', depends('--with-foo')(lambda x: x or None)) option('--with-qux', help='qux') imply_option('--with-foo', depends('--with-qux')(lambda x: x or None)) set_config('FOO', depends('--with-foo')(lambda x: x)) set_config('QUX', depends('--with-qux')(lambda x: x)) """ ): # Note: no error is detected when the depends function in the # imply_options resolve to None, which disables the imply_option. with self.assertRaises(ConfigureError) as e: self.get_config() self.assertEqual(str(e.exception), message) with self.assertRaises(ConfigureError) as e: self.get_config(["--with-qux"]) self.assertEqual(str(e.exception), message) with self.assertRaises(ConfigureError) as e: self.get_config(["--without-foo", "--with-qux"]) self.assertEqual(str(e.exception), message) def test_option_failures(self): with self.assertRaises(ConfigureError) as e: with self.moz_configure('option("--with-foo", help="foo")'): self.get_config() self.assertEqual( str(e.exception), "Option `--with-foo` is not handled ; reference it with a @depends", ) with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ option("--with-foo", help="foo") option("--with-foo", help="foo") """ ): self.get_config() self.assertEqual(str(e.exception), "Option `--with-foo` already defined") with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ option(env="MOZ_FOO", help="foo") option(env="MOZ_FOO", help="foo") """ ): self.get_config() self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined") with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ option('--with-foo', env="MOZ_FOO", help="foo") option(env="MOZ_FOO", help="foo") """ ): self.get_config() self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined") with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ option(env="MOZ_FOO", help="foo") option('--with-foo', env="MOZ_FOO", help="foo") """ ): self.get_config() self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined") with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ option('--with-foo', env="MOZ_FOO", help="foo") option('--with-foo', help="foo") """ ): self.get_config() self.assertEqual(str(e.exception), "Option `--with-foo` already defined") def test_option_when(self): with self.moz_configure( """ option('--with-foo', help='foo', when=True) option('--with-bar', help='bar', when=False) option('--with-qux', env="QUX", help='qux', when='--with-foo') set_config('FOO', depends('--with-foo', when=True)(lambda x: x)) set_config('BAR', depends('--with-bar', when=False)(lambda x: x)) set_config('QUX', depends('--with-qux', when='--with-foo')(lambda x: x)) """ ): config = self.get_config() self.assertEqual( config, { "FOO": NegativeOptionValue(), }, ) config = self.get_config(["--with-foo"]) self.assertEqual( config, { "FOO": PositiveOptionValue(), "QUX": NegativeOptionValue(), }, ) config = self.get_config(["--with-foo", "--with-qux"]) self.assertEqual( config, { "FOO": PositiveOptionValue(), "QUX": PositiveOptionValue(), }, ) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--with-bar"]) self.assertEqual( str(e.exception), "--with-bar is not available in this configuration" ) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--with-qux"]) self.assertEqual( str(e.exception), "--with-qux is not available in this configuration" ) with self.assertRaises(InvalidOptionError) as e: self.get_config(["QUX=1"]) self.assertEqual( str(e.exception), "QUX is not available in this configuration" ) config = self.get_config(env={"QUX": "1"}) self.assertEqual( config, { "FOO": NegativeOptionValue(), }, ) help, config = self.get_config(["--help"]) self.assertEqual( help.replace("\\", "/"), textwrap.dedent( """\ Usage: configure [options] Options: [defaults in brackets after descriptions] Help options: --help print this message Options from python/mozbuild/mozbuild/test/configure/data/moz.configure: --with-foo foo Environment variables: """ ), ) help, config = self.get_config(["--help", "--with-foo"]) self.assertEqual( help.replace("\\", "/"), textwrap.dedent( """\ Usage: configure [options] Options: [defaults in brackets after descriptions] Help options: --help print this message Options from python/mozbuild/mozbuild/test/configure/data/moz.configure: --with-foo foo --with-qux qux Environment variables: """ ), ) with self.moz_configure( """ option('--with-foo', help='foo', when=True) set_config('FOO', depends('--with-foo')(lambda x: x)) """ ): with self.assertRaises(ConfigureError) as e: self.get_config() self.assertEqual( str(e.exception), "@depends function needs the same `when` as " "options it depends on", ) with self.moz_configure( """ @depends(when=True) def always(): return True @depends(when=True) def always2(): return True option('--with-foo', help='foo', when=always) set_config('FOO', depends('--with-foo', when=always2)(lambda x: x)) """ ): with self.assertRaises(ConfigureError) as e: self.get_config() self.assertEqual( str(e.exception), "@depends function needs the same `when` as " "options it depends on", ) with self.moz_configure( """ @depends(when=True) def always(): return True @depends(when=True) def always2(): return True with only_when(always2): option('--with-foo', help='foo', when=always) # include() triggers resolution of its dependencies, and their # side effects. include(depends('--with-foo', when=always)(lambda x: x)) # The sandbox should figure that the `when` here is # appropriate. Bad behavior in CombinedDependsFunction.__eq__ # made this fail in the past. set_config('FOO', depends('--with-foo', when=always)(lambda x: x)) """ ): self.get_config() with self.moz_configure( """ option('--with-foo', help='foo') option('--without-bar', help='bar', when='--with-foo') option('--with-qux', help='qux', when='--with-bar') set_config('QUX', True, when='--with-qux') """ ): # These are valid: self.get_config(["--with-foo"]) self.get_config(["--with-foo", "--with-bar"]) self.get_config(["--with-foo", "--without-bar"]) self.get_config(["--with-foo", "--with-bar", "--with-qux"]) self.get_config(["--with-foo", "--with-bar", "--without-qux"]) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--with-bar"]) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--without-bar"]) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--with-qux"]) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--without-qux"]) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--with-foo", "--without-bar", "--with-qux"]) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--with-foo", "--without-bar", "--without-qux"]) def test_include_failures(self): with self.assertRaises(ConfigureError) as e: with self.moz_configure('include("../foo.configure")'): self.get_config() self.assertEqual( str(e.exception), "Cannot include `%s` because it is not in a subdirectory of `%s`" % ( mozpath.normpath(mozpath.join(test_data_path, "..", "foo.configure")), mozpath.normsep(test_data_path), ), ) with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ include('extra.configure') include('extra.configure') """ ): self.get_config() self.assertEqual( str(e.exception), "Cannot include `%s` because it was included already." % mozpath.normpath(mozpath.join(test_data_path, "extra.configure")), ) with self.assertRaises(TypeError) as e: with self.moz_configure( """ include(42) """ ): self.get_config() self.assertEqual(str(e.exception), "Unexpected type: 'int'") def test_include_when(self): with MockedOpen( { os.path.join(test_data_path, "moz.configure"): textwrap.dedent( """ option('--with-foo', help='foo') include('always.configure', when=True) include('never.configure', when=False) include('foo.configure', when='--with-foo') set_config('FOO', foo) set_config('BAR', bar) set_config('QUX', qux) """ ), os.path.join(test_data_path, "always.configure"): textwrap.dedent( """ option('--with-bar', help='bar') @depends('--with-bar') def bar(x): if x: return 'bar' """ ), os.path.join(test_data_path, "never.configure"): textwrap.dedent( """ option('--with-qux', help='qux') @depends('--with-qux') def qux(x): if x: return 'qux' """ ), os.path.join(test_data_path, "foo.configure"): textwrap.dedent( """ option('--with-foo-really', help='really foo') @depends('--with-foo-really') def foo(x): if x: return 'foo' include('foo2.configure', when='--with-foo-really') """ ), os.path.join(test_data_path, "foo2.configure"): textwrap.dedent( """ set_config('FOO2', True) """ ), } ): config = self.get_config() self.assertEqual(config, {}) config = self.get_config(["--with-foo"]) self.assertEqual(config, {}) config = self.get_config(["--with-bar"]) self.assertEqual( config, { "BAR": "bar", }, ) with self.assertRaises(InvalidOptionError) as e: self.get_config(["--with-qux"]) self.assertEqual( str(e.exception), "--with-qux is not available in this configuration" ) config = self.get_config(["--with-foo", "--with-foo-really"]) self.assertEqual( config, { "FOO": "foo", "FOO2": True, }, ) def test_sandbox_failures(self): with self.assertRaises(KeyError) as e: with self.moz_configure( """ include = 42 """ ): self.get_config() self.assertIn("Cannot reassign builtins", str(e.exception)) with self.assertRaises(KeyError) as e: with self.moz_configure( """ foo = 42 """ ): self.get_config() self.assertIn( "Cannot assign `foo` because it is neither a @depends nor a " "@template", str(e.exception), ) def test_depends_failures(self): with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ @depends() def foo(): return """ ): self.get_config() self.assertEqual(str(e.exception), "@depends needs at least one argument") with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ @depends('--with-foo') def foo(value): return value """ ): self.get_config() self.assertEqual( str(e.exception), "'--with-foo' is not a known option. Maybe it's " "declared too late?", ) with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ @depends('--with-foo=42') def foo(value): return value """ ): self.get_config() self.assertEqual(str(e.exception), "Option must not contain an '='") with self.assertRaises(TypeError) as e: with self.moz_configure( """ @depends(42) def foo(value): return value """ ): self.get_config() self.assertEqual( str(e.exception), "Cannot use object of type 'int' as argument " "to @depends", ) with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ @depends('--help') def foo(value): yield """ ): self.get_config() self.assertEqual( str(e.exception), "Cannot decorate generator functions with @depends" ) with self.assertRaises(TypeError) as e: with self.moz_configure( """ @depends('--help') def foo(value): return value depends('--help')(foo) """ ): self.get_config() self.assertEqual(str(e.exception), "Cannot nest @depends functions") with self.assertRaises(TypeError) as e: with self.moz_configure( """ @template def foo(f): pass depends('--help')(foo) """ ): self.get_config() self.assertEqual(str(e.exception), "Cannot use a @template function here") with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ option('--foo', help='foo') @depends('--foo') def foo(value): return value foo() """ ): self.get_config() self.assertEqual(str(e.exception), "The `foo` function may not be called") with self.assertRaises(TypeError) as e: with self.moz_configure( """ @depends('--help', foo=42) def foo(_): return """ ): self.get_config() self.assertEqual( str(e.exception), "depends_impl() got an unexpected keyword argument 'foo'" ) def test_depends_when(self): with self.moz_configure( """ @depends(when=True) def foo(): return 'foo' set_config('FOO', foo) @depends(when=False) def bar(): return 'bar' set_config('BAR', bar) option('--with-qux', help='qux') @depends(when='--with-qux') def qux(): return 'qux' set_config('QUX', qux) """ ): config = self.get_config() self.assertEqual( config, { "FOO": "foo", }, ) config = self.get_config(["--with-qux"]) self.assertEqual( config, { "FOO": "foo", "QUX": "qux", }, ) def test_depends_value(self): with self.moz_configure( """ foo = depends(when=True)('foo') set_config('FOO', foo) bar = depends(when=False)('bar') set_config('BAR', bar) option('--with-qux', help='qux') @depends(when='--with-qux') def qux(): return 'qux' set_config('QUX', qux) """ ): config = self.get_config() self.assertEqual( config, { "FOO": "foo", }, ) with self.assertRaises(TypeError) as e: with self.moz_configure( """ option('--foo', help='foo') depends('--foo')('foo') """ ): self.get_config() self.assertEqual( str(e.exception), "Cannot wrap literal values in @depends with dependencies" ) def test_imports_failures(self): with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ @imports('os') @template def foo(value): return value """ ): self.get_config() self.assertEqual(str(e.exception), "@imports must appear after @template") with self.assertRaises(ConfigureError) as e: with self.moz_configure( """ option('--foo', help='foo') @imports('os') @depends('--foo') def foo(value): return value """ ): self.get_config() self.assertEqual(str(e.exception), "@imports must appear after @depends") for import_ in ( "42", "_from=42, _import='os'", "_from='os', _import='path', _as=42", ): with self.assertRaises(TypeError) as e: with self.moz_configure( """ @imports(%s) @template def foo(value): return value """ % import_ ): self.get_config() self.assertEqual(str(e.exception), "Unexpected type: 'int'") with self.assertRaises(TypeError) as e: with self.moz_configure( """ @imports('os', 42) @template def foo(value): return value """ ): self.get_config() self.assertEqual(str(e.exception), "Unexpected type: 'int'") with self.assertRaises(ValueError) as e: with self.moz_configure( """ @imports('os*') def foo(value): return value """ ): self.get_config() self.assertEqual(str(e.exception), "Invalid argument to @imports: 'os*'") def test_only_when(self): moz_configure = """ option('--enable-when', help='when') @depends('--enable-when', '--help') def when(value, _): return bool(value) with only_when(when): option('--foo', nargs='*', help='foo') @depends('--foo') def foo(value): return value set_config('FOO', foo) set_define('FOO', foo) # It is possible to depend on a function defined in a only_when # block. It then resolves to `None`. set_config('BAR', depends(foo)(lambda x: x)) set_define('BAR', depends(foo)(lambda x: x)) """ with self.moz_configure(moz_configure): config = self.get_config() self.assertEqual( config, { "DEFINES": {}, }, ) config = self.get_config(["--enable-when"]) self.assertEqual( config, { "BAR": NegativeOptionValue(), "FOO": NegativeOptionValue(), "DEFINES": { "BAR": NegativeOptionValue(), "FOO": NegativeOptionValue(), }, }, ) config = self.get_config(["--enable-when", "--foo=bar"]) self.assertEqual( config, { "BAR": PositiveOptionValue(["bar"]), "FOO": PositiveOptionValue(["bar"]), "DEFINES": { "BAR": PositiveOptionValue(["bar"]), "FOO": PositiveOptionValue(["bar"]), }, }, ) # The --foo option doesn't exist when --enable-when is not given. with self.assertRaises(InvalidOptionError) as e: self.get_config(["--foo"]) self.assertEqual( str(e.exception), "--foo is not available in this configuration" ) # Cannot depend on an option defined in a only_when block, because we # don't know what OptionValue would make sense. with self.moz_configure( moz_configure + """ set_config('QUX', depends('--foo')(lambda x: x)) """ ): with self.assertRaises(ConfigureError) as e: self.get_config() self.assertEqual( str(e.exception), "@depends function needs the same `when` as " "options it depends on", ) with self.moz_configure( moz_configure + """ set_config('QUX', depends('--foo', when=when)(lambda x: x)) """ ): self.get_config(["--enable-when"]) # Using imply_option for an option defined in a only_when block fails # similarly if the imply_option happens outside the block. with self.moz_configure( """ imply_option('--foo', True) """ + moz_configure ): with self.assertRaises(InvalidOptionError) as e: self.get_config() self.assertEqual( str(e.exception), "--foo is not available in this configuration" ) # And similarly doesn't fail when the condition is true. with self.moz_configure( """ imply_option('--foo', True) """ + moz_configure ): self.get_config(["--enable-when"]) def test_depends_binary_ops(self): with self.moz_configure( """ option('--foo', nargs=1, help='foo') @depends('--foo') def foo(value): return value or 0 option('--bar', nargs=1, help='bar') @depends('--bar') def bar(value): return value or '' option('--baz', nargs=1, help='baz') @depends('--baz') def baz(value): return value set_config('FOOorBAR', foo | bar) set_config('FOOorBARorBAZ', foo | bar | baz) set_config('FOOandBAR', foo & bar) set_config('FOOandBARandBAZ', foo & bar & baz) """ ): for foo_opt, foo_value in ( ("", 0), ("--foo=foo", PositiveOptionValue(("foo",))), ): for bar_opt, bar_value in ( ("", ""), ("--bar=bar", PositiveOptionValue(("bar",))), ): for baz_opt, baz_value in ( ("", NegativeOptionValue()), ("--baz=baz", PositiveOptionValue(("baz",))), ): config = self.get_config( [x for x in (foo_opt, bar_opt, baz_opt) if x] ) self.assertEqual( config, { "FOOorBAR": foo_value or bar_value, "FOOorBARorBAZ": foo_value or bar_value or baz_value, "FOOandBAR": foo_value and bar_value, "FOOandBARandBAZ": foo_value and bar_value and baz_value, }, ) def test_depends_getattr(self): with self.moz_configure( """ @imports(_from='mozbuild.util', _import='ReadOnlyNamespace') def namespace(**kwargs): return ReadOnlyNamespace(**kwargs) option('--foo', nargs=1, help='foo') @depends('--foo') def foo(value): return value option('--bar', nargs=1, help='bar') @depends('--bar') def bar(value): return value or None @depends(foo, bar) def foobar(foo, bar): return namespace(foo=foo, bar=bar) set_config('FOO', foobar.foo) set_config('BAR', foobar.bar) set_config('BAZ', foobar.baz) """ ): config = self.get_config() self.assertEqual( config, { "FOO": NegativeOptionValue(), }, ) config = self.get_config(["--foo=foo"]) self.assertEqual( config, { "FOO": PositiveOptionValue(("foo",)), }, ) config = self.get_config(["--bar=bar"]) self.assertEqual( config, { "FOO": NegativeOptionValue(), "BAR": PositiveOptionValue(("bar",)), }, ) config = self.get_config(["--foo=foo", "--bar=bar"]) self.assertEqual( config, { "FOO": PositiveOptionValue(("foo",)), "BAR": PositiveOptionValue(("bar",)), }, ) if __name__ == "__main__": main()