# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['pytest_golden']

package_data = \
{'': ['*']}

install_requires = \
['atomicwrites>=1.4.0,<2.0.0',
 'pytest>=6.1.2,<7.0.0',
 'ruamel.yaml>=0.16.12,<0.17.0',
 'testfixtures>=6.15.0,<7.0.0']

extras_require = \
{':python_version < "3.7"': ['dataclasses>=0.7,<0.8']}

entry_points = \
{'pytest11': ['pytest-golden = pytest_golden.plugin']}

setup_kwargs = {
    'name': 'pytest-golden',
    'version': '0.2.1',
    'description': 'Plugin for pytest that offloads expected outputs to data files',
    'long_description': '# pytest-golden\n\nPlugin for [pytest] that offloads expected outputs to data files.\n\n[pytest]: https://pytest.org/\n\n## Usage, in short\n\n(see also: [example/](example/))\n\n[Install the pytest plugin](https://docs.pytest.org/en/latest/plugins.html):\n\n```shell\npip install pytest-golden\n```\n\nCreate a test file (e.g. *tests/test_foo.py*):\n\n```python\n@pytest.mark.golden_test("test_bar/*.yml")\ndef test_bar(golden):\n    assert foo.bar(golden["input"]) == golden.out["output"]\n```\n\nThe wildcard selects the "golden" files which serve as both the input and the expected output for the test. The test is basically parameterized on the files.\n\nCreate one or more of such YAML files (e.g. *tests/test_bar/basic.yml*):\n\n```yaml\ninput: Abc\noutput: Nop\n```\n\nRun `pytest` to execute the test(s).\n\nWhenever the function under test gets changed, its result may change as well, and the test won\'t pass anymore. You can run `pytest --update-goldens` to automatically re-populate the output.\n\n**See [detailed usage](#usage).**\n\n## The case for golden testing\n\nConsider this normal situation when testing a function (e.g. a function to list all words in a sentence).\n\n#### *foo.py*\n\n```python\ndef find_words(text: str) -> list:\n    return text.split()\n```\n\n#### *tests/test_foo.py*\n\n```python\nfrom foo import find_words\n\ndef test_find_words():\n    assert find_words("If at first you don\'t succeed, try, try again.") == [\n        "If", "at", "first", "you", "don\'t", "succeed,", "try,", "try", "again."\n    ]\n```\n\nYou wrote a basic test for that function, but it can be quite tedious to manually write out what the expected output is, especially if the output was something bigger. Sometimes perhaps you\'d resort to just writing a dummy test first and copying the actual output from the failure message. And there\'s nothing really wrong with that, because then you\'d still manually inspect whether the new output is good.\n\n### With golden testing\n\nBut let\'s rewrite this test using "golden testing".\n\n#### *tests/test_foo.py*\n\n```python\nfrom foo import find_words\n\ndef test_find_words(golden):\n    golden = golden.open("test_find_words/test_basic.yml")\n    assert find_words(golden["input"]) == golden.out["output"]\n```\n\nHere `golden["xxx"]` will be a value read directly from the associated file. Let\'s create that (YAML) file:\n\n#### *tests/test_find_words/test_basic.yml*\n\n```yaml\ninput: |-\n  If at first you don\'t succeed, try, try again.\n```\n\nUnlike the input, `golden.out["yyy"]` works a little differently. Normally it will also be just an input for the test, taken from the file (and the assertion will be a completely normal [pytest][] assertion), but in a special "update" mode it will instead accept whatever the result is at runtime and put it back into the "golden" file. Both updating and initially populating the file is done automatically with the command **`pytest --update-goldens`**:\n\n#### *tests/test_find_words/test_basic.yml*\n\n```yaml\ninput: |-\n  If at first you don\'t succeed, try, try again.\noutput:\n- If\n- at\n- first\n- you\n- don\'t\n- succeed,\n- try,\n- try\n- again.\n```\n\nNow, when running just `pytest`, the test will always assert that the result is exactly equal to the expected output. Which is just how unittests work.\n\nNow you can add all of this into your source control system.\n\n### Introducing a change\n\nLet\'s say you\'re not happy that the punctuation gets clumped with the words, so you devise a different implementation for this function.\n\n#### *foo.py*\n\n```python\nimport re\n\ndef find_words(text: str) -> list:\n    return re.findall(r"\\w+", text)\n```\n\nYou also want to add another test case for it:\n\n#### *tests/test_find_words/test_quotation.yml*\n\n```yaml\ninput: |-\n  Dr. King said, \'I have a dream.\'\noutput:\n- Dr\n- King\n- said\n- I\n- have\n- a\n- dream\n```\n\nAnd let\'s just turn this into a *parameterized* golden test (one test generated per each file that matches the wildcard):\n\n#### *tests/test_foo.py*\n\n```python\nimport pytest\nfrom foo import find_words\n\n@pytest.mark.golden_test("test_find_words/*.yml")\ndef test_find_words(golden):\n    assert find_words(golden["input"]) == golden.out["output"]\n```\n\nNow if we run `pytest -v`, we see that all is well with the new test, which gets picked up as `test_find_words[test_quotation.yml]`, but the code changes also made it so the previous test now disagrees! You get a normal failure message from *pytest* itself.\n\nNormally in such situations you\'d go back to the test file and edit the expected output (if you indeed expected it to change). But with this you can instead just run `pytest --update-goldens`, and you\'ll see that instead the "golden" file gets updated by itself (with no test failure). The resulting diff can then still be viewed in your source control system:\n\n```diff\n--- a/tests/test_find_words/test_basic.yml\n+++ b/tests/test_find_words/test_basic.yml\n@@ -5,8 +5,9 @@ output:\n - at\n - first\n - you\n-- don\'t\n-- succeed,\n-- try,\n+- don\n+- t\n+- succeed\n - try\n-- again.\n+- try\n+- again\n```\n\nNow you (and potentially your code reviewers) get to decide whether this diff is an acceptable one, or whether more changes are needed. You can do another iteration on the code, and the unittest will get updated as you go, and you never need to manually edit it -- just visually inspect the changes and check them in.\n\n## Usage\n\n### `golden` fixture\n\nAdd a `golden` parameter to your [pytest][] test function, and it will be passed a `GoldenTestFixtureFactory`.\n\n### class `GoldenTestFixtureFactory`\n\n#### `golden.open(path) -> GoldenTestFixture`\n\nCall this method on the `golden` object to get an actual usable [fixture](#class-goldentestfixture).\n\nThe `path` argument is a path to a file, relative to the calling Python test file. Teardown is done automatically when the test function finishes.\n\n### `@pytest.mark.golden_test(*patterns: str)`\n\nUse this decorator to:\n\n1. avoid having to call `.open` and get a [proper fixture](#class-goldentestfixture) directly as the `golden` argument of your test function and\n2. add parameterization to your "golden" test.\n\nThe `patterns` are one or more [glob patterns](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob), relative to the calling Python test file. One test will be created for each matched file.\n\n### class `GoldenTestFixture`\n\n#### `golden[input_key: str] -> Any`\n\nGet a value from the associated YAML file, at the top-level key. May raise `KeyError`.\n\n#### `golden.get(input_key: str) -> Optional[Any]`\n\nDitto, but returns `None` if the key is missing.\n\n#### `golden.out[output_key: str] -> Any`\n\n* In normal mode:\n\n  Get a value from the associated YAML file, at the top-level key. May raise `KeyError`.\n\n* If `--update-goldens` flag is passed:\n\n  Get a proxy object for the key, which, upon being compared for equality (and subsequently asserted on), marks that the "golden" file should get an updated value for this top-level key. Such updates get performed upon teardown of the fixture: the original file always gets rewritten once.\n\n#### `golden.out.get(output_key: str) -> Optional[Any]`\n\nDitto, but when compared to `None`, marks the key as deleted from the file, rather than just having the value `None`.\n\n## How to...\n\n### Make a custom type representable in YAML\n\nWe will make these types known to the underlying implementation -- [ruamel.yaml](https://yaml.readthedocs.io/), but let\'s use only the passthrough functions provided by the module `pytest_golden.yaml`. It is best to apply this globally, in *conftest.py*.\n\n```python\nimport pytest_golden.yaml\n\npytest_golden.yaml.register_class(MyClass)\n```\n\n(and see [details for `ruamel.yaml`](https://yaml.readthedocs.io/en/latest/dumpcls.html))\n\nAlternate example if your class is equivalent to a single value:\n\n```python\nclass MyClass:\n    def __init__(self, value: str):\n        self.value = value\n\npytest_golden.yaml.add_representer(MyClass, lambda dumper, data: dumper.represent_scalar("!MyClass", data.value))\npytest_golden.yaml.add_constructor(\'!MyClass\', lambda loader, node: MyClass(node.value))\n```\n\nOr in the particular case of subclassing a standard type, you could just drop the tag altogether and rely on equality to the base type.\n\n```python\nclass MyClass(str):\n    pass\n\npytest_golden.yaml.add_representer(MyClass, lambda dumper, data: dumper.represent_str(data))\n```\n',
    'author': 'Oleh Prypin',
    'author_email': 'oleh@pryp.in',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/oprypin/pytest-golden',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'extras_require': extras_require,
    'entry_points': entry_points,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
