From cf67fa8b155ece432ff16da2ed27c6e6e4adde79 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Fri, 25 Mar 2016 11:48:35 +0900 Subject: [PATCH] Bug 1257516 - Add a logging handler class to print out configure output on stdout/stderr. r=ted --- python/mozbuild/mozbuild/configure/util.py | 62 +++++++ .../mozbuild/test/configure/test_util.py | 167 +++++++++++++++++- 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/python/mozbuild/mozbuild/configure/util.py b/python/mozbuild/mozbuild/configure/util.py index 25625eadedde..f0145fc81853 100644 --- a/python/mozbuild/mozbuild/configure/util.py +++ b/python/mozbuild/mozbuild/configure/util.py @@ -5,6 +5,9 @@ from __future__ import absolute_import, print_function, unicode_literals import itertools +import logging +import os +import sys from distutils.version import LooseVersion @@ -34,3 +37,62 @@ class Version(LooseVersion): if isinstance(other, unicode): other = other.encode('ascii') return LooseVersion.__cmp__(self, other) + + +class ConfigureOutputHandler(logging.Handler): + '''A logging handler class that sends info messages to stdout and other + messages to stderr. + + Messages sent to stdout are not formatted with the attached Formatter. + Additionally, if they end with '... ', no newline character is printed, + making the next message printed follow the '... '. + ''' + def __init__(self, stdout=sys.stdout, stderr=sys.stderr): + super(ConfigureOutputHandler, self).__init__() + self._stdout, self._stderr = stdout, stderr + try: + fd1 = self._stdout.fileno() + fd2 = self._stderr.fileno() + self._same_output = self._is_same_output(fd1, fd2) + except AttributeError: + self._same_output = self._stdout == self._stderr + self._stdout_waiting = None + + @staticmethod + def _is_same_output(fd1, fd2): + if fd1 == fd2: + return True + stat1 = os.fstat(fd1) + stat2 = os.fstat(fd2) + return stat1.st_ino == stat2.st_ino and stat1.st_dev == stat2.st_dev + + WAITING = 1 + INTERRUPTED = 2 + + def emit(self, record): + try: + if record.levelno == logging.INFO: + stream = self._stdout + msg = record.getMessage() + if (self._stdout_waiting == self.INTERRUPTED and + self._same_output): + msg = ' ... %s' % msg + self._stdout_waiting = msg.endswith('... ') + if msg.endswith('... '): + self._stdout_waiting = self.WAITING + else: + self._stdout_waiting = None + msg = '%s\n' % msg + else: + if self._stdout_waiting == self.WAITING and self._same_output: + self._stdout_waiting = self.INTERRUPTED + self._stdout.write('\n') + self._stdout.flush() + stream = self._stderr + msg = '%s\n' % self.format(record) + stream.write(msg) + stream.flush() + except (KeyboardInterrupt, SystemExit): + raise + except: + self.handleError(record) diff --git a/python/mozbuild/mozbuild/test/configure/test_util.py b/python/mozbuild/mozbuild/test/configure/test_util.py index 9c2f213df458..88b8e54c0159 100644 --- a/python/mozbuild/mozbuild/test/configure/test_util.py +++ b/python/mozbuild/mozbuild/test/configure/test_util.py @@ -4,11 +4,176 @@ from __future__ import absolute_import, print_function, unicode_literals +import logging +import os +import tempfile import unittest +import sys + +from StringIO import StringIO from mozunit import main -from mozbuild.configure.util import Version +from mozbuild.configure.util import ( + ConfigureOutputHandler, + Version, +) + + +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') + logger.debug('qux') + + self.assertEqual(out.getvalue(), 'baz\n') + self.assertEqual(err.getvalue(), 'foo\nbar\nqux\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') + logger.debug('qux') + + self.assertEqual(out.getvalue(), 'baz\n') + self.assertEqual( + err.getvalue(), + 'ERROR:foo\n' + 'WARNING:bar\n' + 'DEBUG:qux\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_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 TestVersion(unittest.TestCase):