diff options
Diffstat (limited to 'python/mozbuild/mozbuild/test/configure/common.py')
-rw-r--r-- | python/mozbuild/mozbuild/test/configure/common.py | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/configure/common.py b/python/mozbuild/mozbuild/test/configure/common.py new file mode 100644 index 0000000000..7dc1b85b22 --- /dev/null +++ b/python/mozbuild/mozbuild/test/configure/common.py @@ -0,0 +1,307 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import copy +import errno +import os +import subprocess +import sys +import tempfile +import unittest + +import six +from buildconfig import topobjdir, topsrcdir +from mozpack import path as mozpath +from six import StringIO, string_types + +from mozbuild.configure import ConfigureSandbox +from mozbuild.util import ReadOnlyNamespace, memoized_property + + +def fake_short_path(path): + if sys.platform.startswith("win"): + return "/".join( + p.split(" ", 1)[0] + "~1" if " " in p else p for p in mozpath.split(path) + ) + return path + + +def ensure_exe_extension(path): + if sys.platform.startswith("win"): + return path + ".exe" + return path + + +class ConfigureTestVFS(object): + def __init__(self, paths): + self._paths = set(mozpath.abspath(p) for p in paths) + + def _real_file(self, path): + return mozpath.basedir(path, [topsrcdir, topobjdir, tempfile.gettempdir()]) + + def exists(self, path): + if path in self._paths: + return True + if self._real_file(path): + return os.path.exists(path) + return False + + def isfile(self, path): + path = mozpath.abspath(path) + if path in self._paths: + return True + if self._real_file(path): + return os.path.isfile(path) + return False + + def expanduser(self, path): + return os.path.expanduser(path) + + def isdir(self, path): + path = mozpath.abspath(path) + if any(mozpath.basedir(mozpath.dirname(p), [path]) for p in self._paths): + return True + if self._real_file(path): + return os.path.isdir(path) + return False + + def getsize(self, path): + if not self._real_file(path): + raise FileNotFoundError(path) + return os.path.getsize(path) + + +class ConfigureTestSandbox(ConfigureSandbox): + """Wrapper around the ConfigureSandbox for testing purposes. + + Its arguments are the same as ConfigureSandbox, except for the additional + `paths` argument, which is a dict where the keys are file paths and the + values are either None or a function that will be called when the sandbox + calls an implemented function from subprocess with the key as command. + When the command is CONFIG_SHELL, the function for the path of the script + that follows will be called. + + The API for those functions is: + retcode, stdout, stderr = func(stdin, args) + + This class is only meant to implement the minimal things to make + moz.configure testing possible. As such, it takes shortcuts. + """ + + def __init__(self, paths, config, environ, *args, **kwargs): + self._search_path = environ.get("PATH", "").split(os.pathsep) + + self._subprocess_paths = { + mozpath.abspath(k): v for k, v in six.iteritems(paths) if v + } + + paths = list(paths) + + environ = copy.copy(environ) + if "CONFIG_SHELL" not in environ: + environ["CONFIG_SHELL"] = mozpath.abspath("/bin/sh") + self._subprocess_paths[environ["CONFIG_SHELL"]] = self.shell + paths.append(environ["CONFIG_SHELL"]) + self._subprocess_paths[ + mozpath.join(topsrcdir, "build/win32/vswhere.exe") + ] = self.vswhere + + vfs = ConfigureTestVFS(paths) + + os_path = {k: getattr(vfs, k) for k in dir(vfs) if not k.startswith("_")} + + os_path.update(self.OS.path.__dict__) + + os_contents = {} + exec("from os import *", {}, os_contents) + os_contents["path"] = ReadOnlyNamespace(**os_path) + os_contents["environ"] = dict(environ) + self.imported_os = ReadOnlyNamespace(**os_contents) + + super(ConfigureTestSandbox, self).__init__(config, environ, *args, **kwargs) + + @memoized_property + def _wrapped_mozfile(self): + return ReadOnlyNamespace(which=self.which) + + @memoized_property + def _wrapped_os(self): + return self.imported_os + + @memoized_property + def _wrapped_subprocess(self): + return ReadOnlyNamespace( + CalledProcessError=subprocess.CalledProcessError, + check_output=self.check_output, + PIPE=subprocess.PIPE, + STDOUT=subprocess.STDOUT, + Popen=self.Popen, + ) + + @memoized_property + def _wrapped_ctypes(self): + class CTypesFunc(object): + def __init__(self, func): + self._func = func + + def __call__(self, *args, **kwargs): + return self._func(*args, **kwargs) + + return ReadOnlyNamespace( + create_unicode_buffer=self.create_unicode_buffer, + windll=ReadOnlyNamespace( + kernel32=ReadOnlyNamespace( + GetShortPathNameW=CTypesFunc(self.GetShortPathNameW) + ) + ), + wintypes=ReadOnlyNamespace(LPCWSTR=0, LPWSTR=1, DWORD=2), + ) + + @memoized_property + def _wrapped__winreg(self): + def OpenKey(*args, **kwargs): + raise WindowsError() + + return ReadOnlyNamespace(HKEY_LOCAL_MACHINE=0, OpenKey=OpenKey) + + def create_unicode_buffer(self, *args, **kwargs): + class Buffer(object): + def __init__(self): + self.value = "" + + return Buffer() + + def GetShortPathNameW(self, path_in, path_out, length): + path_out.value = fake_short_path(path_in) + return length + + def which(self, command, mode=None, path=None, exts=None): + if isinstance(path, string_types): + path = path.split(os.pathsep) + + for parent in path or self._search_path: + c = mozpath.abspath(mozpath.join(parent, command)) + for candidate in (c, ensure_exe_extension(c)): + if self.imported_os.path.exists(candidate): + return candidate + return None + + def Popen(self, args, stdin=None, stdout=None, stderr=None, **kargs): + program = self.which(args[0]) + if not program: + raise OSError(errno.ENOENT, "File not found") + + func = self._subprocess_paths.get(program) + retcode, stdout, stderr = func(stdin, args[1:]) + + class Process(object): + def communicate(self, stdin=None): + return stdout, stderr + + def wait(self): + return retcode + + return Process() + + def check_output(self, args, **kwargs): + proc = self.Popen(args, **kwargs) + stdout, stderr = proc.communicate() + retcode = proc.wait() + if retcode: + raise subprocess.CalledProcessError(retcode, args, stdout) + return stdout + + def shell(self, stdin, args): + script = mozpath.abspath(args[0]) + if script in self._subprocess_paths: + return self._subprocess_paths[script](stdin, args[1:]) + return 127, "", "File not found" + + def vswhere(self, stdin, args): + return 0, "[]", "" + + def get_config(self, name): + # Like the loop in ConfigureSandbox.run, but only execute the code + # associated with the given config item. + for func, args in self._execution_queue: + if ( + func == self._resolve_and_set + and args[0] is self._config + and args[1] == name + ): + func(*args) + return self._config.get(name) + + +class BaseConfigureTest(unittest.TestCase): + HOST = "x86_64-pc-linux-gnu" + + def setUp(self): + self._cwd = os.getcwd() + os.chdir(topobjdir) + + def tearDown(self): + os.chdir(self._cwd) + + def config_guess(self, stdin, args): + return 0, self.HOST, "" + + def config_sub(self, stdin, args): + return 0, args[0], "" + + def get_sandbox( + self, + paths, + config, + args=[], + environ={}, + mozconfig="", + out=None, + logger=None, + cls=ConfigureTestSandbox, + ): + kwargs = {} + if logger: + kwargs["logger"] = logger + else: + if not out: + out = StringIO() + kwargs["stdout"] = out + kwargs["stderr"] = out + + if hasattr(self, "TARGET"): + target = ["--target=%s" % self.TARGET] + else: + target = [] + + if mozconfig: + fh, mozconfig_path = tempfile.mkstemp(text=True) + os.write(fh, six.ensure_binary(mozconfig)) + os.close(fh) + else: + mozconfig_path = os.path.join( + os.path.dirname(__file__), "data", "empty_mozconfig" + ) + + try: + environ = dict( + environ, + OLD_CONFIGURE=os.path.join(topsrcdir, "old-configure"), + MOZCONFIG=mozconfig_path, + ) + + paths = dict(paths) + autoconf_dir = mozpath.join(topsrcdir, "build", "autoconf") + paths[mozpath.join(autoconf_dir, "config.guess")] = self.config_guess + paths[mozpath.join(autoconf_dir, "config.sub")] = self.config_sub + + sandbox = cls( + paths, config, environ, ["configure"] + target + args, **kwargs + ) + sandbox.include_file(os.path.join(topsrcdir, "moz.configure")) + + return sandbox + finally: + if mozconfig: + os.remove(mozconfig_path) |