diff --git a/.gitignore b/.gitignore index bda3c483a7..d4a0a947ff 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,9 @@ *.log .glance-venv .venv +.testrepository/ .tox -.coverage +.coverage* cover/* nosetests.xml coverage.xml diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000000..cf71b487b4 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,8 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \ + ${PYTHON:-python} -m subunit.run discover -t ./ ./glance/tests $LISTOPT $IDOPTION + +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/glance/tests/__init__.py b/glance/tests/__init__.py index b39fef3587..be82c8f8df 100644 --- a/glance/tests/__init__.py +++ b/glance/tests/__init__.py @@ -14,7 +14,7 @@ # under the License. # See http://code.google.com/p/python-nose/issues/detail?id=373 -# The code below enables nosetests to work with i18n _() blocks +# The code below enables tests to work with i18n _() blocks import __builtin__ setattr(__builtin__, '_', lambda x: x) diff --git a/glance/tests/unit/test_scrubber.py b/glance/tests/unit/test_scrubber.py index 5f9245f09f..e4eccbad74 100644 --- a/glance/tests/unit/test_scrubber.py +++ b/glance/tests/unit/test_scrubber.py @@ -42,6 +42,9 @@ class TestScrubber(test_utils.BaseTestCase): def tearDown(self): self.mox.UnsetStubs() shutil.rmtree(self.data_dir) + # These globals impact state outside of this test class, kill them. + scrubber._file_queue = None + scrubber._db_queue = None super(TestScrubber, self).tearDown() def _scrubber_cleanup_with_store_delete_exception(self, ex): diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py index d37cca89f0..0f88ab7ecc 100644 --- a/glance/tests/unit/test_store_location.py +++ b/glance/tests/unit/test_store_location.py @@ -462,19 +462,19 @@ class TestStoreLocation(base.StoreClearingUnitTest): self.stubs.Set(glance.store, 'get_size_from_backend', fake_get_size_from_backend) - glance.store._check_image_location = mock.Mock() - loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}} - loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}} + with mock.patch('glance.store._check_image_location') as _: + loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}} + loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}} - # Test for insert location - image1 = FakeImageProxy() - locations = glance.store.StoreLocations(image1, []) - locations.insert(0, loc2) - self.assertEqual(image1.size, 1) + # Test for insert location + image1 = FakeImageProxy() + locations = glance.store.StoreLocations(image1, []) + locations.insert(0, loc2) + self.assertEqual(image1.size, 1) - # Test for set_attr of _locations_proxy - image2 = FakeImageProxy() - locations = glance.store.StoreLocations(image2, [loc1]) - locations[0] = loc2 - self.assertTrue(loc2 in locations) - self.assertEqual(image2.size, 1) + # Test for set_attr of _locations_proxy + image2 = FakeImageProxy() + locations = glance.store.StoreLocations(image2, [loc1]) + locations[0] = loc2 + self.assertTrue(loc2 in locations) + self.assertEqual(image2.size, 1) diff --git a/glance/tests/unit/test_vmware_store.py b/glance/tests/unit/test_vmware_store.py index fe1bd29bef..c379dad5e7 100644 --- a/glance/tests/unit/test_vmware_store.py +++ b/glance/tests/unit/test_vmware_store.py @@ -160,19 +160,20 @@ class TestStore(base.StoreClearingUnitTest): expected_contents = "*" * expected_size hash_code = hashlib.md5(expected_contents) expected_checksum = hash_code.hexdigest() - hashlib.md5 = mock.Mock(return_value=hash_code) - expected_location = format_location( - VMWARE_DATASTORE_CONF['vmware_server_host'], - VMWARE_DATASTORE_CONF['vmware_store_image_dir'], - expected_image_id, - VMWARE_DATASTORE_CONF['vmware_datacenter_path'], - VMWARE_DATASTORE_CONF['vmware_datastore_name']) - image = StringIO.StringIO(expected_contents) - with mock.patch('httplib.HTTPConnection') as HttpConn: - HttpConn.return_value = FakeHTTPConnection() - location, size, checksum, _ = self.store.add(expected_image_id, - image, - expected_size) + with mock.patch('hashlib.md5') as md5: + md5.return_value = hash_code + expected_location = format_location( + VMWARE_DATASTORE_CONF['vmware_server_host'], + VMWARE_DATASTORE_CONF['vmware_store_image_dir'], + expected_image_id, + VMWARE_DATASTORE_CONF['vmware_datacenter_path'], + VMWARE_DATASTORE_CONF['vmware_datastore_name']) + image = StringIO.StringIO(expected_contents) + with mock.patch('httplib.HTTPConnection') as HttpConn: + HttpConn.return_value = FakeHTTPConnection() + location, size, checksum, _ = self.store.add(expected_image_id, + image, + expected_size) self.assertEqual(expected_location, location) self.assertEqual(expected_size, size) self.assertEqual(expected_checksum, checksum) diff --git a/run_tests.sh b/run_tests.sh index 397d3cc814..3228a12bda 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,17 +1,29 @@ #!/bin/bash +set -eu + function usage { echo "Usage: $0 [OPTION]..." echo "Run Glance's test suite(s)" echo "" - echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" - echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" - echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." - echo " -u, --update Update the virtual environment with any newer package versions" - echo " --unittests-only Run unit tests only, exclude functional tests." - echo " -p, --flake8 Just run flake8" - echo " -P, --no-flake8 Don't run static code checks" - echo " -h, --help Print this usage message" + echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" + echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" + echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment" + echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." + echo " -u, --update Update the virtual environment with any newer package versions" + echo " -p, --pep8 Just run PEP8 and HACKING compliance check" + echo " -P, --no-pep8 Don't run static code checks" + echo " -c, --coverage Generate coverage report" + echo " -d, --debug Run tests with testtools instead of testr. This allows you to use the debugger." + echo " -h, --help Print this usage message" + echo " --virtual-env-path Location of the virtualenv directory" + echo " Default: \$(pwd)" + echo " --virtual-env-name Name of the virtualenv directory" + echo " Default: .venv" + echo " --tools-path Location of the tools directory" + echo " Default: \$(pwd)" + echo " --concurrency How many processes to use when running the tests. A value of 0 autodetects concurrency from your CPU count" + echo " Default: 1" echo "" echo "Note: with no options specified, the script will try to run the tests in a virtual environment," echo " If no virtualenv is found, the script will ask if you would like to create one. If you " @@ -19,65 +31,152 @@ function usage { exit } -function process_option { - case "$1" in - -h|--help) usage;; - -V|--virtual-env) let always_venv=1; let never_venv=0;; - -N|--no-virtual-env) let always_venv=0; let never_venv=1;; - -p|--flake8) let just_flake8=1;; - -P|--no-flake8) let no_flake8=1;; - -f|--force) let force=1;; - -u|--update) update=1;; - --unittests-only) noseopts="$noseopts --exclude-dir=glance/tests/functional";; - -c|--coverage) noseopts="$noseopts --with-coverage --cover-package=glance";; - -*) noseopts="$noseopts $1";; - *) noseargs="$noseargs $1" - esac +function process_options { + i=1 + while [ $i -le $# ]; do + case "${!i}" in + -h|--help) usage;; + -V|--virtual-env) always_venv=1; never_venv=0;; + -N|--no-virtual-env) always_venv=0; never_venv=1;; + -s|--no-site-packages) no_site_packages=1;; + -f|--force) force=1;; + -u|--update) update=1;; + -p|--pep8) just_pep8=1;; + -P|--no-pep8) no_pep8=1;; + -c|--coverage) coverage=1;; + -d|--debug) debug=1;; + --virtual-env-path) + (( i++ )) + venv_path=${!i} + ;; + --virtual-env-name) + (( i++ )) + venv_dir=${!i} + ;; + --tools-path) + (( i++ )) + tools_path=${!i} + ;; + --concurrency) + (( i++ )) + concurrency=${!i} + ;; + -*) testropts="$testropts ${!i}";; + *) testrargs="$testrargs ${!i}" + esac + (( i++ )) + done } -venv=.venv +tool_path=${tools_path:-$(pwd)} +venv_path=${venv_path:-$(pwd)} +venv_dir=${venv_name:-.venv} with_venv=tools/with_venv.sh always_venv=0 never_venv=0 force=0 -noseopts= -noseargs= +no_site_packages=0 +installvenvopts= +testrargs= +testropts= wrapper="" -just_flake8=0 -no_flake8=0 +just_pep8=0 +no_pep8=0 +coverage=0 +debug=0 update=0 +concurrency=1 -export NOSE_WITH_OPENSTACK=1 -export NOSE_OPENSTACK_COLOR=1 -export NOSE_OPENSTACK_RED=0.05 -export NOSE_OPENSTACK_YELLOW=0.025 -export NOSE_OPENSTACK_SHOW_ELAPSED=1 -export NOSE_OPENSTACK_STDOUT=1 +LANG=en_US.UTF-8 +LANGUAGE=en_US:en +LC_ALL=C -for arg in "$@"; do - process_option $arg -done +process_options $@ +# Make our paths available to other scripts we call +export venv_path +export venv_dir +export venv_name +export tools_dir +export venv=${venv_path}/${venv_dir} + +if [ $no_site_packages -eq 1 ]; then + installvenvopts="--no-site-packages" +fi function run_tests { # Cleanup *pyc ${wrapper} find . -type f -name "*.pyc" -delete - # Just run the test suites in current environment - ${wrapper} rm -f tests.sqlite - ${wrapper} $NOSETESTS -} -function run_flake8 { - echo "Running flake8 ..." - if [ $never_venv -eq 1 ]; then - echo "**WARNING**:" >&2 - echo "Running flake8 without virtual env may miss OpenStack HACKING detection" >&2 + if [ $debug -eq 1 ]; then + if [ "$testropts" = "" ] && [ "$testrargs" = "" ]; then + # Default to running all tests if specific test is not + # provided. + testrargs="discover ./glance/tests" + fi + ${wrapper} python -m testtools.run $testropts $testrargs + + # Short circuit because all of the testr and coverage stuff + # below does not make sense when running testtools.run for + # debugging purposes. + return $? fi - ${wrapper} flake8 + if [ $coverage -eq 1 ]; then + TESTRTESTS="$TESTRTESTS --coverage" + else + TESTRTESTS="$TESTRTESTS" + fi + + # Just run the test suites in current environment + set +e + testrargs=`echo "$testrargs" | sed -e's/^\s*\(.*\)\s*$/\1/'` + TESTRTESTS="$TESTRTESTS --testr-args='--subunit --concurrency $concurrency $testropts $testrargs'" + if [ setup.cfg -nt glance.egg-info/entry_points.txt ] + then + ${wrapper} python setup.py egg_info + fi + echo "Running \`${wrapper} $TESTRTESTS\`" + if ${wrapper} which subunit-2to1 2>&1 > /dev/null + then + # subunit-2to1 is present, testr subunit stream should be in version 2 + # format. Convert to version one before colorizing. + bash -c "${wrapper} $TESTRTESTS | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py" + else + bash -c "${wrapper} $TESTRTESTS | ${wrapper} tools/colorizer.py" + fi + RESULT=$? + set -e + + copy_subunit_log + + if [ $coverage -eq 1 ]; then + echo "Generating coverage report in covhtml/" + # Don't compute coverage for common code, which is tested elsewhere + ${wrapper} coverage combine + ${wrapper} coverage html --include='glance/*' --omit='glance/openstack/common/*' -d covhtml -i + fi + + return $RESULT +} + +function copy_subunit_log { + LOGNAME=`cat .testrepository/next-stream` + LOGNAME=$(($LOGNAME - 1)) + LOGNAME=".testrepository/${LOGNAME}" + cp $LOGNAME subunit.log +} + +function run_pep8 { + echo "Running flake8 ..." + if [ $never_venv -eq 1 ]; then + echo "**WARNING**:" + echo "Running flake8 without virtual env may miss OpenStack HACKING detection" + fi + bash -c "${wrapper} flake8" } -NOSETESTS="nosetests $noseopts $noseargs" +TESTRTESTS="python -m glance.openstack.common.lockutils python setup.py testr" if [ $never_venv -eq 0 ] then @@ -87,37 +186,46 @@ then rm -rf ${venv} fi if [ $update -eq 1 ]; then - echo "Updating virtualenv..." - python tools/install_venv.py + echo "Updating virtualenv..." + python tools/install_venv.py $installvenvopts fi if [ -e ${venv} ]; then wrapper="${with_venv}" else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv - python tools/install_venv.py + python tools/install_venv.py $installvenvopts wrapper="${with_venv}" else echo -e "No virtual environment found...create one? (Y/n) \c" read use_ve if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then # Install the virtualenv and run the test suite in it - python tools/install_venv.py - wrapper=${with_venv} + python tools/install_venv.py $installvenvopts + wrapper=${with_venv} fi fi fi fi -if [ $just_flake8 -eq 1 ]; then - run_flake8 +# Delete old coverage data from previous runs +if [ $coverage -eq 1 ]; then + ${wrapper} coverage erase +fi + +if [ $just_pep8 -eq 1 ]; then + run_pep8 exit fi -run_tests || exit +run_tests -if [ -z "$noseargs" ]; then - if [ $no_flake8 -eq 0 ]; then - run_flake8 - fi +# NOTE(sirp): we only want to run pep8 when we're running the full-test suite, +# not when we're running tests individually. To handle this, we need to +# distinguish between options (testropts), which begin with a '-', and +# arguments (testrargs). +if [ -z "$testrargs" ]; then + if [ $no_pep8 -eq 0 ]; then + run_pep8 + fi fi diff --git a/setup.cfg b/setup.cfg index 03c4a22a1b..5d54bc49fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,14 +61,3 @@ input_file = glance/locale/glance.pot keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = glance/locale/glance.pot - -[nosetests] -# NOTE(jkoelker) To run the test suite under nose install the following -# coverage http://pypi.python.org/pypi/coverage -# tissue http://pypi.python.org/pypi/tissue (pep8 checker) -# openstack-nose https://github.com/jkoelker/openstack-nose -verbosity=2 -tests=glance/tests -cover-package = glance -cover-html = true -cover-erase = true diff --git a/test-requirements.txt b/test-requirements.txt index aca3ec6138..2f72e125bf 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,21 +6,20 @@ Babel>=1.3 # Needed for testing coverage>=3.6 +discover fixtures>=0.3.14 mox>=0.5.3 -nose -nose-exclude -openstack.nose_plugin>=0.7 mock>=1.0 -nosehtmloutput>=0.0.3 sphinx>=1.1.2,<1.2 requests>=1.1 +testrepository>=0.0.17 testtools>=0.9.34 psutil>1.1.0 # Optional packages that should be installed when testing MySQL-python psycopg2 +-f http://pysendfile.googlecode.com/files/pysendfile-2.0.0.tar.gz pysendfile==2.0.0 qpid-python xattr>=0.4 diff --git a/tools/colorizer.py b/tools/colorizer.py new file mode 100755 index 0000000000..8e638416bd --- /dev/null +++ b/tools/colorizer.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python + +# Copyright (c) 2013, Nebula, Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Colorizer Code is borrowed from Twisted: +# Copyright (c) 2001-2010 Twisted Matrix Laboratories. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Display a subunit stream through a colorized unittest test runner.""" + +import heapq +import subunit +import sys +import unittest + +import testtools + + +class _AnsiColorizer(object): + """ + A colorizer is an object that loosely wraps around a stream, allowing + callers to write text to the stream in a particular color. + + Colorizer classes must implement C{supported()} and C{write(text, color)}. + """ + _colors = dict(black=30, red=31, green=32, yellow=33, + blue=34, magenta=35, cyan=36, white=37) + + def __init__(self, stream): + self.stream = stream + + def supported(cls, stream=sys.stdout): + """ + A class method that returns True if the current platform supports + coloring terminal output using this method. Returns False otherwise. + """ + if not stream.isatty(): + return False # auto color only on TTYs + try: + import curses + except ImportError: + return False + else: + try: + try: + return curses.tigetnum("colors") > 2 + except curses.error: + curses.setupterm() + return curses.tigetnum("colors") > 2 + except Exception: + # guess false in case of error + return False + supported = classmethod(supported) + + def write(self, text, color): + """ + Write the given text to the stream in the given color. + + @param text: Text to be written to the stream. + + @param color: A string label for a color. e.g. 'red', 'white'. + """ + color = self._colors[color] + self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) + + +class _Win32Colorizer(object): + """ + See _AnsiColorizer docstring. + """ + def __init__(self, stream): + import win32console + red, green, blue, bold = (win32console.FOREGROUND_RED, + win32console.FOREGROUND_GREEN, + win32console.FOREGROUND_BLUE, + win32console.FOREGROUND_INTENSITY) + self.stream = stream + self.screenBuffer = win32console.GetStdHandle( + win32console.STD_OUT_HANDLE) + self._colors = { + 'normal': red | green | blue, + 'red': red | bold, + 'green': green | bold, + 'blue': blue | bold, + 'yellow': red | green | bold, + 'magenta': red | blue | bold, + 'cyan': green | blue | bold, + 'white': red | green | blue | bold + } + + def supported(cls, stream=sys.stdout): + try: + import win32console + screenBuffer = win32console.GetStdHandle( + win32console.STD_OUT_HANDLE) + except ImportError: + return False + import pywintypes + try: + screenBuffer.SetConsoleTextAttribute( + win32console.FOREGROUND_RED | + win32console.FOREGROUND_GREEN | + win32console.FOREGROUND_BLUE) + except pywintypes.error: + return False + else: + return True + supported = classmethod(supported) + + def write(self, text, color): + color = self._colors[color] + self.screenBuffer.SetConsoleTextAttribute(color) + self.stream.write(text) + self.screenBuffer.SetConsoleTextAttribute(self._colors['normal']) + + +class _NullColorizer(object): + """ + See _AnsiColorizer docstring. + """ + def __init__(self, stream): + self.stream = stream + + def supported(cls, stream=sys.stdout): + return True + supported = classmethod(supported) + + def write(self, text, color): + self.stream.write(text) + + +def get_elapsed_time_color(elapsed_time): + if elapsed_time > 1.0: + return 'red' + elif elapsed_time > 0.25: + return 'yellow' + else: + return 'green' + + +class SubunitTestResult(testtools.TestResult): + def __init__(self, stream, descriptions, verbosity): + super(SubunitTestResult, self).__init__() + self.stream = stream + self.showAll = verbosity > 1 + self.num_slow_tests = 10 + self.slow_tests = [] # this is a fixed-sized heap + self.colorizer = None + # NOTE(vish): reset stdout for the terminal check + stdout = sys.stdout + sys.stdout = sys.__stdout__ + for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: + if colorizer.supported(): + self.colorizer = colorizer(self.stream) + break + sys.stdout = stdout + self.start_time = None + self.last_time = {} + self.results = {} + self.last_written = None + + def _writeElapsedTime(self, elapsed): + color = get_elapsed_time_color(elapsed) + self.colorizer.write(" %.2f" % elapsed, color) + + def _addResult(self, test, *args): + try: + name = test.id() + except AttributeError: + name = 'Unknown.unknown' + test_class, test_name = name.rsplit('.', 1) + + elapsed = (self._now() - self.start_time).total_seconds() + item = (elapsed, test_class, test_name) + if len(self.slow_tests) >= self.num_slow_tests: + heapq.heappushpop(self.slow_tests, item) + else: + heapq.heappush(self.slow_tests, item) + + self.results.setdefault(test_class, []) + self.results[test_class].append((test_name, elapsed) + args) + self.last_time[test_class] = self._now() + self.writeTests() + + def _writeResult(self, test_name, elapsed, long_result, color, + short_result, success): + if self.showAll: + self.stream.write(' %s' % str(test_name).ljust(66)) + self.colorizer.write(long_result, color) + if success: + self._writeElapsedTime(elapsed) + self.stream.writeln() + else: + self.colorizer.write(short_result, color) + + def addSuccess(self, test): + super(SubunitTestResult, self).addSuccess(test) + self._addResult(test, 'OK', 'green', '.', True) + + def addFailure(self, test, err): + if test.id() == 'process-returncode': + return + super(SubunitTestResult, self).addFailure(test, err) + self._addResult(test, 'FAIL', 'red', 'F', False) + + def addError(self, test, err): + super(SubunitTestResult, self).addFailure(test, err) + self._addResult(test, 'ERROR', 'red', 'E', False) + + def addSkip(self, test, reason=None, details=None): + super(SubunitTestResult, self).addSkip(test, reason, details) + self._addResult(test, 'SKIP', 'blue', 'S', True) + + def startTest(self, test): + self.start_time = self._now() + super(SubunitTestResult, self).startTest(test) + + def writeTestCase(self, cls): + if not self.results.get(cls): + return + if cls != self.last_written: + self.colorizer.write(cls, 'white') + self.stream.writeln() + for result in self.results[cls]: + self._writeResult(*result) + del self.results[cls] + self.stream.flush() + self.last_written = cls + + def writeTests(self): + time = self.last_time.get(self.last_written, self._now()) + if not self.last_written or (self._now() - time).total_seconds() > 2.0: + diff = 3.0 + while diff > 2.0: + classes = self.results.keys() + oldest = min(classes, key=lambda x: self.last_time[x]) + diff = (self._now() - self.last_time[oldest]).total_seconds() + self.writeTestCase(oldest) + else: + self.writeTestCase(self.last_written) + + def done(self): + self.stopTestRun() + + def stopTestRun(self): + for cls in list(self.results.iterkeys()): + self.writeTestCase(cls) + self.stream.writeln() + self.writeSlowTests() + + def writeSlowTests(self): + # Pare out 'fast' tests + slow_tests = [item for item in self.slow_tests + if get_elapsed_time_color(item[0]) != 'green'] + if slow_tests: + slow_total_time = sum(item[0] for item in slow_tests) + slow = ("Slowest %i tests took %.2f secs:" + % (len(slow_tests), slow_total_time)) + self.colorizer.write(slow, 'yellow') + self.stream.writeln() + last_cls = None + # sort by name + for elapsed, cls, name in sorted(slow_tests, + key=lambda x: x[1] + x[2]): + if cls != last_cls: + self.colorizer.write(cls, 'white') + self.stream.writeln() + last_cls = cls + self.stream.write(' %s' % str(name).ljust(68)) + self._writeElapsedTime(elapsed) + self.stream.writeln() + + def printErrors(self): + if self.showAll: + self.stream.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def printErrorList(self, flavor, errors): + for test, err in errors: + self.colorizer.write("=" * 70, 'red') + self.stream.writeln() + self.colorizer.write(flavor, 'red') + self.stream.writeln(": %s" % test.id()) + self.colorizer.write("-" * 70, 'red') + self.stream.writeln() + self.stream.writeln("%s" % err) + + +test = subunit.ProtocolTestCase(sys.stdin, passthrough=None) + +if sys.version_info[0:2] <= (2, 6): + runner = unittest.TextTestRunner(verbosity=2) +else: + runner = unittest.TextTestRunner( + verbosity=2, resultclass=SubunitTestResult) + +if runner.run(test).wasSuccessful(): + exit_code = 0 +else: + exit_code = 1 +sys.exit(exit_code) diff --git a/tools/install_venv.py b/tools/install_venv.py index 548ece223d..b9c4795066 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -67,7 +67,6 @@ def main(argv): install.install_dependencies() install.run_command([os.path.join(venv, 'bin/python'), 'setup.py', 'develop']) - install.post_process() print_help() if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index 98e26b0921..8102f5a41a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,27 +1,30 @@ [tox] +minversion = 1.6 envlist = py26,py27,py33,pep8 +skipsdist = True [testenv] setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_OPENSTACK=1 - NOSE_OPENSTACK_COLOR=1 - NOSE_OPENSTACK_RED=0.05 - NOSE_OPENSTACK_YELLOW=0.025 - NOSE_OPENSTACK_SHOW_ELAPSED=1 - NOSE_OPENSTACK_STDOUT=1 + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C +usedevelop = True +install_command = pip install --allow-all-external --allow-insecure netaddr -U {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -commands = nosetests {posargs} +commands = python -m glance.openstack.common.lockutils python setup.py test --slowest \ + --testr-args='--concurrency 1 {posargs}' [tox:jenkins] downloadcache = ~/cache/pip [testenv:pep8] commands = - flake8 + flake8 {posargs} [testenv:cover] -setenv = NOSE_WITH_COVERAGE=1 +setenv = VIRTUAL_ENV={envdir} +commands = python setup.py testr --coverage --testr-args='^(?!.*test.*coverage).*$' [testenv:venv] commands = {posargs}