diff options
Diffstat (limited to 'python/mozbuild/mozbuild/test/configure/test_util.py')
-rw-r--r-- | python/mozbuild/mozbuild/test/configure/test_util.py | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/configure/test_util.py b/python/mozbuild/mozbuild/test/configure/test_util.py new file mode 100644 index 0000000000..81c2e2a8bf --- /dev/null +++ b/python/mozbuild/mozbuild/test/configure/test_util.py @@ -0,0 +1,539 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import logging +import os +import sys +import tempfile +import textwrap +import unittest + +import six +from buildconfig import topsrcdir +from mozpack import path as mozpath +from mozunit import main +from six import StringIO + +from common import ConfigureTestSandbox +from mozbuild.configure import ConfigureSandbox +from mozbuild.configure.util import ( + ConfigureOutputHandler, + LineIO, + Version, + getpreferredencoding, +) +from mozbuild.util import exec_ + + +class TestConfigureOutputHandler(unittest.TestCase): + def test_separation(self): + out = StringIO() + err = StringIO() + name = "%s.test_separation" % self.__class__.__name__ + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + logger.addHandler(ConfigureOutputHandler(out, err)) + + logger.error("foo") + logger.warning("bar") + logger.info("baz") + # DEBUG level is not printed out by this handler + logger.debug("qux") + + self.assertEqual(out.getvalue(), "baz\n") + self.assertEqual(err.getvalue(), "foo\nbar\n") + + def test_format(self): + out = StringIO() + err = StringIO() + name = "%s.test_format" % self.__class__.__name__ + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + handler = ConfigureOutputHandler(out, err) + handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s")) + logger.addHandler(handler) + + logger.error("foo") + logger.warning("bar") + logger.info("baz") + # DEBUG level is not printed out by this handler + logger.debug("qux") + + self.assertEqual(out.getvalue(), "baz\n") + self.assertEqual(err.getvalue(), "ERROR:foo\n" "WARNING:bar\n") + + def test_continuation(self): + out = StringIO() + name = "%s.test_continuation" % self.__class__.__name__ + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + handler = ConfigureOutputHandler(out, out) + handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s")) + logger.addHandler(handler) + + logger.info("foo") + logger.info("checking bar... ") + logger.info("yes") + logger.info("qux") + + self.assertEqual(out.getvalue(), "foo\n" "checking bar... yes\n" "qux\n") + + out.seek(0) + out.truncate() + + logger.info("foo") + logger.info("checking bar... ") + logger.warning("hoge") + logger.info("no") + logger.info("qux") + + self.assertEqual( + out.getvalue(), + "foo\n" "checking bar... \n" "WARNING:hoge\n" " ... no\n" "qux\n", + ) + + out.seek(0) + out.truncate() + + logger.info("foo") + logger.info("checking bar... ") + logger.warning("hoge") + logger.warning("fuga") + logger.info("no") + logger.info("qux") + + self.assertEqual( + out.getvalue(), + "foo\n" + "checking bar... \n" + "WARNING:hoge\n" + "WARNING:fuga\n" + " ... no\n" + "qux\n", + ) + + out.seek(0) + out.truncate() + err = StringIO() + + logger.removeHandler(handler) + handler = ConfigureOutputHandler(out, err) + handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s")) + logger.addHandler(handler) + + logger.info("foo") + logger.info("checking bar... ") + logger.warning("hoge") + logger.warning("fuga") + logger.info("no") + logger.info("qux") + + self.assertEqual(out.getvalue(), "foo\n" "checking bar... no\n" "qux\n") + + self.assertEqual(err.getvalue(), "WARNING:hoge\n" "WARNING:fuga\n") + + def test_queue_debug(self): + out = StringIO() + name = "%s.test_queue_debug" % self.__class__.__name__ + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + handler = ConfigureOutputHandler(out, out, maxlen=3) + handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s")) + logger.addHandler(handler) + + with handler.queue_debug(): + logger.info("checking bar... ") + logger.debug("do foo") + logger.info("yes") + logger.info("qux") + + self.assertEqual(out.getvalue(), "checking bar... yes\n" "qux\n") + + out.seek(0) + out.truncate() + + with handler.queue_debug(): + logger.info("checking bar... ") + logger.debug("do foo") + logger.info("no") + logger.error("fail") + + self.assertEqual( + out.getvalue(), "checking bar... no\n" "DEBUG:do foo\n" "ERROR:fail\n" + ) + + out.seek(0) + out.truncate() + + with handler.queue_debug(): + logger.info("checking bar... ") + logger.debug("do foo") + logger.debug("do bar") + logger.debug("do baz") + logger.info("no") + logger.error("fail") + + self.assertEqual( + out.getvalue(), + "checking bar... no\n" + "DEBUG:do foo\n" + "DEBUG:do bar\n" + "DEBUG:do baz\n" + "ERROR:fail\n", + ) + + out.seek(0) + out.truncate() + + with handler.queue_debug(): + logger.info("checking bar... ") + logger.debug("do foo") + logger.debug("do bar") + logger.debug("do baz") + logger.debug("do qux") + logger.debug("do hoge") + logger.info("no") + logger.error("fail") + + self.assertEqual( + out.getvalue(), + "checking bar... no\n" + "DEBUG:<truncated - see config.log for full output>\n" + "DEBUG:do baz\n" + "DEBUG:do qux\n" + "DEBUG:do hoge\n" + "ERROR:fail\n", + ) + + out.seek(0) + out.truncate() + + try: + with handler.queue_debug(): + logger.info("checking bar... ") + logger.debug("do foo") + logger.debug("do bar") + logger.info("no") + e = Exception("fail") + raise e + except Exception as caught: + self.assertIs(caught, e) + + self.assertEqual( + out.getvalue(), "checking bar... no\n" "DEBUG:do foo\n" "DEBUG:do bar\n" + ) + + def test_queue_debug_reentrant(self): + out = StringIO() + name = "%s.test_queue_debug_reentrant" % self.__class__.__name__ + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + handler = ConfigureOutputHandler(out, out, maxlen=10) + handler.setFormatter(logging.Formatter("%(levelname)s| %(message)s")) + logger.addHandler(handler) + + try: + with handler.queue_debug(): + logger.info("outer info") + logger.debug("outer debug") + with handler.queue_debug(): + logger.info("inner info") + logger.debug("inner debug") + e = Exception("inner exception") + raise e + except Exception as caught: + self.assertIs(caught, e) + + self.assertEqual( + out.getvalue(), + "outer info\n" "inner info\n" "DEBUG| outer debug\n" "DEBUG| inner debug\n", + ) + + out.seek(0) + out.truncate() + + try: + with handler.queue_debug(): + logger.info("outer info") + logger.debug("outer debug") + with handler.queue_debug(): + logger.info("inner info") + logger.debug("inner debug") + e = Exception("outer exception") + raise e + except Exception as caught: + self.assertIs(caught, e) + + self.assertEqual( + out.getvalue(), + "outer info\n" "inner info\n" "DEBUG| outer debug\n" "DEBUG| inner debug\n", + ) + + out.seek(0) + out.truncate() + + with handler.queue_debug(): + logger.info("outer info") + logger.debug("outer debug") + with handler.queue_debug(): + logger.info("inner info") + logger.debug("inner debug") + logger.error("inner error") + self.assertEqual( + out.getvalue(), + "outer info\n" + "inner info\n" + "DEBUG| outer debug\n" + "DEBUG| inner debug\n" + "ERROR| inner error\n", + ) + + out.seek(0) + out.truncate() + + with handler.queue_debug(): + logger.info("outer info") + logger.debug("outer debug") + with handler.queue_debug(): + logger.info("inner info") + logger.debug("inner debug") + logger.error("outer error") + self.assertEqual( + out.getvalue(), + "outer info\n" + "inner info\n" + "DEBUG| outer debug\n" + "DEBUG| inner debug\n" + "ERROR| outer error\n", + ) + + def test_is_same_output(self): + fd1 = sys.stderr.fileno() + fd2 = os.dup(fd1) + try: + self.assertTrue(ConfigureOutputHandler._is_same_output(fd1, fd2)) + finally: + os.close(fd2) + + fd2, path = tempfile.mkstemp() + try: + self.assertFalse(ConfigureOutputHandler._is_same_output(fd1, fd2)) + + fd3 = os.dup(fd2) + try: + self.assertTrue(ConfigureOutputHandler._is_same_output(fd2, fd3)) + finally: + os.close(fd3) + + with open(path, "a") as fh: + fd3 = fh.fileno() + self.assertTrue(ConfigureOutputHandler._is_same_output(fd2, fd3)) + + finally: + os.close(fd2) + os.remove(path) + + +class TestLineIO(unittest.TestCase): + def test_lineio(self): + lines = [] + l = LineIO(lambda l: lines.append(l)) + + l.write("a") + self.assertEqual(lines, []) + + l.write("b") + self.assertEqual(lines, []) + + l.write("\n") + self.assertEqual(lines, ["ab"]) + + l.write("cdef") + self.assertEqual(lines, ["ab"]) + + l.write("\n") + self.assertEqual(lines, ["ab", "cdef"]) + + l.write("ghi\njklm") + self.assertEqual(lines, ["ab", "cdef", "ghi"]) + + l.write("nop\nqrst\nuv\n") + self.assertEqual(lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv"]) + + l.write("wx\nyz") + self.assertEqual(lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv", "wx"]) + + l.close() + self.assertEqual( + lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv", "wx", "yz"] + ) + + def test_lineio_contextmanager(self): + lines = [] + with LineIO(lambda l: lines.append(l)) as l: + l.write("a\nb\nc") + + self.assertEqual(lines, ["a", "b"]) + + self.assertEqual(lines, ["a", "b", "c"]) + + +class TestLogSubprocessOutput(unittest.TestCase): + def test_non_ascii_subprocess_output(self): + out = StringIO() + sandbox = ConfigureSandbox({}, {}, ["configure"], out, out) + + sandbox.include_file( + mozpath.join(topsrcdir, "build", "moz.configure", "util.configure") + ) + sandbox.include_file( + mozpath.join( + topsrcdir, + "python", + "mozbuild", + "mozbuild", + "test", + "configure", + "data", + "subprocess.configure", + ) + ) + status = 0 + try: + sandbox.run() + except SystemExit as e: + status = e.code + + self.assertEqual(status, 0) + quote_char = "'" + if getpreferredencoding().lower() == "utf-8": + quote_char = "\u00B4" + self.assertEqual(six.ensure_text(out.getvalue().strip()), quote_char) + + +class TestVersion(unittest.TestCase): + def test_version_simple(self): + v = Version("1") + self.assertEqual(v, "1") + self.assertLess(v, "2") + self.assertGreater(v, "0.5") + self.assertEqual(v.major, 1) + self.assertEqual(v.minor, 0) + self.assertEqual(v.patch, 0) + + def test_version_more(self): + v = Version("1.2.3b") + self.assertLess(v, "2") + self.assertEqual(v.major, 1) + self.assertEqual(v.minor, 2) + self.assertEqual(v.patch, 3) + + def test_version_bad(self): + # A version with a letter in the middle doesn't really make sense, + # so everything after it should be ignored. + v = Version("1.2b.3") + self.assertLess(v, "2") + self.assertEqual(v.major, 1) + self.assertEqual(v.minor, 2) + self.assertEqual(v.patch, 0) + + def test_version_badder(self): + v = Version("1b.2.3") + self.assertLess(v, "2") + self.assertEqual(v.major, 1) + self.assertEqual(v.minor, 0) + self.assertEqual(v.patch, 0) + + +class TestCheckCmdOutput(unittest.TestCase): + def get_result(self, command="", paths=None): + paths = paths or {} + config = {} + out = StringIO() + sandbox = ConfigureTestSandbox(paths, config, {}, ["/bin/configure"], out, out) + sandbox.include_file( + mozpath.join(topsrcdir, "build", "moz.configure", "util.configure") + ) + status = 0 + try: + exec_(command, sandbox) + sandbox.run() + except SystemExit as e: + status = e.code + return config, out.getvalue(), status + + def test_simple_program(self): + def mock_simple_prog(_, args): + if len(args) == 1 and args[0] == "--help": + return 0, "simple program help...", "" + self.fail("Unexpected arguments to mock_simple_program: %s" % args) + + prog_path = mozpath.abspath("/simple/prog") + cmd = "log.info(check_cmd_output('%s', '--help'))" % prog_path + config, out, status = self.get_result(cmd, paths={prog_path: mock_simple_prog}) + self.assertEqual(config, {}) + self.assertEqual(status, 0) + self.assertEqual(out, "simple program help...\n") + + def test_failing_program(self): + def mock_error_prog(_, args): + if len(args) == 1 and args[0] == "--error": + return (127, "simple program output", "simple program error output") + self.fail("Unexpected arguments to mock_error_program: %s" % args) + + prog_path = mozpath.abspath("/simple/prog") + cmd = "log.info(check_cmd_output('%s', '--error'))" % prog_path + config, out, status = self.get_result(cmd, paths={prog_path: mock_error_prog}) + self.assertEqual(config, {}) + self.assertEqual(status, 1) + self.assertEqual( + out, + textwrap.dedent( + """\ + DEBUG: Executing: `%s --error` + DEBUG: The command returned non-zero exit status 127. + DEBUG: Its output was: + DEBUG: | simple program output + DEBUG: Its error output was: + DEBUG: | simple program error output + ERROR: Command `%s --error` failed with exit status 127. + """ + % (prog_path, prog_path) + ), + ) + + def test_error_callback(self): + def mock_error_prog(_, args): + if len(args) == 1 and args[0] == "--error": + return 127, "simple program error...", "" + self.fail("Unexpected arguments to mock_error_program: %s" % args) + + prog_path = mozpath.abspath("/simple/prog") + cmd = textwrap.dedent( + """\ + check_cmd_output('%s', '--error', + onerror=lambda: die('`prog` produced an error')) + """ + % prog_path + ) + config, out, status = self.get_result(cmd, paths={prog_path: mock_error_prog}) + self.assertEqual(config, {}) + self.assertEqual(status, 1) + self.assertEqual( + out, + textwrap.dedent( + """\ + DEBUG: Executing: `%s --error` + DEBUG: The command returned non-zero exit status 127. + DEBUG: Its output was: + DEBUG: | simple program error... + ERROR: `prog` produced an error + """ + % prog_path + ), + ) + + +if __name__ == "__main__": + main() |