2011-09-10 11:13:51 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# encoding: utf-8
|
|
|
|
# Carlos Rafael Giani, 2006
|
|
|
|
# Thomas Nagy, 2010
|
|
|
|
|
|
|
|
"""
|
|
|
|
Unit testing system for C/C++/D providing test execution:
|
|
|
|
|
|
|
|
* in parallel, by using ``waf -j``
|
|
|
|
* partial (only the tests that have changed) or full (by using ``waf --alltests``)
|
|
|
|
|
|
|
|
The tests are declared by adding the **test** feature to programs::
|
|
|
|
|
|
|
|
def options(opt):
|
|
|
|
opt.load('compiler_cxx waf_unit_test')
|
|
|
|
def configure(conf):
|
|
|
|
conf.load('compiler_cxx waf_unit_test')
|
|
|
|
def build(bld):
|
|
|
|
bld(features='cxx cxxprogram test', source='main.cpp', target='app')
|
|
|
|
# or
|
|
|
|
bld.program(features='test', source='main2.cpp', target='app2')
|
|
|
|
|
|
|
|
When the build is executed, the program 'test' will be built and executed without arguments.
|
|
|
|
The success/failure is detected by looking at the return code. The status and the standard output/error
|
|
|
|
are stored on the build context.
|
|
|
|
|
|
|
|
The results can be displayed by registering a callback function. Here is how to call
|
|
|
|
the predefined callback::
|
|
|
|
|
|
|
|
def build(bld):
|
|
|
|
bld(features='cxx cxxprogram test', source='main.c', target='app')
|
|
|
|
from waflib.Tools import waf_unit_test
|
|
|
|
bld.add_post_fun(waf_unit_test.summary)
|
|
|
|
"""
|
|
|
|
|
2014-04-14 18:54:04 +02:00
|
|
|
import os
|
2014-09-20 17:41:10 +02:00
|
|
|
from waflib.TaskGen import feature, after_method, taskgen_method
|
2011-09-10 11:13:51 +02:00
|
|
|
from waflib import Utils, Task, Logs, Options
|
|
|
|
testlock = Utils.threading.Lock()
|
|
|
|
|
|
|
|
@feature('test')
|
|
|
|
@after_method('apply_link')
|
|
|
|
def make_test(self):
|
|
|
|
"""Create the unit test task. There can be only one unit test task by task generator."""
|
|
|
|
if getattr(self, 'link_task', None):
|
|
|
|
self.create_task('utest', self.link_task.outputs)
|
|
|
|
|
2014-09-20 17:41:10 +02:00
|
|
|
|
|
|
|
@taskgen_method
|
|
|
|
def add_test_results(self, tup):
|
|
|
|
"""Override and return tup[1] to interrupt the build immediately if a test does not run"""
|
|
|
|
Logs.debug("ut: %r", tup)
|
|
|
|
self.utest_result = tup
|
|
|
|
try:
|
|
|
|
self.bld.utest_results.append(tup)
|
|
|
|
except AttributeError:
|
|
|
|
self.bld.utest_results = [tup]
|
|
|
|
|
2011-09-10 11:13:51 +02:00
|
|
|
class utest(Task.Task):
|
|
|
|
"""
|
|
|
|
Execute a unit test
|
|
|
|
"""
|
|
|
|
color = 'PINK'
|
|
|
|
after = ['vnum', 'inst']
|
|
|
|
vars = []
|
|
|
|
def runnable_status(self):
|
|
|
|
"""
|
2012-09-13 06:07:26 +02:00
|
|
|
Always execute the task if `waf --alltests` was used or no
|
2013-09-05 05:20:51 +02:00
|
|
|
tests if ``waf --notests`` was used
|
2011-09-10 11:13:51 +02:00
|
|
|
"""
|
2013-09-05 05:20:51 +02:00
|
|
|
if getattr(Options.options, 'no_tests', False):
|
|
|
|
return Task.SKIP_ME
|
2012-09-13 06:07:26 +02:00
|
|
|
|
2011-09-10 11:13:51 +02:00
|
|
|
ret = super(utest, self).runnable_status()
|
|
|
|
if ret == Task.SKIP_ME:
|
|
|
|
if getattr(Options.options, 'all_tests', False):
|
|
|
|
return Task.RUN_ME
|
|
|
|
return ret
|
|
|
|
|
2014-09-21 10:58:52 +02:00
|
|
|
def add_path(self, dct, path, var):
|
|
|
|
dct[var] = os.pathsep.join(Utils.to_list(path) + [os.environ.get(var, '')])
|
|
|
|
|
2014-09-21 11:03:34 +02:00
|
|
|
def get_test_env(self):
|
2011-09-10 11:13:51 +02:00
|
|
|
"""
|
2014-09-21 11:03:34 +02:00
|
|
|
In general, tests may require any library built anywhere in the project.
|
|
|
|
Override this method if fewer paths are needed
|
2011-09-10 11:13:51 +02:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
fu = getattr(self.generator.bld, 'all_test_paths')
|
|
|
|
except AttributeError:
|
2013-05-19 08:49:40 +02:00
|
|
|
# this operation may be performed by at most #maxjobs
|
2011-09-10 11:13:51 +02:00
|
|
|
fu = os.environ.copy()
|
|
|
|
|
|
|
|
lst = []
|
|
|
|
for g in self.generator.bld.groups:
|
|
|
|
for tg in g:
|
|
|
|
if getattr(tg, 'link_task', None):
|
2013-05-19 08:49:40 +02:00
|
|
|
s = tg.link_task.outputs[0].parent.abspath()
|
|
|
|
if s not in lst:
|
|
|
|
lst.append(s)
|
2011-09-10 11:13:51 +02:00
|
|
|
|
|
|
|
if Utils.is_win32:
|
2014-09-21 10:58:52 +02:00
|
|
|
self.add_path(fu, lst, 'PATH')
|
2011-11-19 13:35:21 +01:00
|
|
|
elif Utils.unversioned_sys_platform() == 'darwin':
|
2014-09-21 10:58:52 +02:00
|
|
|
self.add_path(fu, lst, 'DYLD_LIBRARY_PATH')
|
|
|
|
self.add_path(fu, lst, 'LD_LIBRARY_PATH')
|
2011-09-10 11:13:51 +02:00
|
|
|
else:
|
2014-09-21 10:58:52 +02:00
|
|
|
self.add_path(fu, lst, 'LD_LIBRARY_PATH')
|
2012-09-19 08:11:42 +02:00
|
|
|
self.generator.bld.all_test_paths = fu
|
2014-09-21 11:03:34 +02:00
|
|
|
return fu
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""
|
|
|
|
Execute the test. The execution is always successful, and the results
|
|
|
|
are stored on ``self.generator.bld.utest_results`` for postprocessing.
|
|
|
|
|
|
|
|
Override ``add_test_results`` to interrupt the build
|
|
|
|
"""
|
|
|
|
|
|
|
|
filename = self.inputs[0].abspath()
|
|
|
|
self.ut_exec = getattr(self.generator, 'ut_exec', [filename])
|
|
|
|
if getattr(self.generator, 'ut_fun', None):
|
|
|
|
self.generator.ut_fun(self)
|
2011-09-10 11:13:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
cwd = getattr(self.generator, 'ut_cwd', '') or self.inputs[0].parent.abspath()
|
2012-09-13 06:07:26 +02:00
|
|
|
|
2014-04-14 18:54:04 +02:00
|
|
|
testcmd = getattr(self.generator, 'ut_cmd', False) or getattr(Options.options, 'testcmd', False)
|
2013-09-05 05:20:51 +02:00
|
|
|
if testcmd:
|
|
|
|
self.ut_exec = (testcmd % self.ut_exec[0]).split(' ')
|
2012-09-13 06:07:26 +02:00
|
|
|
|
2014-09-21 11:03:34 +02:00
|
|
|
proc = Utils.subprocess.Popen(self.ut_exec, cwd=cwd, env=self.get_test_env(), stderr=Utils.subprocess.PIPE, stdout=Utils.subprocess.PIPE)
|
2011-09-10 11:13:51 +02:00
|
|
|
(stdout, stderr) = proc.communicate()
|
|
|
|
|
|
|
|
tup = (filename, proc.returncode, stdout, stderr)
|
|
|
|
testlock.acquire()
|
|
|
|
try:
|
2014-09-20 17:41:10 +02:00
|
|
|
return self.generator.add_test_results(tup)
|
2011-09-10 11:13:51 +02:00
|
|
|
finally:
|
|
|
|
testlock.release()
|
|
|
|
|
|
|
|
def summary(bld):
|
|
|
|
"""
|
|
|
|
Display an execution summary::
|
|
|
|
|
|
|
|
def build(bld):
|
|
|
|
bld(features='cxx cxxprogram test', source='main.c', target='app')
|
|
|
|
from waflib.Tools import waf_unit_test
|
|
|
|
bld.add_post_fun(waf_unit_test.summary)
|
|
|
|
"""
|
|
|
|
lst = getattr(bld, 'utest_results', [])
|
|
|
|
if lst:
|
|
|
|
Logs.pprint('CYAN', 'execution summary')
|
|
|
|
|
|
|
|
total = len(lst)
|
|
|
|
tfail = len([x for x in lst if x[1]])
|
|
|
|
|
|
|
|
Logs.pprint('CYAN', ' tests that pass %d/%d' % (total-tfail, total))
|
|
|
|
for (f, code, out, err) in lst:
|
|
|
|
if not code:
|
|
|
|
Logs.pprint('CYAN', ' %s' % f)
|
|
|
|
|
|
|
|
Logs.pprint('CYAN', ' tests that fail %d/%d' % (tfail, total))
|
|
|
|
for (f, code, out, err) in lst:
|
|
|
|
if code:
|
|
|
|
Logs.pprint('CYAN', ' %s' % f)
|
|
|
|
|
2012-09-13 06:07:26 +02:00
|
|
|
def set_exit_code(bld):
|
2013-09-05 05:20:51 +02:00
|
|
|
"""
|
|
|
|
If any of the tests fail waf will exit with that exit code.
|
|
|
|
This is useful if you have an automated build system which need
|
|
|
|
to report on errors from the tests.
|
|
|
|
You may use it like this:
|
2012-09-13 06:07:26 +02:00
|
|
|
|
|
|
|
def build(bld):
|
|
|
|
bld(features='cxx cxxprogram test', source='main.c', target='app')
|
|
|
|
from waflib.Tools import waf_unit_test
|
|
|
|
bld.add_post_fun(waf_unit_test.set_exit_code)
|
2013-09-05 05:20:51 +02:00
|
|
|
"""
|
|
|
|
lst = getattr(bld, 'utest_results', [])
|
|
|
|
for (f, code, out, err) in lst:
|
|
|
|
if code:
|
|
|
|
msg = []
|
|
|
|
if out:
|
|
|
|
msg.append('stdout:%s%s' % (os.linesep, out.decode('utf-8')))
|
|
|
|
if err:
|
|
|
|
msg.append('stderr:%s%s' % (os.linesep, err.decode('utf-8')))
|
|
|
|
bld.fatal(os.linesep.join(msg))
|
2012-09-13 06:07:26 +02:00
|
|
|
|
|
|
|
|
2011-09-10 11:13:51 +02:00
|
|
|
def options(opt):
|
|
|
|
"""
|
2012-09-13 06:07:26 +02:00
|
|
|
Provide the ``--alltests``, ``--notests`` and ``--testcmd`` command-line options.
|
2011-09-10 11:13:51 +02:00
|
|
|
"""
|
2013-09-05 05:20:51 +02:00
|
|
|
opt.add_option('--notests', action='store_true', default=False, help='Exec no unit tests', dest='no_tests')
|
2011-09-10 11:13:51 +02:00
|
|
|
opt.add_option('--alltests', action='store_true', default=False, help='Exec all unit tests', dest='all_tests')
|
2013-09-05 05:20:51 +02:00
|
|
|
opt.add_option('--testcmd', action='store', default=False,
|
|
|
|
help = 'Run the unit tests using the test-cmd string'
|
|
|
|
' example "--test-cmd="valgrind --error-exitcode=1'
|
|
|
|
' %s" to run under valgrind', dest='testcmd')
|
2011-09-10 11:13:51 +02:00
|
|
|
|