Skip to content

Commit

Permalink
Merge 7b9d137 into af32280
Browse files Browse the repository at this point in the history
  • Loading branch information
tony committed May 18, 2016
2 parents af32280 + 7b9d137 commit 8e556f5
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 10 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ test:
watch_test:
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

nose:
nosetests tmuxp/testsuite/*.py -m "^test*."

build_docs:
cd doc && $(MAKE) html

Expand Down
2 changes: 1 addition & 1 deletion bootstrap_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def which(exe=None, throw=True):
python_bin = os.path.join(env_dir, 'bin', 'python')
virtualenv_bin = which('virtualenv', throw=False)
virtualenv_exists = os.path.exists(env_dir) and os.path.isfile(python_bin)
sphinx_requirements_filepath = os.path.join(project_dir, 'doc', 'requirements.pip')
sphinx_requirements_filepath = os.path.join(project_dir, 'requirements', 'doc.txt')


try:
Expand Down
1 change: 1 addition & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-r ../requirements/doc.txt
File renamed without changes.
2 changes: 1 addition & 1 deletion doc/requirements.pip → requirements/doc.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-r ../requirements.pip
-r ./base.txt
docutils==0.12
sphinx
reportlab
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
with open("tmuxp/__about__.py") as fp:
exec(fp.read(), about)

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

Expand Down
172 changes: 171 additions & 1 deletion tmuxp/testsuite/helpers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# -*- coding: utf-8 -*-
"""Helper methods for tmuxp unittests."""
"""Helper methods for tmuxp unittests.
_CallableContext, WhateverIO, decorator and stdouts are from the case project,
https://github.com/celery/case, license BSD 3-clause.
"""

from __future__ import (absolute_import, division, print_function,
unicode_literals, with_statement)

import contextlib
import functools
import inspect
import io
import logging
import os
import sys
from contextlib import contextmanager
from random import randint

from tmuxp import exc
Expand Down Expand Up @@ -194,3 +204,163 @@ def bootstrap(self):
self.TEST_SESSION_NAME = TEST_SESSION_NAME
self.server = t
self.session = session


StringIO = io.StringIO
_SIO_write = StringIO.write
_SIO_init = StringIO.__init__


def update_wrapper(wrapper, wrapped, *args, **kwargs):
wrapper = functools.update_wrapper(wrapper, wrapped, *args, **kwargs)
wrapper.__wrapped__ = wrapped
return wrapper


def wraps(wrapped,
assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
return functools.partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)


class _CallableContext(object):

def __init__(self, context, cargs, ckwargs, fun):
self.context = context
self.cargs = cargs
self.ckwargs = ckwargs
self.fun = fun

def __call__(self, *args, **kwargs):
return self.fun(*args, **kwargs)

def __enter__(self):
self.ctx = self.context(*self.cargs, **self.ckwargs)
return self.ctx.__enter__()

def __exit__(self, *einfo):
if self.ctx:
return self.ctx.__exit__(*einfo)


def decorator(predicate):
context = contextmanager(predicate)

@wraps(predicate)
def take_arguments(*pargs, **pkwargs):

@wraps(predicate)
def decorator(cls):
if inspect.isclass(cls):
orig_setup = cls.setUp
orig_teardown = cls.tearDown

@wraps(cls.setUp)
def around_setup(*args, **kwargs):
try:
contexts = args[0].__rb3dc_contexts__
except AttributeError:
contexts = args[0].__rb3dc_contexts__ = []
p = context(*pargs, **pkwargs)
p.__enter__()
contexts.append(p)
return orig_setup(*args, **kwargs)
around_setup.__wrapped__ = cls.setUp
cls.setUp = around_setup

@wraps(cls.tearDown)
def around_teardown(*args, **kwargs):
try:
contexts = args[0].__rb3dc_contexts__
except AttributeError:
pass
else:
for context in contexts:
context.__exit__(*sys.exc_info())
orig_teardown(*args, **kwargs)
around_teardown.__wrapped__ = cls.tearDown
cls.tearDown = around_teardown

return cls
else:
@wraps(cls)
def around_case(self, *args, **kwargs):
with context(*pargs, **pkwargs) as context_args:
context_args = context_args or ()
if not isinstance(context_args, tuple):
context_args = (context_args,)
return cls(*(self,) + args + context_args, **kwargs)
return around_case

if len(pargs) == 1 and callable(pargs[0]):
fun, pargs = pargs[0], ()
return decorator(fun)
return _CallableContext(context, pargs, pkwargs, decorator)
assert take_arguments.__wrapped__
return take_arguments


class WhateverIO(StringIO):

def __init__(self, v=None, *a, **kw):
_SIO_init(self, v.decode() if isinstance(v, bytes) else v, *a, **kw)

def write(self, data):
_SIO_write(self, data.decode() if isinstance(data, bytes) else data)


@decorator
def stdouts():
"""Override `sys.stdout` and `sys.stderr` with `StringIO`
instances.
Decorator example::
@mock.stdouts
def test_foo(self, stdout, stderr):
something()
self.assertIn('foo', stdout.getvalue())
Context example::
with mock.stdouts() as (stdout, stderr):
something()
self.assertIn('foo', stdout.getvalue())
"""
prev_out, prev_err = sys.stdout, sys.stderr
prev_rout, prev_rerr = sys.__stdout__, sys.__stderr__
mystdout, mystderr = WhateverIO(), WhateverIO()
sys.stdout = sys.__stdout__ = mystdout
sys.stderr = sys.__stderr__ = mystderr

try:
yield mystdout, mystderr
finally:
sys.stdout = prev_out
sys.stderr = prev_err
sys.__stdout__ = prev_rout
sys.__stderr__ = prev_rerr


@decorator
def mute():
"""Redirect `sys.stdout` and `sys.stderr` to /dev/null, silencent them.
Decorator example::
@mute
def test_foo(self):
something()
Context example::
with mute():
something()
"""
prev_out, prev_err = sys.stdout, sys.stderr
prev_rout, prev_rerr = sys.__stdout__, sys.__stderr__
devnull = open(os.devnull, 'w')
mystdout, mystderr = devnull, devnull
sys.stdout = sys.__stdout__ = mystdout
sys.stderr = sys.__stderr__ = mystderr

try:
yield
finally:
sys.stdout = prev_out
sys.stderr = prev_err
sys.__stdout__ = prev_rout
sys.__stderr__ = prev_rerr
6 changes: 4 additions & 2 deletions tmuxp/testsuite/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from tmuxp import exc
from tmuxp.exc import BeforeLoadScriptError, BeforeLoadScriptNotExists
from tmuxp.testsuite.helpers import TestCase, TmuxTestCase
from tmuxp.testsuite.helpers import TestCase, TmuxTestCase, stdouts
from tmuxp.util import has_required_tmux_version, run_before_script

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -113,10 +113,12 @@ def test_raise_BeforeLoadScriptError_if_retcode(self):
with self.assertRaises(BeforeLoadScriptError):
run_before_script(script_file)

def test_return_stdout_if_ok(self):
@stdouts
def test_return_stdout_if_ok(self, stdout, stderr):
script_file = os.path.join(fixtures_dir, 'script_complete.sh')

run_before_script(script_file)
self.assertIn('hello', stdout.getvalue())


class BeforeLoadScriptErrorTestCase(TestCase):
Expand Down
8 changes: 5 additions & 3 deletions tmuxp/testsuite/workspacebuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from tmuxp import Window, config, exc
from tmuxp._compat import text_type
from tmuxp.testsuite.helpers import TmuxTestCase
from tmuxp.testsuite.helpers import TmuxTestCase, mute
from tmuxp.workspacebuilder import WorkspaceBuilder

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -883,8 +883,8 @@ class BeforeLoadScript(TmuxTestCase):
- pane
"""

@mute()
def test_throw_error_if_retcode_error(self):

sconfig = kaptan.Kaptan(handler='yaml')
yaml = self.config_script_fails.format(
fixtures_dir=fixtures_dir,
Expand All @@ -909,8 +909,8 @@ def test_throw_error_if_retcode_error(self):
msg="Kills session if before_script exits with errcode"
)

@mute()
def test_throw_error_if_file_not_exists(self):

sconfig = kaptan.Kaptan(handler='yaml')
yaml = self.config_script_not_exists.format(
fixtures_dir=fixtures_dir,
Expand Down Expand Up @@ -941,6 +941,7 @@ def test_throw_error_if_file_not_exists(self):
msg="Kills session if before_script doesn't exist"
)

@mute()
def test_true_if_test_passes(self):
assert(
os.path.exists(os.path.join(fixtures_dir, 'script_complete.sh'))
Expand All @@ -960,6 +961,7 @@ def test_true_if_test_passes(self):
with self.temp_session():
builder.build(session=self.session)

@mute()
def test_true_if_test_passes_with_args(self):
assert(
os.path.exists(os.path.join(fixtures_dir, 'script_complete.sh'))
Expand Down
5 changes: 4 additions & 1 deletion tmuxp/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ def run_before_script(script_file):
try:
proc = subprocess.Popen(
shlex.split(str(script_file)),
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
stdout=subprocess.PIPE
)
for line in iter(proc.stdout.readline, b''):
sys.stdout.write(console_to_str(line))
proc.wait()

if proc.returncode:
Expand Down

0 comments on commit 8e556f5

Please sign in to comment.