Hello,
I've split more pytest plugins from FreeIPA, so they can be used to test other projects.


Releasing these plugins properly to Fedora is getting delayed, but they are on fedorahosted:
https://fedorahosted.org/python-pytest-beakerlib/
https://fedorahosted.org/python-pytest-sourceorder/

and in my COPR:
https://copr.fedoraproject.org/coprs/pviktori/pytest-plugins/

and also in the freeipa-master COPR:
https://copr.fedoraproject.org/coprs/mkosek/freeipa-master/builds/

Note that the BeakerLib plugin does not need to be installed to run tests. There's some integration code left in IPA to forward logging and to keep the --with-beakerlib option in ipa-test-task, but that's only activated if the plugin is available.

--
PetrĀ³
From 2ad6bd49a4ef027c8b55f029a9a30a8165ddf2d7 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pvikt...@redhat.com>
Date: Tue, 2 Dec 2014 14:09:23 +0100
Subject: [PATCH] ipatests: Use pytest-beakerlib

The plugin for BeakerLib integration was split into a separate project.
If BeakerLib integration is desired, python-pytest-beakerlib shoule be
installed separately.
The IPA-specific beakerlib integration only sets up logging to BeakerLib,
if the plugin is active.
---
 ipatests/ipa-test-task               |  13 ++-
 ipatests/pytest_plugins/beakerlib.py | 205 +++--------------------------------
 2 files changed, 24 insertions(+), 194 deletions(-)

diff --git a/ipatests/ipa-test-task b/ipatests/ipa-test-task
index d89af841de9f8558ca620989fb665e6f3e2c573c..8c9ab082f343df299b174cc81f8a3ebac3fde9ee 100755
--- a/ipatests/ipa-test-task
+++ b/ipatests/ipa-test-task
@@ -28,9 +28,13 @@ from ipapython.ipa_log_manager import log_mgr, standard_logging_setup
 from ipatests.test_integration import config
 from ipatests.test_integration import tasks
 from ipatests.test_integration.host import Host
-from ipatests.pytest_plugins.beakerlib import BeakerLibProcess
 from ipatests.pytest_plugins.integration import collect_logs
 
+try:
+    from pytest_beakerlib import BeakerLibProcess
+except ImportError:
+    BeakerLibProcess = None
+
 
 log = log_mgr.get_logger(__name__)
 
@@ -245,8 +249,8 @@ class TaskRunner(object):
         return parser
 
     def main(self, argv):
-
-        args = self.get_parser().parse_args(argv)
+        parser = self.get_parser()
+        args = parser.parse_args(argv)
         self.config = config.Config.from_env(os.environ)
         if not self.config:
             raise EnvironmentError('Multihost environment not configured')
@@ -259,6 +263,9 @@ class TaskRunner(object):
         self.collect_log = collect_log
 
         if args.with_beakerlib:
+            if BeakerLibProcess is None:
+                parser.error(
+                    'pytest_beakerlib not installed, cannot use BeakerLib')
             beakerlib_process = BeakerLibProcess()
             args.verbose = True
 
diff --git a/ipatests/pytest_plugins/beakerlib.py b/ipatests/pytest_plugins/beakerlib.py
index 45bbb05394bf61de90a8c53627c36bb5f60f1f9d..c94f7f417914660563c41f2833c08bcf90f9ba96 100644
--- a/ipatests/pytest_plugins/beakerlib.py
+++ b/ipatests/pytest_plugins/beakerlib.py
@@ -16,52 +16,29 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-"""pytest integration with BeakerLib
+"""Test integration with BeakerLib
 
-Runs a Bash process on the side, and feeds BeakerLib commands to it
-(rlPhaseStart, rlPhaseEnd, rlPass, rlFail, ...)
+IPA-specific configuration for the BeakerLib plugin (from pytest-beakerlib).
+If the plugin is active, sets up IPA logging to also log to Beaker.
 
-Other plugins may integrate with this using pytest's
-config.pluginmanager.getplugin('BeakerLibPlugin'). If this is None,
-BeakerLib integration is not active, otherwise the result's
-run_beakerlib_command method can be used to run additional commands.
-
-IPA logging is also redirected to the Bash process.
 """
 
-import os
-import re
 import logging
-import subprocess
 
-import pytest
-
-from ipapython import ipautil
 from ipapython.ipa_log_manager import log_mgr
 
 
-@pytest.fixture
-def log_files_to_collect():
-    return []
-
-
-def pytest_addoption(parser):
-    parser.addoption(
-        '--with-beakerlib', action="store_true",
-        dest="with_beakerlib", default=None,
-        help="Report test results via beakerlib")
-
-
-@pytest.mark.tryfirst
-def pytest_load_initial_conftests(args, early_config, parser):
-    ns = early_config.known_args_namespace
-    if ns.with_beakerlib:
-        if 'BEAKERLIB' not in os.environ:
-            raise exit('$BEAKERLIB not set, cannot use --with-beakerlib')
-
-        plugin = BeakerLibPlugin()
-        pluginmanager = early_config.pluginmanager.register(
-            plugin, 'BeakerLibPlugin')
+def pytest_configure(config):
+    plugin = config.pluginmanager.getplugin('BeakerLibPlugin')
+    if plugin:
+        handler = BeakerLibLogHandler(plugin.run_beakerlib_command)
+        log_mgr.configure(
+            {
+                'default_level': 'DEBUG',
+                'handlers': [{'log_handler': handler,
+                              'format': '[%(name)s] %(message)s',
+                              'level': 'info'}]},
+            configure_state='beakerlib_plugin')
 
 
 class BeakerLibLogHandler(logging.Handler):
@@ -78,157 +55,3 @@ def emit(self, record):
             'CRITICAL': 'rlLogFatal',
         }.get(record.levelname, 'rlLog')
         self.beakerlib_command([command, self.format(record)])
-
-
-class BeakerLibProcess(object):
-    """Manager of a Bash process that is being fed beakerlib commands
-    """
-    def __init__(self, env=os.environ):
-        self.log = log_mgr.get_logger(self)
-
-        if 'BEAKERLIB' not in env:
-            raise RuntimeError('$BEAKERLIB not set, cannot use BeakerLib')
-
-        self.env = env
-        # Set up the Bash process
-        self.bash = subprocess.Popen(['bash'],
-                                     stdin=subprocess.PIPE,
-                                     stdout=open(os.devnull, 'w'),
-                                     stderr=open(os.devnull, 'w'))
-        source_path = os.path.join(self.env['BEAKERLIB'], 'beakerlib.sh')
-        self.run_beakerlib_command(['.', source_path])
-
-        # Redirect logging to our own handlers
-        self.setup_log_handler(BeakerLibLogHandler(self.run_beakerlib_command))
-
-    def setup_log_handler(self, handler):
-        log_mgr.configure(
-            {
-                'default_level': 'DEBUG',
-                'handlers': [{'log_handler': handler,
-                              'format': '[%(name)s] %(message)s',
-                              'level': 'info'}]},
-            configure_state='beakerlib_plugin')
-
-    def run_beakerlib_command(self, cmd):
-        """Given a command as a Popen-style list, run it in the Bash process"""
-        if not self.bash:
-            return
-        for word in cmd:
-            self.bash.stdin.write(ipautil.shell_quote(word))
-            self.bash.stdin.write(' ')
-        self.bash.stdin.write('\n')
-        self.bash.stdin.flush()
-        assert self.bash.returncode is None, "BeakerLib Bash process exited"
-
-    def log_links(self, docstring):
-        for match in LINK_RE.finditer(docstring or ''):
-            self.log.info('Link: %s', match.group())
-
-    def end(self):
-        """End the Bash process"""
-        self.run_beakerlib_command(['exit'])
-        bash = self.bash
-        self.bash = None
-        bash.communicate()
-
-    def log_exception(self, err=None):
-        """Log an exception
-
-        err is a 3-tuple as returned from sys.exc_info(); if not given,
-        sys.exc_info() is used.
-        """
-        if err is None:
-            err = sys.exc_info()
-        message = ''.join(traceback.format_exception(*err)).rstrip()
-        self.run_beakerlib_command(['rlLogError', message])
-
-
-class BeakerLibPlugin(object):
-    def __init__(self):
-        self.log = log_mgr.get_logger(self)
-
-        self.process = BeakerLibProcess(env=os.environ)
-
-        self._current_item = None
-
-    def run_beakerlib_command(self, cmd):
-        """Given a command as a Popen-style list, run it in the Bash process"""
-        self.process.run_beakerlib_command(cmd)
-
-    def get_item_name(self, item):
-        """Return a "identifier-style" name for the given item
-
-        The name only contains the characters [^a-zA-Z0-9_].
-        """
-        bad_char_re = re.compile('[^a-zA-Z0-9_]')
-        parts = []
-        current = item
-        while current:
-            if isinstance(current, pytest.Module):
-                name = current.name
-                if name.endswith('.py'):
-                    name = name[:-3]
-                name = bad_char_re.sub('-', name)
-                parts.append(name)
-                break
-            if isinstance(current, pytest.Instance):
-                pass
-            else:
-                name = current.name
-                name = bad_char_re.sub('-', name)
-                parts.append(name)
-            current = current.parent
-        return '-'.join(reversed(parts))
-
-    def set_current_item(self, item):
-        """Set the item that is currently being processed
-
-        No-op if the same item is already being processed.
-        Ends the phase for the previous item, if any.
-        """
-        if item != self._current_item:
-            item_name = self.get_item_name(item)
-            if self._current_item:
-                self.run_beakerlib_command(['rlPhaseEnd'])
-            if item:
-                self.run_beakerlib_command(['rlPhaseStart', 'FAIL', item_name])
-            self._current_item = item
-
-    def pytest_collection_modifyitems(self, session, config, items):
-        """Log all collected items at start of test"""
-        self.run_beakerlib_command(['rlLogInfo', 'Collected pytest tests:'])
-        for item in items:
-            self.run_beakerlib_command(['rlLogInfo',
-                                        '  - ' + self.get_item_name(item)])
-
-    def pytest_runtest_setup(self, item):
-        """Log item before running it"""
-        self.set_current_item(item)
-
-    def pytest_runtest_makereport(self, item, call):
-        """Report pass/fail for setup/call/teardown of an item"""
-        self.set_current_item(item)
-        desc = '%s: %s' % (call.when, item)
-
-        if not call.excinfo:
-            self.run_beakerlib_command(['rlPass', 'PASS %s' % desc])
-        else:
-            self.run_beakerlib_command(['rlLogError', call.excinfo.exconly()])
-            short_repr = str(call.excinfo.getrepr(style='short'))
-            self.run_beakerlib_command(['rlLogInfo', short_repr])
-
-            # Give super-detailed traceback for DEBUG=1
-            long_repr = str(call.excinfo.getrepr(
-                showlocals=True, funcargs=True))
-            self.run_beakerlib_command(['rlLogDebug', long_repr])
-
-            if call.excinfo.errisinstance(pytest.skip.Exception):
-                self.run_beakerlib_command(['rlPass', 'SKIP %s' % desc])
-            else:
-                self.run_beakerlib_command(['rlFail', 'FAIL %s' % desc])
-
-    def pytest_unconfigure(self, config):
-        """Clean up and exit"""
-        self.set_current_item(None)
-        self.process.end()
-- 
2.1.0

From 9be72e7592734bbd7bca909adda30ad504e73abb Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pvikt...@redhat.com>
Date: Tue, 2 Dec 2014 13:30:20 +0100
Subject: [PATCH] ipatests: Use pytest-sourceorder

The plugin to run tests within a class in the order they're defined
in the source was split into a separate project.
Use this project instead of a FreeIPA-specific copy.
---
 freeipa.spec.in                            |  1 +
 ipatests/pytest.ini                        |  1 -
 ipatests/pytest_plugins/ordering.py        | 88 ------------------------------
 ipatests/test_integration/base.py          |  2 +-
 ipatests/test_integration/test_ordering.py |  2 +-
 5 files changed, 3 insertions(+), 91 deletions(-)
 delete mode 100644 ipatests/pytest_plugins/ordering.py

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 588871450a76ccf5e5affd123c46af85bf08752a..ea2f596d3c8be5006a025a863336ee3fdf98ee72 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -309,6 +309,7 @@ Requires: python-paste
 Requires: python-coverage
 Requires: python-polib
 Requires: python-pytest-multihost >= 0.4
+Requires: python-pytest-sourceorder
 
 Conflicts: %{alt_name}-tests
 Obsoletes: %{alt_name}-tests < %{version}
diff --git a/ipatests/pytest.ini b/ipatests/pytest.ini
index 275593682673f25baf75a64ffd90ef84a80d3c4d..a24466a2aa7d592880a8abd529b874ebf5715637 100644
--- a/ipatests/pytest.ini
+++ b/ipatests/pytest.ini
@@ -10,7 +10,6 @@ addopts = --doctest-modules
           --junit-prefix ipa
           -p ipatests.pytest_plugins.nose_compat
           -p ipatests.pytest_plugins.declarative
-          -p ipatests.pytest_plugins.ordering
           -p ipatests.pytest_plugins.integration
           -p ipatests.pytest_plugins.beakerlib
             # Ignore files for doc tests.
diff --git a/ipatests/pytest_plugins/ordering.py b/ipatests/pytest_plugins/ordering.py
deleted file mode 100644
index 3af496a88c3072372d17c743ad4b84f7a75c5ff0..0000000000000000000000000000000000000000
--- a/ipatests/pytest_plugins/ordering.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Authors:
-#   Petr Viktorin <pvikt...@redhat.com>
-#
-# Copyright (C) 2014  Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-"""Pytest plugin for IPA
-
-Adds support for the @pytest.mark.source_order decorator which,
-when applied to a class, runs the test methods in source order.
-
-See test_ordering for an example.
-"""
-
-import unittest
-
-import pytest
-
-
-def ordered(cls):
-    """Decorator that marks a test class as ordered
-
-    Methods within the marked class will be executed in definition order
-    (or more strictly, in ordered by the line number where they're defined).
-
-    Subclasses of unittest.TestCase can not be ordered.
-
-    Generator methods will not be ordered by this plugin.
-    """
-    cls._order_plugin__ordered = True
-    assert not isinstance(cls, unittest.TestCase), (
-        "A unittest.TestCase may not be ordered.")
-    cls = pytest.mark.source_order(cls)
-    return cls
-
-
-def decorate_items(items):
-    node_indexes = {}
-    for index, item in enumerate(items):
-        try:
-            func = item.function
-        except AttributeError:
-            yield (index, ), item
-            continue
-
-        key = (index, )
-        for node in reversed(item.listchain()):
-            # Find the corresponding class
-            if isinstance(node, pytest.Class):
-                cls = node.cls
-            else:
-                continue
-            if getattr(cls, '_order_plugin__ordered', False):
-                node_index = node_indexes.setdefault(node, index)
-                # Find first occurence of the method in class hierarchy
-                for i, parent_class in enumerate(reversed(cls.mro())):
-                    if getattr(parent_class, '_order_plugin__ordered', False):
-                        method = getattr(parent_class, func.__name__, None)
-                        if method:
-                            # Sort methods as tuples  (position of the class
-                            # in the inheritance chain, position of the method
-                            # within that class)
-                            key = (node_index, 0,
-                                   i, method.func_code.co_firstlineno, node)
-                            break
-                else:
-                    # Weird case fallback
-                    # Method name not in any of the classes in MRO, run it last
-                    key = node_index, 1, func.func_code.co_firstlineno, node
-                break
-        yield key, item
-
-
-def pytest_collection_modifyitems(session, config, items):
-    items[:] = [item for i, item in sorted(decorate_items(items))]
diff --git a/ipatests/test_integration/base.py b/ipatests/test_integration/base.py
index d291c36c26f69e65dd43c56a94f5c77d7b44dbed..ee41b4142294dcff5db808c72005bf3901a7ef35 100644
--- a/ipatests/test_integration/base.py
+++ b/ipatests/test_integration/base.py
@@ -23,7 +23,7 @@
 
 from ipapython.ipa_log_manager import log_mgr
 from ipatests.test_integration import tasks
-from ipatests.pytest_plugins.ordering import ordered
+from pytest_sourceorder import ordered
 
 log = log_mgr.get_logger(__name__)
 
diff --git a/ipatests/test_integration/test_ordering.py b/ipatests/test_integration/test_ordering.py
index ee224901c5ee05355f148cf132c15427adfee497..583d87c5e9d2b46e13d19ea01779256e0549270e 100644
--- a/ipatests/test_integration/test_ordering.py
+++ b/ipatests/test_integration/test_ordering.py
@@ -25,7 +25,7 @@
 - Within a class, test methods are ordered according to source line
 """
 
-from ipatests.pytest_plugins.ordering import ordered
+from pytest_sourceorder import ordered
 
 
 @ordered
-- 
2.1.0

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to