diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 7f430bea..86182372 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -36,7 +36,6 @@ runs: key: ${{ inputs.os }}-${{ inputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - name: Install Project and Dependencies - if: steps.cache.outputs.cache-hit != 'true' run: | poetry install -E dev -E lint -E test -E commitizen_legacy shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0dd6bfe8..ad304a51 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -91,17 +91,22 @@ repos: (https://copier.readthedocs.io/en/stable/updating/) language: fail files: \.rej$ + # Note: there are occasional errors when running doit in pre-commit + # Moving the .doit.db outside of the git directory helps, but there + # are obvious drawbacks + # "doit.dependency.DatabaseException: [Errno 35] Resource + # temporarily unavailable: '.doit.db'" - id: python-formatter name: Python Auto-Formatter description: Apply calcipy formatting language: system - entry: poetry run doit run format_py + entry: poetry run doit run --db-file=../.pre-commit-doit.db format_py types: [python] stages: [push] - id: toml-formatter name: Optional TOML Auto-Formatter description: Install taplo with 'npm install -g @taplo/cli' language: system - entry: poetry run doit run format_toml + entry: poetry run doit run --db-file=../.pre-commit-doit.db format_toml types: [toml] exclude: poetry\.lock diff --git a/calcipy/log_helpers.py b/calcipy/log_helpers.py index d99c6365..ff3385ca 100644 --- a/calcipy/log_helpers.py +++ b/calcipy/log_helpers.py @@ -18,6 +18,7 @@ from inspect import signature from pathlib import Path +import loguru from beartype import beartype from beartype.typing import Any, Callable, Dict, Generator, Iterable, List, Optional from decorator import contextmanager, decorator @@ -74,14 +75,13 @@ def serializable_compact(record: Dict[str, Any]) -> str: return str_json + '\n' -# Note: loguru.Logger is PEP563 Postponed and can't be use with beartype runtime - +# FYI: loguru.Logger is PEP563 Postponed and can't be use with beartype runtime def _log_action( message: str, level: str = 'INFO', _logger: loguru.Logger = logger, # pylint: disable=no-member **kwargs: Any, ) -> Generator[loguru.Logger, None, None]: # pylint: disable=no-member - """Log the beggining and end of an action. + """Log the beginning and end of an action. Args: message: string message to describe the context diff --git a/poetry.lock b/poetry.lock index 757af774..d16e3a6e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -75,7 +75,7 @@ name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." category = "main" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] @@ -987,6 +987,34 @@ python-versions = ">=3.5" falcon = "2.0.0" requests = "*" +[[package]] +name = "hypothesis" +version = "6.38.0" +description = "A library for property-based testing" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "importlib-metadata (>=3.6)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2021.5)"] +cli = ["click (>=7.0)", "black (>=19.10b0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["django (>=2.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark-parser (>=0.6.5)"] +numpy = ["numpy (>=1.9.0)"] +pandas = ["pandas (>=0.25)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2021.5)"] + [[package]] name = "identify" version = "2.4.11" @@ -1028,7 +1056,7 @@ name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -1558,7 +1586,7 @@ name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -1622,9 +1650,17 @@ name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "main" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "py-cpuinfo" +version = "8.0.0" +description = "Get CPU info with pure Python 2 & 3" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pycln" version = "1.2.4" @@ -1754,7 +1790,7 @@ name = "pytest" version = "7.0.1" description = "pytest: simple powerful testing with Python" category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -1771,6 +1807,23 @@ tomli = ">=1.0.0" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-benchmark" +version = "3.4.1" +description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +py-cpuinfo = "*" +pytest = ">=3.8" + +[package.extras] +aspect = ["aspectlib"] +elasticsearch = ["elasticsearch"] +histogram = ["pygal", "pygaljs"] + [[package]] name = "pytest-cache-assert" version = "1.1.1" @@ -1834,6 +1887,22 @@ attrs = "*" pytest = ">=3.5.0" vcrpy = ">=2.0.1" +[[package]] +name = "pytest-subprocess" +version = "1.4.1" +description = "A plugin to fake subprocess for pytest" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytest = ">=4.0.0" + +[package.extras] +dev = ["nox", "changelogd"] +docs = ["sphinx", "furo", "sphinxcontrib-napoleon", "sphinx-autodoc-typehints", "changelogd"] +test = ["pytest (>=4.0)", "coverage", "docutils (>=0.12)", "Pygments (>=2.0)", "pytest-rerunfailures", "pytest-asyncio (>=0.15.1)", "anyio"] + [[package]] name = "pytest-watcher" version = "0.2.3" @@ -2025,6 +2094,14 @@ category = "main" optional = true python-versions = "*" +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "sqlparse" version = "0.4.2" @@ -2374,7 +2451,7 @@ test = ["coverage", "diff-cover", "lxml", "mypy", "nox-poetry", "pytest-cache-as [metadata] lock-version = "1.1" python-versions = "^3.7.6" -content-hash = "1ab5f8d27d9c8b5b8a468c78c6f14188a1b0fe50c8e5368e80517cec70e854a3" +content-hash = "1e8f4702224c5684a54dee4a33619f10a773b7f2008368c386c33a97e8bcfc76" [metadata.files] absolufy-imports = [ @@ -2761,6 +2838,10 @@ hug = [ {file = "hug-2.6.1-py2.py3-none-any.whl", hash = "sha256:31c8fc284f81377278629a4b94cbb619ae9ce829cdc2da9564ccc66a121046b4"}, {file = "hug-2.6.1.tar.gz", hash = "sha256:b0edace2acb618873779c9ce6ecf9165db54fef95c22262f5700fcdd9febaec9"}, ] +hypothesis = [ + {file = "hypothesis-6.38.0-py3-none-any.whl", hash = "sha256:3cc2ea360d0c3fc6395f6ab224fd80bc73b5a552bc2acb6561a984ff92c46693"}, + {file = "hypothesis-6.38.0.tar.gz", hash = "sha256:c61152db9e0b6e14276ff5e799a1bbdc5d3f3c3174be09e1260787fa59d4a162"}, +] identify = [ {file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"}, {file = "identify-2.4.11.tar.gz", hash = "sha256:2986942d3974c8f2e5019a190523b0b0e2a07cb8e89bf236727fb4b26f27f8fd"}, @@ -3350,6 +3431,9 @@ py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] +py-cpuinfo = [ + {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, +] pycln = [ {file = "pycln-1.2.4-py3-none-any.whl", hash = "sha256:62f76f6638fd523542cf04c66cd127fbf25f2ab327c7335ede73768c3492d723"}, {file = "pycln-1.2.4.tar.gz", hash = "sha256:d5a43ad06acd06492c8172029d3b74ecf5829d8e82fa6cc57a700a9cf904400b"}, @@ -3400,6 +3484,10 @@ pytest = [ {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, ] +pytest-benchmark = [ + {file = "pytest-benchmark-3.4.1.tar.gz", hash = "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47"}, + {file = "pytest_benchmark-3.4.1-py2.py3-none-any.whl", hash = "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809"}, +] pytest-cache-assert = [ {file = "pytest_cache_assert-1.1.1-py3-none-any.whl", hash = "sha256:6a04499af47f9433dee4bb0e9f40e33024b90854c4f235efeda82dfaf64b43aa"}, {file = "pytest_cache_assert-1.1.1.tar.gz", hash = "sha256:a7e1737525b0c83c655d627b83bd66daf5e1909411b898c077c4f266bb823d72"}, @@ -3420,6 +3508,10 @@ pytest-recording = [ {file = "pytest-recording-0.12.0.tar.gz", hash = "sha256:9039bf488f80c016055ffd039a91e33b1e89f3ee2ee0005c0bbce298fd417ee1"}, {file = "pytest_recording-0.12.0-py3-none-any.whl", hash = "sha256:a94b000640fe5d05b34fa9e25dca1671dd7f21195502c5f4d2f600c31dc14f7e"}, ] +pytest-subprocess = [ + {file = "pytest-subprocess-1.4.1.tar.gz", hash = "sha256:8a5061319133c72ab53e02adc144ac32a7418834c6b909a2161ab691ba4e9564"}, + {file = "pytest_subprocess-1.4.1-py3-none-any.whl", hash = "sha256:903667a6dc1ac75f4f27bd620e5728017fa5e119718f6cec71740f2610889571"}, +] pytest-watcher = [ {file = "pytest-watcher-0.2.3.tar.gz", hash = "sha256:1937dd97e72caafd371d8cea7b3d70c88ff4fe35e6cdecb29c41bbdcbf1dcc2b"}, {file = "pytest_watcher-0.2.3-py3-none-any.whl", hash = "sha256:af935963399509a5b0e855740ba7227852f1a7fccfbb1cbb79fa19a445af02d2"}, @@ -3515,6 +3607,10 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] +sortedcontainers = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] sqlparse = [ {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, diff --git a/pyproject.toml b/pyproject.toml index 423a7080..afc7b406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,14 +169,17 @@ yamllint = { version = ">=1.26", optional = true } # Test coverage = { version = ">=6", optional = true } diff-cover = { version = ">=6.4", optional = true } +hypothesis = { version = ">=6.38.0", optional = true } lxml = { version = ">=4.7.1", optional = true } # required for the HTML mypy report format mypy = { version = ">=0.812", optional = true } nox-poetry = { version = ">=0.8", optional = true } pytest = { version = ">=6", optional = true } +pytest-benchmark = { version = ">=3.4.1", optional = true } pytest-cache-assert = { version = ">=1.1.1", optional = true } pytest-html = { version = ">=3.1", optional = true } pytest-randomly = { version = ">=3.8", optional = true } pytest-recording = { version = ">=0.11", optional = true } +pytest-subprocess = { version = ">=1.4.1", optional = true } pytest-watcher = { version = ">=0.2", optional = true } # https://github.com/joeyespo/pytest-watch/issues/121 # commitizen_legacy @@ -186,9 +189,9 @@ cz_legacy = { version = ">=0.1.2", optional = true } dev = [ "better-exceptions", "commitizen", + "livereload", "mkdocs-git-revision-date-localized-plugin", "mkdocs-material", - "livereload", "pdbpp", "pdocs", "pyupgrade", @@ -251,15 +254,18 @@ lint = [ test = [ "coverage", "diff-cover", + "hypothesis", "lxml", "mypy", "nox-poetry", + "pytest", + "pytest-benchmark", "pytest-cache-assert", "pytest-html", "pytest-randomly", "pytest-recording", + "pytest-subprocess", "pytest-watcher", - "pytest", ] commitizen_legacy = ["cz_legacy"] diff --git a/tests/assert-cache/README.md b/tests/assert-cache/README.md new file mode 100644 index 00000000..4929e2ea --- /dev/null +++ b/tests/assert-cache/README.md @@ -0,0 +1,6 @@ +# Pytest Assert Cache + +This folder is automatically generated by `pytest_cache_assert`. + +Files can be regenerated by deleting and allowing `pytest_cache_assert` to +recreate them when next running the test suite. diff --git a/tests/assert-cache/test_format_report.json b/tests/assert-cache/test_format_report.json new file mode 100644 index 00000000..a654a5c4 --- /dev/null +++ b/tests/assert-cache/test_format_report.json @@ -0,0 +1,19 @@ +{ + "_info": [ + { + "func_args": {}, + "test_file": "test_code_tag_collector.py", + "test_name": "test_format_report" + } + ], + "_json": { + "output": [ + "| Type | Comment | Last Edit | Source File |", + "|:-------|:----------|:------------|:------------------------|", + "| TODO | Example 2 | N/A | .test_calcipy_project:1 |", + "", + "Found code tags for TODO (1)", + "" + ] + } +} diff --git a/tests/assert-cache/test_search_lines.json b/tests/assert-cache/test_search_lines.json new file mode 100644 index 00000000..d64595da --- /dev/null +++ b/tests/assert-cache/test_search_lines.json @@ -0,0 +1,41 @@ +{ + "_info": [ + { + "func_args": {}, + "test_file": "test_code_tag_collector.py", + "test_name": "test_search_lines" + } + ], + "_json": [ + { + "lineno": 2, + "tag": "FIXME", + "text": "Show README.md in the documentation (may need to update paths?)\")" + }, + { + "lineno": 3, + "tag": "FYI", + "text": "Replace src_examples_dir and make more generic to specify code to include in documentation" + }, + { + "lineno": 4, + "tag": "HACK", + "text": "Show table of contents in __init__.py file" + }, + { + "lineno": 7, + "tag": "REVIEW", + "text": "Show table of contents in __init__.py file" + }, + { + "lineno": 10, + "tag": "HACK", + "text": "Support unconventional dashed code tags" + }, + { + "lineno": 13, + "tag": "FIXME", + "text": "and FYI: in the same line, but only match the first" + } + ] +} diff --git a/tests/test_code_tag_collector.py b/tests/test_code_tag_collector.py index fe2313bf..09e28f22 100644 --- a/tests/test_code_tag_collector.py +++ b/tests/test_code_tag_collector.py @@ -2,17 +2,18 @@ import re +import attrs + from calcipy.code_tag_collector import CODE_TAG_RE, _CodeTag, _format_report, _search_lines, _Tags, write_code_tag_file from .configuration import PATH_TEST_PROJECT -def test_search_lines(): +def test_search_lines(assert_against_cache, benchmark): """Test _search_lines.""" lines = [ - '', '# DEBUG: Show dodo.py in the documentation', # noqa: T001 - '# FIXME: Show README.md in the documentation (may need to update paths?)', # noqa: T100 + 'print("FIXME: Show README.md in the documentation (may need to update paths?)")', # noqa: T100 '# FYI: Replace src_examples_dir and make more generic to specify code to include in documentation', '# HACK: Show table of contents in __init__.py file', # noqa: T103 '# NOTE: Show table of contents in __init__.py file', @@ -28,44 +29,36 @@ def test_search_lines(): tag_order = ['FIXME', 'FYI', 'HACK', 'REVIEW'] # noqa: T100 regex_compiled = re.compile(CODE_TAG_RE.format(tag='|'.join(tag_order))) - comments = _search_lines(lines, regex_compiled) # act + comments = benchmark(_search_lines, lines, regex_compiled) # act - assert len(comments) == 6 - assert comments[0].lineno == 3 + assert comments[0].lineno == 2 assert comments[0].tag == 'FIXME' # noqa: T100 - assert comments[0].text == 'Show README.md in the documentation (may need to update paths?)' - assert comments[-2].text == 'Support unconventional dashed code tags' - assert comments[-1].tag == 'FIXME' # noqa: T100 - assert comments[-1].text == 'and FYI: in the same line, but only match the first' + assert comments[0].text == 'Show README.md in the documentation (may need to update paths?)")' + assert_against_cache([*map(attrs.asdict, comments)]) -def test_format_report(): +def test_format_report(assert_against_cache, benchmark, fake_process): """Test _format_report.""" + fake_process.pass_command([fake_process.any()]) # Allow "git blame" and other commands to run unregistered + fake_process.keep_last_process(True) lines = ['# DEBUG: Example 1', '# TODO: Example 2'] # noqa: T101 comments = [_CodeTag(lineno, *line.split('# ')[1].split(': ')) for lineno, line in enumerate(lines)] tagged_collection = [_Tags(path_source=PATH_TEST_PROJECT, code_tags=comments)] tag_order = ['TODO'] # noqa: T101 - # Expected that DEBUG won't be matched - expected_content = [ - 'TODO', - 'Example 2', - f'{PATH_TEST_PROJECT.name}:1', - 'Found code tags for TODO (1)', - ] # noqa: T100,T101 - output = _format_report(PATH_TEST_PROJECT.parent, tagged_collection, tag_order=tag_order) # act + output = benchmark(_format_report, PATH_TEST_PROJECT.parent, tagged_collection, tag_order=tag_order) # act - for line in expected_content: - assert line in output, f'Received: `{output}` and expected: `{line}`' + assert_against_cache({'output': output.split('\n')}) -def test_write_code_tag_file(fix_test_cache): +def test_write_code_tag_file_when_no_matches(fix_test_cache, benchmark): """Test _write_code_tag_file for an empty file.""" path_tag_summary = fix_test_cache / 'code_tags.md' tmp_code_file = fix_test_cache / 'tmp.code' - tmp_code_file.write_text('') + tmp_code_file.write_text('No FIXMES or TODOS here') - write_code_tag_file( + benchmark( + write_code_tag_file, path_tag_summary=path_tag_summary, paths_source=[tmp_code_file], base_dir=fix_test_cache, ) # act diff --git a/tests/test_dot_dict.py b/tests/test_dot_dict.py index 5f66913c..6153b939 100644 --- a/tests/test_dot_dict.py +++ b/tests/test_dot_dict.py @@ -1,24 +1,24 @@ """Test dot_dict.""" -from datetime import datetime +import pendulum +import pytest from calcipy.dot_dict import ddict -def test_ddict(): +# TODO: Convert to hypothesis test! +@pytest.mark.parametrize(['key', 'value'], [ + ('int', 1), + ('number', -1.23), + ('unicode', '✓'), + ('is_bool', False), + ('datetime', pendulum.now()), +]) +def test_ddict(key, value): """Test ddict.""" - now = datetime.now() - - result = ddict(int=1, number=-1.23, date=now, unicode='✓', is_bool=False) + result = ddict(**{key: value}) + assert getattr(result, key) == value + assert result[key] == value assert isinstance(result, dict) - assert result.int == 1 - assert result['int'] == 1 - assert result.number == -1.23 - assert result['number'] == -1.23 - assert result.date == now - assert result['date'] == now - assert result.unicode == '✓' - assert result['unicode'] == '✓' - assert not result.is_bool - assert not result['is_bool'] + assert result.get(f'--{key}--') is None diff --git a/tests/test_file_search.py b/tests/test_file_search.py index 228fd254..20161161 100644 --- a/tests/test_file_search.py +++ b/tests/test_file_search.py @@ -6,9 +6,11 @@ def test_find_project_files_by_suffix(): """Test find_project_files_by_suffix.""" + expected_suffixes = ['', 'ini', 'js', 'json', 'lock', 'md', 'py', 'toml', 'yaml', 'yml'] + result = find_project_files_by_suffix(DG.meta.path_project, DG.meta.ignore_patterns) assert len(result) != 0, f'Error: see {DG.meta.path_project}/README.md for configuring the directory' - assert sorted(result.keys()) == ['', 'ini', 'js', 'json', 'lock', 'md', 'py', 'toml', 'yaml', 'yml'] + assert sorted(result.keys()) == expected_suffixes assert result[''][0].name == '.flake8' assert result['md'][-1].relative_to(DG.meta.path_project).as_posix() == 'tests/data/README.md' diff --git a/tests/test_proc_helpers.py b/tests/test_proc_helpers.py index 549703d9..a528008e 100644 --- a/tests/test_proc_helpers.py +++ b/tests/test_proc_helpers.py @@ -1,5 +1,6 @@ """Test proc_helpers.""" +import shlex from subprocess import CalledProcessError # nosec import pytest @@ -11,3 +12,14 @@ def test_run_cmd(): """Test run_cmd.""" with pytest.raises(CalledProcessError): run_cmd('gibberish') + + +def test_run_cmd(fake_process): + """Test run_cmd.""" + process = 'git branch' + expected = 'fake output' + fake_process.register(shlex.split(process), stdout=[expected, '']) + + result = run_cmd(process) + + assert result == expected + '\n\n'