diff options
author | Jenkins <jenkins@review.openstack.org> | 2013-06-18 15:26:06 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2013-06-18 15:26:06 +0000 |
commit | 17ac6ab00d7396d2d8c4ec11322612d96d103917 (patch) | |
tree | c3d3ff4fa870b8687fddb8e39ad4978c8311e202 | |
parent | 182feb30610500687a67adad2801a4e54bdea7d8 (diff) | |
parent | 04316feeb9590e31824a5a018f497989b04f45ba (diff) | |
download | pbr-17ac6ab00d7396d2d8c4ec11322612d96d103917.tar.gz |
Merge "Make python setup.py test do the right thing."
-rw-r--r-- | pbr/hooks/backwards.py | 2 | ||||
-rw-r--r-- | pbr/hooks/commands.py | 9 | ||||
-rw-r--r-- | pbr/packaging.py | 96 | ||||
-rw-r--r-- | pbr/testr_command.py | 94 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | test-requirements.txt | 1 |
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 |