From 08f3a0791d0aa4f5288079b801a7e1ac86413e05 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 21 Nov 2013 15:25:16 +0100 Subject: [PATCH] fix issue357 - special case "-k" expressions to allow for filtering with simple strings that are not valid python expressions. Examples: "-k 1.3" matches all tests parametrized with 1.3. "-k None" filters all tests that have "None" in their name and conversely "-k 'not None'". Previously these examples would raise syntax errors. Also add a note to the docs about what is allowed. --- CHANGELOG | 7 +++++++ _pytest/mark.py | 8 +++++++- doc/en/example/markers.txt | 11 +++++++++++ testing/test_mark.py | 24 +++++++++++++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 54aa46a21..3553abe23 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,13 @@ Unreleased help with random "xdist" collection failures. Thanks to Ronny Pfannschmidt and Donald Stufft for helping to isolate it. +- fix issue357 - special case "-k" expressions to allow for + filtering with simple strings that are not valid python expressions. + Examples: "-k 1.3" matches all tests parametrized with 1.3. + "-k None" filters all tests that have "None" in their name + and conversely "-k 'not None'". + Previously these examples would raise syntax errors. + - fix issue384 by removing the trial support code since the unittest compat enhancements allow trial to handle it on its own diff --git a/_pytest/mark.py b/_pytest/mark.py index cfeec6518..22eea5ec6 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -140,7 +140,13 @@ def matchkeyword(colitem, keywordexpr): for name in colitem.function.__dict__: mapped_names.add(name) - return eval(keywordexpr, {}, KeywordMapping(mapped_names)) + mapping = KeywordMapping(mapped_names) + if " " not in keywordexpr: + # special case to allow for simple "-k pass" and "-k 1.3" + return mapping[keywordexpr] + elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]: + return not mapping[keywordexpr[4:]] + return eval(keywordexpr, {}, mapping) def pytest_configure(config): diff --git a/doc/en/example/markers.txt b/doc/en/example/markers.txt index 55f9e6e3e..097450826 100644 --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -95,6 +95,17 @@ Or to select "http" and "quick" tests:: ================= 1 tests deselected by '-khttp or quick' ================== ================== 2 passed, 1 deselected in 0.01 seconds ================== +.. note:: + + If you are using expressions such as "X and Y" then both X and Y + need to be simple non-keyword names. For example, "pass" or "from" + will result in SyntaxErrors because "-k" evaluates the expression. + + However, if the "-k" argument is a simple string, no such restrictions + apply. Also "-k 'not STRING'" has no restrictions. You can also + specify numbers like "-k 1.3" to match tests which are parametrized + with the float "1.3". + Registering markers ------------------------------------- diff --git a/testing/test_mark.py b/testing/test_mark.py index 1e292c56d..d53af59cc 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -174,7 +174,9 @@ def test_mark_option_custom(spec, testdir): @pytest.mark.parametrize("spec", [ ("interface", ("test_interface",)), - ("not interface", ("test_nointer",)), + ("not interface", ("test_nointer", "test_pass")), + ("pass", ("test_pass",)), + ("not pass", ("test_interface", "test_nointer")), ]) def test_keyword_option_custom(spec, testdir): testdir.makepyfile(""" @@ -182,6 +184,8 @@ def test_keyword_option_custom(spec, testdir): pass def test_nointer(): pass + def test_pass(): + pass """) opt, passed_result = spec rec = testdir.inline_run("-k", opt) @@ -191,6 +195,24 @@ def test_keyword_option_custom(spec, testdir): assert list(passed) == list(passed_result) +@pytest.mark.parametrize("spec", [ + ("None", ("test_func[None]",)), + ("1.3", ("test_func[1.3]",)) +]) +def test_keyword_option_parametrize(spec, testdir): + testdir.makepyfile(""" + import pytest + @pytest.mark.parametrize("arg", [None, 1.3]) + def test_func(arg): + pass + """) + opt, passed_result = spec + rec = testdir.inline_run("-k", opt) + passed, skipped, fail = rec.listoutcomes() + passed = [x.nodeid.split("::")[-1] for x in passed] + assert len(passed) == len(passed_result) + assert list(passed) == list(passed_result) + class TestFunctional: def test_mark_per_function(self, testdir):