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 <[email protected]>
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
[email protected]
-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")
-
-
[email protected]
-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 <[email protected]>
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 <[email protected]>
-#
-# 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
[email protected]
https://www.redhat.com/mailman/listinfo/freeipa-devel