I am using scons to build a large project containing a mix of C++ and Python. I would like scons to run Python unit tests either using nose or not. Currently, we have a long list of tests files and run a test builder on each one. This causes each test file to be run as a separate script which feel inelegant and inefficient.

Is there a better way of doing this?


1 Answer 1


In the end, I created a custom builder which called an external test runner. Just save the following to site_scons/site_tools/run_py_test.py, import it in the main SConstruct file, and use it as you would any other builder.

import sys
import os
import subprocess
    from fabulous.color import red, green, fg256
    utest_fail = lambda x: sys.stderr.write(red(x).as_utf8+'\n')
    utest_pass = lambda x: sys.stdout.write(green(x).as_utf8+'\n')
    utest_directory = lambda x: sys.stdout.write(fg256(63, x).as_utf8+'\n')
except ImportError:
    sys.stdout.write('Fabulous module not found: '
            'To install it, run sudo pip-python install fabulous\n')
    utest_fail = lambda x: sys.stderr.write(x)
    utest_pass = lambda x: sys.stdout.write(x)
    utest_directory = lambda x: sys.stdout.write(x+'\n')

def which (program):
    """Reimplementation of the shell command 'which'.

    @see: http://stackoverflow.com/a/377028/232794
    @type program: String
    @param program: The program name.
    @return The full path to the program or None.
    is_exe = lambda x: os.path.isfile(x) and os.access(x, os.X_OK)
    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file
    return None

def runTests (target = None, source = None, env = None):
    """Run a test aggregation tool (by default py.test) in directories 
    provided in TEST_DIR_LIST.

    @type target: String
    @param target: A target.
    @type source: String
    @param source: A source.
    @type env: SCONS ENV.
    @param env: This should contain TEST_RUNNER or TEST_DIR_LIST otherwise
                defaults values are used.
    if not 'TEST_RUNNER' in env:
    if not 'TEST_DIR_LIST' in env:
        sys.stderr.write("+++ TEST_DIR_LIST is not set, using project root.\n")
    cmd = which(env['TEST_RUNNER'])
    if cmd:
        retCode = 0
        for path in env['TEST_DIR_LIST']:
            utest_directory('\n+++ Running unit tests in directory %s:' % (path))
            retCode += subprocess.call([cmd], cwd=path)
        line = "="*80 + "\n"
        if retCode:
                utest_fail(line + u" ✘  Unit tests failed!\n" + line)
            except UnicodeEncodeError:
                utest_fail(line + " -  Unit tests failed!\n" + line)
                utest_pass(line + u" ✓  Unit tests passed.\n" + line)
            except UnicodeEncodeError:
                utest_pass(line + " +  Unit tests passed.\n" + line)
        sys.stderr.write("*** Could not find %s command\n" % (

If you are going to down vote, at least leave a comment as to why this is a bad answer so it can be improved!!!


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.