Commits

Sebastian Wiesner committed dc1a557

Reworked tests, test with real builds now

Comments (0)

Files changed (8)

programoutput/test_programoutput.py

-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Copyright (c) 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
-# All rights reserved.
-
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-#    notice, this list of conditions and the following disclaimer in the
-#    documentation and/or other materials provided with the distribution.
-
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-
-from itertools import product
-
-import pytest
-from mock import Mock, MagicMock
-from docutils.parsers.rst import directives
-from docutils import nodes
-
-from sphinxcontrib import programoutput
-
-
-RUN_PROGRAMS_TESTDATA = dict(
-    simple={},
-    extraargs=dict(extraargs='foo "spam and eggs"'),
-    prompt=dict(show_prompt=True),
-    prompt_with_extraargs=dict(show_prompt=True,
-                               extraargs='foo "spam and eggs"'),
-    no_stderr=dict(hide_standard_error=True),
-    shell=dict(use_shell=True),
-    shell_with_prompt=dict(use_shell=True, show_prompt=True),
-    shell_with_extraargs=dict(use_shell=True, show_prompt=True,
-                              extraargs='foo "spam and eggs"')
-    )
-
-
-def pytest_generate_tests(metafunc):
-    if metafunc.function is test_run_programs:
-        for id, use_ansi in product(RUN_PROGRAMS_TESTDATA, (True, False)):
-            params = RUN_PROGRAMS_TESTDATA[id]
-            if use_ansi:
-                id += ',ansi'
-            args = dict(use_ansi=use_ansi, params=params)
-            metafunc.addcall(funcargs=args, id=id)
-
-
-def pytest_funcarg__nodecls(request):
-    if request.getfuncargvalue('use_ansi'):
-        ansi = pytest.importorskip('sphinxcontrib.ansi')
-        return ansi.ansi_literal_block
-    else:
-        return nodes.literal_block
-
-
-def pytest_funcarg__cache(request):
-    cache = MagicMock()
-    cache.__getitem__.return_value = 'some output'
-    return cache
-
-
-def pytest_funcarg__app(request):
-    app = Mock()
-    app.env = Mock()
-    app.env.programoutput_cache = request.getfuncargvalue('cache')
-    app.config = Mock()
-    app.config.programoutput_use_ansi = request.getfuncargvalue('use_ansi')
-    app.config.programoutput_prompt_template = '$ %(command)s\n%(output)s'
-    return app
-
-
-def pytest_funcarg__directive(request):
-    state_machine = Mock()
-    state_machine.get_source_and_line.return_value = (Mock(), Mock())
-    return programoutput.ProgramOutputDirective(
-        'program-output', ['some command'], {}, [], 1, 1, '',
-        Mock(), state_machine)
-
-
-def test_slice():
-    assert programoutput._slice('2') == (2, None)
-    assert programoutput._slice('2,2') == (2, 2)
-    with pytest.raises(ValueError) as exc:
-        assert programoutput._slice('')
-    assert str(exc.value) == "invalid literal for int() with base 10: ''"
-    with pytest.raises(ValueError) as exc:
-        programoutput._slice('foo,2')
-    assert str(exc.value) == "invalid literal for int() with base 10: 'foo'"
-    with pytest.raises(ValueError) as exc:
-        programoutput._slice('2,2,2')
-    assert str(exc.value) == 'too many slice parts'
-
-
-def test_program_output_directive():
-    cls = programoutput.ProgramOutputDirective
-    assert not cls.has_content
-    assert cls.final_argument_whitespace
-    assert cls.required_arguments == 1
-    assert cls.option_spec == dict(
-        shell=directives.flag,
-        prompt=directives.flag,
-        nostderr=directives.flag,
-        ellipsis=programoutput._slice,
-        extraargs=directives.unchanged)
-
-
-def test_program_output_directive_run_command(directive):
-    assert directive.run()[0]['command'] ==  'some command'
-
-def test_program_output_directive_run_nostderr(directive):
-    assert not directive.run()[0]['hide_standard_error']
-    directive.options['nostderr'] = True
-    assert directive.run()[0]['hide_standard_error']
-
-def test_program_output_directive_run_extraargs(directive):
-    assert directive.run()[0]['extraargs'] == ''
-    directive.options['extraargs'] = 'foo bar'
-    assert directive.run()[0]['extraargs'] == 'foo bar'
-
-def test_program_output_directive_run_use_shell(directive):
-    assert not directive.run()[0]['use_shell']
-    directive.options['shell'] = True
-    assert directive.run()[0]['use_shell']
-
-def test_program_output_directive_run_ellipsis(directive):
-    assert 'strip_lines' not in directive.run()[0].attributes
-    directive.options['ellipsis'] = (2, 2)
-    assert directive.run()[0]['strip_lines'] == (2, 2)
-
-def test_program_output_directive_run_show_prompt(directive):
-    assert not directive.run()[0]['show_prompt']
-    directive.options['prompt'] = True
-    assert directive.run()[0]['show_prompt']
-
-def test_command_output_directive_run(directive):
-    directive.name = 'command-output'
-    assert directive.run()[0]['show_prompt']
-
-
-def test_program_output_cache():
-    cache = programoutput.ProgramOutputCache()
-    cmd = ('python', '-c', 'import sys; '
-           'sys.stdout.write("hello world"); '
-           'sys.stderr.write("goodbye world")')
-    assert (cmd, False, True) not in cache
-    assert cache[(cmd, False, True)] == 'hello world'
-    assert (cmd, False, True) in cache
-    assert (cmd, False, False) not in cache
-    assert cache[(cmd, False, False)] == 'goodbye worldhello world'
-    assert (cmd, False, False) in cache
-    cmd = ('python -c \'import sys; '
-           'sys.stdout.write("hello world"); '
-           'sys.stderr.write("goodbye world")\'')
-    assert (cmd, True, False) not in cache
-    assert cache[(cmd, True, False)] == 'goodbye worldhello world'
-    assert (cmd, True, False) in cache
-
-
-def _make_node(show_prompt=False, hide_standard_error=False,
-               use_shell=False, ellipsis=None, extraargs=None):
-    node = programoutput.program_output()
-    node['command'] = ("hello 'eggs with spam'")
-    node['show_prompt'] = show_prompt
-    node['hide_standard_error'] = hide_standard_error
-    node['use_shell'] = use_shell
-    if ellipsis:
-        node['strip_lines'] = ellipsis
-    if extraargs:
-        node['extraargs'] = extraargs
-    return node
-
-def test_run_programs(app, cache, use_ansi, nodecls, params):
-    paragraph = nodes.paragraph()
-    paragraph.append(_make_node(**params))
-    programoutput.run_programs(app, paragraph)
-    if params.get('show_prompt'):
-        output = "$ hello 'eggs with spam'\nsome output"
-    else:
-        output = 'some output'
-    node = paragraph[0]
-    assert node.astext() == output
-    assert isinstance(node, nodecls)
-    if params.get('use_shell'):
-        cmd = "hello 'eggs with spam'"
-        if params.get('extraargs'):
-            cmd += ' foo "spam and eggs"'
-    else:
-        cmd = ('hello', 'eggs with spam')
-        if params.get('extraargs'):
-            cmd += ('foo', 'spam and eggs')
-    cache_key = (cmd, params.get('use_shell', False),
-                 params.get('hide_standard_error', False))
-    cache.__getitem__.assert_called_with(cache_key)
-
-
-def test_init_cache():
-    app = Mock()
-    # no mock object here, because we need a conservative __getattr__, which
-    # only returns, what really is defined
-    app.env = type('obj', (object,), {})()
-    assert not hasattr(app.env, 'programoutput_cache')
-    programoutput.init_cache(app)
-    assert hasattr(app.env, 'programoutput_cache')
-    cache = app.env.programoutput_cache
-    programoutput.init_cache(app)
-    assert app.env.programoutput_cache is cache
-
-
-def test_setup():
-    app = Mock()
-    programoutput.setup(app)
-    assert app.add_config_value.call_args_list == [
-        (('programoutput_use_ansi', False, 'env'), {}),
-        (('programoutput_prompt_template',
-          '$ %(command)s\n%(output)s', 'env'), {})]
-    assert app.add_directive.call_args_list == [
-        (('program-output', programoutput.ProgramOutputDirective), {}),
-        (('command-output', programoutput.ProgramOutputDirective), {})]
-    assert app.connect.call_args_list == [
-        (('builder-inited', programoutput.init_cache), {}),
-        (('doctree-read', programoutput.run_programs), {})]
-
-
-
-def main():
-    import py
-    py.cmdline.pytest()
-
-
-if __name__ == '__main__':
-    main()

programoutput/tests/conftest.py

+# Copyright (c) 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import (print_function, division, unicode_literals,
+                        absolute_import)
+
+import pytest
+from sphinx.application import Sphinx
+
+
+#: conf.py for tests
+CONF_PY = """\
+extensions = ['sphinxcontrib.programoutput']
+
+source_suffix = '.rst'
+
+master_doc = 'index'
+
+project = u'epydoc-test'
+copyright = u'2011, foo'
+
+version = '1'
+release = '1'
+
+exclude_patterns = []
+
+pygments_style = 'sphinx'
+html_theme = 'default'
+"""
+
+
+def pytest_funcarg__content(request):
+    """
+    Return the content for the current test, extracted from ``with_content``
+    marker.
+    """
+    return request.keywords['with_content'].args[0]
+
+
+def pytest_funcarg__srcdir(request):
+    """
+    Generated source directory for test Sphinx application.
+    """
+    tmpdir = request.getfuncargvalue('tmpdir')
+    srcdir = tmpdir.join('src')
+    srcdir.ensure(dir=True)
+    confpy = srcdir.join('conf.py')
+    confpy.write(CONF_PY)
+    content = request.getfuncargvalue('content')
+    srcdir.join('index.rst').write(content)
+    return srcdir
+
+
+def pytest_funcarg__outdir(request):
+    """
+    Output directory for current test.
+    """
+    tmpdir = request.getfuncargvalue('tmpdir')
+    return tmpdir.join('html')
+
+
+def pytest_funcarg__doctreedir(request):
+    """
+    The doctree directory for the current.
+    """
+    tmpdir = request.getfuncargvalue('tmpdir')
+    return tmpdir.join('doctrees')
+
+
+def pytest_funcarg__confoverrides(request):
+    """
+    Confoverrides for the current test as dict, extracted from the keyword
+    arguments to the ``confoverrides`` marker.
+    """
+    confoverrides_marker = request.keywords.get('confoverrides')
+    if confoverrides_marker:
+        return confoverrides_marker.kwargs
+    else:
+        return {}
+
+
+def pytest_funcarg__app(request):
+    """
+    Sphinx application for the current test.
+    """
+    srcdir = request.getfuncargvalue('srcdir')
+    outdir = request.getfuncargvalue('outdir')
+    doctreedir = request.getfuncargvalue('doctreedir')
+    confoverrides = request.getfuncargvalue('confoverrides')
+    app = Sphinx(str(srcdir), str(srcdir), str(outdir), str(doctreedir), 'html',
+                 status=None, warning=None, freshenv=None, warningiserror=True,
+                 confoverrides=confoverrides)
+    if 'build_app' in request.keywords:
+        app.build()
+    return app
+
+
+def pytest_funcarg__doctree(request):
+    request.applymarker(pytest.mark.build_app)
+    app = request.getfuncargvalue('app')
+    return app.env.get_doctree('index')
+
+
+def pytest_funcarg__cache(request):
+    request.applymarker(pytest.mark.build_app)
+    app = request.getfuncargvalue('app')
+    return app.env.programoutput_cache

programoutput/tests/requirements.txt

+Sphinx>=1.0.7
+pytest>=2.1
+sphinxcontrib-ansi

programoutput/tests/test_cache.py

+# -*- coding: utf-8 -*-
+# Copyright (c) 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import (print_function, division, unicode_literals,
+                        absolute_import)
+
+from subprocess import CalledProcessError
+
+import pytest
+
+from sphinxcontrib.programoutput import ProgramOutputCache
+
+
+def pytest_funcarg__cache(request): # pylint: disable=W0613
+    return ProgramOutputCache()
+
+
+def test_simple(cache):
+    key = (('echo', 'spam'), False, False)
+    assert not cache
+    assert cache[key] == 'spam'
+    assert cache == {key: 'spam'}
+
+
+def test_shell(cache):
+    key = ('echo spam', True, False)
+    assert not cache
+    assert cache[key] == 'spam'
+    assert cache == {key: 'spam'}
+
+
+def test_hidden_standard_error(cache):
+    key = (('python', '-c', 'import sys; sys.stderr.write("spam")'),
+           False, True)
+    assert not cache
+    assert cache[key] == ''
+    assert cache == {key: ''}
+
+
+def test_nonzero_return_code(cache):
+    key = (('python', '-c', 'import sys; sys.exit(1)'), False, False)
+    assert not cache
+    with pytest.raises(CalledProcessError) as excinfo:
+        cache[key] # pylint: disable=W0104
+    assert not cache
+    exc = excinfo.value
+    assert exc.cmd == key[0]
+    assert exc.returncode == 1
+
+
+@pytest.mark.with_content('dummy content')
+def test_cache_pickled(app, doctreedir):
+    key = (('echo', 'spam'), False, False)
+    assert app.env.programoutput_cache[key] == 'spam'
+    app.build()
+    pickled_env = doctreedir.join('environment.pickle').load()
+    assert pickled_env.programoutput_cache == {key: 'spam'}

programoutput/tests/test_directive.py

+# -*- coding: utf-8 -*-
+# Copyright (c) 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import (print_function, division, unicode_literals,
+                        absolute_import)
+
+import os
+import sys
+from subprocess import CalledProcessError
+
+import pytest
+from docutils.nodes import literal_block
+
+
+def assert_output(doctree, output):
+    __tracebackhide__ = True
+    literal = doctree.next_node(literal_block)
+    assert literal
+    assert literal.astext() == output
+
+
+def assert_cache(cache, cmd, output, use_shell=False,
+                 hide_standard_error=False):
+    if isinstance(cmd, list):
+        cmd = tuple(cmd)
+    cache_key = (cmd, use_shell, hide_standard_error)
+    assert cache[cache_key] == output
+
+
+@pytest.mark.with_content('.. program-output:: echo eggs')
+def test_simple(doctree, cache):
+    assert_output(doctree, 'eggs')
+    assert_cache(cache, ['echo', 'eggs'], 'eggs')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -c 'print("spam with eggs")'""")
+def test_with_spaces(doctree, cache):
+    """
+    Test for command splitting with spaces involved.  Do *not* use ``echo`` for
+    this test, because ``echo`` handles multiple arguments just as well as
+    single arguments.
+    """
+    assert_output(doctree, 'spam with eggs')
+    assert_cache(cache, ['python', '-c', 'print("spam with eggs")'],
+                 'spam with eggs')
+
+
+@pytest.mark.with_content('.. program-output:: python -V')
+def test_standard_error(doctree, cache):
+    output = 'Python {0}.{1}.{2}'.format(*sys.version_info)
+    assert_output(doctree, output)
+    assert_cache(cache, ['python', '-V'], output)
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -V
+   :nostderr:""")
+def test_standard_error_disabled(doctree, cache):
+    assert_output(doctree, '')
+    assert_cache(cache, ['python', '-V'], '', hide_standard_error=True)
+
+
+@pytest.mark.with_content('.. program-output:: echo "${VIRTUAL_ENV}"')
+def test_no_expansion_without_shell(doctree, cache):
+    assert_output(doctree, '${VIRTUAL_ENV}')
+    assert_cache(cache, ['echo', '${VIRTUAL_ENV}'], '${VIRTUAL_ENV}')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: echo "${VIRTUAL_ENV}"
+   :shell:""")
+def test_expansion_with_shell(doctree, cache):
+    assert_output(doctree, os.environ['VIRTUAL_ENV'])
+    assert_cache(cache, 'echo ${VIRTUAL_ENV}', os.environ['VIRTUAL_ENV'],
+                 use_shell=True)
+
+
+@pytest.mark.with_content("""\
+.. program-output:: echo "spam with eggs"
+   :prompt:""")
+def test_prompt(doctree, cache):
+    assert_output(doctree, """\
+$ echo "spam with eggs"
+spam with eggs""")
+    assert_cache(cache, ['echo', 'spam with eggs'], 'spam with eggs')
+
+
+@pytest.mark.with_content('.. command-output:: echo "spam with eggs"')
+def test_command(doctree, cache):
+    assert_output(doctree, """\
+$ echo "spam with eggs"
+spam with eggs""")
+    assert_cache(cache, ['echo', 'spam with eggs'], 'spam with eggs')
+
+
+@pytest.mark.with_content('.. command-output:: echo spam')
+@pytest.mark.confoverrides(programoutput_prompt_template='>> %(command)s\n<< %(output)s')
+def test_command_non_default_prompt(doctree, cache):
+    assert_output(doctree, '>> echo spam\n<< spam')
+    assert_cache(cache, ['echo', 'spam'], 'spam')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: echo spam
+   :extraargs: with eggs""")
+def test_extraargs(doctree, cache):
+    assert_output(doctree, 'spam with eggs')
+    assert_cache(cache, ['echo', 'spam', 'with', 'eggs'], 'spam with eggs')
+
+
+@pytest.mark.with_content('''\
+.. program-output:: echo
+   :shell:
+   :extraargs: "${VIRTUAL_ENV}"''')
+def test_extraargs_with_shell(doctree, cache):
+    assert_output(doctree, os.environ['VIRTUAL_ENV'])
+    assert_cache(cache, 'echo "${VIRTUAL_ENV}"', os.environ['VIRTUAL_ENV'],
+                 use_shell=True)
+
+
+@pytest.mark.with_content("""\
+.. program-output:: echo spam
+   :prompt:
+   :extraargs: with eggs""")
+def test_extraargs_with_prompt(doctree, cache):
+    assert_output(doctree, '$ echo spam\nspam with eggs')
+    assert_cache(cache, ['echo', 'spam', 'with', 'eggs'], 'spam with eggs')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -c 'print("spam\\nwith\\neggs")'
+   :ellipsis: 2""")
+def test_ellipsis_stop_only(doctree, cache):
+    assert_output(doctree, 'spam\nwith\n...')
+    assert_cache(cache, ['python', '-c', r'print("spam\nwith\neggs")'],
+                 'spam\nwith\neggs')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -c 'print("spam\\nwith\\neggs")'
+   :ellipsis: -2""")
+def test_ellipsis_negative_stop(doctree, cache):
+    assert_output(doctree, 'spam\n...')
+    assert_cache(cache, ['python', '-c', r'print("spam\nwith\neggs")'],
+                 'spam\nwith\neggs')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -c 'print("spam\\nwith\\neggs")'
+   :ellipsis: 1, 2""")
+def test_ellipsis_start_and_stop(doctree, cache):
+    assert_output(doctree, 'spam\n...\neggs')
+    assert_cache(cache, ['python', '-c', r'print("spam\nwith\neggs")'],
+                 'spam\nwith\neggs')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -c 'print("spam\\nwith\\neggs")'
+   :ellipsis: 1, -1""")
+def test_ellipsis_start_and_negative_stop(doctree, cache):
+    assert_output(doctree, 'spam\n...\neggs')
+    assert_cache(cache, ['python', '-c', r'print("spam\nwith\neggs")'],
+                 'spam\nwith\neggs')
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -c 'import sys; sys.exit(1)'""")
+def test_non_zero_return_code(app):
+    with pytest.raises(CalledProcessError) as excinfo:
+        app.build()
+    exc = excinfo.value
+    assert exc.cmd == ('python', '-c', 'import sys; sys.exit(1)')
+    assert exc.returncode == 1
+
+
+@pytest.mark.with_content("""\
+.. program-output:: python -c 'import sys; sys.exit(1)'
+   :shell:""")
+def test_shell_non_zero_return_code(app):
+    with pytest.raises(CalledProcessError) as excinfo:
+        app.build()
+    exc = excinfo.value
+    assert exc.cmd == "python -c 'import sys; sys.exit(1)'"
+    assert exc.returncode == 1

programoutput/tests/test_setup.py

+# -*- coding: utf-8 -*-
+# Copyright (c) 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import (print_function, division, unicode_literals,
+                        absolute_import)
+
+
+from sphinxcontrib.programoutput import ProgramOutputCache
+
+
+def pytest_funcarg__content(request): # pylint: disable=W0613
+    return 'dummy content'
+
+
+def test_init_cache(app):
+    assert isinstance(app.env.programoutput_cache, ProgramOutputCache)
+    assert not app.env.programoutput_cache

programoutput/tests/test_util.py

+# -*- coding: utf-8 -*-
+# Copyright (c) 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import (print_function, division, unicode_literals,
+                        absolute_import)
+
+import pytest
+
+from sphinxcontrib.programoutput import _slice
+
+
+def test_slice_simple():
+    assert _slice('2') == (2, None)
+    assert _slice('2,2') == (2, 2)
+
+
+def test_slice_empty():
+    with pytest.raises(ValueError) as exc:
+        assert _slice('')
+    assert str(exc.value) == "invalid literal for int() with base 10: ''"
+
+
+def test_slice_no_int():
+    with pytest.raises(ValueError) as exc:
+        _slice('foo,2')
+    assert str(exc.value) == "invalid literal for int() with base 10: 'foo'"
+
+
+def test_slice_too_many():
+    with pytest.raises(ValueError) as exc:
+        _slice('2,2,2')
+    assert str(exc.value) == 'too many slice parts'

programoutput/tox.ini

 
 [testenv]
 deps=
-    sphinx>=1.0.7
-    mock>=0.7
-    pytest>=2.0
-    sphinxcontrib-ansi
+    -r test_requiremens.txt
 commands=
-    py.test --junitxml={envlogdir}/tests.xml []
+    py.test {posargs:--junitxml={envlogdir}/tests.xml}
 
 [testenv:doc]
 deps=