├── tests ├── __init__.py ├── conftest.py ├── test_fixtrue_graph.py └── test_fixture_duplicates.py ├── pytest_fixture_tools ├── __init__.py └── plugin.py ├── requirements-testing.txt ├── imgs └── graph_example.png ├── MANIFEST.in ├── .travis.yml ├── tox.ini ├── .gitignore ├── CHANGES.rst ├── LICENSE.txt ├── pyproject.toml └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytest_fixture_tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-testing.txt: -------------------------------------------------------------------------------- 1 | pytest-cov 2 | pytest-cache 3 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Configuration for pytest runner.""" 2 | 3 | pytest_plugins = 'pytester' 4 | -------------------------------------------------------------------------------- /imgs/graph_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytest-dev/pytest-fixture-tools/HEAD/imgs/graph_example.png -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGES.rst 2 | include README.rst 3 | include requirements-testing.txt 4 | include setup.py 5 | include LICENSE -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | - "nightly" 8 | # command to install dependencies 9 | install: 10 | - pip install python-coveralls virtualenv 11 | # # command to run tests 12 | script: 13 | - pip install -r requirements-testing.txt -e . 14 | - py.test --cov=pytest_fixture_tools --cov-report=term-missing tests 15 | - coveralls 16 | notifications: 17 | email: 18 | - opensource-tests@paylogic.com 19 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | distshare={homedir}/.tox/distshare 3 | envlist=py26,py27,py27-pytest-latest,py33 4 | indexserver= 5 | pypi = https://pypi.python.org/simple 6 | 7 | [testenv] 8 | commands= py.test --junitxml={envlogdir}/junit-{envname}.xml 9 | deps = -r{toxinidir}/requirements-testing.txt 10 | 11 | [testenv:py27-coverage] 12 | commands= py.test --cov=pytest_fixture_tools --junitxml={envlogdir}/junit-{envname}.xml 13 | 14 | [testenv:py27-pytest-latest] 15 | basepython=python2.7 16 | deps = 17 | {[testenv]deps} 18 | hg+https://bitbucket.org/hpk42/pytest 19 | 20 | [pytest] 21 | addopts = pytest_fixture_tools tests 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # Distribution / packaging 6 | bin/ 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | eggs/ 11 | lib/ 12 | lib64/ 13 | parts/ 14 | sdist/ 15 | var/ 16 | include/ 17 | *.egg-info/ 18 | .installed.cfg 19 | *.egg 20 | .Python 21 | 22 | # Installer logs 23 | pip-log.txt 24 | pip-delete-this-directory.txt 25 | 26 | # Unit test / coverage reports 27 | .tox/ 28 | .coverage 29 | .cache 30 | nosetests.xml 31 | coverage.xml 32 | 33 | # Translations 34 | *.mo 35 | 36 | # Mr Developer 37 | .mr.developer.cfg 38 | .project 39 | .pydevproject 40 | .idea/ 41 | 42 | # Rope 43 | .ropeproject 44 | 45 | # Django stuff: 46 | *.log 47 | *.pot 48 | 49 | # Sphinx documentation 50 | docs/_build/ 51 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 5 | 1.2.1 6 | ----- 7 | 8 | * Fix typo in README.rst by @lebrice in https://github.com/pytest-dev/pytest-fixture-tools/pull/10 9 | * Fix #8 - Remove migrate setup.py to pyproject.toml by @ogajduse in https://github.com/pytest-dev/pytest-fixture-tools/pull/9 10 | * Fix getfixturedefs call by @MaksimKravchuk in https://github.com/pytest-dev/pytest-fixture-tools/pull/12 11 | 12 | 1.2.0 13 | ----- 14 | 15 | * Fix --fixture-graph-output-dir and --fixture-graph-output-type by @youtux in https://github.com/pytest-dev/pytest-fixture-tools/pull/5 16 | * Python and pytest compatibility fixes by @ogajduse in https://github.com/pytest-dev/pytest-fixture-tools/pull/7 17 | * Drop support for EOL Python versions 18 | 19 | 1.1.0 20 | ----- 21 | 22 | * Added fixture-graph option. 23 | 24 | 25 | 1.0.0 26 | ----- 27 | 28 | * Initial public release 29 | -------------------------------------------------------------------------------- /tests/test_fixtrue_graph.py: -------------------------------------------------------------------------------- 1 | import py 2 | 3 | 4 | def test_fixture_graph_created(testdir): 5 | """Check that --fixture-graph will create the graph .dot file""" 6 | sub1 = testdir.mkpydir("sub1") 7 | sub2 = testdir.mkpydir("sub1/sub2") 8 | sub1.join("conftest.py").write(py.code.Source(""" 9 | import pytest 10 | 11 | @pytest.fixture 12 | def arg1(request): 13 | pass 14 | """)) 15 | sub2.join("conftest.py").write(py.code.Source(""" 16 | import pytest 17 | 18 | @pytest.fixture 19 | def arg1(request): 20 | pass 21 | """)) 22 | sub1.join("test_in_sub1.py").write("def test_1(arg1): pass") 23 | sub2.join("test_in_sub2.py").write("def test_2(arg1): pass") 24 | 25 | result = testdir.runpytest_subprocess('--fixture-graph', '-s', '--fixture-graph-output-type', 'dot') 26 | 27 | result.stdout.fnmatch_lines("created artifacts/fixture-graph-sub1-test_in_sub1.py__test_1.dot.") 28 | result.stdout.fnmatch_lines('created artifacts/fixture-graph-sub1-sub2-test_in_sub2.py__test_2.dot.') 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Paylogic International 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pytest-fixture-tools" 7 | version = "1.2.1" 8 | description = "Plugin for pytest which provides tools for fixtures" 9 | dynamic = ["readme"] 10 | authors = [ 11 | { name = "Paylogic International", email = "developers@paylogic.com" }, 12 | ] 13 | license = {text = "MIT license"} 14 | classifiers = [ 15 | "Development Status :: 6 - Mature", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: POSIX", 19 | "Operating System :: Microsoft :: Windows", 20 | "Operating System :: MacOS :: MacOS X", 21 | "Topic :: Software Development :: Testing", 22 | "Topic :: Software Development :: Libraries", 23 | "Topic :: Utilities", 24 | "Programming Language :: Python :: 2", 25 | "Programming Language :: Python :: 3", 26 | "Programming Language :: Python :: 3.8", 27 | "Programming Language :: Python :: 3.9", 28 | "Programming Language :: Python :: 3.10", 29 | "Programming Language :: Python :: 3.11", 30 | "Programming Language :: Python :: 3.12", 31 | ] 32 | 33 | dependencies = ["pytest", "pydot", "py"] 34 | 35 | [project.entry-points.pytest11] 36 | pytest-fixture-tools = "pytest_fixture_tools.plugin" 37 | 38 | [tool.setuptools] 39 | packages = ["pytest_fixture_tools"] 40 | 41 | [tool.setuptools.dynamic] 42 | readme = {file = ["README.rst", "CHANGES.rst"], content-type = "text/x-rst"} 43 | -------------------------------------------------------------------------------- /tests/test_fixture_duplicates.py: -------------------------------------------------------------------------------- 1 | """Test for checking getting of duplicates.""" 2 | import py 3 | import pytest 4 | 5 | 6 | def test_there_are_not_fixture_duplicates(testdir): 7 | """Check that --show-fixture-duplicates wont give us list of duplicates.""" 8 | sub1 = testdir.mkpydir("sub1") 9 | sub2 = testdir.mkpydir("sub2") 10 | sub1.join("conftest.py").write(py.code.Source(""" 11 | import pytest 12 | 13 | @pytest.fixture 14 | def arg1(request): 15 | pass 16 | """)) 17 | sub2.join("conftest.py").write(py.code.Source(""" 18 | import pytest 19 | 20 | @pytest.fixture 21 | def arg2(request): 22 | pass 23 | """)) 24 | sub1.join("test_in_sub1.py").write("def test_1(arg1): pass") 25 | sub2.join("test_in_sub2.py").write("def test_2(arg2): pass") 26 | 27 | result = testdir.runpytest("--show-fixture-duplicates") 28 | 29 | assert result.stdout.lines.count('arg1') == 0 30 | 31 | 32 | def test_there_are_fixture_duplicates(testdir): 33 | """Check that --show-fixture-duplicates will give us list of duplicates.""" 34 | sub1 = testdir.mkpydir("sub1") 35 | sub2 = testdir.mkpydir("sub1/sub2") 36 | sub1.join("conftest.py").write(py.code.Source(""" 37 | import pytest 38 | 39 | @pytest.fixture 40 | def arg1(request): 41 | pass 42 | """)) 43 | sub2.join("conftest.py").write(py.code.Source(""" 44 | import pytest 45 | 46 | @pytest.fixture 47 | def arg1(request): 48 | pass 49 | """)) 50 | sub1.join("test_in_sub1.py").write("def test_1(arg1): pass") 51 | sub2.join("test_in_sub2.py").write("def test_2(arg1): pass") 52 | 53 | result = testdir.runpytest_subprocess('--show-fixture-duplicates', '-s') 54 | 55 | result.stdout.fnmatch_lines('sub1/conftest.py:5') 56 | result.stdout.fnmatch_lines('sub1/sub2/conftest.py:5') 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pytest-fixture-tools: Pytest fixture tools plugin 2 | ================================================= 3 | 4 | The ``pytest-fixture-tools`` package is a pytest plugin which provides various tools for fixture. 5 | 6 | .. image:: https://api.travis-ci.org/paylogic/pytest-fixture-tools.png 7 | :target: https://travis-ci.org/paylogic/pytest-fixture-tools 8 | .. image:: https://pypip.in/v/pytest-fixture-tools/badge.png 9 | :target: https://crate.io/packages/pytest-fixture-tools/ 10 | .. image:: https://coveralls.io/repos/paylogic/pytest-fixture-tools/badge.png?branch=master 11 | :target: https://coveralls.io/r/paylogic/pytest-fixture-tools 12 | 13 | 14 | Installation 15 | ------------ 16 | 17 | .. sourcecode:: 18 | 19 | pip install pytest-fixture-tools 20 | 21 | 22 | Usage 23 | ----- 24 | 25 | show-fixture-duplicates 26 | *********************** 27 | 28 | If you have already installed ``pytest-fixture-tools`` plugin then you can use one of its commands. 29 | 30 | ``--show-fixture-duplicates`` - will collect all fixtures and print you a list of duplicates for each fixture. 31 | 32 | With ``--show-fixture-duplicates`` you can use ``--fixture name_of_fixture`` option to get list of duplicates only for specific fixture 33 | 34 | .. sourcecode:: 35 | 36 | py.test tests/ --show-fixture-duplicates --fixture order 37 | 38 | Output can look like this: 39 | 40 | .. sourcecode:: 41 | 42 | ========================================== test session starts ========================================== 43 | platform linux2 -- Python 2.7.3 -- pytest-2.5.1 -- /home/batman/.virtualenvs/arkham-city/bin/python 44 | Tests are shuffled using seed number 355495648184. 45 | cachedir: /home/batman/.virtualenvs/arkham-city/.cache 46 | plugins: fixture-tools, random, bdd-splinter, pep8, cov, contextfixture, bdd, xdist, instafail, cache 47 | collected 2347 items / 1 skipped 48 | 49 | ------------------------------------------------- order ------------------------------------------------- 50 | tests/fixtures/order.py:30 51 | tests/unit/api/conftest.py:261 52 | 53 | fixture-graph 54 | ************* 55 | 56 | You can generate the usage fixture graph like that: 57 | 58 | .. sourcecode:: bash 59 | 60 | # on windows gitbash as example 61 | export PATH=$PATH:/c/Program\ Files\ \(x86\)/Graphviz2.38/bin/ 62 | pytest --fixture-graph -s 63 | 64 | # or you can select the output direcotry like that: 65 | pytest --fixture-graph --fixture-graph-output-dir=./test_output 66 | 67 | # you can also change the output type of the graphs (any of graphvis supported outputs types): 68 | pytest --fixture-graph --fixture-graph-output-type=jpg 69 | 70 | The output would be like that: 71 | 72 | .. sourcecode:: 73 | 74 | ============================= test session starts ============================= 75 | platform win32 -- Python 2.7.10, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 76 | rootdir: C:\Users\ifruchte\Projects\pytest-fixture-tools, inifile: tox.ini 77 | plugins: pep8-1.0.6, cov-2.5.1, fixture-tools-1.0.0 78 | collected 7 items 79 | 80 | pytest_fixture_tools\__init__.py . [ 14%] 81 | pytest_fixture_tools\plugin.py . [ 28%] 82 | tests\__init__.py . [ 42%] 83 | tests\conftest.py . [ 57%] 84 | tests\test_fixture_duplicates.py . 85 | -------------------------------- fixture-graph -------------------------------- 86 | created artifacts/fixture-graph-tests-test_fixture_duplicates.py__test_there_are_fixture_duplicates.png. 87 | ============================= test session starts ============================= 88 | platform win32 -- Python 2.7.10, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 89 | rootdir: c:\users\ifruchte\appdata\local\temp\pytest-of-ifruchte\pytest-445\test_there_are_not_fixture_duplicates0, inifile: 90 | plugins: pep8-1.0.6, cov-2.5.1, fixture-tools-1.0.0 91 | collected 2 items 92 | 93 | ======================== no tests ran in 0.06 seconds ========================= 94 | .s [100%] 95 | 96 | ===================== 6 passed, 1 skipped in 0.29 seconds ===================== 97 | 98 | Final output, that can help with tests that depend on large amount of fixtures: 99 | 100 | .. image:: imgs/graph_example.png 101 | :alt: alternate text 102 | 103 | Contact 104 | ------- 105 | 106 | If you have questions, bug reports, suggestions, etc. please create an issue on 107 | the `GitHub project page `_. 108 | 109 | 110 | License 111 | ------- 112 | 113 | This software is licensed under the `MIT license `_ 114 | 115 | See `License `_ 116 | 117 | © 2013 Paylogic International. 118 | -------------------------------------------------------------------------------- /pytest_fixture_tools/plugin.py: -------------------------------------------------------------------------------- 1 | """Pytest fixture tools plugin.""" 2 | 3 | import py 4 | import os 5 | import errno 6 | 7 | from _pytest.compat import getlocation 8 | from collections import defaultdict 9 | 10 | import pydot 11 | 12 | tw = py.io.TerminalWriter() 13 | verbose = 1 14 | 15 | 16 | def mkdir_recursive(path): 17 | try: 18 | os.makedirs(path) 19 | except OSError as exc: 20 | if exc.errno == errno.EEXIST and os.path.isdir(path): 21 | pass 22 | else: 23 | raise 24 | 25 | 26 | def pytest_addoption(parser): 27 | """Add commandline options show-fixture-duplicates and fixture.""" 28 | group = parser.getgroup("general") 29 | group.addoption('--show-fixture-duplicates', 30 | action="store_true", dest="show_fixture_duplicates", default=False, 31 | help="show list of duplicates from available fixtures") 32 | group.addoption('--fixture', 33 | action="store", type=str, dest="fixture_name", default='', 34 | help="Name of specific fixture for which you want to get duplicates") 35 | 36 | group.addoption('--fixture-graph', 37 | action="store_true", dest="fixture_graph", default=False, 38 | help="create .dot fixture graph for each test") 39 | group.addoption('--fixture-graph-output-dir', 40 | action="store", dest="fixture_graph_output_dir", default="artifacts", 41 | help="select the location for the output of fixture graph. defaults to 'artifacts'") 42 | group.addoption('--fixture-graph-output-type', 43 | action="store", dest="fixture_graph_output_type", default="png", 44 | help="select the type of the output for the fixture graph. defaults to 'png'") 45 | 46 | 47 | def pytest_cmdline_main(config): 48 | """Check show_fixture_duplicates option to show fixture duplicates.""" 49 | if config.option.show_fixture_duplicates: 50 | show_fixture_duplicates(config) 51 | return 0 52 | 53 | 54 | def show_fixture_duplicates(config): 55 | """Wrap pytest session to show duplicates.""" 56 | from _pytest.main import wrap_session 57 | return wrap_session(config, _show_fixture_duplicates_main) 58 | 59 | 60 | def print_duplicates(argname, fixtures, previous_argname): 61 | """Print duplicates with TerminalWriter.""" 62 | if len(fixtures) > 1: 63 | fixtures = sorted(fixtures, key=lambda key: key[2]) 64 | 65 | for baseid, module, bestrel, fixturedef in fixtures: 66 | 67 | if previous_argname != argname: 68 | tw.line() 69 | tw.sep("-", argname) 70 | previous_argname = argname 71 | 72 | if verbose <= 0 and argname[0] == "_": 73 | continue 74 | 75 | funcargspec = bestrel 76 | 77 | tw.line(funcargspec) 78 | 79 | 80 | def _show_fixture_duplicates_main(config, session): 81 | """Preparing fixture duplicates for output.""" 82 | session.perform_collect() 83 | curdir = py.path.local() 84 | 85 | fm = session._fixturemanager 86 | 87 | fixture_name = config.option.fixture_name 88 | available = defaultdict(list) 89 | arg2fixturedefs = ([fixture_name] 90 | if fixture_name and fixture_name in fm._arg2fixturedefs 91 | else fm._arg2fixturedefs) 92 | for item in session.items: 93 | for argname in arg2fixturedefs: 94 | fixturedefs = fm.getfixturedefs(argname, item) 95 | assert fixturedefs is not None 96 | if not fixturedefs: 97 | continue 98 | 99 | for fixturedef in fixturedefs: 100 | loc = getlocation(fixturedef.func, curdir) 101 | 102 | fixture = ( 103 | len(fixturedef.baseid), 104 | fixturedef.func.__module__, 105 | curdir.bestrelpath(loc), 106 | fixturedef 107 | ) 108 | if fixture[2] not in [f[2] for f in available[argname]]: 109 | available[argname].append(fixture) 110 | 111 | if fixture_name: 112 | print_duplicates(fixture_name, available[fixture_name], None) 113 | else: 114 | available = sorted([(key, items) for key, items in available.items()], key=lambda key: key[0]) 115 | 116 | previous_argname = None 117 | for argname, fixtures in available: 118 | print_duplicates(argname, fixtures, previous_argname) 119 | previous_argname = argname 120 | 121 | 122 | def pytest_runtest_setup(item): 123 | if item.config.option.fixture_graph and hasattr(item, "_fixtureinfo"): 124 | # fixtures came from function parameters names 125 | data = dict() 126 | data['func_args'] = item._fixtureinfo.argnames, 'red' 127 | for fixture_name, fixture_data in list(item._fixtureinfo.name2fixturedefs.items()): 128 | 129 | color = 'green' 130 | data[fixture_name] = fixture_data[0].argnames, color 131 | 132 | graph = pydot.Dot(graph_type='digraph') 133 | 134 | for name, depended_list in list(data.items()): 135 | depended_list, color = depended_list 136 | 137 | node = pydot.Node(name, style="filled", fillcolor=color) 138 | graph.add_node(node) 139 | for i in depended_list: 140 | edge = pydot.Edge(node, i) 141 | graph.add_edge(edge) 142 | 143 | log_dir = item.config.option.fixture_graph_output_dir 144 | output_type = item.config.option.fixture_graph_output_type 145 | mkdir_recursive(log_dir) 146 | filename = "{0}/fixture-graph-{1}".format(log_dir, item._nodeid.replace(":", "_").replace("/", "-")) 147 | tw.line() 148 | tw.sep("-", "fixture-graph") 149 | try: 150 | graph.write("{}.{}".format(filename, output_type), format=output_type) 151 | tw.line("created {}.{}.".format(filename, output_type)) 152 | except Exception: 153 | tw.line("graphvis wasn't found in PATH") 154 | graph.write(filename + ".dot") 155 | tw.line("created {}.dot.".format(filename)) 156 | tw.line("You can convert it to a PNG using:\n\t'dot -Tpng {0}.dot -o {0}.png'".format(filename)) 157 | --------------------------------------------------------------------------------