From b92892894235db13a854c210b5980f0eb8283938 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Oct 2014 19:27:10 -0300 Subject: [PATCH 1/3] added support for glob-style patterns to python_classes and python_functions config options fixes #600 --HG-- branch : python-classes-glob --- _pytest/python.py | 24 ++++++++++++++++------- doc/en/customize.txt | 30 ++++++++++++++++++++++------- doc/en/example/pythoncollection.txt | 18 ++++++++--------- testing/test_collection.py | 24 +++++++++++++++++++++++ 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 93144815c..0160b18ae 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1,4 +1,5 @@ """ Python test discovery, setup and run of test functions. """ +import fnmatch import py import inspect import sys @@ -127,9 +128,10 @@ def pytest_addoption(parser): default=['test_*.py', '*_test.py'], help="glob-style file patterns for Python test module discovery") parser.addini("python_classes", type="args", default=["Test",], - help="prefixes for Python test class discovery") + help="prefixes or glob names for Python test class discovery") parser.addini("python_functions", type="args", default=["test",], - help="prefixes for Python test function and method discovery") + help="prefixes or glob names for Python test function and " + "method discovery") def pytest_cmdline_main(config): if config.option.showfixtures: @@ -307,14 +309,22 @@ class PyobjMixin(PyobjContext): class PyCollector(PyobjMixin, pytest.Collector): def funcnamefilter(self, name): - for prefix in self.config.getini("python_functions"): - if name.startswith(prefix): - return True + return self._matches_prefix_or_glob_option('python_functions', name) def classnamefilter(self, name): - for prefix in self.config.getini("python_classes"): - if name.startswith(prefix): + return self._matches_prefix_or_glob_option('python_classes', name) + + def _matches_prefix_or_glob_option(self, option_name, name): + """ + checks if the given name matches the prefix or glob-pattern defined + in ini configuration. + """ + for option in self.config.getini(option_name): + if name.startswith(option): return True + elif fnmatch.fnmatch(name, option): + return True + return False def collect(self): if not getattr(self.obj, "__test__", True): diff --git a/doc/en/customize.txt b/doc/en/customize.txt index bea6f1dee..74d30f7ab 100644 --- a/doc/en/customize.txt +++ b/doc/en/customize.txt @@ -115,17 +115,33 @@ Builtin configuration file options .. confval:: python_classes - One or more name prefixes determining which test classes - are considered as test modules. + One or more name prefixes or glob-style patterns determining which classes + are considered for test collection. Here is an example of how to collect + tests from classes that end in ``Suite``:: + + # content of pytest.ini + [pytest] + python_classes = *Suite + + Note that ``unittest.TestCase`` derived classes are always collected + regardless of this option, as ``unittest``'s own collection framework is used + to collect those tests. .. confval:: python_functions - One or more name prefixes determining which test functions - and methods are considered as test modules. Note that this - has no effect on methods that live on a ``unittest.TestCase`` - derived class. + One or more name prefixes or glob-patterns determining which test functions + and methods are considered tests. Here is an example of how + to collect test functions and methods that end in ``_test``:: - See :ref:`change naming conventions` for examples. + # content of pytest.ini + [pytest] + python_functions = *_test + + Note that this has no effect on methods that live on a ``unittest + .TestCase`` derived class, as ``unittest``'s own collection framework is used + to collect those tests. + + See :ref:`change naming conventions` for more detailed examples. .. confval:: doctest_optionflags diff --git a/doc/en/example/pythoncollection.txt b/doc/en/example/pythoncollection.txt index e7226b3c2..c51ad97d2 100644 --- a/doc/en/example/pythoncollection.txt +++ b/doc/en/example/pythoncollection.txt @@ -26,17 +26,17 @@ the :confval:`python_files`, :confval:`python_classes` and [pytest] python_files=check_*.py python_classes=Check - python_functions=check + python_functions=*_check -This would make ``pytest`` look for ``check_`` prefixes in -Python filenames, ``Check`` prefixes in classes and ``check`` prefixes -in functions and classes. For example, if we have:: +This would make ``pytest`` look for tests in files that match the ``check_* +.py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods +that match ``*_check``. For example, if we have:: # content of check_myapp.py class CheckMyApp: - def check_simple(self): + def simple_check(self): pass - def check_complex(self): + def complex_check(self): pass then the test collection looks like this:: @@ -48,14 +48,14 @@ then the test collection looks like this:: - - + + ============================= in 0.01 seconds ============================= .. note:: - the ``python_functions`` and ``python_classes`` has no effect + the ``python_functions`` and ``python_classes`` options has no effect for ``unittest.TestCase`` test discovery because pytest delegates detection of test case methods to unittest code. diff --git a/testing/test_collection.py b/testing/test_collection.py index 754f3c9ab..e8e47690f 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -528,6 +528,30 @@ class Test_genitems: assert s.endswith("test_example_items1.testone") print(s) + def test_class_and_functions_discovery_using_glob(self, testdir): + """ + tests that python_classes and python_functions config options work + as prefixes and glob-like patterns (issue #600). + """ + testdir.makeini(""" + [pytest] + python_classes = *Suite Test + python_functions = *_test test + """) + p = testdir.makepyfile(''' + class MyTestSuite: + def x_test(self): + pass + + class TestCase: + def test_y(self): + pass + ''') + items, reprec = testdir.inline_genitems(p) + ids = [x.getmodpath() for x in items] + assert ids == ['MyTestSuite.x_test', 'TestCase.test_y'] + + def test_matchnodes_two_collections_same_file(testdir): testdir.makeconftest(""" import pytest From 0b620c304bc5c55fa5ca8842173539487ee71f68 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Oct 2014 18:36:31 -0200 Subject: [PATCH 2/3] checking that option contains glob characters before calling fnmatch requested during code review --HG-- branch : python-classes-glob --- _pytest/python.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/_pytest/python.py b/_pytest/python.py index 0160b18ae..ed3443e9e 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -322,7 +322,11 @@ class PyCollector(PyobjMixin, pytest.Collector): for option in self.config.getini(option_name): if name.startswith(option): return True - elif fnmatch.fnmatch(name, option): + # check that name looks like a glob-string before calling fnmatch + # because this is called for every name in each collected module, + # and fnmatch is somewhat expensive to call + elif ('*' in option or '?' in option or '[' in option) and \ + fnmatch.fnmatch(name, option): return True return False From 16f0d100ccfc37fea6945520363c2fa162fc3baf Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Oct 2014 19:22:53 -0200 Subject: [PATCH 3/3] added changelog entry about glob-patterns in python_classes and python_functions - also fixed small typo --HG-- branch : python-classes-glob --- CHANGELOG | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index bfafc8d91..009ece156 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,9 @@ parameter is a callable, you also need to pass in a reason to disambiguate it from the "decorator" case. Thanks Tom Viner. +- "python_classes" and "python_functions" options now support glob-patterns + for test discovery, as discussed in issue600. Thanks Ldiary Translations. + 2.6.4.dev ---------- @@ -86,7 +89,7 @@ Thanks sontek. - Implement issue549: user-provided assertion messages now no longer - replace the py.test instrospection message but are shown in addition + replace the py.test introspection message but are shown in addition to them. 2.6.1