* fix capturing and unicode printing in tests
* introduce "_encoding" to py/io/terminalwriter writing * beautify a few __repr__ for better internal debugging --HG-- branch : 1.0.x
This commit is contained in:
		
							parent
							
								
									91597f4100
								
							
						
					
					
						commit
						8fcdac9dd6
					
				|  | @ -1,3 +1,10 @@ | |||
| Changes between 1.0.0 and 1.0.1 | ||||
| ===================================== | ||||
| 
 | ||||
| * various unicode fixes: capturing and prints of unicode strings now  | ||||
|   work within tests, they are encoded as "utf8" by default, terminalwriting  | ||||
|   was adapted and somewhat unified between windows and linux  | ||||
| 
 | ||||
| Changes between 1.0.0b9 and 1.0.0 | ||||
| ===================================== | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								py/_com.py
								
								
								
								
							
							
						
						
									
										10
									
								
								py/_com.py
								
								
								
								
							|  | @ -17,6 +17,16 @@ class MultiCall: | |||
|         self.kwargs = kwargs  | ||||
|         self.results = [] | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         args = [] | ||||
|         if self.args: | ||||
|             args.append("posargs=%r" %(self.args,)) | ||||
|         kw = self.kwargs | ||||
|         args.append(", ".join(["%s=%r" % x for x in self.kwargs.items()])) | ||||
|         args = " ".join(args) | ||||
|         status = "results: %r, rmethods: %r" % (self.results, self.methods) | ||||
|         return "<MultiCall %s %s>" %(args, status) | ||||
| 
 | ||||
|     def execute(self, firstresult=False): | ||||
|         while self.methods: | ||||
|             currentmethod = self.methods.pop() | ||||
|  |  | |||
|  | @ -76,7 +76,7 @@ class StdCaptureFD(Capture): | |||
|             os.close(fd) | ||||
|         if out:  | ||||
|             tmpfile = None | ||||
|             if isinstance(out, file): | ||||
|             if hasattr(out, 'write'): | ||||
|                 tmpfile = out | ||||
|             self.out = py.io.FDCapture(1, tmpfile=tmpfile) | ||||
|             if patchsys:  | ||||
|  | @ -84,7 +84,7 @@ class StdCaptureFD(Capture): | |||
|         if err:  | ||||
|             if mixed and out: | ||||
|                 tmpfile = self.out.tmpfile  | ||||
|             elif isinstance(err, file): | ||||
|             elif hasattr(err, 'write'): | ||||
|                 tmpfile = err | ||||
|             else: | ||||
|                 tmpfile = None | ||||
|  |  | |||
|  | @ -139,6 +139,7 @@ class TerminalWriter(object): | |||
|                      Black=40, Red=41, Green=42, Yellow=43,  | ||||
|                      Blue=44, Purple=45, Cyan=46, White=47, | ||||
|                      bold=1, light=2, blink=5, invert=7) | ||||
|     _encoding = "utf-8" | ||||
| 
 | ||||
|     def __init__(self, file=None, stringio=False): | ||||
|         if file is None: | ||||
|  | @ -194,58 +195,27 @@ class TerminalWriter(object): | |||
| 
 | ||||
|     def write(self, s, **kw): | ||||
|         if s: | ||||
|             s = str(s) | ||||
|             s = self._getbytestring(s) | ||||
|             if self.hasmarkup and kw: | ||||
|                 s = self.markup(s, **kw) | ||||
|             self._file.write(s) | ||||
|             self._file.flush() | ||||
| 
 | ||||
|     def _getbytestring(self, s): | ||||
|             if isinstance(s, unicode): | ||||
|                 return s.encode(self._encoding) | ||||
|             elif not isinstance(s, str): | ||||
|                 return str(s) | ||||
|             return s | ||||
| 
 | ||||
|     def line(self, s='', **kw): | ||||
|         self.write(s, **kw) | ||||
|         self.write('\n') | ||||
| 
 | ||||
| class Win32ConsoleWriter(object): | ||||
| 
 | ||||
|     def __init__(self, file=None, stringio=False): | ||||
|         if file is None: | ||||
|             if stringio: | ||||
|                 self.stringio = file = py.std.cStringIO.StringIO() | ||||
|             else: | ||||
|                 file = py.std.sys.stdout  | ||||
|         elif callable(file): | ||||
|             file = WriteFile(file) | ||||
|         self._file = file | ||||
|         self.fullwidth = get_terminal_width() | ||||
|         self.hasmarkup = should_do_markup(file) | ||||
| 
 | ||||
|     def sep(self, sepchar, title=None, fullwidth=None, **kw): | ||||
|         if fullwidth is None: | ||||
|             fullwidth = self.fullwidth | ||||
|         # the goal is to have the line be as long as possible | ||||
|         # under the condition that len(line) <= fullwidth | ||||
|         if title is not None: | ||||
|             # we want 2 + 2*len(fill) + len(title) <= fullwidth | ||||
|             # i.e.    2 + 2*len(sepchar)*N + len(title) <= fullwidth | ||||
|             #         2*len(sepchar)*N <= fullwidth - len(title) - 2 | ||||
|             #         N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) | ||||
|             N = (fullwidth - len(title) - 2) // (2*len(sepchar)) | ||||
|             fill = sepchar * N | ||||
|             line = "%s %s %s" % (fill, title, fill) | ||||
|         else: | ||||
|             # we want len(sepchar)*N <= fullwidth | ||||
|             # i.e.    N <= fullwidth // len(sepchar) | ||||
|             line = sepchar * (fullwidth // len(sepchar)) | ||||
|         # in some situations there is room for an extra sepchar at the right, | ||||
|         # in particular if we consider that with a sepchar like "_ " the | ||||
|         # trailing space is not important at the end of the line | ||||
|         if len(line) + len(sepchar.rstrip()) <= fullwidth: | ||||
|             line += sepchar.rstrip() | ||||
| 
 | ||||
|         self.line(line, **kw) | ||||
| 
 | ||||
| class Win32ConsoleWriter(TerminalWriter): | ||||
|     def write(self, s, **kw): | ||||
|         if s: | ||||
|             s = str(s) | ||||
|             s = self._getbytestring(s) | ||||
|             if self.hasmarkup: | ||||
|                 handle = GetStdHandle(STD_OUTPUT_HANDLE) | ||||
| 
 | ||||
|  | @ -269,8 +239,8 @@ class Win32ConsoleWriter(object): | |||
|             if self.hasmarkup: | ||||
|                 SetConsoleTextAttribute(handle, FOREGROUND_WHITE) | ||||
| 
 | ||||
|     def line(self, s='', **kw): | ||||
|         self.write(s + '\n', **kw) | ||||
|     def line(self, s="", **kw): | ||||
|         self.write(s+"\n", **kw) | ||||
| 
 | ||||
| if sys.platform == 'win32': | ||||
|     TerminalWriter = Win32ConsoleWriter | ||||
|  |  | |||
|  | @ -37,6 +37,16 @@ class BaseTests: | |||
|         assert len(l) == 1 | ||||
|         assert l[0] == "hello\n" | ||||
| 
 | ||||
|     def test_line_unicode(self):     | ||||
|         tw = self.getwriter() | ||||
|         for encoding in 'utf8', 'latin1': | ||||
|             tw._encoding = encoding  | ||||
|             msg = unicode('b\u00f6y', 'utf8') | ||||
|             tw.line(msg) | ||||
|             l = self.getlines() | ||||
|             assert not isinstance(l[0], unicode)  | ||||
|             assert unicode(l[0], encoding) == msg + "\n" | ||||
| 
 | ||||
|     def test_sep_no_title(self): | ||||
|         tw = self.getwriter() | ||||
|         tw.sep("-", fullwidth=60)  | ||||
|  | @ -85,6 +95,16 @@ class BaseTests: | |||
|         l = self.getlines() | ||||
|         assert len(l[0]) == len(l[1]) | ||||
| 
 | ||||
| class TestTmpfile(BaseTests): | ||||
|     def getwriter(self): | ||||
|         self.path = py.test.config.ensuretemp("terminalwriter").ensure("tmpfile") | ||||
|         self.tw = py.io.TerminalWriter(self.path.open('w+')) | ||||
|         return self.tw | ||||
|     def getlines(self): | ||||
|         io = self.tw._file | ||||
|         io.flush() | ||||
|         return self.path.open('r').readlines() | ||||
| 
 | ||||
| class TestStringIO(BaseTests): | ||||
|     def getwriter(self): | ||||
|         self.tw = py.io.TerminalWriter(stringio=True) | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ class TestMultiCall: | |||
|     def test_uses_copy_of_methods(self): | ||||
|         l = [lambda: 42] | ||||
|         mc = MultiCall(l) | ||||
|         repr(mc) | ||||
|         l[:] = [] | ||||
|         res = mc.execute() | ||||
|         return res == 42 | ||||
|  | @ -33,16 +34,27 @@ class TestMultiCall: | |||
|         p1 = P1()  | ||||
|         p2 = P2()  | ||||
|         multicall = MultiCall([p1.m, p2.m], 23) | ||||
|         assert "23" in repr(multicall) | ||||
|         reslist = multicall.execute() | ||||
|         assert len(reslist) == 2 | ||||
|         # ensure reversed order  | ||||
|         assert reslist == [23, 17] | ||||
| 
 | ||||
|     def test_keyword_args(self): | ||||
|         def f(x):  | ||||
|             return x + 1 | ||||
|         multicall = MultiCall([f], x=23) | ||||
|         assert "x=23" in repr(multicall) | ||||
|         reslist = multicall.execute() | ||||
|         assert reslist == [24] | ||||
|         assert "24" in repr(multicall) | ||||
| 
 | ||||
|     def test_optionalcallarg(self): | ||||
|         class P1: | ||||
|             def m(self, x): | ||||
|                 return x | ||||
|         call = MultiCall([P1().m], 23) | ||||
|         assert "23" in repr(call) | ||||
|         assert call.execute() == [23] | ||||
|         assert call.execute(firstresult=True) == 23 | ||||
|   | ||||
|  |  | |||
|  | @ -145,11 +145,9 @@ class SlaveNode(object): | |||
|         if call.excinfo: | ||||
|             # likely it is not collectable here because of | ||||
|             # platform/import-dependency induced skips  | ||||
|             # XXX somewhat ugly shortcuts - also makes a collection | ||||
|             #     failure into an ItemTestReport - this might confuse | ||||
|             #     pytest_runtest_logreport hooks  | ||||
|             # we fake a setup-error report with the obtained exception | ||||
|             # and do not care about capturing or non-runner hooks  | ||||
|             rep = self.runner.pytest_runtest_makereport(item=item, call=call) | ||||
|             self.pytest_runtest_logreport(rep) | ||||
|             return | ||||
|         item.config.hook.pytest_runtest_protocol(item=item)  | ||||
| 
 | ||||
|  |  | |||
|  | @ -107,11 +107,24 @@ class CaptureManager: | |||
|     def __init__(self): | ||||
|         self._method2capture = {} | ||||
| 
 | ||||
|     def _maketempfile(self): | ||||
|         f = py.std.tempfile.TemporaryFile() | ||||
|         newf = py.io.dupfile(f)  | ||||
|         f.close() | ||||
|         return ustream(newf) | ||||
| 
 | ||||
|     def _makestringio(self): | ||||
|         return py.std.StringIO.StringIO()  | ||||
| 
 | ||||
|     def _startcapture(self, method): | ||||
|         if method == "fd":  | ||||
|             return py.io.StdCaptureFD() | ||||
|             return py.io.StdCaptureFD( | ||||
|                 out=self._maketempfile(), err=self._maketempfile() | ||||
|             ) | ||||
|         elif method == "sys": | ||||
|             return py.io.StdCapture() | ||||
|             return py.io.StdCapture( | ||||
|                 out=self._makestringio(), err=self._makestringio() | ||||
|             ) | ||||
|         else: | ||||
|             raise ValueError("unknown capturing method: %r" % method) | ||||
| 
 | ||||
|  | @ -252,3 +265,13 @@ class CaptureFuncarg: | |||
|     def close(self): | ||||
|         self.capture.reset() | ||||
|         del self.capture | ||||
| 
 | ||||
| def ustream(f): | ||||
|     import codecs | ||||
|     encoding = getattr(f, 'encoding', None) or "UTF-8" | ||||
|     reader = codecs.getreader(encoding) | ||||
|     writer = codecs.getwriter(encoding) | ||||
|     srw = codecs.StreamReaderWriter(f, reader, writer) | ||||
|     srw.encoding = encoding | ||||
|     return srw  | ||||
| 
 | ||||
|  |  | |||
|  | @ -108,6 +108,13 @@ class CallInfo: | |||
|         except: | ||||
|             self.excinfo = py.code.ExceptionInfo() | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         if self.excinfo: | ||||
|             status = "exception: %s" % str(self.excinfo.value) | ||||
|         else: | ||||
|             status = "result: %r" % (self.result,) | ||||
|         return "<CallInfo when=%r %s>" % (self.when, status) | ||||
| 
 | ||||
| def forked_run_report(item): | ||||
|     # for now, we run setup/teardown in the subprocess  | ||||
|     # XXX optionally allow sharing of setup/teardown  | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import py, os, sys | ||||
| from py.__.test.plugin.pytest_capture import CaptureManager | ||||
| from py.__.test.plugin.pytest_capture import CaptureManager, ustream | ||||
| 
 | ||||
| class TestCaptureManager: | ||||
| 
 | ||||
|  | @ -54,6 +54,29 @@ class TestCaptureManager: | |||
|         finally: | ||||
|             capouter.reset() | ||||
| 
 | ||||
| @py.test.mark.multi(method=['fd', 'sys']) | ||||
| def test_capturing_unicode(testdir, method): | ||||
|     testdir.makepyfile(""" | ||||
|         # taken from issue 227 from nosests  | ||||
|         def test_unicode(): | ||||
|             import sys | ||||
|             print sys.stdout | ||||
|             print u'b\\u00f6y' | ||||
|     """) | ||||
|     result = testdir.runpytest("--capture=%s" % method) | ||||
|     result.stdout.fnmatch_lines([ | ||||
|         "*1 passed*" | ||||
|     ]) | ||||
| 
 | ||||
| def test_ustream_helper(testdir): | ||||
|     p = testdir.makepyfile("hello") | ||||
|     f = p.open('w') | ||||
|     #f.encoding = "utf8" | ||||
|     x = ustream(f) | ||||
|     x.write(u'b\\00f6y') | ||||
|     x.close() | ||||
|      | ||||
| 
 | ||||
| def test_collect_capturing(testdir): | ||||
|     p = testdir.makepyfile(""" | ||||
|         print "collect %s failure" % 13 | ||||
|  |  | |||
|  | @ -271,3 +271,13 @@ def test_functional_boxed(testdir): | |||
|         "*1 failed*" | ||||
|     ]) | ||||
| 
 | ||||
| def test_callinfo(): | ||||
|     ci = runner.CallInfo(lambda: 0, '123') | ||||
|     assert ci.when == "123" | ||||
|     assert ci.result == 0 | ||||
|     assert "result" in repr(ci)  | ||||
|     ci = runner.CallInfo(lambda: 0/0, '123') | ||||
|     assert ci.when == "123" | ||||
|     assert not hasattr(ci, 'result') | ||||
|     assert ci.excinfo  | ||||
|     assert "exc" in repr(ci) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue