jenkins-bot has submitted this change and it was merged.
Change subject: [FIX] ui tests: Monkey patch pwb's UI class ......................................................................
[FIX] ui tests: Monkey patch pwb's UI class
This monkey patches the ui class used in pywikibot.bot. It is necessary as on loading the pywikibot package it is creating a logger and handlers which use the current std* streams (which are at that moment sys.std*) as the target streams. This is intercepting a print to the console and changing the targetStream (which was defined on creation of the handler) to a internal buffer stream. Also reading from the console is intercepted and redirected to returning the value of the internal stream.
This is only patching stderr and stdout so if any other targetStream is used it'll crash (intentionally so we can determine what is happening there).
The ui_tests are now also activated so they are run by Travis.
Change-Id: I388f6253a401ef3be49f00b945e5414e47638109 --- M tests/__init__.py M tests/ui_tests.py 2 files changed, 59 insertions(+), 39 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/tests/__init__.py b/tests/__init__.py index edea8ba..3cf20fd 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -59,6 +59,7 @@
library_test_modules = [ 'deprecation', + 'ui', 'tests', 'date', 'mediawikiversion', @@ -100,7 +101,6 @@
disabled_test_modules = [ 'tests', # tests of the tests package - 'ui', # these tests havent been designed to be run in the test runner. ]
disabled_tests = { diff --git a/tests/ui_tests.py b/tests/ui_tests.py index 7045a40..365b329 100644 --- a/tests/ui_tests.py +++ b/tests/ui_tests.py @@ -33,26 +33,40 @@ import time import io
+import pywikibot +from pywikibot.bot import ( + ui, DEBUG, VERBOSE, INFO, STDOUT, INPUT, WARNING, ERROR, CRITICAL +) +from tests.utils import unittest + if sys.version_info[0] > 2: unicode = str
class Stream(object):
- """Handler for a StrigIO or BytesIO instance able to patch itself.""" + """Handler for a StringIO or BytesIO instance able to patch itself."""
- def __init__(self, name): + def __init__(self, name, patched_streams): + """ + Create a new stream with a StringIO or BytesIO instance. + + @param name: The part after 'std' (e.g. 'err'). + @type name: str + @param patched_streams: A mapping which maps the original stream to + the patched stream. + @type patched_streams: dict + """ self._stream = io.StringIO() if sys.version_info[0] > 2 else io.BytesIO() self._name = 'std{0}'.format(name) self._original = getattr(sys, self._name) + patched_streams[self._original] = self._stream
- def patch(self): - setattr(sys, self._name, self._stream) + def reset(self): + """Reset own stream.""" self._stream.truncate(0) self._stream.seek(0)
- def unpatch(self): - setattr(sys, self._name, self._original)
if os.name == "nt": from multiprocessing.managers import BaseManager @@ -106,36 +120,41 @@ s = _manager.get_server() s.serve_forever()
-if __name__ == "__main__": - strout = Stream('out') - strerr = Stream('err') - strin = Stream('in') + +def patched_print(text, targetStream): + org_print(text, patched_streams[targetStream]) + + +def patched_input(): + return strin._stream.readline().strip() + + +# TODO: This complete section doesn't depend on __name__ == '__main__' anymore +if True: + patched_streams = {} + strout = Stream('out', patched_streams) + strerr = Stream('err', patched_streams) + strin = Stream('in', {})
newstdout = strout._stream newstderr = strerr._stream newstdin = strin._stream
+ org_print = ui._print + org_input = ui._raw_input + def patch(): """Patch standard terminal files.""" - strout.patch() - strerr.patch() - strin.patch() + strout.reset() + strerr.reset() + strin.reset() + ui._print = patched_print + ui._raw_input = patched_input
def unpatch(): """un-patch standard terminal files.""" - strout.unpatch() - strerr.unpatch() - strin.unpatch() - - try: - patch() - import pywikibot - finally: - unpatch() - - from tests.utils import unittest - - from pywikibot.bot import DEBUG, VERBOSE, INFO, STDOUT, INPUT, WARNING, ERROR, CRITICAL + ui._print = org_print + ui._raw_input = org_input
logger = logging.getLogger('pywiki') loggingcontext = {'caller_name': "ui_tests", @@ -146,6 +165,8 @@ class UITestCase(unittest.TestCase):
"""UI tests.""" + + net = False
def setUp(self): patch() @@ -265,7 +286,7 @@ self.assertEqual(stderrlines[0], "ERROR: TestException: Testing Exception") self.assertEqual(stderrlines[1], "Traceback (most recent call last):") self.assertEqual(stderrlines[3], """ raise TestException("Testing Exception")""") - self.assertEqual(stderrlines[4], "TestException: Testing Exception") + self.assertTrue(stderrlines[4].endswith(': Testing Exception'))
self.assertNotEqual(stderrlines[-1], "\n")
@@ -436,6 +457,12 @@ """MS Windows terminal tests."""
@classmethod + def setUpClass(cls): + if os.name != 'nt': + raise unittest.SkipTest('requires Windows console') + super(WindowsTerminalTestCase, cls).setUpClass() + + @classmethod def setUpProcess(cls, command): import pywinauto import subprocess @@ -504,6 +531,7 @@
@classmethod def setUpClass(cls): + super(TestWindowsTerminalUnicode, cls).setUpClass() import inspect fn = inspect.getfile(inspect.currentframe()) cls.setUpProcess(["python", "pwb.py", fn, "--run-as-slave-interpreter"]) @@ -553,6 +581,7 @@
@classmethod def setUpClass(cls): + super(TestWindowsTerminalUnicodeArguments, cls).setUpClass() cls.setUpProcess(["cmd", "/k", "echo off"])
@classmethod @@ -571,6 +600,8 @@ # empty line is the new command line self.assertEqual(lines, [u"Alpha", u"Bετα", u"Гамма", u"دلتا", u""])
+ +if __name__ == "__main__": try: try: unittest.main() @@ -578,14 +609,3 @@ pass finally: unpatch() - -else: - from tests.utils import unittest - - class TestTerminalUI(unittest.TestCase): - - """Class to show all tests skipped under unittest.""" - - @unittest.skip("Terminal UI tests can only be run by directly running tests/ui_tests.py") - def testCannotBeRun(self): - pass
pywikibot-commits@lists.wikimedia.org