mozlog: move the capture io class from web-platform/tests to mozlog (bug 1021926)

The ability to capture the parent process' stdio is suggested to be a useful feature
to move from web-platform/tests into mozlog. To do so, I have created a new capture.py
file within mozlog/mozlog. This includes the CaptureIO class and its dependencies,
including the LoggingWrapper and LogThread classes. These have been removed from their
original location, to avoid duplication, and the files depending on them updated
accordingly.

It would be useful to add unittests testing the CaptureIO enter and exit methods, and
the original_stdio, logging_queue and logging_thread properties. I have begun such a
file with test_capture.py in mozlog/tests. This is a work in progress, however I may
need some guidance, please, in regards to creating appropriate mock data to assert.

Differential Revision: https://phabricator.services.mozilla.com/D22166

gecko-commit: f6705b8320496d0e67213299773e0c429f2b63ef
gecko-integration-branch: central
gecko-reviewers: jgraham
diff --git a/tools/wptrunner/wptrunner/testrunner.py b/tools/wptrunner/wptrunner/testrunner.py
index 9228af6..44b5401 100644
--- a/tools/wptrunner/wptrunner/testrunner.py
+++ b/tools/wptrunner/wptrunner/testrunner.py
@@ -7,9 +7,7 @@
 from collections import namedtuple
 from multiprocessing import Process, current_process, Queue
 
-from mozlog import structuredlog
-
-import wptlogging
+from mozlog import structuredlog, capture
 
 # Special value used as a sentinal in various commands
 Stop = object()
@@ -142,7 +140,7 @@
 
     logger = MessageLogger(send_message)
 
-    with wptlogging.CaptureIO(logger, capture_stdio):
+    with capture.CaptureIO(logger, capture_stdio):
         try:
             browser = executor_browser_cls(**executor_browser_kwargs)
             executor = executor_cls(browser, **executor_kwargs)
diff --git a/tools/wptrunner/wptrunner/wptlogging.py b/tools/wptrunner/wptrunner/wptlogging.py
index 9e3ff54..2070f77 100644
--- a/tools/wptrunner/wptrunner/wptlogging.py
+++ b/tools/wptrunner/wptrunner/wptlogging.py
@@ -1,12 +1,9 @@
 import logging
-import sys
-import threading
-from six import StringIO
-from multiprocessing import Queue
 
 from mozlog import commandline, stdadapter, set_default_logger
 from mozlog.structuredlog import StructuredLogger
 
+
 def setup(args, defaults):
     logger = args.pop('log', None)
     if logger:
@@ -49,86 +46,3 @@
             data = data.copy()
             data["level"] = self.to_level
         return self.inner(data)
-
-
-class LogThread(threading.Thread):
-    def __init__(self, queue, logger, level):
-        self.queue = queue
-        self.log_func = getattr(logger, level)
-        threading.Thread.__init__(self, name="Thread-Log")
-        self.daemon = True
-
-    def run(self):
-        while True:
-            try:
-                msg = self.queue.get()
-            except (EOFError, IOError):
-                break
-            if msg is None:
-                break
-            else:
-                self.log_func(msg)
-
-
-class LoggingWrapper(StringIO):
-    """Wrapper for file like objects to redirect output to logger
-    instead"""
-
-    def __init__(self, queue, prefix=None):
-        StringIO.__init__(self)
-        self.queue = queue
-        self.prefix = prefix
-
-    def write(self, data):
-        if isinstance(data, str):
-            try:
-                data = data.decode("utf8")
-            except UnicodeDecodeError:
-                data = data.encode("string_escape").decode("ascii")
-
-        if data.endswith("\n"):
-            data = data[:-1]
-        if data.endswith("\r"):
-            data = data[:-1]
-        if not data:
-            return
-        if self.prefix is not None:
-            data = "%s: %s" % (self.prefix, data)
-        self.queue.put(data)
-
-    def flush(self):
-        pass
-
-
-class CaptureIO(object):
-    def __init__(self, logger, do_capture):
-        self.logger = logger
-        self.do_capture = do_capture
-        self.logging_queue = None
-        self.logging_thread = None
-        self.original_stdio = None
-
-    def __enter__(self):
-        if self.do_capture:
-            self.original_stdio = (sys.stdout, sys.stderr)
-            self.logging_queue = Queue()
-            self.logging_thread = LogThread(self.logging_queue, self.logger, "info")
-            sys.stdout = LoggingWrapper(self.logging_queue, prefix="STDOUT")
-            sys.stderr = LoggingWrapper(self.logging_queue, prefix="STDERR")
-            self.logging_thread.start()
-
-    def __exit__(self, *args, **kwargs):
-        if self.do_capture:
-            sys.stdout, sys.stderr = self.original_stdio
-            if self.logging_queue is not None:
-                self.logger.info("Closing logging queue")
-                self.logging_queue.put(None)
-                if self.logging_thread is not None:
-                    self.logging_thread.join(10)
-                while not self.logging_queue.empty():
-                    try:
-                        self.logger.warning("Dropping log message: %r", self.logging_queue.get())
-                    except Exception:
-                        pass
-                self.logging_queue.close()
-                self.logger.info("queue closed")
diff --git a/tools/wptrunner/wptrunner/wptrunner.py b/tools/wptrunner/wptrunner/wptrunner.py
index 79ec284..42712f6 100644
--- a/tools/wptrunner/wptrunner/wptrunner.py
+++ b/tools/wptrunner/wptrunner/wptrunner.py
@@ -12,6 +12,7 @@
 import wptcommandline
 import wptlogging
 import wpttest
+from mozlog import capture
 from font import FontInstaller
 from testrunner import ManagerGroup
 from browsers.base import NullBrowser
@@ -132,7 +133,7 @@
 
 
 def run_tests(config, test_paths, product, **kwargs):
-    with wptlogging.CaptureIO(logger, not kwargs["no_capture_stdio"]):
+    with capture.CaptureIO(logger, not kwargs["no_capture_stdio"]):
         env.do_delayed_imports(logger, test_paths)
 
         product = products.load_product(config, product, load_cls=True)