summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-06-18 15:26:06 +0000
committerGerrit Code Review <review@openstack.org>2013-06-18 15:26:06 +0000
commit17ac6ab00d7396d2d8c4ec11322612d96d103917 (patch)
treec3d3ff4fa870b8687fddb8e39ad4978c8311e202
parent182feb30610500687a67adad2801a4e54bdea7d8 (diff)
parent04316feeb9590e31824a5a018f497989b04f45ba (diff)
downloadpbr-17ac6ab00d7396d2d8c4ec11322612d96d103917.tar.gz
Merge "Make python setup.py test do the right thing."
-rw-r--r--pbr/hooks/backwards.py2
-rw-r--r--pbr/hooks/commands.py9
-rw-r--r--pbr/packaging.py96
-rw-r--r--pbr/testr_command.py94
-rw-r--r--requirements.txt1
-rw-r--r--test-requirements.txt1
6 files changed, 197 insertions, 6 deletions
diff --git a/pbr/hooks/backwards.py b/pbr/hooks/backwards.py
index bcc69a5..d9183b3 100644
--- a/pbr/hooks/backwards.py
+++ b/pbr/hooks/backwards.py
@@ -31,4 +31,4 @@ class BackwardsCompatConfig(base.BaseConfig):
packaging.append_text_list(
self.config, 'tests_require',
packaging.parse_requirements(
- ["test-requirements.txt", "tools/test-requires"]))
+ packaging.TEST_REQUIREMENTS_FILES))
diff --git a/pbr/hooks/commands.py b/pbr/hooks/commands.py
index 21fc4c5..d14baec 100644
--- a/pbr/hooks/commands.py
+++ b/pbr/hooks/commands.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
+
from pbr.hooks import base
from pbr import packaging
@@ -41,6 +43,13 @@ class CommandsConfig(base.BaseConfig):
self.add_command('pbr.packaging.LocalBuildDoc')
self.add_command('pbr.packaging.LocalBuildLatex')
+ if os.path.exists('.testr.conf') and packaging.have_testr():
+ # There is a .testr.conf file. We want to use it.
+ self.add_command('pbr.packaging.TestrTest')
+ elif self.config.get('nosetests', False) and packaging.have_nose():
+ # We seem to still have nose configured
+ self.add_command('pbr.packaging.NoseTest')
+
use_egg = packaging.get_boolean_option(
self.pbr_config, 'use-egg', 'PBR_USE_EGG')
# We always want non-egg install unless explicitly requested
diff --git a/pbr/packaging.py b/pbr/packaging.py
index 66c988c..a64c2b0 100644
--- a/pbr/packaging.py
+++ b/pbr/packaging.py
@@ -37,6 +37,7 @@ from setuptools.command import sdist
log.set_verbosity(log.INFO)
TRUE_VALUES = ('true', '1', 'yes')
REQUIREMENTS_FILES = ('requirements.txt', 'tools/pip-requires')
+TEST_REQUIREMENTS_FILES = ('test-requirements.txt', 'tools/test-requires')
def append_text_list(config, key, text_list):
@@ -112,12 +113,15 @@ def canonicalize_emails(changelog, mapping):
return changelog
+def _any_existing(file_list):
+ return [f for f in file_list if os.path.exists(f)]
+
+
# Get requirements from the first file that exists
def get_reqs_from_files(requirements_files):
- for requirements_file in requirements_files:
- if os.path.exists(requirements_file):
- with open(requirements_file, 'r') as fil:
- return fil.read().split('\n')
+ for requirements_file in _any_existing(requirements_files):
+ with open(requirements_file, 'r') as fil:
+ return fil.read().split('\n')
return []
@@ -312,6 +316,90 @@ class LocalInstall(install.install):
return du_install.install.run(self)
+def _newer_requires_files(egg_info_dir):
+ """Check to see if any of the requires files are newer than egg-info."""
+ for target, sources in (('requires.txt', REQUIREMENTS_FILES),
+ ('test-requires.txt', TEST_REQUIREMENTS_FILES)):
+ target_path = os.path.join(egg_info_dir, target)
+ for src in _any_existing(sources):
+ if (not os.path.exists(target_path) or
+ os.path.getmtime(target_path)
+ < os.path.getmtime(os.path.join(egg_info_dir, src))):
+ return True
+ return False
+
+
+def _copy_test_requires_to(egg_info_dir):
+ """Copy the requirements file to egg-info/test-requires.txt."""
+ with open(os.path.join(egg_info_dir, 'test-requires.txt'), 'w') as dest:
+ for source in _any_existing(TEST_REQUIREMENTS_FILES):
+ dest.write(open(source, 'r').read().rstrip('\n') + '\n')
+
+
+class _PipInstallTestRequires(object):
+ """Mixin class to install test-requirements.txt before running tests."""
+
+ def install_test_requirements(self):
+
+ links = _make_links_args(_any_existing(TEST_REQUIREMENTS_FILES))
+ if self.distribution.tests_require:
+ _pip_install(links, self.distribution.tests_requires)
+
+ def pre_run(self):
+ self.egg_name = pkg_resources.safe_name(self.distribution.get_name())
+ self.egg_info = "%s.egg-info" % self.egg_name
+ if (not os.path.exists(self.egg_info) or
+ _newer_requires_files(self.egg_info)):
+ ei_cmd = self.get_finalized_command('egg_info')
+ ei_cmd.run()
+ self.install_test_requirements()
+ _copy_test_requires_to(self.egg_info)
+
+try:
+ from pbr import testr_command
+
+ class TestrTest(testr_command.Testr, _PipInstallTestRequires):
+ """Make setup.py test do the right thing."""
+
+ command_name = 'test'
+
+ def run(self):
+ self.pre_run()
+ # Can't use super - base class old-style class
+ testr_command.Testr.run(self)
+
+ _have_testr = True
+
+except ImportError:
+ _have_testr = False
+
+
+def have_testr():
+ return _have_testr
+
+try:
+ from nose import commands
+
+ class NoseTest(commands.nosetests, _PipInstallTestRequires):
+ """Fallback test runner if testr is a no-go."""
+
+ command_name = 'test'
+
+ def run(self):
+ self.pre_run()
+ # Can't use super - base class old-style class
+ commands.nosetests.run(self)
+
+ _have_nose = True
+
+except ImportError:
+ _have_nose = False
+
+
+def have_nose():
+ return _have_nose
+
+
class LocalSDist(sdist.sdist):
"""Builds the ChangeLog and Authors files from VC first."""
diff --git a/pbr/testr_command.py b/pbr/testr_command.py
new file mode 100644
index 0000000..2718e3e
--- /dev/null
+++ b/pbr/testr_command.py
@@ -0,0 +1,94 @@
+#
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+# Copyright (c) 2013 Testrepository Contributors
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+"""setuptools/distutils commands to run testr via setup.py
+
+Currently provides 'testr' which runs tests using testr. You can pass
+--coverage which will also export PYTHON='coverage run --source <your package>'
+and automatically combine the coverage from each testr backend test runner
+after the run completes.
+
+To use, just use setuptools/distribute and depend on testr, and it should be
+picked up automatically (as the commands are exported in the testrepository
+package metadata.
+"""
+
+from distutils import cmd
+import distutils.errors
+import os
+import sys
+
+from testrepository import commands
+
+
+class Testr(cmd.Command):
+
+ description = "Run unit tests using testr"
+
+ user_options = [
+ ('coverage', None, "Replace PYTHON with coverage and merge coverage "
+ "from each testr worker."),
+ ('testr-args=', 't', "Run 'testr' with these args"),
+ ('omit=', 'o', 'Files to omit from coverage calculations'),
+ ('slowest', None, "Show slowest test times after tests complete."),
+ ]
+
+ boolean_options = ['coverage', 'slowest']
+
+ def _run_testr(self, *args):
+ return commands.run_argv([sys.argv[0]] + list(args),
+ sys.stdin, sys.stdout, sys.stderr)
+
+ def initialize_options(self):
+ self.testr_args = None
+ self.coverage = None
+ self.omit = ""
+ self.slowest = None
+
+ def finalize_options(self):
+ if self.testr_args is None:
+ self.testr_args = []
+ else:
+ self.testr_args = self.testr_args.split()
+ if self.omit:
+ self.omit = "--omit=%s" % self.omit
+
+ def run(self):
+ """Set up testr repo, then run testr"""
+ if not os.path.isdir(".testrepository"):
+ self._run_testr("init")
+
+ if self.coverage:
+ self._coverage_before()
+ testr_ret = self._run_testr("run", "--parallel", *self.testr_args)
+ if testr_ret:
+ raise distutils.errors.DistutilsError(
+ "testr failed (%d)" % testr_ret)
+ if self.slowest:
+ print "Slowest Tests"
+ self._run_testr("slowest")
+ if self.coverage:
+ self._coverage_after()
+
+ def _coverage_before(self):
+ package = self.distribution.get_name()
+ if package.startswith('python-'):
+ package = package[7:]
+ options = "--source %s --parallel-mode" % package
+ os.environ['PYTHON'] = ("coverage run %s" % options)
+
+ def _coverage_after(self):
+ os.system("coverage combine")
+ os.system("coverage html -d ./cover %s" % self.omit)
diff --git a/requirements.txt b/requirements.txt
index 23525ab..ef4dd08 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
d2to1>=0.2.10,<0.3
setuptools_git>=0.4
+testrepository>=0.0.13
diff --git a/test-requirements.txt b/test-requirements.txt
index 1b82ab0..3313db1 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,7 +4,6 @@ fixtures>=0.3.12
flake8
python-subunit
sphinx>=1.1.2
-testrepository>=0.0.13
testresources
testscenarios
testtools>=0.9.27