Skip to content

Commit a35e473

Browse files
committed
Port stdouts decorator from celery/case
1 parent 0fdeee2 commit a35e473

File tree

3 files changed

+156
-10
lines changed

3 files changed

+156
-10
lines changed

tmuxp/testsuite/helpers.py

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
# -*- coding: utf-8 -*-
2-
"""Helper methods for tmuxp unittests."""
2+
"""Helper methods for tmuxp unittests.
3+
4+
_CallableContext, WhateverIO, decorator and stdouts are from the case project,
5+
https://github.com/celery/case, license BSD 3-clause.
6+
"""
37

48
from __future__ import (absolute_import, division, print_function,
59
unicode_literals, with_statement)
610

711
import contextlib
12+
import functools
13+
import inspect
14+
import io
815
import logging
16+
import sys
17+
from contextlib import contextmanager
918
from random import randint
1019

1120
from tmuxp import exc
@@ -194,3 +203,136 @@ def bootstrap(self):
194203
self.TEST_SESSION_NAME = TEST_SESSION_NAME
195204
self.server = t
196205
self.session = session
206+
207+
208+
StringIO = io.StringIO
209+
_SIO_write = StringIO.write
210+
_SIO_init = StringIO.__init__
211+
212+
213+
def update_wrapper(wrapper, wrapped, *args, **kwargs):
214+
wrapper = functools.update_wrapper(wrapper, wrapped, *args, **kwargs)
215+
wrapper.__wrapped__ = wrapped
216+
return wrapper
217+
218+
219+
def wraps(wrapped,
220+
assigned=functools.WRAPPER_ASSIGNMENTS,
221+
updated=functools.WRAPPER_UPDATES):
222+
return functools.partial(update_wrapper, wrapped=wrapped,
223+
assigned=assigned, updated=updated)
224+
225+
226+
class _CallableContext(object):
227+
228+
def __init__(self, context, cargs, ckwargs, fun):
229+
self.context = context
230+
self.cargs = cargs
231+
self.ckwargs = ckwargs
232+
self.fun = fun
233+
234+
def __call__(self, *args, **kwargs):
235+
return self.fun(*args, **kwargs)
236+
237+
def __enter__(self):
238+
self.ctx = self.context(*self.cargs, **self.ckwargs)
239+
return self.ctx.__enter__()
240+
241+
def __exit__(self, *einfo):
242+
if self.ctx:
243+
return self.ctx.__exit__(*einfo)
244+
245+
246+
def decorator(predicate):
247+
context = contextmanager(predicate)
248+
249+
@wraps(predicate)
250+
def take_arguments(*pargs, **pkwargs):
251+
252+
@wraps(predicate)
253+
def decorator(cls):
254+
if inspect.isclass(cls):
255+
orig_setup = cls.setUp
256+
orig_teardown = cls.tearDown
257+
258+
@wraps(cls.setUp)
259+
def around_setup(*args, **kwargs):
260+
try:
261+
contexts = args[0].__rb3dc_contexts__
262+
except AttributeError:
263+
contexts = args[0].__rb3dc_contexts__ = []
264+
p = context(*pargs, **pkwargs)
265+
p.__enter__()
266+
contexts.append(p)
267+
return orig_setup(*args, **kwargs)
268+
around_setup.__wrapped__ = cls.setUp
269+
cls.setUp = around_setup
270+
271+
@wraps(cls.tearDown)
272+
def around_teardown(*args, **kwargs):
273+
try:
274+
contexts = args[0].__rb3dc_contexts__
275+
except AttributeError:
276+
pass
277+
else:
278+
for context in contexts:
279+
context.__exit__(*sys.exc_info())
280+
orig_teardown(*args, **kwargs)
281+
around_teardown.__wrapped__ = cls.tearDown
282+
cls.tearDown = around_teardown
283+
284+
return cls
285+
else:
286+
@wraps(cls)
287+
def around_case(self, *args, **kwargs):
288+
with context(*pargs, **pkwargs) as context_args:
289+
context_args = context_args or ()
290+
if not isinstance(context_args, tuple):
291+
context_args = (context_args,)
292+
return cls(*(self,) + args + context_args, **kwargs)
293+
return around_case
294+
295+
if len(pargs) == 1 and callable(pargs[0]):
296+
fun, pargs = pargs[0], ()
297+
return decorator(fun)
298+
return _CallableContext(context, pargs, pkwargs, decorator)
299+
assert take_arguments.__wrapped__
300+
return take_arguments
301+
302+
303+
class WhateverIO(StringIO):
304+
305+
def __init__(self, v=None, *a, **kw):
306+
_SIO_init(self, v.decode() if isinstance(v, bytes) else v, *a, **kw)
307+
308+
def write(self, data):
309+
_SIO_write(self, data.decode() if isinstance(data, bytes) else data)
310+
311+
312+
@decorator
313+
def stdouts():
314+
"""Override `sys.stdout` and `sys.stderr` with `StringIO`
315+
instances.
316+
Decorator example::
317+
@mock.stdouts
318+
def test_foo(self, stdout, stderr):
319+
something()
320+
self.assertIn('foo', stdout.getvalue())
321+
Context example::
322+
with mock.stdouts() as (stdout, stderr):
323+
something()
324+
self.assertIn('foo', stdout.getvalue())
325+
"""
326+
prev_out, prev_err = sys.stdout, sys.stderr
327+
prev_rout, prev_rerr = sys.__stdout__, sys.__stderr__
328+
mystdout, mystderr = WhateverIO(), WhateverIO()
329+
sys.stdout = sys.__stdout__ = mystdout
330+
sys.stderr = sys.__stderr__ = mystderr
331+
332+
try:
333+
yield mystdout, mystderr
334+
finally:
335+
sys.stdout = prev_out
336+
sys.stderr = prev_err
337+
sys.__stdout__ = prev_rout
338+
sys.__stderr__ = prev_rerr

tmuxp/testsuite/util.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from tmuxp import exc
1717
from tmuxp.exc import BeforeLoadScriptError, BeforeLoadScriptNotExists
18-
from tmuxp.testsuite.helpers import TestCase, TmuxTestCase
18+
from tmuxp.testsuite.helpers import TestCase, TmuxTestCase, stdouts
1919
from tmuxp.util import has_required_tmux_version, run_before_script
2020

2121
logger = logging.getLogger(__name__)
@@ -113,10 +113,12 @@ def test_raise_BeforeLoadScriptError_if_retcode(self):
113113
with self.assertRaises(BeforeLoadScriptError):
114114
run_before_script(script_file)
115115

116-
def test_return_stdout_if_ok(self):
116+
@stdouts()
117+
def test_return_stdout_if_ok(self, stdout, stderr):
117118
script_file = os.path.join(fixtures_dir, 'script_complete.sh')
118119

119120
run_before_script(script_file)
121+
self.assertIn('hello', stdout.getvalue())
120122

121123

122124
class BeforeLoadScriptErrorTestCase(TestCase):

tmuxp/testsuite/workspacebuilder.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from tmuxp import Window, config, exc
2121
from tmuxp._compat import text_type
22-
from tmuxp.testsuite.helpers import TmuxTestCase
22+
from tmuxp.testsuite.helpers import TmuxTestCase, stdouts
2323
from tmuxp.workspacebuilder import WorkspaceBuilder
2424

2525
logger = logging.getLogger(__name__)
@@ -883,8 +883,8 @@ class BeforeLoadScript(TmuxTestCase):
883883
- pane
884884
"""
885885

886-
def test_throw_error_if_retcode_error(self):
887-
886+
@stdouts()
887+
def test_throw_error_if_retcode_error(self, stdout, stderr):
888888
sconfig = kaptan.Kaptan(handler='yaml')
889889
yaml = self.config_script_fails.format(
890890
fixtures_dir=fixtures_dir,
@@ -909,8 +909,8 @@ def test_throw_error_if_retcode_error(self):
909909
msg="Kills session if before_script exits with errcode"
910910
)
911911

912-
def test_throw_error_if_file_not_exists(self):
913-
912+
@stdouts()
913+
def test_throw_error_if_file_not_exists(self, stdout, stderr):
914914
sconfig = kaptan.Kaptan(handler='yaml')
915915
yaml = self.config_script_not_exists.format(
916916
fixtures_dir=fixtures_dir,
@@ -941,7 +941,8 @@ def test_throw_error_if_file_not_exists(self):
941941
msg="Kills session if before_script doesn't exist"
942942
)
943943

944-
def test_true_if_test_passes(self):
944+
@stdouts()
945+
def test_true_if_test_passes(self, stdout, stderr):
945946
assert(
946947
os.path.exists(os.path.join(fixtures_dir, 'script_complete.sh'))
947948
)
@@ -960,7 +961,8 @@ def test_true_if_test_passes(self):
960961
with self.temp_session():
961962
builder.build(session=self.session)
962963

963-
def test_true_if_test_passes_with_args(self):
964+
@stdouts()
965+
def test_true_if_test_passes_with_args(self, *args):
964966
assert(
965967
os.path.exists(os.path.join(fixtures_dir, 'script_complete.sh'))
966968
)

0 commit comments

Comments
 (0)