--- /dev/null
+#!/usr/bin/env python
+
+import re
+from sys import stdout, stderr
+from optparse import OptionParser
+from glob import glob
+from os.path import basename, splitext, abspath
+from subprocess import Popen, PIPE
+from ctypes import cdll, c_int
+
+API_VERSION = 1
+
+V_QUIET = 0
+V_ERROR = 1
+V_SUMMARY = 2
+V_SUITE = 3
+V_CASE = 4
+V_CHECK = 5
+
+verbose_level = V_ERROR
+
+
+SUMMARY_TEXT = '''
+Tests done:
+ %(passed_suites)s test suite(s) passed, %(failed_suites)s failed, \
+%(skipped_suites)s skipped.
+ %(passed_cases)s test case(s) passed, %(failed_cases)s failed.
+ %(passed_checks)s check(s) passed, %(failed_checks)s failed.
+'''
+
+def log(level, msg, *args):
+ global verbose_level
+ out = stdout
+ if level == V_ERROR:
+ stderr
+ if verbose_level >= level:
+ out.write((msg % args) + '\n')
+
+
+#class SOError (Exception):
+# pass
+
+
+class TestCase(object):
+
+ def __init__(self, so, name):
+ self.so = so
+ self.name = name
+ self.testcase = self.get_fun(name)
+ self.reset_counters = self.get_fun('mutest_reset_counters')
+ self.set_verbose_level = self.get_fun(
+ 'mutest_set_verbose_level', argtype=[c_int])
+
+ @property
+ def passed_count(self):
+ return self.get_val('mutest_passed_count')
+
+ @property
+ def failed_count(self):
+ return self.get_val('mutest_failed_count')
+
+ def get_fun(self, name, argtype=None, restype=None):
+ f = getattr(self.so, name)
+ f.argtypes = argtype
+ f.restype = restype
+ return f
+
+ def get_val(self, name):
+ return c_int.in_dll(self.so, name).value
+
+ def run(self):
+ global verbose_level
+ self.set_verbose_level(verbose_level)
+ self.reset_counters()
+ self.testcase()
+ return (self.passed_count, self.failed_count)
+
+
+class TestSuiteResult (object):
+ failed = False
+ passed_cases = 0
+ failed_cases = 0
+ passed_checks = 0
+ failed_checks = 0
+
+ def __repr__(self):
+ return 'TestSuiteResult(failed=%s, passed_cases=%s, '\
+ 'failed_cases=%s, passed_checks=%s, failed_checks=%s)'\
+ % (self.failed,
+ self.passed_cases, self.failed_cases,
+ self.passed_checks, self.failed_checks)
+
+
+class TestSuite (object):
+
+ def __init__(self, so, name, case_names):
+ self.name = name
+ self.so = so
+ try:
+ self.api_version = c_int.in_dll(self.so,
+ 'mutest_api_version').value
+ except ValueError:
+ self.api_version = 0
+ return
+ self.cases = [TestCase(self.so, name) for name in case_names]
+
+ def run(self):
+ r = TestSuiteResult()
+ for case in self.cases:
+ log(V_CASE, "\t* Executing test case '%s'...",
+ case.name)
+ (case_passed_checks, case_failed_checks) = case.run()
+ log(V_CASE, '\t Results: %s check(s) passed, %s '
+ 'failed.', case_passed_checks,
+ case_failed_checks)
+ if case_failed_checks:
+ r.failed = True
+ r.failed_cases += 1
+ else:
+ r.passed_cases += 1
+ r.passed_checks += case_passed_checks
+ r.failed_checks += case_failed_checks
+ return r
+
+
+case_names_re = re.compile(r'[0-9a-f]{8} T (mu_test_\w+)', re.I)
+
+def get_case_names(so_name):
+ proc = Popen(['nm', '-p', so_name], stdout=PIPE)
+ output = proc.communicate()[0]
+ return case_names_re.findall(output)
+
+
+def parse_arguments(args):
+ verbose_help = ('Show a short result summary, add more for extra '
+ 'verbosity: -vv for test suites progress, -vvv for '
+ 'test cases progress and -vvvv for printing each '
+ 'and every check done')
+ quiet_help = ('Be quiet (overrides -v)')
+ search_help = ('Search for all test suites in the current directory '
+ '(*.so) and add them')
+ parser = OptionParser()
+ parser.add_option('-v', dest='verbose_level', action='count',
+ default=1, help=verbose_help)
+ parser.add_option('-q', '--verbose-level', dest='quiet',
+ action='store_true', default=False, help=quiet_help)
+ parser.add_option('-a', '--search-all', dest='search_all',
+ action='store_true', default=False, help=search_help)
+ return parser.parse_args()
+
+
+def main(args):
+ global verbose_level
+
+ (opts, args) = parse_arguments(args)
+
+ if opts.quiet:
+ verbose_level = 0
+ else:
+ verbose_level = opts.verbose_level
+
+ if opts.search_all:
+ args.extend(glob('*.so'))
+
+ if not args:
+ log(V_SUMMARY, 'No test suites to run')
+ return 0
+
+ results = dict(passed_suites=0, failed_suites=0, skipped_suites=0,
+ passed_cases=0, failed_cases=0,
+ passed_checks=0, failed_checks=0)
+
+ for so_name in args:
+ suite_name = splitext(basename(so_name))[0]
+ log(V_SUITE, '\nRunning test suite "%s"...', suite_name)
+
+ try:
+ so = cdll.LoadLibrary(abspath(so_name))
+ except OSError, e:
+ log(V_ERROR, 'Error loading "%s" (%s), skipping '
+ 'test suite "%s"', so_name, e,
+ suite_name)
+ results['skipped_suites'] += 1
+ continue
+
+ case_names = get_case_names(so_name)
+
+ suite = TestSuite(so, suite_name, case_names)
+
+ if suite.api_version != API_VERSION:
+ log(V_ERROR, 'Wrong API version (%s expected, %s '
+ 'found), skipping test suite "%s"',
+ API_VERSION, suite.api_version,
+ suite.name)
+ results['skipped_suites'] += 1
+ continue
+
+ r = suite.run()
+
+ log(V_SUITE, 'Results: %s test case(s) passed, %s failed, '
+ '%s check(s) passed, %s failed.',
+ r.passed_cases, r.failed_cases,
+ r.passed_checks, r.failed_checks)
+
+ if r.failed:
+ results['failed_suites'] += 1
+ else:
+ results['passed_suites'] += 1
+ results['failed_cases'] += r.failed_cases
+ results['passed_cases'] += r.passed_cases
+ results['failed_checks'] += r.failed_checks
+ results['passed_checks'] += r.passed_checks
+
+ log(V_SUMMARY, SUMMARY_TEXT % results)
+
+ return 0
+
+
+if __name__ == '__main__':
+ import sys
+ sys.exit(main(sys.argv[1:]))
+
--- /dev/null
+
+#include <stdio.h> /* printf(), fprintf() */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* this increments when the "API" changes */
+int mutest_api_version = 1;
+
+int mutest_passed_count;
+int mutest_failed_count;
+void mutest_reset_counters() {
+ mutest_passed_count = 0;
+ mutest_failed_count = 0;
+}
+
+/*
+ * Verbose level:
+ * 0: quiet
+ * 1: errors
+ * 2: summary
+ * 3: suites
+ * 4: cases
+ * 5: checks
+ */
+int mutest_verbose_level = 1;
+void mutest_set_verbose_level(int val) {
+ mutest_verbose_level = val;
+}
+
+#define mu_check(exp) \
+ do { \
+ if (exp) { \
+ ++mutest_passed_count; \
+ if (mutest_verbose_level >= 5) \
+ printf("%s:%d: mu_check(%s) passed\n", \
+ __FILE__, __LINE__, #exp); \
+ } else { \
+ ++mutest_failed_count; \
+ if (mutest_verbose_level) \
+ fprintf(stderr, "%s:%d: mu_check(%s) " \
+ "failed, resuming test case\n", \
+ __FILE__, __LINE__, #exp); \
+ } \
+ } while (0)
+
+#define mu_ensure(exp) \
+ do { \
+ if (exp) { \
+ ++mutest_passed_count; \
+ if (mutest_verbose_level >=5) \
+ printf("%s:%d: mu_ensure(%s) passed\n", \
+ __FILE__, __LINE__, #exp); \
+ } else { \
+ ++mutest_failed_count; \
+ if (mutest_verbose_level) \
+ fprintf(stderr, "%s:%d: mu_ensure(%s) " \
+ "failed, aborting test case\n", \
+ __FILE__, __LINE__, #exp); \
+ return; \
+ } \
+ } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+