[Pytest-commit] commit/pytest: 3 new changesets

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Fri Mar 14 08:11:48 CET 2014


3 new commits in pytest:

https://bitbucket.org/hpk42/pytest/commits/a016be612113/
Changeset:   a016be612113
User:        hpk42
Date:        2014-03-11 22:10:17
Summary:     introduce warning system with this API:

- node.warn() for a node-specific warning
- config.warn() for a global non-node specific warning

Each warning is accompanied by a "warning number" so that we can later
introduce mechanisms for surpressing them.

Each warning will trigger a call to pytest_report_warn(number, node, message)
which is by default implemented by the TerminalReporter which introduces
a new option "-rw" to show details about warnings.
Affected #:  5 files

diff -r 8d2d44cf0345a9e4e5992768c50c5918b8dc08cc -r a016be6121137d7e9bf6c534a7070e5cf1ee267b _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -613,6 +613,11 @@
         self.hook.pytest_unconfigure(config=self)
         self.pluginmanager.ensure_shutdown()
 
+    def warn(self, code, message):
+        """ generate a warning for this test session. """
+        self.hook.pytest_logwarning(code=code, message=message,
+                                    fslocation=None, nodeid=None)
+
     def pytest_cmdline_parse(self, pluginmanager, args):
         assert self == pluginmanager.config, (self, pluginmanager.config)
         self.parse(args)

diff -r 8d2d44cf0345a9e4e5992768c50c5918b8dc08cc -r a016be6121137d7e9bf6c534a7070e5cf1ee267b _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -227,6 +227,11 @@
 def pytest_terminal_summary(terminalreporter):
     """ add additional section in terminal summary reporting.  """
 
+def pytest_logwarning(message, code, nodeid, fslocation):
+    """ process a warning specified by a message, a code string,
+    a nodeid and fslocation (both of which may be None
+    if the warning is not tied to a partilar node/location)."""
+
 # -------------------------------------------------------------------------
 # doctest hooks
 # -------------------------------------------------------------------------

diff -r 8d2d44cf0345a9e4e5992768c50c5918b8dc08cc -r a016be6121137d7e9bf6c534a7070e5cf1ee267b _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -263,6 +263,20 @@
         return "<%s %r>" %(self.__class__.__name__,
                            getattr(self, 'name', None))
 
+    def warn(self, code, message):
+        """ generate a warning with the given code and message for this
+        item. """
+        assert isinstance(code, str)
+        fslocation = getattr(self, "location", None)
+        if fslocation is None:
+            fslocation = getattr(self, "fspath", None)
+        else:
+            fslocation = "%s:%s" % fslocation[:2]
+
+        self.ihook.pytest_logwarning(code=code, message=message,
+                                     nodeid=self.nodeid,
+                                     fslocation=fslocation)
+
     # methods for ordering nodes
     @property
     def nodeid(self):

diff -r 8d2d44cf0345a9e4e5992768c50c5918b8dc08cc -r a016be6121137d7e9bf6c534a7070e5cf1ee267b _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -75,6 +75,14 @@
             letter = "f"
     return report.outcome, letter, report.outcome.upper()
 
+class WarningReport:
+    def __init__(self, code, message, nodeid=None, fslocation=None):
+        self.code = code
+        self.message = message
+        self.nodeid = nodeid
+        self.fslocation = fslocation
+
+
 class TerminalReporter:
     def __init__(self, config, file=None):
         self.config = config
@@ -151,6 +159,12 @@
             self.write_line("INTERNALERROR> " + line)
         return 1
 
+    def pytest_logwarning(self, code, fslocation, message, nodeid):
+        warnings = self.stats.setdefault("warnings", [])
+        warning = WarningReport(code=code, fslocation=fslocation,
+                                message=message, nodeid=nodeid)
+        warnings.append(warning)
+
     def pytest_plugin_registered(self, plugin):
         if self.config.option.traceconfig:
             msg = "PLUGIN registered: %s" % (plugin,)
@@ -335,6 +349,7 @@
             self.summary_errors()
             self.summary_failures()
             self.summary_hints()
+            self.summary_warnings()
             self.config.hook.pytest_terminal_summary(terminalreporter=self)
         if exitstatus == 2:
             self._report_keyboardinterrupt()
@@ -405,6 +420,16 @@
             for hint in self.config.pluginmanager._hints:
                 self._tw.line("hint: %s" % hint)
 
+    def summary_warnings(self):
+        if self.hasopt("w"):
+            warnings = self.stats.get("warnings")
+            if not warnings:
+                return
+            self.write_sep("=", "warning summary")
+            for w in warnings:
+                self._tw.line("W%s %s %s" % (w.code,
+                              w.fslocation, w.message))
+
     def summary_failures(self):
         if self.config.option.tbstyle != "no":
             reports = self.getreports('failed')
@@ -449,7 +474,8 @@
     def summary_stats(self):
         session_duration = py.std.time.time() - self._sessionstarttime
 
-        keys = "failed passed skipped deselected xfailed xpassed".split()
+        keys = ("failed passed skipped deselected "
+               "xfailed xpassed warnings").split()
         for key in self.stats.keys():
             if key not in keys:
                 keys.append(key)

diff -r 8d2d44cf0345a9e4e5992768c50c5918b8dc08cc -r a016be6121137d7e9bf6c534a7070e5cf1ee267b testing/test_config.py
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -360,4 +360,43 @@
     assert l[-2] == m.pytest_load_initial_conftests
     assert l[-3].__module__ == "_pytest.config"
 
+class TestWarning:
+    def test_warn_config(self, testdir):
+        testdir.makeconftest("""
+            l = []
+            def pytest_configure(config):
+                config.warn("C1", "hello")
+            def pytest_logwarning(code, message):
+                assert code == "C1"
+                assert message == "hello"
+                l.append(1)
+        """)
+        testdir.makepyfile("""
+            def test_proper(pytestconfig):
+                import conftest
+                assert conftest.l == [1]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
 
+    def test_warn_on_test_item_from_request(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def fix(request):
+                request.node.warn("T1", "hello")
+            def test_hello(fix):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            *1 warning*
+        """)
+        assert "hello" not in result.stdout.str()
+        result = testdir.runpytest("-rw")
+        result.stdout.fnmatch_lines("""
+            ===*warning summary*===
+            *WT1*test_warn_on_test_item*:5*hello*
+            *1 warning*
+        """)


https://bitbucket.org/hpk42/pytest/commits/a1ff8ba50ac5/
Changeset:   a1ff8ba50ac5
User:        hpk42
Date:        2014-03-11 22:10:18
Summary:     warn if instances are callable and have a test name
Affected #:  3 files

diff -r a016be6121137d7e9bf6c534a7070e5cf1ee267b -r a1ff8ba50ac5617a2151a162f31887de7e48f761 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -11,7 +11,8 @@
 
 NoneType = type(None)
 NOTSET = object()
-
+isfunction = inspect.isfunction
+isclass = inspect.isclass
 callable = py.builtin.callable
 
 def getfslineno(obj):
@@ -44,7 +45,7 @@
         self.ids = ids
 
     def __call__(self, function):
-        if inspect.isclass(function):
+        if isclass(function):
             raise ValueError(
                     "class fixtures not supported (may be in the future)")
         function._pytestfixturefunction = self
@@ -213,14 +214,19 @@
     res = __multicall__.execute()
     if res is not None:
         return res
-    if inspect.isclass(obj):
+    if isclass(obj):
         #if hasattr(collector.obj, 'unittest'):
         #    return # we assume it's a mixin class for a TestCase derived one
         if collector.classnamefilter(name):
             Class = collector._getcustomclass("Class")
             return Class(name, parent=collector)
-    elif collector.funcnamefilter(name) and hasattr(obj, '__call__') and \
+    elif collector.funcnamefilter(name) and hasattr(obj, "__call__") and \
         getfixturemarker(obj) is None:
+        if not isfunction(obj):
+            collector.warn(code="C2", message=
+                "cannot collect %r because it is not a function."
+                % name, )
+            return
         if is_generator(obj):
             return Generator(name, parent=collector)
         else:
@@ -498,10 +504,9 @@
     """ Collector for test methods. """
     def collect(self):
         if hasinit(self.obj):
-            pytest.skip("class %s.%s with __init__ won't get collected" % (
-                self.obj.__module__,
-                self.obj.__name__,
-            ))
+            self.warn("C1", "cannot collect test class %r because it has a "
+                "__init__ constructor" % self.obj.__name__)
+            return []
         return [self._getcustomclass("Instance")(name="()", parent=self)]
 
     def setup(self):

diff -r a016be6121137d7e9bf6c534a7070e5cf1ee267b -r a1ff8ba50ac5617a2151a162f31887de7e48f761 _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -15,7 +15,7 @@
     group._addoption('-r',
          action="store", dest="reportchars", default=None, metavar="chars",
          help="show extra test summary info as specified by chars (f)ailed, "
-              "(E)error, (s)skipped, (x)failed, (X)passed.")
+              "(E)error, (s)skipped, (x)failed, (X)passed (w)warnings.")
     group._addoption('-l', '--showlocals',
          action="store_true", dest="showlocals", default=False,
          help="show locals in tracebacks (disabled by default).")

diff -r a016be6121137d7e9bf6c534a7070e5cf1ee267b -r a1ff8ba50ac5617a2151a162f31887de7e48f761 testing/python/collect.py
--- a/testing/python/collect.py
+++ b/testing/python/collect.py
@@ -33,8 +33,8 @@
         pytest.raises(ImportError, lambda: modcol.obj)
 
 class TestClass:
-    def test_class_with_init_skip_collect(self, testdir):
-        modcol = testdir.getmodulecol("""
+    def test_class_with_init_warning(self, testdir):
+        testdir.makepyfile("""
             class TestClass1:
                 def __init__(self):
                     pass
@@ -42,11 +42,11 @@
                 def __init__(self):
                     pass
         """)
-        l = modcol.collect()
-        assert len(l) == 2
-
-        for classcol in l:
-            pytest.raises(pytest.skip.Exception, classcol.collect)
+        result = testdir.runpytest("-rw")
+        result.stdout.fnmatch_lines("""
+            WC1*test_class_with_init_warning.py*__init__*
+            *2 warnings*
+        """)
 
     def test_class_subclassobject(self, testdir):
         testdir.getmodulecol("""
@@ -276,6 +276,17 @@
         assert isinstance(modcol, pytest.Module)
         assert hasattr(modcol.obj, 'test_func')
 
+    def test_function_as_object_instance_ignored(self, testdir):
+        item = testdir.makepyfile("""
+            class A:
+                def __call__(self, tmpdir):
+                    0/0
+
+            test_a = A()
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome()
+
     def test_function_equality(self, testdir, tmpdir):
         from _pytest.python import FixtureManager
         config = testdir.parseconfigure()


https://bitbucket.org/hpk42/pytest/commits/e18da3213547/
Changeset:   e18da3213547
User:        hpk42
Date:        2014-03-11 22:10:51
Summary:     shrink and merge the somewhat obscure and undocumented internal hinting
system with the new warnings one
Affected #:  6 files

diff -r a1ff8ba50ac5617a2151a162f31887de7e48f761 -r e18da321354757558ffd286a07e94d2b1cd42b18 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -82,6 +82,9 @@
         config.addinivalue_line("markers",
             "trylast: mark a hook implementation function such that the "
             "plugin machinery will try to call it last/as late as possible.")
+        while self._warnings:
+            warning = self._warnings.pop(0)
+            config.warn(code="I1", message=warning)
 
 
 class Parser:
@@ -94,7 +97,6 @@
         self._usage = usage
         self._inidict = {}
         self._ininames = []
-        self.hints = []
 
     def processoption(self, option):
         if self._processopt:
@@ -379,14 +381,6 @@
         py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage,
             add_help=False, formatter_class=DropShorterLongHelpFormatter)
 
-    def format_epilog(self, formatter):
-        hints = self._parser.hints
-        if hints:
-            s = "\n".join(["hint: " + x for x in hints]) + "\n"
-            s = "\n" + s + "\n"
-            return s
-        return ""
-
     def parse_args(self, args=None, namespace=None):
         """allow splitting of positional arguments"""
         args, argv = self.parse_known_args(args, namespace)
@@ -716,7 +710,6 @@
         self._preparse(args)
         # XXX deprecated hook:
         self.hook.pytest_cmdline_preparse(config=self, args=args)
-        self._parser.hints.extend(self.pluginmanager._hints)
         args = self._parser.parse_setoption(args, self.option)
         if not args:
             args.append(py.std.os.getcwd())

diff -r a1ff8ba50ac5617a2151a162f31887de7e48f761 -r e18da321354757558ffd286a07e94d2b1cd42b18 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -71,7 +71,7 @@
         self._name2plugin = {}
         self._listattrcache = {}
         self._plugins = []
-        self._hints = []
+        self._warnings = []
         self.trace = TagTracer().get("pluginmanage")
         self._plugin_distinfo = []
         self._shutdown = []
@@ -225,7 +225,7 @@
                 raise
             elif not isinstance(e, py.test.skip.Exception):
                 raise
-            self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
+            self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
         else:
             self.register(mod, modname)
             self.consider_module(mod)

diff -r a1ff8ba50ac5617a2151a162f31887de7e48f761 -r e18da321354757558ffd286a07e94d2b1cd42b18 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -64,7 +64,6 @@
 def showhelp(config):
     tw = py.io.TerminalWriter()
     tw.write(config._parser.optparser.format_help())
-    tw.write(config._parser.optparser.format_epilog(None))
     tw.line()
     tw.line()
     #tw.sep( "=", "config file settings")
@@ -86,6 +85,8 @@
     tw.line("to see available fixtures type: py.test --fixtures")
     tw.line("(shown according to specified file_or_dir or current dir "
             "if not specified)")
+    for warning in config.pluginmanager._warnings:
+        tw.line("warning: %s" % (warning,))
     return
 
     tw.line("conftest.py options:")

diff -r a1ff8ba50ac5617a2151a162f31887de7e48f761 -r e18da321354757558ffd286a07e94d2b1cd42b18 _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -348,7 +348,6 @@
         if exitstatus in (0, 1, 2, 4):
             self.summary_errors()
             self.summary_failures()
-            self.summary_hints()
             self.summary_warnings()
             self.config.hook.pytest_terminal_summary(terminalreporter=self)
         if exitstatus == 2:
@@ -415,11 +414,6 @@
                 l.append(x)
         return l
 
-    def summary_hints(self):
-        if self.config.option.traceconfig:
-            for hint in self.config.pluginmanager._hints:
-                self._tw.line("hint: %s" % hint)
-
     def summary_warnings(self):
         if self.hasopt("w"):
             warnings = self.stats.get("warnings")

diff -r a1ff8ba50ac5617a2151a162f31887de7e48f761 -r e18da321354757558ffd286a07e94d2b1cd42b18 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -43,11 +43,11 @@
         """)
         p.copy(p.dirpath("skipping2.py"))
         monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
-        result = testdir.runpytest("-p", "skipping1", "--traceconfig")
+        result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig")
         assert result.ret == 0
         result.stdout.fnmatch_lines([
-            "*hint*skipping1*hello*",
-            "*hint*skipping2*hello*",
+            "WI1*skipped plugin*skipping1*hello*",
+            "WI1*skipped plugin*skipping2*hello*",
         ])
 
     def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):

diff -r a1ff8ba50ac5617a2151a162f31887de7e48f761 -r e18da321354757558ffd286a07e94d2b1cd42b18 testing/test_parseopt.py
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -236,16 +236,6 @@
         help = parser.optparser.format_help()
         assert '-doit, --func-args  foo' in  help
 
- at pytest.mark.skipif("sys.version_info < (2,5)")
-def test_addoption_parser_epilog(testdir):
-    testdir.makeconftest("""
-        def pytest_addoption(parser):
-            parser.hints.append("hello world")
-            parser.hints.append("from me too")
-    """)
-    result = testdir.runpytest('--help')
-    #assert result.ret != 0
-    result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"])
 
 @pytest.mark.skipif("sys.version_info < (2,6)")
 def test_argcomplete(testdir, monkeypatch):

Repository URL: https://bitbucket.org/hpk42/pytest/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.


More information about the pytest-commit mailing list