Use nose to run unit tests.

1. Adding a plugin to keep track of the total number of tests run as well as the
number of tests with failures/errors.
2. Some nose plugins (including xunit which will be added in a future commit)
assign stdout to a stream object that does not have a .fileno attribute.
spack.util.executable.Executable now avoids passing stdout to subprocess (and
always uses subprocess.PIPE)

TODO:
1. Still need to figure out how to activate the plugin (as of now it is
being ignored by nose). Newer versions of nose appear to make this simpler
(e.g. the "addplugins" argument to nose.run)
2. Need to include new version of nose in order to use xunit
This commit is contained in:
Peter Scheibel 2015-11-12 13:23:19 -08:00
parent cf3d236b9f
commit 099fa1df34
4 changed files with 93 additions and 20 deletions

View File

@ -24,7 +24,9 @@
############################################################################## ##############################################################################
import sys import sys
import unittest import unittest
import nose
from spack.test.tally_plugin import Tally
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
@ -81,28 +83,23 @@ def run(names, verbose=False):
"Valid names are:") "Valid names are:")
colify(sorted(test_names), indent=4) colify(sorted(test_names), indent=4)
sys.exit(1) sys.exit(1)
runner = unittest.TextTestRunner(verbosity=verbosity) tally = Tally()
tally.enabled = True
testsRun = errors = failures = 0
for test in names: for test in names:
module = 'spack.test.' + test module = 'spack.test.' + test
print module print module
suite = unittest.defaultTestLoader.loadTestsFromName(module)
tty.msg("Running test: %s" % test) tty.msg("Running test: %s" % test)
result = runner.run(suite) result = nose.run(argv=["", module], plugins=[tally])
testsRun += result.testsRun
errors += len(result.errors)
failures += len(result.failures)
succeeded = not errors and not failures succeeded = not tally.failCount and not tally.errorCount
tty.msg("Tests Complete.", tty.msg("Tests Complete.",
"%5d tests run" % testsRun, "%5d tests run" % tally.numberOfTests,
"%5d failures" % failures, "%5d failures" % tally.failCount,
"%5d errors" % errors) "%5d errors" % tally.errorCount)
if not errors and not failures: if succeeded:
tty.info("OK", format='g') tty.info("OK", format='g')
else: else:
tty.info("FAIL", format='r') tty.info("FAIL", format='r')

View File

@ -0,0 +1,73 @@
##############################################################################
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://scalability-llnl.github.io/spack
# Please also see the LICENSE file for our notice and the LGPL.
#
# 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) version 2.1 dated February 1999.
#
# 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 terms and
# conditions of the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
from nose.plugins import Plugin
import os
class Tally(Plugin):
name = 'tally'
def __init__(self):
super(Tally, self).__init__()
self.successes = set()
self.failures = set()
self.errors = set()
@property
def successCount(self):
return len(self.successes)
@property
def failCount(self):
return len(self.failures)
@property
def errorCount(self):
return len(self.errors)
@property
def numberOfTests(self):
return self.errorCount + self.failCount + self.successCount
def options(self, parser, env=os.environ):
super(Tally, self).options(parser, env=env)
def configure(self, options, conf):
super(Tally, self).configure(options, conf)
def begin(self):
print ">>> TALLY PLUGIN BEGIN"
def addSuccess(self, test):
self.successes.add(test)
def addError(self, test, err):
self.errors.add(test)
def addFailure(self, test, err):
test.failures.add(test)
def finalize(self, result):
pass

View File

@ -90,7 +90,7 @@ def test_installing_both(self):
pkgX.installed = True pkgX.installed = True
pkgY.installed = True pkgY.installed = True
test_install.create_test_output(specX, [specX, specY], mo, getLogFunc=test_fetch_log) test_install.create_test_output(specX, [specX, specY], mo, getLogFunc=mock_fetch_log)
self.assertEqual(mo.results, self.assertEqual(mo.results,
{bIdX:test_install.TestResult.PASSED, {bIdX:test_install.TestResult.PASSED,
@ -101,7 +101,7 @@ def test_dependency_already_installed(self):
pkgX.installed = True pkgX.installed = True
pkgY.installed = True pkgY.installed = True
test_install.create_test_output(specX, [specX], mo, getLogFunc=test_fetch_log) test_install.create_test_output(specX, [specX], mo, getLogFunc=mock_fetch_log)
self.assertEqual(mo.results, {bIdX:test_install.TestResult.PASSED}) self.assertEqual(mo.results, {bIdX:test_install.TestResult.PASSED})
@ -116,6 +116,6 @@ def __init__(self, init=None):
def get(self, spec): def get(self, spec):
return self.specToPkg[spec] return self.specToPkg[spec]
def test_fetch_log(path): def mock_fetch_log(path):
return [] return []

View File

@ -95,11 +95,14 @@ def streamify(arg, mode):
proc = subprocess.Popen( proc = subprocess.Popen(
cmd, cmd,
stdin=input, stdin=input,
stderr=error, stderr=subprocess.PIPE,
stdout=subprocess.PIPE if return_output else output) stdout=subprocess.PIPE)
out, err = proc.communicate() out, err = proc.communicate()
self.returncode = proc.returncode self.returncode = proc.returncode
output.write(out)
error.write(err)
rc = proc.returncode rc = proc.returncode
if fail_on_error and rc != 0 and (rc not in ignore_errors): if fail_on_error and rc != 0 and (rc not in ignore_errors):
raise ProcessError("Command exited with status %d:" raise ProcessError("Command exited with status %d:"