pax_global_header00006660000000000000000000000064146015617500014517gustar00rootroot0000000000000052 comment=788259b6f135a66566cc3601bfdc9907a99ff494 pytest-relaxed-2.0.2/000077500000000000000000000000001460156175000144725ustar00rootroot00000000000000pytest-relaxed-2.0.2/.circleci/000077500000000000000000000000001460156175000163255ustar00rootroot00000000000000pytest-relaxed-2.0.2/.circleci/config.yml000066400000000000000000000016141460156175000203170ustar00rootroot00000000000000version: 2.1 orbs: orb: invocations/orb@1.3.1 jobs: confirm-loaded: executor: name: orb/default version: "3.6" steps: - orb/setup - run: pytest -VV | grep pytest-relaxed - orb/debug workflows: main: jobs: - orb/lint: name: Lint - orb/format: name: Style check - orb/coverage: name: Test - confirm-loaded: name: Confirm plugin loads into pytest - orb/docs: name: Docs task: "docs --nitpick" - orb/test-release: name: Release test - orb/test: name: << matrix.version >> # It's not worth testing on other interpreters if the baseline one # failed. Can't run >4 jobs at a time anyhow! requires: ["Test"] matrix: parameters: version: ["3.7", "3.8", "3.9", "3.10", "3.11"] pytest-relaxed-2.0.2/.codecov.yml000066400000000000000000000000501460156175000167100ustar00rootroot00000000000000comment: false coverage: precision: 0 pytest-relaxed-2.0.2/.flake8000066400000000000000000000001511460156175000156420ustar00rootroot00000000000000[flake8] exclude = .git,build,dist ignore = E124,E125,E128,E261,E301,E302,E303,W503 max-line-length = 79 pytest-relaxed-2.0.2/.gitignore000066400000000000000000000000451460156175000164610ustar00rootroot00000000000000.cache .coverage htmlcov docs/_build pytest-relaxed-2.0.2/LICENSE000066400000000000000000000024421460156175000155010ustar00rootroot00000000000000Copyright (c) 2020 Jeff Forcier. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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 HOLDER 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. pytest-relaxed-2.0.2/MANIFEST.in000066400000000000000000000003101460156175000162220ustar00rootroot00000000000000include LICENSE include README.rst include tasks.py recursive-include docs * recursive-exclude docs/_build * include dev-requirements.txt recursive-include tests * recursive-exclude tests *.pyc *.pyo pytest-relaxed-2.0.2/README.rst000066400000000000000000000227561460156175000161750ustar00rootroot00000000000000|version| |python| |license| |ci| |coverage| .. |version| image:: https://img.shields.io/pypi/v/pytest-relaxed :target: https://pypi.org/project/pytest-relaxed/ :alt: PyPI - Package Version .. |python| image:: https://img.shields.io/pypi/pyversions/pytest-relaxed :target: https://pypi.org/project/pytest-relaxed/ :alt: PyPI - Python Version .. |license| image:: https://img.shields.io/pypi/l/pytest-relaxed :target: https://github.com/bitprophet/pytest-relaxed/blob/main/LICENSE :alt: PyPI - License .. |ci| image:: https://img.shields.io/circleci/build/github/bitprophet/pytest-relaxed/main :target: https://app.circleci.com/pipelines/github/bitprophet/pytest-relaxed :alt: CircleCI .. |coverage| image:: https://img.shields.io/codecov/c/gh/bitprophet/pytest-relaxed :target: https://app.codecov.io/gh/bitprophet/pytest-relaxed :alt: Codecov ============== pytest-relaxed ============== ``pytest-relaxed`` provides 'relaxed' test discovery for pytest. It is the spiritual successor to https://pypi.python.org/pypi/spec, but is built for ``pytest`` instead of ``nosetests``, and rethinks some aspects of the design (such as increased ability to opt-in to various behaviors.) For a development roadmap, see the maintainer's `roadmap page `_. Rationale ========= Has it ever felt strange to you that we put our tests in ``tests/``, then name the files ``test_foo.py``, name the test classes ``TestFoo``, and finally name the test methods ``test_foo_bar``? Especially when almost all of the code inside of ``tests/`` is, well, *tests*? This pytest plugin takes a page from the rest of Python, where you don't have to explicitly note public module/class members, but only need to hint as to which ones are private. By default, all files and objects pytest is told to scan will be considered tests; to mark something as not-a-test, simply prefix it with an underscore. Relaxed discovery ================= The "it's a test by default unless underscored" approach works for files:: tests ├── _util.py ├── one_module.py └── another_module.py It's applied to module members:: def _helper(): pass def one_thing(): assert True def another_thing(): assert False def yet_another(): assert _helper() == 'something' And to class members:: class SomeObject: def behavior_one(self): assert True def another_behavior(self): assert False def _helper(self): pass def it_does_things(self): assert self._helper() == 'whatever' Special cases ------------- As you might expect, there are a few more special cases around discovery to avoid fouling up common test extensions: - Files named ``conftest.py`` aren't treated as tests, because they do special pytest things; - Module and class members named ``setup_(module|class|method|function)`` are not considered tests, as they are how pytest implements classic/xunit style setup and teardown; - Objects decorated as fixtures with ``@pytest.fixture`` are, of course, also skipped. Backwards compatibility ----------------------- If you like the idea of pytest-relaxed but have a large test suite, it may be daunting to think about "upgrading" it all in one go. It's relatively simple to arrive at a 'hybrid' test suite where your legacy tests still run normally (as long as they're already pytest-compatible, which is true for most unittest suites) but 'relaxed' style tests also work as expected. - The only change you'll still have to make is renaming 'helper' files (any whose name doesn't start with ``test_``) so their names begin with an underscore; then, of course, search and replace any imports of such files. - ``pytest-relaxed`` explicitly sidesteps around anything that looks like "classic" test files (i.e. named ``test_*``), allowing pytest's native collection to take effect. Such files should not need any alteration. - Our reporter (display) functionality still works pretty well with legacy style tests; test prefixes and suffixes are stripped at display time, so ``TestMyThing.test_something`` still shows up as if it was written in relaxed style: ``MyThing`` w/ nested ``something``. - However, because we don't *collect* such tests, nesting and other features we offer won't work until you've renamed the files to not start with ``test_``, and changed any classes to not inherit from ``unittest.TestCase`` or similar. Nested class organization ========================= On top of the relaxed discovery algorithm, ``pytest-relaxed`` also lets you organize tests in a nested fashion, again like the ``spec`` nose plugin or the tools that inspired it, such as Ruby's ``rspec``. This is purely optional, but we find it's a nice middle ground between having a proliferation of files or suffering a large, flat test namespace making it hard to see which feature areas have been impacted by a bug (or whatnot). The feature is enabled by using nested/inner classes, like so:: class SomeObject: def basic_behavior(self): assert True class init: "__init__" def no_args_required(self): assert True def accepts_some_arg(self): assert True def sets_up_config(self): assert False class some_method: def accepts_whatever_params(self): assert False def base_behavior(self): assert True class when_config_says_foo: def it_behaves_like_this(self): assert False class when_config_says_bar: def it_behaves_like_this(self): assert True Test discovery on these inner classes is recursive, so you *can* nest them as deeply as you like. Naturally, as with all Python code, sometimes you can have too much of a good thing...but that's up to you. Nested class attributes ----------------------- If you're namespacing your tests via nested classes, you may find yourself wanting to reference the enclosing "scope" of the outer classes they live in, such as class attributes. pytest-relaxed automatically copies such attributes onto inner classes during the test collection phase, allowing you to write code like this:: class Outer: behavior_one = True def outer_test(self): assert self.behavior_one class Inner: behavior_two = True def inner_test(self): assert self.behavior_one and self.behavior_two Notably: - The behavior is nested, infinitely, as you might expect; - Attributes that look like test classes or methods themselves, are not copied (though others, i.e. ones named with a leading underscore, are); - Only attributes _not_ already present on the inner class are copied; thus inner classes may naturally "override" attributes, just as with class inheritance. Other test helpers ================== ``pytest-relaxed`` offers a few other random lightweight test-related utilities that don't merit their own PyPI entries (most ported from ``spec``), such as: - ``trap``, a decorator for use on test functions and/or test helpers/subroutines which is similar to pytest's own ``capsys``/``capfd`` fixtures in that it allows capture of stdout/err. - It offers a slightly simpler API: it replaces ``sys.(stdout|stderr)`` with ``IO`` objects which can be ``getvalue()``'d as needed. - More importantly, it can wrap arbitrary callables, which is useful for code-sharing use cases that don't easily fit into the design of fixtures. - ``raises``, a wrapper around ``pytest.raises`` which works as a decorator, similar to the Nose testing tool of the same name. Nested output display ===================== Continuing in the "port of ``spec`` / inspired by RSpec and friends" vein, ``pytest-relaxed`` greatly enhances pytest's verbose display mode: - Tests are shown in a nested, tree-like fashion, with 'header' lines shown for modules, classes (including nested classes) and so forth. - The per-test-result lines thus consist of just the test names, and are colorized (similar to the built-in verbose mode) based on success/failure/skip. - Headers and test names are massaged to look more human-readable, such as replacing underscores with spaces. *Unlike* ``spec``, this functionality doesn't affect normal/non-verbose output at all, and can be disabled entirely, allowing you to use the relaxed test discovery alongside normal pytest verbose display or your favorite pytest output plugins (such as ``pytest-sugar``.) Installation & use ================== As with most pytest plugins, it's quite simple: - ``pip install pytest-relaxed``; - Tell pytest where your tests live via the ``testpaths`` option; otherwise pytest-relaxed will cause pytest to load all of your non-test code as tests! - Not required, but **strongly recommended**: configure pytest's default filename pattern (``python_files``) to be an unqualified glob (``*``). - This doesn't impact (our) test discovery, but pytest's assertion 'rewriting' (the feature that turns ``assert var == othervar`` into ``assert 17 == 2`` during error display) reuses this setting when determining which files to manipulate. - Thus, a recommended ``setup.cfg`` (or ``pytest.ini``, sans the header) is:: [tool:pytest] testpaths = tests python_files = * - Write some tests, as exampled above; - ``pytest`` to run the tests, and you're done! pytest-relaxed-2.0.2/dev-requirements.txt000066400000000000000000000005061460156175000205330ustar00rootroot00000000000000# # Direct dev dependencies # # Linting/formatting black==22.8.0 flake8==5.0.4 # Packaging twine==3.8.0 setuptools>=56.0.0 # Test coverage (see note in tasks.py::coverage) coverage==6.2 # Task running invoke>=2 invocations>=3.3 # Docs (also transitively via invocations; consider nuking again) releases>=2 # Ourselves! -e . pytest-relaxed-2.0.2/docs/000077500000000000000000000000001460156175000154225ustar00rootroot00000000000000pytest-relaxed-2.0.2/docs/changelog.rst000066400000000000000000000116251460156175000201100ustar00rootroot00000000000000========= Changelog ========= - :release:`2.0.2 <2024-03-29>` - :bug:`32` Fix dangling compatibility issues with pytest version 8.x. Thanks to Alex Gaynor for the patch! - :release:`2.0.1 <2023-05-22>` - :bug:`9` Don't try loading Pytest fixture functions as if they were test functions. Classifying this as a bug even though it's a moderately sized change in behavior; it's vanishingly unlikely anybody was relying on this somehow! Thanks to ``@cajopa`` for the report. - :release:`2.0.0 <2022-12-31>` - :bug:`- major` Prior to version 2, we failed to correctly support true Pytest setup/teardown methods (i.e. ``setup_method`` and ``teardown_method``) and these would not get copied to inner class scopes. This has been fixed. We still support old nose-style ``setup``/``teardown`` for now, despite them going away in Pytest 8. - :support:`-` Modernize codebase/project a bunch: - Dropped support for Python <3.6 (including 2.7) - Pytest support upgraded to support, **and require**, Pytest >=7. - This plugin never worked on Pytests 5 and 6 anyways, and supporting 5-7 appears to require a lot more effort than just 7. - Behavioral changes in Pytest internals have fixed a handful of sorta-bugs present in pytest-relaxed under Pytest versions 3 and 4: - The order of nested test display may change slightly, typically for the better; eg under older versions, tests defined on a class might have been displayed after subclasses/nested tests - now they're more likely to be listed first, which was the original intent. - These bugs sometimes enabled "state bleed", such as outer scopes appearing to grant inner ones attributes set at runtime (eg by the outer scope's ``setup``, even when the inner scope redefined ``setup``). - If you encounter odd bugs after upgrading, please take a close look at your code and make sure you weren't accidentally using such a "feature". One good way to test for this is to run the "newly failing" tests by themselves on the old dependencies -- they will likely also fail there. - :release:`1.1.5 <2019-06-14>` - :bug:`2` Fix compatibility with pytest versions 3.3 and above. - :release:`1.1.4 <2018-07-24>` - :release:`1.0.2 <2018-07-24>` - :support:`- backported` Add missing universal wheel indicator in setup metadata. - :release:`1.1.3 <2018-07-24>` - :release:`1.0.1 <2018-07-24>` - :bug:`-` Fix the ``@raises`` helper decorator so it actually raises an exception when the requested exception is not raised by the decorated function. That's definitely not a confusing sentence. - :release:`1.1.2 <2018-04-16>` - :bug:`-` Neglected to update setup metadata when setting up a tiny Read The Docs instance. Homepage link now fixed! - :release:`1.1.1 <2018-04-16>` - :bug:`-` Installation and other ``setup.py`` activities implicitly assumed native Unicode support due to naively opening ``README.rst``. ``setup.py`` now explicitly opens that file with a ``utf-8`` encoding argument. Thanks to Ondřej Súkup for catch & patch. - :bug:`-` Bypass ``pytestmark`` objects and attributes during our custom collection phase; we don't need to process them ourselves, pytest is already picking up the original top level copies, and having them percolate into nested classes was causing loud pytest collection-step warnings. - :release:`1.1.0 <2017-11-21>` - :feature:`-` Add support for collecting/displaying hybrid/legacy test suites -- specifically, by getting out of pytest's way on collection of ``test_named_files`` and stripping test prefixes/suffixes when displaying tests in verbose mode. This makes it easier to take an existing test suite and slowly port it to 'relaxed' style. - :release:`1.0.0 <2017-11-06>` - :support:`-` Drop Python 2.6 and 3.3 support. - :feature:`-` Implement early drafts of Spec-like nested test display (which fires only when verbose output is enabled, unlike Spec which completely took over all output of nosetests.) - :support:`-` Revert internal tests to *not* eat our own dogfood; typical TDD lifecycles don't work very well when partly-implemented new features cause all of the older tests to fail as well! - :feature:`-` Create a ``@raises`` decorator which wraps ``pytest.raises`` (we're not sure why it's not natively offered as a decorator!) and thus ends up appearing very similar to Nose's API member of same name. - :feature:`-` Port ``@trap`` from Spec as it's currently a lot more natural to use than pytest's builtin capture fixtures. May back it out again later if we can make better sense of the latter / fit it into how our existing suites are organized. - :support:`-` Basic Travis and CodeCov support. - :bug:`- major` Various and sundry bugfixes, including "didn't skip underscore-named directories." - :release:`0.1.0 <2017-04-08>` - :feature:`-` Early draft functionality (test discovery only; zero display features.) This includes "releases" 0.0.1-0.0.4. pytest-relaxed-2.0.2/docs/conf.py000066400000000000000000000016051460156175000167230ustar00rootroot00000000000000from datetime import datetime import os import sys extensions = ["releases"] templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" exclude_patterns = ["_build"] project = "pytest-relaxed" year = datetime.now().year copyright = f"{year} Jeff Forcier" # Ensure project directory is on PYTHONPATH for version, autodoc access sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), ".."))) # Extension/theme settings html_theme_options = { "description": "Relaxed pytest discovery", "github_user": "bitprophet", "github_repo": "pytest-relaxed", "fixed_sidebar": True, } html_sidebars = { "**": [ "about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html", ] } # TODO: make it easier to configure this and alabaster at same time, jeez releases_github_path = "bitprophet/pytest-relaxed" pytest-relaxed-2.0.2/docs/index.rst000066400000000000000000000001101460156175000172530ustar00rootroot00000000000000.. include:: ../README.rst .. toctree:: :hidden: :glob: * pytest-relaxed-2.0.2/invoke.yml000066400000000000000000000005121460156175000165060ustar00rootroot00000000000000packaging: wheel: true # TODO: this wants to really be 'related' to the docs settings somehow; used # to use os.path.join() when it was in-python. changelog_file: docs/changelog.rst run: env: # Our ANSI color tests test against hardcoded codes appropriate for # this terminal, for now. TERM: screen-256color pytest-relaxed-2.0.2/pytest.ini000066400000000000000000000000331460156175000165170ustar00rootroot00000000000000[pytest] testpaths = tests pytest-relaxed-2.0.2/pytest_relaxed/000077500000000000000000000000001460156175000175265ustar00rootroot00000000000000pytest-relaxed-2.0.2/pytest_relaxed/__init__.py000066400000000000000000000001301460156175000216310ustar00rootroot00000000000000# Convenience imports. # flake8: noqa from .trap import trap from .raises import raises pytest-relaxed-2.0.2/pytest_relaxed/_version.py000066400000000000000000000001201460156175000217150ustar00rootroot00000000000000__version_info__ = (2, 0, 2) __version__ = ".".join(map(str, __version_info__)) pytest-relaxed-2.0.2/pytest_relaxed/classes.py000066400000000000000000000142551460156175000215440ustar00rootroot00000000000000import inspect import logging import types from pytest import Class, Module # NOTE: don't see any other way to get access to pytest innards besides using # the underscored name :( from _pytest.python import PyCollector log = logging.getLogger("relaxed") # NOTE: these are defined here for reuse by both pytest's own machinery and our # internal bits. def istestclass(name): return not name.startswith("_") # NOTE: this is defined at top level due to a couple spots of reuse outside of # the mixin class itself. def istestfunction(obj, name): is_hidden_name = name.startswith("_") or name in ( "setup", "setup_method", "teardown", "teardown_method", ) # TODO: is this reliable? how about __pytest_wrapped__? is_fixture = hasattr(obj, "_pytestfixturefunction") return not (is_hidden_name or is_fixture) # All other classes in here currently inherit from PyCollector, and it is what # defines the default istestfunction/istestclass, so makes sense to inherit # from it for our mixin. (PyobjMixin, another commonly found class, offers # nothing of interest to us however.) class RelaxedMixin(PyCollector): """ A mixin applying collection rules to both modules and inner/nested classes. """ # TODO: # - worth calling super() in these? Difficult to know what to do with it; # it would say "no" to lots of stuff we want to say "yes" to. # - are there other tests to apply to 'obj' in a vacuum? so far only thing # we test 'obj' for is its membership in a module, which must happen inside # SpecModule's override. def istestclass(self, obj, name): return istestclass(name) def istestfunction(self, obj, name): return istestfunction(obj, name) class SpecModule(RelaxedMixin, Module): def _is_test_obj(self, test_func, obj, name): # First run our super() test, which should be RelaxedMixin's. good_name = getattr(super(), test_func)(obj, name) # If RelaxedMixin said no, we can't really say yes, as the name itself # was bad - private, other non test name like setup(), etc if not good_name: return False # Here, we dig further based on our own wrapped module obj, by # rejecting anything not defined locally. if inspect.getmodule(obj) is not self.obj: return False # No other complaints -> it's probably good return True def istestfunction(self, obj, name): return self._is_test_obj("istestfunction", obj, name) def istestclass(self, obj, name): return self._is_test_obj("istestclass", obj, name) def collect(self): # Given we've overridden naming constraints etc above, just use # superclass' collection logic for the rest of the necessary behavior. items = super().collect() collected = [] for item in items: # Replace Class objects with recursive SpecClasses # NOTE: we could explicitly skip unittest objects here (we'd want # them to be handled by pytest's own unittest support) but since # those are almost always in test_prefixed_filenames anyways...meh if isinstance(item, Class): item = SpecClass.from_parent(item.parent, name=item.name) collected.append(item) return collected class SpecClass(RelaxedMixin, Class): def _getobj(self): # Regular object-making first obj = super()._getobj() # Short circuit if this obj isn't a nested class (aka child): # - no parent attr: implies module-level obj definition # - parent attr, but isn't a class: implies method if not hasattr(self, "parent") or not isinstance( self.parent, SpecClass ): return obj # Then decorate it with our parent's extra attributes, allowing nested # test classes to appear as an aggregate of parents' "scopes". parent_obj = self.parent.obj # Obtain parent attributes, etc not found on our obj (serves as both a # useful identifier of "stuff added to an outer class" and a way of # ensuring that we can override such attrs), and set them on obj delta = set(dir(parent_obj)).difference(set(dir(obj))) for name in delta: value = getattr(parent_obj, name) # Pytest's pytestmark attributes always get skipped, we don't want # to spread that around where it's not wanted. (Besides, it can # cause a lot of collection level warnings.) if name == "pytestmark": continue # Classes get skipped; they'd always just be other 'inner' classes # that we don't want to copy elsewhere. if isinstance(value, type): continue # Functions (methods) may get skipped, or not, depending: # NOTE: as of pytest 7, for some reason the value appears as a # function and not a method (???) so covering both bases... if isinstance(value, (types.MethodType, types.FunctionType)): # If they look like tests, they get skipped; don't want to copy # tests around! if istestfunction(obj, name): continue # Non-test == they're probably lifecycle methods # (setup/teardown) or helpers (_do_thing). Rebind them to the # target instance, otherwise the 'self' in the setup/helper is # not the same 'self' as that in the actual test method it runs # around or within! setattr(obj, name, value) # Anything else should be some data-type attribute, which is copied # verbatim / by-value. else: setattr(obj, name, value) return obj def collect(self): ret = [] for item in super().collect(): # More pytestmark skipping. if item.name == "pytestmark": continue if isinstance(item, Class): item = SpecClass.from_parent( parent=item.parent, name=item.name, obj=item.obj ) ret.append(item) return ret pytest-relaxed-2.0.2/pytest_relaxed/fixtures.py000066400000000000000000000014741460156175000217570ustar00rootroot00000000000000import os from pytest import fixture # TODO: consider making this a "no param/funcarg required" fixture (i.e. one # that gets decorated onto test classes instead of injected as magic kwargs) # and have uses of it simply call os.environ as normal. Pro: test code looks # less magical, con: removes any ability to do anything more interesting with # the yielded value (like proxying or whatever.) See the pytest 3.1.2 docs at: # /fixture.html#using-fixtures-from-classes-modules-or-projects @fixture def environ(): """ Enforce restoration of current shell environment after modifications. Yields the ``os.environ`` dict after snapshotting it; restores the original value (wholesale) during fixture teardown. """ current_environ = os.environ.copy() yield os.environ os.environ = current_environ pytest-relaxed-2.0.2/pytest_relaxed/plugin.py000066400000000000000000000042721460156175000214030ustar00rootroot00000000000000import pytest from .classes import SpecModule from .reporter import RelaxedReporter # NOTE: fixtures must be present in the module listed under our setup.py's # pytest11 entry_points value (i.e., this one.) Just being in the import path # (e.g. package __init__.py) was not sufficient! from .fixtures import environ # noqa def pytest_ignore_collect(collection_path, config): # Ignore files and/or directories marked as private via Python convention. return collection_path.name.startswith("_") # We need to use collect_file, not pycollect_makemodule, as otherwise users # _must_ specify a config blob to use us, vs that being optional. # TODO: otoh, I personally use it all the time and we "strongly recommend it" # so maybe find a way to make that config bit default somehow (update # docs/changelog appropriately), and then switch hooks? def pytest_collect_file(file_path, parent): # Modify file selection to choose all .py files besides conftest.py. # (Skipping underscored names is handled up in pytest_ignore_collect, which # applies to directories too.) if ( file_path.suffix != ".py" or file_path.name == "conftest.py" # Also skip anything prefixed with test_; pytest's own native # collection will get that stuff, and we don't _want_ to try modifying # such files anyways. or file_path.name.startswith("test_") ): return # Then use our custom module class which performs modified # function/class selection as well as class recursion return SpecModule.from_parent(parent=parent, path=file_path) @pytest.mark.trylast # So we can be sure builtin terminalreporter exists def pytest_configure(config): # TODO: we _may_ sometime want to do the isatty/slaveinput/etc checks that # pytest-sugar does? builtin = config.pluginmanager.getplugin("terminalreporter") # Pass the configured, instantiated builtin terminal reporter to our # instance so it can refer to e.g. the builtin reporter's configuration ours = RelaxedReporter(builtin) # Unregister the builtin first so only our output appears config.pluginmanager.unregister(builtin) config.pluginmanager.register(ours, "terminalreporter") pytest-relaxed-2.0.2/pytest_relaxed/raises.py000066400000000000000000000006451460156175000213730ustar00rootroot00000000000000from decorator import decorator # Thought pytest.raises was like nose.raises, but nooooooo. So let's make it # like that. def raises(klass): @decorator def inner(f, *args, **kwargs): try: f(*args, **kwargs) except klass: pass else: raise Exception( "Did not receive expected {}!".format(klass.__name__) ) return inner pytest-relaxed-2.0.2/pytest_relaxed/reporter.py000066400000000000000000000154751460156175000217560ustar00rootroot00000000000000import re from _pytest.terminal import TerminalReporter # TODO: # - how can we be sure the tests are in the right order? # - aka how can we 'sort' them? in post-collection step? # - how to handle display of 'header' lines? Probably state tracking as w/ # spec? # - how to deal with flat modules vs nested classes? # - would be nice to examine all tests in a tree, but that requires waiting # till all results are in, which is no bueno. So we really do just need to # ensure a tree-based sort (which, assuming solid test ID strings, can be a # lexical sort.) # - sadly, what this means is that the parent/child relationship between test # objects doesn't really help us any, since we have to take action on a # per-report basis. Meh. (guess if we NEEDED to access attributes of a parent # in a child, that'd be possible, but...seems unlikely-ish? Maybe indent # based on parent relationships instead of across-the-run state tracking?) TEST_PREFIX = re.compile(r"^(Test|test_)") TEST_SUFFIX = re.compile(r"(Test|_test)$") # NOTE: much of the high level "replace default output bits" approach is # cribbed directly from pytest-sugar at 0.8.0 class RelaxedReporter(TerminalReporter): def __init__(self, builtin): # Pass in the builtin reporter's config so we're not redoing all of its # initial setup/cli parsing/etc. NOTE: TerminalReporter is old-style :( TerminalReporter.__init__(self, builtin.config) # Which headers have already been displayed # TODO: faster data structure probably wise self.headers_displayed = [] # Size of indents. TODO: configuration self.indent = " " * 4 def pytest_runtest_logstart(self, nodeid, location): # Non-verbose: do whatever normal pytest does. if not self.verbosity: return TerminalReporter.pytest_runtest_logstart( self, nodeid, location ) # Verbose: do nothing, preventing normal display of test location/id. # Leaves all display up to other hooks. def pytest_runtest_logreport(self, report): # TODO: if we _need_ access to the test item/node itself, we may want # to implement pytest_runtest_makereport instead? (Feels a little # 'off', but without other grody hax, no real way to get the obj so...) # Non-verbose: do whatever normal pytest does. # TODO: kinda want colors & per-module headers/indent though... if not self.verbosity: return TerminalReporter.pytest_runtest_logreport(self, report) # First, the default impl of this method seems to take care of updating # overall run stats; if we don't repeat that we lose all end-of-run # tallying and whether the run failed...kind of important. (Why that's # not a separate hook, no idea :() self.update_stats(report) # After that, short-circuit if it's not reporting the main call (i.e. # we don't want to display "the test" during its setup or teardown) if report.when != "call": return id_ = report.nodeid # First, make sure we display non-per-test data, i.e. # module/class/nested class headers (which by necessity also includes # tracking indentation state.) self.ensure_headers(id_) # Then we can display the test name/status itself. self.display_result(report) def update_stats(self, report): cat, letter, word = self.config.hook.pytest_report_teststatus( report=report, config=self.config ) self.stats.setdefault(cat, []).append(report) # For use later; apparently some other plugins can yield display markup # in the 'word' field of a report. self.report_word = word def split(self, id_): # Split on pytest's :: joiner, and strip out our intermediate # SpecInstance objects (appear as '()') headers = [x for x in id_.split("::")[1:]] # Last one is the actual test being reported on, not a header leaf = headers.pop() return headers, leaf def transform_name(self, name): """ Take a test class/module/function name and make it human-presentable. """ # TestPrefixes / test_prefixes -> stripped name = re.sub(TEST_PREFIX, "", name) # TestSuffixes / suffixed_test -> stripped name = re.sub(TEST_SUFFIX, "", name) # All underscores become spaces, for sentence-ishness name = name.replace("_", " ") return name def ensure_headers(self, id_): headers, _ = self.split(id_) printed = False # TODO: this works for class-based tests but needs love for module ones # TODO: worth displaying filename ever? # Make sure we print all not-yet-seen headers for i, header in enumerate(headers): # Need to semi-uniq headers by their 'path'. (This is a lot like # "the test id minus the last segment" but since we have to # split/join either way...whatever. I like dots.) header_path = ".".join(headers[: i + 1]) if header_path in self.headers_displayed: continue self.headers_displayed.append(header_path) indent = self.indent * i header = self.transform_name(header) self._tw.write("\n{}{}\n".format(indent, header)) printed = True # No trailing blank line after all headers; only the 'last' one (i.e. # before any actual test names are printed). And only if at least one # header was actually printed! (Otherwise one gets newlines between all # tests.) if printed: self._tw.write("\n") def display_result(self, report): headers, leaf = self.split(report.nodeid) indent = self.indent * len(headers) leaf = self.transform_name(leaf) # This _tw.write() stuff seems to be how vanilla pytest writes its # colorized verbose output. Bit clunky, but it means we automatically # honor things like `--color=no` and whatnot. self._tw.write(indent) self._tw.write(leaf, **self.report_markup(report)) self._tw.write("\n") def report_markup(self, report): # Basically preserved from parent implementation; if something caused # the 'word' field in the report to be a tuple, it's a (word, markup) # tuple. We don't care about the word (possibly bad, but it doesn't fit # with our display ethos right now) but the markup may be worth # preserving. if isinstance(self.report_word, tuple): return self.report_word[1] # Otherwise, assume ye olde pass/fail/skip. if report.passed: color = "green" elif report.failed: color = "red" elif report.skipped: color = "yellow" return {color: True} pytest-relaxed-2.0.2/pytest_relaxed/trap.py000066400000000000000000000043241460156175000210510ustar00rootroot00000000000000""" Test decorator for capturing stdout/stderr/both. Based on original code from Fabric 1.x, specifically: * fabric/tests/utils.py * as of Git SHA 62abc4e17aab0124bf41f9c5f9c4bc86cc7d9412 Though modifications have been made since. """ import io import sys from functools import wraps class CarbonCopy(io.BytesIO): """ An IO wrapper capable of multiplexing its writes to other buffer objects. """ def __init__(self, buffer=b"", cc=None): """ If ``cc`` is given and is a file-like object or an iterable of same, it/they will be written to whenever this instance is written to. """ super().__init__(buffer) if cc is None: cc = [] elif hasattr(cc, "write"): cc = [cc] self.cc = cc def write(self, s): # Ensure we always write bytes. if isinstance(s, str): s = s.encode("utf-8") # Write out to our capturing object & any CC's super().write(s) for writer in self.cc: writer.write(s) # Real sys.std(out|err) requires writing to a buffer attribute obj in some # situations. @property def buffer(self): return self # Make sure we always hand back strings def getvalue(self): ret = super().getvalue() if isinstance(ret, bytes): ret = ret.decode("utf-8") return ret def trap(func): """ Replace sys.std(out|err) with a wrapper during execution, restored after. In addition, a new combined-streams output (another wrapper) will appear at ``sys.stdall``. This stream will resemble what a user sees at a terminal, i.e. both out/err streams intermingled. """ @wraps(func) def wrapper(*args, **kwargs): # Use another CarbonCopy even though we're not cc'ing; for our "write # bytes, return strings" behavior. Meh. sys.stdall = CarbonCopy() my_stdout, sys.stdout = sys.stdout, CarbonCopy(cc=sys.stdall) my_stderr, sys.stderr = sys.stderr, CarbonCopy(cc=sys.stdall) try: return func(*args, **kwargs) finally: sys.stdout = my_stdout sys.stderr = my_stderr del sys.stdall return wrapper pytest-relaxed-2.0.2/setup.py000066400000000000000000000042571460156175000162140ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup, find_packages from io import open # Version info -- read without importing _locals = {} with open("pytest_relaxed/_version.py") as fp: exec(fp.read(), None, _locals) version = _locals["__version__"] setup( name="pytest-relaxed", version=version, description="Relaxed test discovery/organization for pytest", license="BSD", url="https://pytest-relaxed.readthedocs.io/", project_urls={ "Source": "https://github.com/bitprophet/pytest-relaxed", "Changelog": "https://github.com/bitprophet/pytest-relaxed/blob/main/docs/changelog.rst", # noqa "CI": "https://app.circleci.com/pipelines/github/bitprophet/pytest-relaxed", # noqa }, author="Jeff Forcier", author_email="jeff@bitprophet.org", long_description="\n" + open("README.rst", encoding="utf-8").read(), packages=find_packages(), entry_points={ # TODO: do we need to name the LHS 'pytest_relaxed' too? meh "pytest11": ["relaxed = pytest_relaxed.plugin"] }, python_requires=">=3.6", install_requires=[ # Difficult to support Pytest<7 + Pytest>=7 at same time, and # pytest-relaxed never supported pytests 5 or 6, so...why bother! "pytest>=7", # For @raises, primarily. At press time, most available decorator # versions (including 5.x) should work for us / our Python interpreter # versions. "decorator", ], classifiers=[ "Development Status :: 5 - Production/Stable", "Framework :: Pytest", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Testing", ], ) pytest-relaxed-2.0.2/tasks.py000066400000000000000000000041051460156175000161710ustar00rootroot00000000000000from invoke import task, Collection from invocations import checks from invocations.packaging import release from invocations import docs, pytest as pytests @task def coverage(c, html=True, codecov=False): """ Run coverage with coverage.py. """ # NOTE: this MUST use coverage itself, and not pytest-cov, because the # latter is apparently unable to prevent pytest plugins from being loaded # before pytest-cov itself is able to start up coverage.py! The result is # that coverage _always_ skips over all module level code, i.e. constants, # 'def' lines, etc. Running coverage as the "outer" layer avoids this # problem, thus no need for pytest-cov. # NOTE: this does NOT hold true for NON-PYTEST code, so # pytest-relaxed-USING modules can happily use pytest-cov. c.run( "coverage run --source=pytest_relaxed,tests --branch --module pytest" ) if html: c.run("coverage html") if codecov: # Generate XML report from that already-gathered data (otherwise # codecov generates it on its own and gets it wrong!) c.run("coverage xml") # Upload to Codecov c.run("codecov") # TODO: good candidate for builtin-to-invoke "just wrap with a # tiny bit of behavior", and/or args/kwargs style invocations @task def test( c, verbose=True, color=True, capture="sys", opts="", x=False, k=None, module=None, ): """ Run pytest with given options. Wraps ``invocations.pytests.test``. See its docs for details. """ # TODO: could invert this & have our entire test suite manually _enable_ # our own plugin, but given pytest's options around plugin setup, this # seems to be both easier and simpler. opts += " -p no:relaxed" pytests.test( c, verbose=verbose, color=color, capture=capture, opts=opts, x=x, k=k, module=module, ) ns = Collection(checks.blacken, checks, coverage, docs, test, release) ns.configure({"blacken": {"find_opts": "-and -not -path './build*'"}}) pytest-relaxed-2.0.2/tests/000077500000000000000000000000001460156175000156345ustar00rootroot00000000000000pytest-relaxed-2.0.2/tests/test_collection.py000066400000000000000000000347051460156175000214110ustar00rootroot00000000000000import re from pytest import ExitCode, mark # For 'testdir' fixture, mostly pytest_plugins = "pytester" class Test_pytest_collect_file: def test_only_loads_dot_py_files(self, testdir): testdir.makepyfile( somefile=""" def hello_how_are_you(): pass """ ) testdir.makefile(".txt", someotherfile="whatever") stdout = testdir.runpytest().stdout.str() # TODO: find it hard to believe pytest lacks strong "x in y" string # testing, but I cannot find any outside of fnmatch_lines (which is # specific to this testdir stuff, and also lacks an opposite...) assert "somefile.py" in stdout # This wouldn't actually even happen; we'd get an ImportError instead # as pytest tries importing 'someotherfile'. But eh. assert "whatever.txt" not in stdout def test_skips_underscored_files(self, testdir): testdir.makepyfile( hastests=""" from _util import helper def hello_how_are_you(): helper() """ ) testdir.makepyfile( _util=""" def helper(): pass """ ) # TODO: why Result.str() and not str(Result)? Seems unPythonic stdout = testdir.runpytest().stdout.str() assert "hastests.py" in stdout assert "_util.py" not in stdout def test_skips_underscored_directories(self, testdir): testdir.makepyfile( hello=""" def hi_im_a_test_function(): pass """ ) # NOTE: this appears to work due to impl details of pytester._makefile; # namely that the kwarg keys are handed directly to tmpdir.join(), # where tmpdir is a py.path.LocalPath. testdir.makepyfile( **{ "_nope/yallo": """ def hi_im_not_a_test_function(): pass """ } ) stdout = testdir.runpytest("-v").stdout.str() assert "hi im a test function" in stdout assert "hi im not a test function" not in stdout def test_does_not_consume_conftest_files(self, testdir): testdir.makepyfile( actual_tests=""" def hello_how_are_you(): pass """ ) testdir.makepyfile( conftest=""" def this_does_nothing_useful(): pass """ ) stdout = testdir.runpytest().stdout.str() assert "actual_tests.py" in stdout assert "conftest.py" not in stdout class TestRelaxedMixin: def test_selects_all_non_underscored_members(self, testdir): testdir.makepyfile( foo=""" def hello_how_are_you(): pass def _help_me_understand(): pass class YupThisIsTests: def please_test_me_thx(self): pass def _helper_method_hi(self): pass class NestedTestClassAhoy: def hello_I_am_a_test_method(self): pass def _but_I_am_not(self): pass class _NotSureWhyYouWouldDoThisButWhatever: def this_should_not_appear(self): pass class _ForSomeReasonIAmDefinedHereButAmNotATest: def usually_you_would_just_import_this_but_okay(self): pass """ ) stdout = testdir.runpytest("-v").stdout.str() for substring in ( "hello how are you", "please test me thx", "hello I am a test method", ): assert substring in stdout for substring in ( "help me understand", "helper method hi", "NotSureWhyYouWouldDoThisButWhatever", "ForSomeReasonIAmDefinedHereButAmNotATest", ): assert substring not in stdout def test_skips_setup_and_teardown(self, testdir): testdir.makepyfile( foo=""" def setup(): pass def teardown(): pass def setup_method(): pass def teardown_method(): pass def actual_test_here(): pass class Outer: def setup(self): pass def teardown(self): pass def setup_method(self): pass def teardown_method(self): pass def actual_nested_test_here(self): pass """ ) stdout = testdir.runpytest("-v").stdout.str() # These skipped. Gotta regex them because the test name includes the # words 'setup' and 'teardown', heh. assert not re.match(r"^setup$", stdout) assert not re.match(r"^teardown$", stdout) assert not re.match(r"^setup_method$", stdout) assert not re.match(r"^teardown_method$", stdout) # Real tests not skipped assert "actual test here" in stdout assert "actual nested test here" in stdout def test_skips_pytest_fixtures(self, testdir): testdir.makepyfile( foo=""" from pytest import fixture @fixture def pls_noload(): yield def actual_test_here(): pass """ ) stdout = testdir.runpytest("-v").stdout.str() assert "actual test here" in stdout # will be in stdout as a failure and warning if bug present assert "pls_noload" not in stdout def test_setup_given_inner_class_instances_when_inherited(self, testdir): # NOTE: without this functionality in place, we still see setup() # called on a per-test-method basis, but where 'self' is the outer # class, not the inner class! so anything actually touching 'self' # breaks. # TODO: should this pattern change to be something like a pytest # per-class autouse fixture method? # (https://docs.pytest.org/en/latest/fixture.html#autouse-fixtures-xunit-setup-on-steroids) testdir.makepyfile( foo=""" class Outer: def setup_method(self): self.some_attr = 17 class inner: def actual_nested_test(self): assert self.some_attr == 17 """ ) assert testdir.runpytest().ret is ExitCode.OK def test_setup_method_given_inner_class_instances(self, testdir): testdir.makepyfile( foo=""" class Outer: def setup_method(self): self.some_attr = 17 class inner: def actual_nested_test(self): assert self.some_attr == 17 """ ) assert testdir.runpytest().ret is ExitCode.OK class TestSpecModule: def test_skips_non_callable_items(self, testdir): testdir.makepyfile( foo=""" some_uncallable = 17 def some_callable(): pass """ ) stdout = testdir.runpytest("-v").stdout.str() assert "some_uncallable" not in stdout def test_skips_imported_objects(self, testdir): testdir.makepyfile( _util=""" def helper(): pass class Helper: pass class NewHelper: pass """ ) testdir.makepyfile( foo=""" from _util import helper, Helper, NewHelper def a_test_is_me(): pass """ ) stdout = testdir.runpytest("-v").stdout.str() assert "a test is me" in stdout assert "helper" not in stdout assert "Helper" not in stdout assert "NewHelper" not in stdout def test_does_not_warn_about_imported_names(self, testdir): # Trigger is something that appears callable but isn't a real function; # almost any callable class seems to suffice. (Real world triggers are # things like invoke/fabric Task objects.) # Can also be triggered if our collection is buggy and does not # explicitly reject imported classes (i.e. if we only reject funcs). testdir.makepyfile( _util=""" class Callable: def __call__(self): pass helper = Callable() class HelperClass: def __init__(self): pass """ ) testdir.makepyfile( foo=""" from _util import helper, HelperClass def a_test(): pass """ ) stdout = testdir.runpytest("-sv").stdout.str() # TODO: more flexible test in case text changes? eh. for warning in ( "cannot collect 'helper' because it is not a function", "cannot collect test class 'HelperClass'", ): assert warning not in stdout def test_replaces_class_tests_with_custom_recursing_classes(self, testdir): testdir.makepyfile( foo=""" class Outer: class Middle: class Inner: def oh_look_an_actual_test_method(self): pass """ ) stdout = testdir.runpytest("-v").stdout.str() expected = """ Outer Middle Inner oh look an actual test method """.lstrip() assert expected in stdout def test_does_not_collect_test_prefixed_files(self, testdir): # Incidentally also tests display stripping; the display test suite has # explicit tests for that too tho. testdir.makepyfile( test_something=""" import unittest class TestMyStuff(unittest.TestCase): def test_things(self): pass """ ) stdout = testdir.runpytest("-v").stdout.str() expected = """ MyStuff things """.lstrip() assert expected in stdout # Make sure no warnings were emitted; much of the time, our collection # bits will cause nasty warnings if they end up consuming unittest # stuff or otherwise doubling up on already-collected objects. assert "warnings summary" not in stdout @mark.skip def test_correctly_handles_marked_test_cases(self, testdir): # I.e. @pytest.mark.someflag objects at the class level...figure out # how real collectors handle these exactly? the "actual" test class we # normally care about is inside of it. pass class TestSpecClass: def test_methods_self_objects_exhibit_class_attributes(self, testdir): # Mostly a sanity test; pytest seems to get out of the way enough that # the test is truly a bound method & the 'self' is truly an instance of # the class. testdir.makepyfile( foo=""" class MyClass: an_attr = 5 def some_test(self): assert hasattr(self, 'an_attr') assert self.an_attr == 5 """ ) # TODO: first thought was "why is this not automatic?", then realized # "duh, it'd be annoying if you wanted to test failure related behavior # a lot"...but still want some slightly nicer helper I think assert testdir.runpytest().ret is ExitCode.OK def test_nested_self_objects_exhibit_parent_attributes(self, testdir): # TODO: really starting to think going back to 'real' fixture files # makes more sense; this is all real python code and is eval'd as such, # but it is only editable and viewable as a string. No highlighting. testdir.makepyfile( foo=""" class MyClass: an_attr = 5 class Inner: def inner_test(self): assert hasattr(self, 'an_attr') assert self.an_attr == 5 """ ) assert testdir.runpytest().ret is ExitCode.OK def test_nesting_is_infinite(self, testdir): testdir.makepyfile( foo=""" class MyClass: an_attr = 5 class Inner: class Deeper: class EvenDeeper: def innermost_test(self): assert hasattr(self, 'an_attr') assert self.an_attr == 5 """ ) assert testdir.runpytest().ret is ExitCode.OK def test_overriding_works_naturally(self, testdir): testdir.makepyfile( foo=""" class MyClass: an_attr = 5 class Inner: an_attr = 7 def inner_test(self): assert self.an_attr == 7 """ ) assert testdir.runpytest().ret is ExitCode.OK def test_normal_methods_from_outer_classes_are_not_copied(self, testdir): testdir.makepyfile( foo=""" class MyClass: def outer_test(self): pass class Inner: def inner_test(self): assert not hasattr(self, 'outer_test') """ ) assert testdir.runpytest().ret is ExitCode.OK def test_private_methods_from_outer_classes_are_copied(self, testdir): testdir.makepyfile( foo=""" class MyClass: def outer_test(self): pass def _outer_helper(self): pass class Inner: def inner_test(self): assert not hasattr(self, 'outer_test') assert hasattr(self, '_outer_helper') """ ) assert testdir.runpytest().ret is ExitCode.OK def test_module_contents_are_not_copied_into_top_level_classes( self, testdir ): testdir.makepyfile( foo=""" module_constant = 17 class MyClass: def outer_test(self): assert not hasattr(self, 'module_constant') """ ) assert testdir.runpytest().ret is ExitCode.OK pytest-relaxed-2.0.2/tests/test_display.py000066400000000000000000000211011460156175000207050ustar00rootroot00000000000000from pytest import skip # Load some fixtures we expose, without actually loading our entire plugin from pytest_relaxed.fixtures import environ # noqa # TODO: how best to make all of this opt-in/out? Reporter as separate plugin? # (May not be feasible if it has to assume something about how our collection # works?) CLI option (99% sure we can hook into that as a plugin)? def _expect_regular_output(testdir): output = testdir.runpytest().stdout.str() # Regular results w/ status letters assert "behaviors.py .." in output assert "other_behaviors.py s.F." in output # Failure/traceback reporting assert "== FAILURES ==" in output assert "AssertionError" in output # Summary assert "== 1 failed, 4 passed, 1 skipped in " in output class TestRegularFunctions: """ Function-oriented test modules, normal display mode. """ def test_acts_just_like_normal_pytest(self, testdir): testdir.makepyfile( behaviors=""" def behavior_one(): pass def behavior_two(): pass """, other_behaviors=""" from pytest import skip def behavior_one(): skip() def behavior_two(): pass def behavior_three(): assert False def behavior_four(): pass """, ) _expect_regular_output(testdir) class TestVerboseFunctions: """ Function-oriented test modules, verbose display mode. """ def test_displays_tests_indented_under_module_header(self, testdir): # TODO: at least, that seems like a reasonable thing to do offhand skip() def test_test_prefixes_are_stripped(self, testdir): testdir.makepyfile( legacy=""" def test_some_things(): pass def test_other_things(): pass """ ) expected = """ some things other things """.lstrip() output = testdir.runpytest_subprocess("-v").stdout.str() assert expected in output class TestNormalClasses: """ Class-oriented test modules, normal display mode. """ def acts_just_like_normal_pytest(self, testdir): testdir.makepyfile( behaviors=""" class Behaviors: def behavior_one(self): pass def behavior_two(self): pass """, other_behaviors=""" from pytest import skip class OtherBehaviors: def behavior_one(self): skip() def behavior_two(self): pass def behavior_three(self): assert False def behavior_four(self): pass """, ) _expect_regular_output(testdir) class TestVerboseClasses: """ Class-oriented test modules, verbose display mode. """ def test_shows_tests_nested_under_classes_without_files(self, testdir): testdir.makepyfile( behaviors=""" class Behaviors: def behavior_one(self): pass def behavior_two(self): pass """, other_behaviors=""" from pytest import skip class OtherBehaviors: def behavior_one(self): pass def behavior_two(self): skip() def behavior_three(self): pass def behavior_four(self): assert False """, ) output = testdir.runpytest_subprocess("-v").stdout.str() results = """ Behaviors behavior one behavior two OtherBehaviors behavior one behavior two behavior three behavior four """.lstrip() assert results in output # Ensure we're not accidentally nixing failure, summary output assert "== FAILURES ==" in output assert "AssertionError" in output # Summary assert "== 1 failed, 4 passed, 1 skipped in " in output def test_tests_are_colorized_by_test_result( self, testdir, environ # noqa: F811,E501 ): # Make sure py._io.TerminalWriters write colors despite pytest output # capturing, which would otherwise trigger a 'False' result for "should # markup output". environ["PY_COLORS"] = "1" testdir.makepyfile( behaviors=""" class Behaviors: def behavior_one(self): pass def behavior_two(self): pass """, other_behaviors=""" from pytest import skip class OtherBehaviors: def behavior_one(self): pass def behavior_two(self): skip() def behavior_three(self): pass def behavior_four(self): assert False """, ) output = testdir.runpytest_subprocess("-v").stdout.str() results = """ Behaviors \x1b[32mbehavior one\x1b[0m \x1b[32mbehavior two\x1b[0m OtherBehaviors \x1b[32mbehavior one\x1b[0m \x1b[33mbehavior two\x1b[0m \x1b[32mbehavior three\x1b[0m \x1b[31mbehavior four\x1b[0m """.lstrip() for chunk in ( # Our own special sauce results, # Failure summary still present "== FAILURES ==", # Ditto error class "AssertionError", # And summary chunks (now colorized apparently?) "1 failed", "4 passed", "1 skipped", ): assert chunk in output def test_nests_many_levels_deep_no_problem(self, testdir): testdir.makepyfile( behaviors=""" class Behaviors: def behavior_one(self): pass def behavior_two(self): pass class MoreBehaviors: def an_behavior(self): pass def another_behavior(self): pass class YetMore: class Behaviors: def yup(self): pass def still_works(self): pass """ ) expected = """ Behaviors behavior one behavior two MoreBehaviors an behavior another behavior YetMore Behaviors yup still works """.lstrip() assert expected in testdir.runpytest("-v").stdout.str() def test_headers_and_tests_have_underscores_turn_to_spaces(self, testdir): testdir.makepyfile( behaviors=""" class some_non_class_name_like_header: def a_test_sentence(self): pass """ ) expected = """ some non class name like header a test sentence """.lstrip() assert expected in testdir.runpytest("-v").stdout.str() def test_test_prefixes_are_stripped(self, testdir): testdir.makepyfile( stripping=""" class TestSomeStuff: def test_the_stuff(self): pass """ ) expected = """ SomeStuff the stuff """.lstrip() assert expected in testdir.runpytest("-v").stdout.str() def test_test_suffixes_are_stripped(self, testdir): testdir.makepyfile( stripping=""" class StuffTest: def test_yup(self): pass """ ) expected = """ Stuff yup """.lstrip() assert expected in testdir.runpytest("-v").stdout.str() class TestNormalMixed: """ Mixed function and class test modules, normal display mode. """ # TODO: currently undefined; spec never even really worked for this pass class TestVerboseMixed: """ Mixed function and class test modules, verbose display mode. """ # TODO: currently undefined; spec never even really worked for this pass pytest-relaxed-2.0.2/tests/test_raises.py000066400000000000000000000017021460156175000205330ustar00rootroot00000000000000import pytest from pytest_relaxed import raises class Boom(Exception): pass class OtherBoom(Exception): pass class Test_raises: def test_when_given_exception_raised_no_problem(self): @raises(Boom) def kaboom(): raise Boom kaboom() # If we got here, we're good... def test_when_given_exception_not_raised_it_raises_Exception(self): # TODO: maybe raise a custom exception later? HEH. @raises(Boom) def kaboom(): pass # Buffalo buffalo with pytest.raises(Exception) as exc: kaboom() assert "Did not receive expected Boom!" in str(exc.value) def test_when_some_other_exception_raised_it_is_untouched(self): @raises(Boom) def kaboom(): raise OtherBoom("sup") # Buffalo buffalo with pytest.raises(OtherBoom) as exc: kaboom() assert "sup" == str(exc.value)