diff --git a/SConstruct b/SConstruct index bf993d3d5..a15ff9045 100644 --- a/SConstruct +++ b/SConstruct @@ -8,6 +8,11 @@ Basic usage: default options. '[sudo] scons install' - Install Cantera. + + 'scons test' - Run regression test suite + + 'scons test-clean' - Delete files created while running the + regression tests. """ from buildutils import * @@ -22,6 +27,7 @@ if not COMMAND_LINE_TARGETS: env = Environment(tools = ['default', 'textfile']) env.AddMethod(RecursiveInstall) subst.TOOL_SUBST(env) +add_RegressionTest(env) # ****************************************************** # *** Set system-dependent defaults for some options *** @@ -701,3 +707,7 @@ File locations: finish_install = env.Command('finish_install', [], postInstallMessage) env.Depends(finish_install, installTargets) install_cantera = Alias('install', finish_install) + +### Tests ### +if 'test' in COMMAND_LINE_TARGETS or 'test-clean' in COMMAND_LINE_TARGETS: + SConscript('test_problems/SConscript') diff --git a/buildutils.py b/buildutils.py index af5ca74ba..d69a24892 100644 --- a/buildutils.py +++ b/buildutils.py @@ -5,6 +5,9 @@ import sys from os.path import join as pjoin import textwrap import re +import subprocess +import difflib +import time class DefineDict(object): def __init__(self, data): @@ -44,6 +47,57 @@ class ConfigBuilder(object): print " %-35s %s" % (key, '*undefined*') +def regression_test(target, source, env): + # unpack: + program = source[0] + blessedFile = source[1] + if len(source) > 2: + clargs = [s.name for s in source[2:]] + else: + clargs = [] + + # Name to use for the output file + if 'blessed' in blessedFile.name: + output = blessedFile.abspath.replace('blessed', 'output') + else: + output = pjoin(blessedFile.dir.abspath, 'test_output.txt') + + dir = str(target[0].dir) + blessed = [line.rstrip() for line in open(blessedFile.abspath).readlines()] + + with open(output, 'w') as outfile: + code = subprocess.call([program.abspath] + clargs, + stdout=outfile, stderr=outfile, cwd=dir) + + outputText = [line.rstrip() for line in open(output).readlines()] + diff = list(difflib.unified_diff(blessed, outputText)) + + if diff or code: + print 'FAILED' + + if diff: + print 'Difference between blessed output and current output:' + print '>>>' + print '\n'.join(diff) + print '<<<' + + if os.path.exists(target[0].abspath): + os.path.unlink(target[0].abspath) + return -1 + else: + print 'PASSED' + open(target[0].path, 'w').write(time.asctime()+'\n') + + +def regression_test_message(target, source, env): + print """* Running test '%s'...""" % source[0].name, + + +def add_RegressionTest(env): + env['BUILDERS']['RegressionTest'] = env.Builder( + action=env.Action(regression_test, regression_test_message)) + + class CopyNoPrefix(object): """ Copy a file, ignoring leading directories that are part diff --git a/test_problems/SConscript b/test_problems/SConscript new file mode 100644 index 000000000..e4c6a30b3 --- /dev/null +++ b/test_problems/SConscript @@ -0,0 +1,94 @@ +from buildutils import * + +Import('env','buildTargets','installTargets') +localenv = env.Clone() + +class Test(object): + def __init__(self, subdir, programName, + blessedName, arguments=(), + extensions=('cpp',), artifacts=()): + self.subdir = subdir + self.programName = programName + if isinstance(arguments, str): + arguments = [arguments] + self.arguments = arguments + self.blessedName = blessedName + self.extensions = extensions + self.artifacts = artifacts + self.passedFile = '.passed-%s-%s' % (programName, blessedName) + + def run(self, env): + prog = env.Program(pjoin(self.subdir, self.programName), + mglob(env, self.subdir, *self.extensions), + LIBS=env['cantera_libs']) + arguments = [pjoin(self.subdir, arg) for arg in self.arguments] + source = [prog, pjoin(self.subdir, self.blessedName)] + arguments + test = env.RegressionTest(pjoin(self.subdir, self.passedFile), source) + + return test + + def clean(self, env): + # Name used for the output file + if 'blessed' in self.blessedName: + outName = self.blessedName.replace('blessed', 'output') + else: + outName = 'test_output.txt' + + files = [self.programName, + self.programName + '.o', + self.passedFile, + 'ct2ctml.log', + outName] + files += list(self.artifacts) + files = [pjoin(os.getcwd(), self.subdir, name) for name in files] + files = [f for f in files if os.path.exists(f)] + + target = env.Command('clean-'+self.programName, [], + [Delete(f) for f in files]) + return target + +tests = [Test(pjoin('cathermo', 'DH_graph_1'), + 'DH_graph_1', + 'DH_NaCl_dilute_blessed.csv', + artifacts=['DH_graph_1.log'], + arguments='DH_NaCl_dilute.xml'), + Test(pjoin('cathermo', 'DH_graph_acommon'), + 'DH_graph_acommon', + 'DH_NaCl_acommon_blessed.csv', + artifacts=['DH_graph_1.log'], + arguments='DH_NaCl_acommon.xml'), + Test(pjoin('cathermo', 'DH_graph_bdotak'), + 'DH_graph_bdotak', + 'DH_NaCl_bdotak_blessed.csv', + artifacts=['DH_graph_1.log'], + arguments='DH_NaCl_bdotak.xml'), + Test(pjoin('cathermo', 'DH_graph_NM'), + 'DH_graph_NM', + 'DH_NaCl_NM_blessed.csv', + artifacts=['DH_graph_1.log'], + arguments='DH_NaCl_NM.xml'), + Test(pjoin('cathermo', 'DH_graph_Pitzer'), + 'DH_graph_Pitzer', + 'DH_NaCl_Pitzer_blessed.csv', + artifacts=['DH_graph_1.log'], + arguments='DH_NaCl_Pitzer.xml'), + Test(pjoin('cathermo', 'HMW_dupl_test'), + 'HMW_dupl_test', + 'output_blessed.txt', + artifacts=['DH_graph_1.log'], + arguments='HMW_NaCl_sp1977_alt.xml'), + Test(pjoin('cathermo', 'HMW_graph_CpvT'), + 'HMW_graph_CpvT', + 'output_blessed.txt', + extensions=['^HMW_graph_CpvT.cpp', '^sortAlgorithms.cpp'], + arguments='HMW_NaCl_sp1977_alt.xml'), + # Skipping cathermo/HMW_graph_GvI because of the way it generates output files. + Test(pjoin('cathermo', 'HMW_graph_GvT'), + 'HMW_graph_GvT', + 'output_blessed.txt', + extensions=['^HMW_graph_GvT.cpp', '^sortAlgorithms.cpp'], + arguments='HMW_NaCl_sp1977_alt.xml') + ] + +env.Alias('test', [test.run(localenv) for test in tests]) +env.Alias('test-clean', sum([test.clean(localenv) for test in tests], []))