Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add tee capability to CliRunner.invoke().
  • Loading branch information
pkrog committed Nov 19, 2015
commit c1bd4213c6d1494d45b0c817ac8675ea2864c64c
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ build
docs/_build
click.egg-info
.tox
.swp
.*.swp
.cache
30 changes: 26 additions & 4 deletions click/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@
import io
from ._compat import _find_binary_reader

# Output stream class for outputing simultaneously to several streams
class MultOutput(object):

def __init__(self, outputs):
self._outputs = outputs

def write(self, s):
for o in self._outputs:
o.write(s)

def writelines(self, lines):
for o in self._outputs:
o.writelines(lines)

def flush(self):
for o in self._outputs:
o.flush()

class EchoingStdin(object):

Expand Down Expand Up @@ -135,7 +152,7 @@ def make_env(self, overrides=None):
return rv

@contextlib.contextmanager
def isolation(self, input=None, env=None, color=False):
def isolation(self, input=None, env=None, color=False, tee=False):
"""A context manager that sets up the isolation for invoking of a
command line tool. This sets up stdin with the given input data
and `os.environ` with the overrides from the given dictionary.
Expand Down Expand Up @@ -163,7 +180,12 @@ def isolation(self, input=None, env=None, color=False):
env = self.make_env(env)

if PY2:
sys.stdout = sys.stderr = bytes_output = StringIO()
if tee:
bytes_output = StringIO()
sys.stdout = MultOutput([sys.stdout, bytes_output])
sys.stderr = MultOutput([sys.stderr, bytes_output])
else:
sys.stdout = sys.stderr = bytes_output = StringIO()
if self.echo_stdin:
input = EchoingStdin(input, bytes_output)
else:
Expand Down Expand Up @@ -241,7 +263,7 @@ def should_strip_ansi(stream=None, color=None):
clickpkg.formatting.FORCED_WIDTH = old_forced_width

def invoke(self, cli, args=None, input=None, env=None,
catch_exceptions=True, color=False, **extra):
catch_exceptions=True, color=False, tee=False, **extra):
"""Invokes a command in an isolated environment. The arguments are
forwarded directly to the command line script, the `extra` keyword
arguments are passed to the :meth:`~clickpkg.Command.main` function of
Expand Down Expand Up @@ -270,7 +292,7 @@ def invoke(self, cli, args=None, input=None, env=None,
application can still override this explicitly.
"""
exc_info = None
with self.isolation(input=input, env=env, color=color) as out:
with self.isolation(input=input, env=env, color=color, tee=tee) as out:
exception = None
exit_code = 0

Expand Down
8 changes: 8 additions & 0 deletions tests/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,24 @@ def test():
o.write(chunk)
o.flush()

# Invoke copy command
runner = CliRunner()
result = runner.invoke(test, input='Hello World!\n')
assert not result.exception
assert result.output == 'Hello World!\n'

# Invoke copy command with input echoing
runner = CliRunner(echo_stdin=True)
result = runner.invoke(test, input='Hello World!\n')
assert not result.exception
assert result.output == 'Hello World!\nHello World!\n'

# Invoke copy command without blocking output to stderr and stdout (change nothing to result output string)
runner = CliRunner()
result = runner.invoke(test, input='Hello World!\n', tee=True)
assert not result.exception
assert result.output == 'Hello World!\n'


def test_runner_with_stream():
@click.command()
Expand Down