regression.py
author Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
Wed, 19 Nov 2008 17:17:07 +0000
changeset 3872 8e757a83fb36
parent 3866 9e946fee902c
child 4064 10222f483860
permissions -rw-r--r--
Add a --with-regression-traces configure option, to allow using externally supplied regression traces instead of fetching them from the network.
gjc@3866
     1
import os
gjc@3866
     2
import sys
gjc@3866
     3
import Params
gjc@3866
     4
import shutil
gjc@3866
     5
import pproc as subprocess
gjc@3866
     6
import urllib
gjc@3866
     7
gjc@3866
     8
import wutils
gjc@3866
     9
gjc@3866
    10
#
gjc@3866
    11
# The directory in which the tarball of the reference traces lives.  This is
gjc@3866
    12
# used if Mercurial is not on the system.
gjc@3866
    13
#
gjc@3866
    14
REGRESSION_TRACES_URL = "http://www.nsnam.org/releases/"
gjc@3866
    15
gjc@3866
    16
#
gjc@3866
    17
# The path to the Mercurial repository used to find the reference traces if
gjc@3866
    18
# we find "hg" on the system.  We expect that the repository will be named
gjc@3866
    19
# identically to refDirName below
gjc@3866
    20
#
gjc@3866
    21
REGRESSION_TRACES_REPO = "http://code.nsnam.org/"
gjc@3866
    22
gjc@3866
    23
#
gjc@3866
    24
# Name of the local directory where the regression code lives.
gjc@3866
    25
#
gjc@3866
    26
REGRESSION_DIR = "regression"
gjc@3866
    27
gjc@3866
    28
#
gjc@3866
    29
# The last part of the path name to use to find the regression traces.  The
gjc@3866
    30
# path will be APPNAME + '-' + VERSION + REGRESSION_SUFFIX, e.g.,
gjc@3866
    31
# ns-3-dev-ref-traces
gjc@3866
    32
#
gjc@3866
    33
REGRESSION_SUFFIX = "-ref-traces"
gjc@3866
    34
gjc@3866
    35
gjc@3866
    36
def dev_null():
gjc@3866
    37
    if sys.platform == 'win32':
gjc@3866
    38
        return open("NUL:", "w")
gjc@3866
    39
    else:
gjc@3866
    40
        return open("/dev/null", "w")
gjc@3866
    41
gjc@3866
    42
gjc@3866
    43
### Regression testing
gjc@3866
    44
class Regression(object):
gjc@3872
    45
    def __init__(self, testdir, reference_traces):
gjc@3866
    46
        self.testdir = testdir
gjc@3872
    47
        self.reference_traces = reference_traces
gjc@3866
    48
        self.env = Params.g_build.env_of_name('default')
gjc@3866
    49
gjc@3872
    50
    def run_test(self, verbose, generate, testName, arguments=[], pyscript=None, refTestName=None):
gjc@3866
    51
        """
gjc@3866
    52
        @param verbose: enable verbose execution
gjc@3866
    53
gjc@3866
    54
        @param generate: generate new traces instead of comparing with the reference
gjc@3866
    55
gjc@3866
    56
        @param testName: name of the test
gjc@3866
    57
gjc@3866
    58
        @arguments: list of extra parameters to pass to the program to be tested
gjc@3866
    59
gjc@3866
    60
        @pyscript: if not None, the test is written in Python and this
gjc@3866
    61
        parameter contains the path to the python script, relative to
gjc@3866
    62
        the project root dir
gjc@3866
    63
gjc@3866
    64
        @param refTestName: if not None, this is the name of the directory under refDirName
gjc@3866
    65
        that contains the reference traces. Otherwise "refDirname/testName + .ref" is used.
gjc@3866
    66
gjc@3866
    67
        """
gjc@3866
    68
        if not isinstance(arguments, list):
gjc@3866
    69
            raise TypeError
gjc@3866
    70
        
gjc@3866
    71
        if refTestName is None:
gjc@3872
    72
            refTestDirName = os.path.join(self.reference_traces, (testName + ".ref"))
gjc@3866
    73
        else:
gjc@3872
    74
            refTestDirName = os.path.join(self.reference_traces, refTestName)
gjc@3866
    75
gjc@3872
    76
        if not os.path.exists(self.reference_traces):
gjc@3866
    77
            print"No reference trace repository"
gjc@3866
    78
            return 1
gjc@3866
    79
gjc@3866
    80
        if generate:
gjc@3866
    81
            if not os.path.exists(refTestDirName):
gjc@3866
    82
                print "creating new " + refTestDirName
gjc@3866
    83
                os.mkdir(refTestDirName)
gjc@3866
    84
gjc@3866
    85
            if pyscript is None:
gjc@3866
    86
                Params.g_options.cwd_launch = refTestDirName
gjc@3866
    87
                tmpl = "%s"
gjc@3866
    88
                for arg in arguments:
gjc@3866
    89
                    tmpl = tmpl + " " + arg
gjc@3866
    90
                wutils.run_program(testName, tmpl)
gjc@3866
    91
            else:
gjc@3872
    92
                argv = [self.env['PYTHON'], os.path.join(Params.g_cwd_launch, *os.path.split(pyscript))] + arguments
gjc@3866
    93
                before = os.getcwd()
gjc@3866
    94
                os.chdir(refTestDirName)
gjc@3866
    95
                try:
gjc@3866
    96
                    wutils.run_argv(argv)
gjc@3866
    97
                finally:
gjc@3866
    98
                    os.chdir(before)
gjc@3866
    99
            print "Remember to commit " + refTestDirName
gjc@3866
   100
            return 0
gjc@3866
   101
        else:
gjc@3866
   102
            if not os.path.exists(refTestDirName):
gjc@3866
   103
                print "Cannot locate reference traces in " + refTestDirName
gjc@3866
   104
                return 1
gjc@3866
   105
gjc@3866
   106
            
gjc@3866
   107
            if refTestName is None:
gjc@3866
   108
                traceDirName = testName + ".ref"
gjc@3866
   109
            else:
gjc@3866
   110
                traceDirName = refTestName
gjc@3866
   111
            traceDirName = os.path.join ('traces', traceDirName)
gjc@3866
   112
gjc@3866
   113
            try:
gjc@3866
   114
                shutil.rmtree(traceDirName)
gjc@3866
   115
            except OSError:
gjc@3866
   116
                pass
gjc@3866
   117
            os.mkdir(traceDirName)
gjc@3866
   118
gjc@3866
   119
            #os.system("./waf --cwd regression/traces --run " +
gjc@3866
   120
            #  testName + " > /dev/null 2>&1")
gjc@3866
   121
gjc@3866
   122
            if pyscript is None:
gjc@3866
   123
                Params.g_options.cwd_launch = traceDirName
gjc@3866
   124
                wutils.run_program(testName, command_template=wutils.get_command_template(*arguments))
gjc@3866
   125
            else:
gjc@3866
   126
                argv = [self.env['PYTHON'], os.path.join('..', '..', '..', *os.path.split(pyscript))] + arguments
gjc@3866
   127
                before = os.getcwd()
gjc@3866
   128
                os.chdir(traceDirName)
gjc@3866
   129
                try:
gjc@3866
   130
                    wutils.run_argv(argv)
gjc@3866
   131
                finally:
gjc@3866
   132
                    os.chdir(before)
gjc@3866
   133
gjc@3866
   134
            if verbose:
gjc@3866
   135
                #diffCmd = "diff traces " + refTestDirName + " | head"
gjc@3866
   136
                diffCmd = subprocess.Popen([self.env['DIFF'], traceDirName, refTestDirName],
gjc@3866
   137
                                           stdout=subprocess.PIPE)
gjc@3866
   138
                headCmd = subprocess.Popen("head", stdin=diffCmd.stdout)
gjc@3866
   139
                rc2 = headCmd.wait()
gjc@3866
   140
                diffCmd.stdout.close()
gjc@3866
   141
                rc1 = diffCmd.wait()
gjc@3866
   142
                rc = rc1 or rc2
gjc@3866
   143
            else:
gjc@3866
   144
                rc = subprocess.Popen([self.env['DIFF'], traceDirName, refTestDirName], stdout=dev_null()).wait()
gjc@3866
   145
            if rc:
gjc@3866
   146
                print "----------"
gjc@3866
   147
                print "Traces differ in test: test-" + testName
gjc@3866
   148
                print "Reference traces in directory: regression/" + refTestDirName
gjc@3866
   149
                print "Traces in directory: traces"
gjc@3866
   150
                print "Rerun regression test as: " + \
gjc@3866
   151
                    "\"./waf --regression --regression-tests=test-" + testName + "\""
gjc@3866
   152
                print "Then do \"diff -u regression/" + refTestDirName + " regression/" + traceDirName +\
gjc@3866
   153
                    "\" for details"
gjc@3866
   154
                print "----------"
gjc@3866
   155
            return rc
gjc@3866
   156
gjc@3866
   157
def _find_tests(testdir):
gjc@3866
   158
    """Return a list of test modules in the test directory
gjc@3866
   159
gjc@3866
   160
    Arguments:
gjc@3866
   161
    testdir -- the directory to look in for tests
gjc@3866
   162
    """
gjc@3866
   163
    names = os.listdir(testdir)
gjc@3866
   164
    tests = []
gjc@3866
   165
    for name in names:
gjc@3866
   166
        if name[:5] == "test-" and name[-3:] == ".py":
gjc@3866
   167
            testname = name[:-3]
gjc@3866
   168
            tests.append(testname)
gjc@3866
   169
    tests.sort()
gjc@3866
   170
    return tests
gjc@3866
   171
gjc@3872
   172
def run_regression(reference_traces):
gjc@3872
   173
    """Execute regression tests.  Called with cwd set to the 'regression' subdir of ns-3.
gjc@3872
   174
gjc@3872
   175
    @param reference_traces: reference traces directory, or None for default.
gjc@3872
   176
gjc@3872
   177
    """
gjc@3866
   178
gjc@3866
   179
    testdir = "tests"
gjc@3866
   180
    if not os.path.exists(testdir):
gjc@3866
   181
        print "Tests directory does not exist"
gjc@3866
   182
        sys.exit(3)
gjc@3872
   183
gjc@3872
   184
    dir_name = (wutils.APPNAME + '-' + wutils.VERSION + REGRESSION_SUFFIX)
gjc@3872
   185
    if reference_traces is None:
gjc@3872
   186
        reference_traces = dir_name
gjc@3872
   187
        no_net = False
gjc@3872
   188
    else:
gjc@3872
   189
        no_net = True
gjc@3866
   190
    
gjc@3866
   191
    sys.path.append(testdir)
gjc@3872
   192
    sys.modules['tracediff'] = Regression(testdir, reference_traces)
gjc@3866
   193
gjc@3866
   194
    if Params.g_options.regression_tests:
gjc@3866
   195
        tests = Params.g_options.regression_tests.split(',')
gjc@3866
   196
    else:
gjc@3866
   197
        tests = _find_tests(testdir)
gjc@3866
   198
gjc@3866
   199
    print "========== Running Regression Tests =========="
gjc@3866
   200
    env = Params.g_build.env_of_name('default')
gjc@3872
   201
    if not no_net:
gjc@3872
   202
        if env['MERCURIAL']:
gjc@3872
   203
            print "Synchronizing reference traces using Mercurial."
gjc@3872
   204
            if not os.path.exists(reference_traces):
gjc@3872
   205
                print "Cloning " + REGRESSION_TRACES_REPO + dir_name + " from repo."
gjc@3872
   206
                argv = ["hg", "clone", REGRESSION_TRACES_REPO + dir_name, reference_traces]
gjc@3872
   207
                rv = subprocess.Popen(argv).wait()
gjc@3872
   208
            else:
gjc@3872
   209
                _dir = os.getcwd()
gjc@3872
   210
                os.chdir(reference_traces)
gjc@3872
   211
                try:
gjc@3872
   212
                    print "Pulling " + REGRESSION_TRACES_REPO + dir_name + " from repo."
gjc@3872
   213
                    result = subprocess.Popen(["hg", "-q", "pull", REGRESSION_TRACES_REPO + dir_name]).wait()
gjc@3872
   214
                    if not result:
gjc@3872
   215
                        result = subprocess.Popen(["hg", "-q", "update"]).wait()
gjc@3872
   216
                finally:
gjc@3872
   217
                    os.chdir("..")
gjc@3872
   218
                if result:
gjc@3872
   219
                    Params.fatal("Synchronizing reference traces using Mercurial failed.")
gjc@3866
   220
        else:
gjc@3872
   221
            if not os.path.exists(reference_traces):
gjc@3872
   222
                traceball = dir_name + wutils.TRACEBALL_SUFFIX
gjc@3872
   223
                print "Retrieving " + traceball + " from web."
gjc@3872
   224
                urllib.urlretrieve(REGRESSION_TRACES_URL + traceball, traceball)
gjc@3872
   225
                os.system("tar -xjf %s -C .." % (traceball))
gjc@3872
   226
                print "Done."
gjc@3866
   227
gjc@3872
   228
    if not os.path.exists(reference_traces):
gjc@3872
   229
        print "Reference traces directory (%s) does not exist" % reference_traces
gjc@3866
   230
        return 3
gjc@3866
   231
    
gjc@3866
   232
    bad = []
gjc@3866
   233
gjc@3866
   234
    for test in tests:
gjc@3866
   235
        try:
gjc@3866
   236
            result = _run_regression_test(test)
gjc@3866
   237
            if result == 0:
gjc@3866
   238
                if Params.g_options.regression_generate:
gjc@3866
   239
                    print "GENERATE " + test
gjc@3866
   240
                else:
gjc@3866
   241
                    print "PASS " + test
gjc@3866
   242
            else:
gjc@3866
   243
                bad.append(test)
gjc@3866
   244
                print "FAIL " + test
gjc@3866
   245
        except NotImplementedError:
gjc@3866
   246
                print "SKIP " + test            
gjc@3866
   247
gjc@3866
   248
    return len(bad) > 0
gjc@3866
   249
gjc@3866
   250
gjc@3866
   251
def _run_regression_test(test):
gjc@3866
   252
    """Run a single test.
gjc@3866
   253
gjc@3866
   254
    Arguments:
gjc@3866
   255
    test -- the name of the test
gjc@3866
   256
    """
gjc@3866
   257
gjc@3866
   258
    if os.path.exists("traces"):
gjc@3866
   259
        files = os.listdir("traces")
gjc@3866
   260
        for file in files:
gjc@3866
   261
            if file == '.' or file == '..':
gjc@3866
   262
                continue
gjc@3866
   263
            shutil.rmtree(os.path.join("traces", file), ignore_errors=True)
gjc@3866
   264
    else:
gjc@3866
   265
        os.mkdir("traces")
gjc@3866
   266
    
gjc@3866
   267
    mod = __import__(test, globals(), locals(), [])
gjc@3866
   268
    return mod.run(verbose=(Params.g_options.verbose > 0),
gjc@3872
   269
                   generate=Params.g_options.regression_generate)
gjc@3872
   270