Skip to content

Commit c2a7200

Browse files
committed
Merge pull request #150 from tony/clean-test-stdout
Clean test stdout
2 parents af32280 + d551863 commit c2a7200

File tree

13 files changed

+215
-34
lines changed

13 files changed

+215
-34
lines changed

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@ test:
44
watch_test:
55
if command -v entr > /dev/null; then find . -type f -not -path '*/\.*' | grep -i '.*[.]py' | entr -c make test; else make test; echo "\nInstall entr(1) to automatically run tests on file change.\n See http://entrproject.org/"; fi
66

7+
nose:
8+
nosetests tmuxp/testsuite/*.py -m "^test*."
9+
10+
nose_time:
11+
nosetests tmuxp/testsuite/*.py -m "^test*." --with-timer --timer-top-n 15
12+
713
build_docs:
814
cd doc && $(MAKE) html
915

1016
watch_docs:
1117
cd doc && $(MAKE) watch_docs
1218

1319
flake8:
14-
flake8 tmuxp
20+
flake8 tmuxp
1521

1622
watch_flake8:
17-
if command -v entr > /dev/null; then find . -type f -not -path '*/\.*' | grep -i '.*[.][py]' | entr -c make flake8; else make flake8; echo "\nInstall entr(1) to automatically run tests on file change.\n See http://entrproject.org/"; fi
23+
if command -v entr > /dev/null; then find . -type f -not -path '*/\.*' | grep -i '.*[.][py]' | entr -c make flake8; else make flake8; echo "\nInstall entr(1) to automatically run tests on file change.\n See http://entrproject.org/"; fi
1824

1925
.PHONY: flake8

bootstrap_env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def which(exe=None, throw=True):
7979
python_bin = os.path.join(env_dir, 'bin', 'python')
8080
virtualenv_bin = which('virtualenv', throw=False)
8181
virtualenv_exists = os.path.exists(env_dir) and os.path.isfile(python_bin)
82-
sphinx_requirements_filepath = os.path.join(project_dir, 'doc', 'requirements.pip')
82+
sphinx_requirements_filepath = os.path.join(project_dir, 'requirements', 'doc.txt')
8383

8484

8585
try:

doc/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-r ../requirements/doc.txt
File renamed without changes.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-r ../requirements.pip
1+
-r ./base.txt
22
docutils==0.12
33
sphinx
44
reportlab

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
with open("tmuxp/__about__.py") as fp:
1515
exec(fp.read(), about)
1616

17-
with open('requirements.pip') as f:
17+
with open('requirements/base.txt') as f:
1818
install_reqs = [line for line in f.read().split('\n') if line]
1919
tests_reqs = []
2020

tmuxp/testsuite/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
import pkgutil
1313
import sys
1414

15-
try:
16-
import unittest2 as unittest
17-
except ImportError: # Python 2.7
18-
import unittest
19-
2015
from tmuxp import log
2116
from tmuxp._compat import string_types, PY2, reraise
2217
from tmuxp.server import Server
2318

19+
if sys.version_info < (2, 7):
20+
import unittest2 as unittest
21+
else:
22+
import unittest
23+
2424
t = Server()
2525
t.socket_name = 'tmuxp_test'
2626

tmuxp/testsuite/helpers.py

Lines changed: 178 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,49 @@
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
9-
from random import randint
16+
import os
17+
import sys
18+
import tempfile
19+
from contextlib import contextmanager
1020

1121
from tmuxp import exc
1222
from tmuxp.testsuite import t
1323

14-
try:
24+
if sys.version_info < (2, 7):
1525
import unittest2 as unittest
16-
except ImportError: # Python 2.7
26+
else:
1727
import unittest
1828

19-
2029
logger = logging.getLogger(__name__)
2130

2231
TEST_SESSION_PREFIX = 'test tmuxp_'
2332

33+
namer = tempfile._RandomNameSequence()
34+
2435

2536
def get_test_session_name(server, prefix=TEST_SESSION_PREFIX):
2637
while True:
27-
session_name = prefix + str(randint(0, 9999999))
38+
session_name = prefix + next(namer)
2839
if not t.has_session(session_name):
2940
break
3041
return session_name
3142

3243

3344
def get_test_window_name(session, prefix=TEST_SESSION_PREFIX):
3445
while True:
35-
window_name = prefix + str(randint(0, 9999999))
46+
window_name = prefix + next(namer)
3647
if not session.findWhere(window_name=window_name):
3748
break
3849
return window_name
@@ -194,3 +205,163 @@ def bootstrap(self):
194205
self.TEST_SESSION_NAME = TEST_SESSION_NAME
195206
self.server = t
196207
self.session = session
208+
209+
210+
StringIO = io.StringIO
211+
_SIO_write = StringIO.write
212+
_SIO_init = StringIO.__init__
213+
214+
215+
def update_wrapper(wrapper, wrapped, *args, **kwargs):
216+
wrapper = functools.update_wrapper(wrapper, wrapped, *args, **kwargs)
217+
wrapper.__wrapped__ = wrapped
218+
return wrapper
219+
220+
221+
def wraps(wrapped,
222+
assigned=functools.WRAPPER_ASSIGNMENTS,
223+
updated=functools.WRAPPER_UPDATES):
224+
return functools.partial(update_wrapper, wrapped=wrapped,
225+
assigned=assigned, updated=updated)
226+
227+
228+
class _CallableContext(object):
229+
230+
def __init__(self, context, cargs, ckwargs, fun):
231+
self.context = context
232+
self.cargs = cargs
233+
self.ckwargs = ckwargs
234+
self.fun = fun
235+
236+
def __call__(self, *args, **kwargs):
237+
return self.fun(*args, **kwargs)
238+
239+
def __enter__(self):
240+
self.ctx = self.context(*self.cargs, **self.ckwargs)
241+
return self.ctx.__enter__()
242+
243+
def __exit__(self, *einfo):
244+
if self.ctx:
245+
return self.ctx.__exit__(*einfo)
246+
247+
248+
def decorator(predicate):
249+
context = contextmanager(predicate)
250+
251+
@wraps(predicate)
252+
def take_arguments(*pargs, **pkwargs):
253+
254+
@wraps(predicate)
255+
def decorator(cls):
256+
if inspect.isclass(cls):
257+
orig_setup = cls.setUp
258+
orig_teardown = cls.tearDown
259+
260+
@wraps(cls.setUp)
261+
def around_setup(*args, **kwargs):
262+
try:
263+
contexts = args[0].__rb3dc_contexts__
264+
except AttributeError:
265+
contexts = args[0].__rb3dc_contexts__ = []
266+
p = context(*pargs, **pkwargs)
267+
p.__enter__()
268+
contexts.append(p)
269+
return orig_setup(*args, **kwargs)
270+
around_setup.__wrapped__ = cls.setUp
271+
cls.setUp = around_setup
272+
273+
@wraps(cls.tearDown)
274+
def around_teardown(*args, **kwargs):
275+
try:
276+
contexts = args[0].__rb3dc_contexts__
277+
except AttributeError:
278+
pass
279+
else:
280+
for context in contexts:
281+
context.__exit__(*sys.exc_info())
282+
orig_teardown(*args, **kwargs)
283+
around_teardown.__wrapped__ = cls.tearDown
284+
cls.tearDown = around_teardown
285+
286+
return cls
287+
else:
288+
@wraps(cls)
289+
def around_case(self, *args, **kwargs):
290+
with context(*pargs, **pkwargs) as context_args:
291+
context_args = context_args or ()
292+
if not isinstance(context_args, tuple):
293+
context_args = (context_args,)
294+
return cls(*(self,) + args + context_args, **kwargs)
295+
return around_case
296+
297+
if len(pargs) == 1 and callable(pargs[0]):
298+
fun, pargs = pargs[0], ()
299+
return decorator(fun)
300+
return _CallableContext(context, pargs, pkwargs, decorator)
301+
assert take_arguments.__wrapped__
302+
return take_arguments
303+
304+
305+
class WhateverIO(StringIO):
306+
307+
def __init__(self, v=None, *a, **kw):
308+
_SIO_init(self, v.decode() if isinstance(v, bytes) else v, *a, **kw)
309+
310+
def write(self, data):
311+
_SIO_write(self, data.decode() if isinstance(data, bytes) else data)
312+
313+
314+
@decorator
315+
def stdouts():
316+
"""Override `sys.stdout` and `sys.stderr` with `StringIO`
317+
instances.
318+
Decorator example::
319+
@mock.stdouts
320+
def test_foo(self, stdout, stderr):
321+
something()
322+
self.assertIn('foo', stdout.getvalue())
323+
Context example::
324+
with mock.stdouts() as (stdout, stderr):
325+
something()
326+
self.assertIn('foo', stdout.getvalue())
327+
"""
328+
prev_out, prev_err = sys.stdout, sys.stderr
329+
prev_rout, prev_rerr = sys.__stdout__, sys.__stderr__
330+
mystdout, mystderr = WhateverIO(), WhateverIO()
331+
sys.stdout = sys.__stdout__ = mystdout
332+
sys.stderr = sys.__stderr__ = mystderr
333+
334+
try:
335+
yield mystdout, mystderr
336+
finally:
337+
sys.stdout = prev_out
338+
sys.stderr = prev_err
339+
sys.__stdout__ = prev_rout
340+
sys.__stderr__ = prev_rerr
341+
342+
343+
@decorator
344+
def mute():
345+
"""Redirect `sys.stdout` and `sys.stderr` to /dev/null, silencent them.
346+
Decorator example::
347+
@mute
348+
def test_foo(self):
349+
something()
350+
Context example::
351+
with mute():
352+
something()
353+
"""
354+
prev_out, prev_err = sys.stdout, sys.stderr
355+
prev_rout, prev_rerr = sys.__stdout__, sys.__stderr__
356+
devnull = open(os.devnull, 'w')
357+
mystdout, mystderr = devnull, devnull
358+
sys.stdout = sys.__stdout__ = mystdout
359+
sys.stderr = sys.__stderr__ = mystderr
360+
361+
try:
362+
yield
363+
finally:
364+
sys.stdout = prev_out
365+
sys.stderr = prev_err
366+
sys.__stdout__ = prev_rout
367+
sys.__stderr__ = prev_rerr

tmuxp/testsuite/session.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111

1212
import logging
1313
import unittest
14-
from random import randint
1514

1615
from tmuxp import Pane, Session, Window
1716
from tmuxp.testsuite import t
18-
from tmuxp.testsuite.helpers import TEST_SESSION_PREFIX, TmuxTestCase
17+
from tmuxp.testsuite.helpers import TEST_SESSION_PREFIX, TmuxTestCase, namer
1918

2019
logger = logging.getLogger(__name__)
2120

@@ -99,7 +98,7 @@ class SessionNewTest(TmuxTestCase):
9998

10099
def test_new_session(self):
101100
"""Server.new_session creates new session."""
102-
new_session_name = TEST_SESSION_PREFIX + str(randint(0, 1337))
101+
new_session_name = TEST_SESSION_PREFIX + next(namer)
103102
new_session = t.new_session(session_name=new_session_name, detach=True)
104103

105104
self.assertIsInstance(new_session, Session)

tmuxp/testsuite/tmuxobject.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
unicode_literals, with_statement)
1111

1212
import logging
13-
import random
1413
import unittest
1514

1615
from tmuxp import Pane, Session, Window
1716
from tmuxp.testsuite import t
18-
from tmuxp.testsuite.helpers import TEST_SESSION_PREFIX, TmuxTestCase
17+
from tmuxp.testsuite.helpers import TEST_SESSION_PREFIX, TmuxTestCase, namer
1918

2019
logger = logging.getLogger(__name__)
2120

@@ -62,9 +61,7 @@ def test_findWhere_None(self):
6261
""".findWhere returns None if no results found."""
6362

6463
while True:
65-
nonexistant_session = TEST_SESSION_PREFIX + str(
66-
random.randint(0, 9999)
67-
)
64+
nonexistant_session = TEST_SESSION_PREFIX + next(namer)
6865

6966
if not t.has_session(nonexistant_session):
7067
break
@@ -176,7 +173,7 @@ def test_getById(self):
176173
self.assertEqual(get_by_id, session)
177174
self.assertIsInstance(get_by_id, Session)
178175
self.assertIsNone(t.getById(
179-
'$' + str(random.randint(50000, 90000))
176+
'$' + next(namer)
180177
))
181178

182179
# session.getById
@@ -189,7 +186,7 @@ def test_getById(self):
189186
self.assertIsInstance(get_by_id, Window)
190187

191188
self.assertIsNone(session.getById(
192-
'@' + str(random.randint(50000, 90000))
189+
'@' + next(namer)
193190
))
194191

195192
# window.getById
@@ -201,7 +198,7 @@ def test_getById(self):
201198
self.assertEqual(get_by_id, pane)
202199
self.assertIsInstance(get_by_id, Pane)
203200
self.assertIsNone(window.getById(
204-
'%' + str(random.randint(50000, 90000))
201+
'%' + next(namer)
205202
))
206203

207204

0 commit comments

Comments
 (0)