Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion cli_test_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
__all__ = [
"ArgvContext",
"EnvironContext",
"RandomDirectoryContext",
"shell",
]

from .commands import shell
from .decorators import ArgvContext, EnvironContext
from .decorators import ArgvContext, EnvironContext, RandomDirectoryContext
30 changes: 30 additions & 0 deletions cli_test_helpers/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"""

import contextlib
import os
import sys
from tempfile import TemporaryDirectory
from unittest.mock import patch

__all__ = []
Expand All @@ -12,6 +14,10 @@
class ArgvContext:
"""
A simple context manager allowing to temporarily override ``sys.argv``.

Use it to mimic the command line arguments of the CLI application.
Note that the first argument (index ``0``) is always the script or
application name.
"""

def __init__(self, *new_args):
Expand Down Expand Up @@ -50,3 +56,27 @@ def __enter__(self):
for key in self.clear_variables:
with contextlib.suppress(KeyError):
self.in_dict.pop(key)


class RandomDirectoryContext(TemporaryDirectory):
"""
Change the execution directory to a random location, temporarily.

Keyword arguments are optional and identical to the ones of
`tempfile.TemporaryDirectory`_ of the Python standard library.

.. _tempfile.TemporaryDirectory:
https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryDirectory
"""

def __enter__(self):
"""Create a temporary directory and ``cd`` into it."""
self.__prev_dir = os.getcwd()
super().__enter__()
os.chdir(self.name)
return os.getcwd()

def __exit__(self, exc_type, exc_value, traceback):
"""Return to the original directory before execution."""
os.chdir(self.__prev_dir)
return super().__exit__(exc_type, exc_value, traceback)
21 changes: 21 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
API Documentation
=================

This section describes the context managers and helper functions provided
by the CLI test helpers package.

.. module:: cli_test_helpers

Context Managers
----------------

.. autoclass:: ArgvContext

.. autoclass:: EnvironContext

.. autoclass:: RandomDirectoryContext

Utilities
---------

.. autofunction:: shell
7 changes: 4 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import os
import sys
sys.path.insert(0, os.path.abspath('..'))


# -- Project information -----------------------------------------------------
Expand All @@ -28,6 +28,7 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.extlinks',
'sphinx.ext.viewcode',
]
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Contents

installation
tutorial
api
other
techniques
contributing
Expand Down
25 changes: 20 additions & 5 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ Start with a simple set of functional tests:
installed)
- Is command XYZ available? etc. Cover your entire CLI usage here!

This is almost a stupid exercise: Run the command as a shell command
and inspect the exit code of the exiting process, e.g.
This is almost a stupid exercise: Run the command as a :func:`~cli_test_helpers.shell`
command and inspect the exit code of the exiting process, e.g.

.. code-block:: python

Expand Down Expand Up @@ -67,7 +67,8 @@ Then you're ready to take advantage of our helpers.
``ArgvContext``
+++++++++++++++

``ArgvContext`` allows you to mimic the use of specific CLI arguments:
:class:`~cli_test_helpers.ArgvContext` allows you to mimic the use of
specific CLI arguments:

.. code-block:: python

Expand Down Expand Up @@ -96,8 +97,8 @@ See more |example code (argparse-cli)|_.
``EnvironContext``
++++++++++++++++++

``EnvironContext`` allows you to mimic the presence (or absence) of
environment variables:
:class:`~cli_test_helpers.EnvironContext` allows you to mimic the presence
(or absence) of environment variables:

.. code-block:: python

Expand All @@ -112,6 +113,20 @@ environment variables:

See more |example code (click-command)|_.

``RandomDirectoryContext``
++++++++++++++++++++++++++

:class:`~cli_test_helpers.RandomDirectoryContext` allows you to verify that
your CLI program logic is independent of where it is executed in the file
system:

.. code-block:: python

def test_load_configfile():
"""Must not fail when executed anywhere in the filesystem."""
with ArgvContext('foobar', 'load'), RandomDirectoryContext():
foobar.cli.main()


.. |example code (argparse-cli)| replace:: example code
.. |example code (click-cli)| replace:: example code
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ extend-ignore = [
"D203",
"D205",
"D212",
"PTH109",
"Q000",
]

Expand Down
19 changes: 18 additions & 1 deletion tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import sys

from cli_test_helpers import ArgvContext, EnvironContext
from cli_test_helpers import ArgvContext, EnvironContext, RandomDirectoryContext


def test_argv_context():
Expand Down Expand Up @@ -45,3 +45,20 @@ def test_environ_context():
assert os.environ == old_environ, "object os.environ was not restored"
assert os.getenv("PATH") == old_path, "env var PATH was not restored"
assert os.getenv("FOO") is None, "env var FOO was not cleared"


def test_random_directory_context():
"""
In a directory context, are we effectively in a different location?
"""
before_dir = os.getcwd()

with RandomDirectoryContext() as random_dir:
new_dir = os.getcwd()

assert new_dir == random_dir, "Doesn't behave like TemporaryDirectory"
assert new_dir != before_dir, "Context not in a different file system location"

after_dir = os.getcwd()

assert after_dir == before_dir, "Execution directory not restored to original"