regression.py
author Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
Tue, 18 Nov 2008 13:48:26 +0000
changeset 3866 9e946fee902c
child 3872 8e757a83fb36
permissions -rw-r--r--
Refactor wscript code to move regression testing code to a separate python module (bug 325)

import os
import sys
import Params
import shutil
import pproc as subprocess
import urllib

import wutils

#
# The directory in which the tarball of the reference traces lives.  This is
# used if Mercurial is not on the system.
#
REGRESSION_TRACES_URL = "http://www.nsnam.org/releases/"

#
# The path to the Mercurial repository used to find the reference traces if
# we find "hg" on the system.  We expect that the repository will be named
# identically to refDirName below
#
REGRESSION_TRACES_REPO = "http://code.nsnam.org/"

#
# Name of the local directory where the regression code lives.
#
REGRESSION_DIR = "regression"

#
# The last part of the path name to use to find the regression traces.  The
# path will be APPNAME + '-' + VERSION + REGRESSION_SUFFIX, e.g.,
# ns-3-dev-ref-traces
#
REGRESSION_SUFFIX = "-ref-traces"


def dev_null():
    if sys.platform == 'win32':
        return open("NUL:", "w")
    else:
        return open("/dev/null", "w")


### Regression testing
class Regression(object):
    def __init__(self, testdir):
        self.testdir = testdir
        self.env = Params.g_build.env_of_name('default')

    def run_test(self, verbose, generate, refDirName, testName, arguments=[], pyscript=None, refTestName=None):
        """
        @param verbose: enable verbose execution

        @param generate: generate new traces instead of comparing with the reference

        @param refDirName: name of the base directory containing reference traces

        @param testName: name of the test

        @arguments: list of extra parameters to pass to the program to be tested

        @pyscript: if not None, the test is written in Python and this
        parameter contains the path to the python script, relative to
        the project root dir

        @param refTestName: if not None, this is the name of the directory under refDirName
        that contains the reference traces. Otherwise "refDirname/testName + .ref" is used.

        """
        if not isinstance(arguments, list):
            raise TypeError
        
        if refTestName is None:
            refTestDirName = os.path.join(refDirName, (testName + ".ref"))
        else:
            refTestDirName = os.path.join(refDirName, refTestName)

        if not os.path.exists(refDirName):
            print"No reference trace repository"
            return 1

        if generate:
            if not os.path.exists(refTestDirName):
                print "creating new " + refTestDirName
                os.mkdir(refTestDirName)

            if pyscript is None:
                Params.g_options.cwd_launch = refTestDirName
                tmpl = "%s"
                for arg in arguments:
                    tmpl = tmpl + " " + arg
                wutils.run_program(testName, tmpl)
            else:
                argv = [self.env['PYTHON'], os.path.join('..', '..', '..', *os.path.split(pyscript))] + arguments
                before = os.getcwd()
                os.chdir(refTestDirName)
                try:
                    wutils.run_argv(argv)
                finally:
                    os.chdir(before)
            print "Remember to commit " + refTestDirName
            return 0
        else:
            if not os.path.exists(refTestDirName):
                print "Cannot locate reference traces in " + refTestDirName
                return 1

            
            if refTestName is None:
                traceDirName = testName + ".ref"
            else:
                traceDirName = refTestName
            traceDirName = os.path.join ('traces', traceDirName)

            try:
                shutil.rmtree(traceDirName)
            except OSError:
                pass
            os.mkdir(traceDirName)

            #os.system("./waf --cwd regression/traces --run " +
            #  testName + " > /dev/null 2>&1")

            if pyscript is None:
                Params.g_options.cwd_launch = traceDirName
                wutils.run_program(testName, command_template=wutils.get_command_template(*arguments))
            else:
                argv = [self.env['PYTHON'], os.path.join('..', '..', '..', *os.path.split(pyscript))] + arguments
                before = os.getcwd()
                os.chdir(traceDirName)
                try:
                    wutils.run_argv(argv)
                finally:
                    os.chdir(before)

            if verbose:
                #diffCmd = "diff traces " + refTestDirName + " | head"
                diffCmd = subprocess.Popen([self.env['DIFF'], traceDirName, refTestDirName],
                                           stdout=subprocess.PIPE)
                headCmd = subprocess.Popen("head", stdin=diffCmd.stdout)
                rc2 = headCmd.wait()
                diffCmd.stdout.close()
                rc1 = diffCmd.wait()
                rc = rc1 or rc2
            else:
                rc = subprocess.Popen([self.env['DIFF'], traceDirName, refTestDirName], stdout=dev_null()).wait()
            if rc:
                print "----------"
                print "Traces differ in test: test-" + testName
                print "Reference traces in directory: regression/" + refTestDirName
                print "Traces in directory: traces"
                print "Rerun regression test as: " + \
                    "\"./waf --regression --regression-tests=test-" + testName + "\""
                print "Then do \"diff -u regression/" + refTestDirName + " regression/" + traceDirName +\
                    "\" for details"
                print "----------"
            return rc

def _find_tests(testdir):
    """Return a list of test modules in the test directory

    Arguments:
    testdir -- the directory to look in for tests
    """
    names = os.listdir(testdir)
    tests = []
    for name in names:
        if name[:5] == "test-" and name[-3:] == ".py":
            testname = name[:-3]
            tests.append(testname)
    tests.sort()
    return tests

def run_regression():
    """Execute regression tests."""

    testdir = "tests"
    if not os.path.exists(testdir):
        print "Tests directory does not exist"
        sys.exit(3)
    
    sys.path.append(testdir)
    sys.modules['tracediff'] = Regression(testdir)

    if Params.g_options.regression_tests:
        tests = Params.g_options.regression_tests.split(',')
    else:
        tests = _find_tests(testdir)

    print "========== Running Regression Tests =========="
    dir_name = wutils.APPNAME + '-' + wutils.VERSION + REGRESSION_SUFFIX
    env = Params.g_build.env_of_name('default')
    if env['MERCURIAL']:
        print "Synchronizing reference traces using Mercurial."
        if not os.path.exists(dir_name):
            print "Cloning " + REGRESSION_TRACES_REPO + dir_name + " from repo."
            argv = ["hg", "clone", REGRESSION_TRACES_REPO + dir_name, dir_name]
            rv = subprocess.Popen(argv).wait()
        else:
            _dir = os.getcwd()
            os.chdir(dir_name)
            try:
                print "Pulling " + REGRESSION_TRACES_REPO + dir_name + " from repo."
                result = subprocess.Popen(["hg", "-q", "pull", REGRESSION_TRACES_REPO + dir_name]).wait()
                if not result:
                    result = subprocess.Popen(["hg", "-q", "update"]).wait()
            finally:
                os.chdir("..")
            if result:
                Params.fatal("Synchronizing reference traces using Mercurial failed.")
    else:
        if not os.path.exists(dir_name):
            traceball = dir_name + wutils.TRACEBALL_SUFFIX
            print "Retrieving " + traceball + " from web."
            urllib.urlretrieve(REGRESSION_TRACES_URL + traceball, traceball)
            os.system("tar -xjf %s -C .." % (traceball))
            print "Done."

    if not os.path.exists(dir_name):
        print "Reference traces directory (%s) does not exist" % dir_name
        return 3
    
    bad = []

    for test in tests:
        try:
            result = _run_regression_test(test)
            if result == 0:
                if Params.g_options.regression_generate:
                    print "GENERATE " + test
                else:
                    print "PASS " + test
            else:
                bad.append(test)
                print "FAIL " + test
        except NotImplementedError:
                print "SKIP " + test            

    return len(bad) > 0


def _run_regression_test(test):
    """Run a single test.

    Arguments:
    test -- the name of the test
    """

    if os.path.exists("traces"):
        files = os.listdir("traces")
        for file in files:
            if file == '.' or file == '..':
                continue
            shutil.rmtree(os.path.join("traces", file), ignore_errors=True)
    else:
        os.mkdir("traces")
    
    dir_name = wutils.APPNAME + '-' + wutils.VERSION + REGRESSION_SUFFIX

    mod = __import__(test, globals(), locals(), [])
    return mod.run(verbose=(Params.g_options.verbose > 0),
                   generate=Params.g_options.regression_generate,
                   refDirName=dir_name)