Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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?

share|improve this question
    
Have you looked at jenkins? See packages.python.org/python-jenkins and alexconrad.org/2011/10/jenkins-and-python.html –  Duncan Sep 19 '12 at 11:44
add comment

1 Answer 1

up vote 1 down vote accepted

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
try:
    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
    else:
        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:
        env.Append(TEST_RUNNER='py.test')
    if not 'TEST_DIR_LIST' in env:
        sys.stderr.write("+++ TEST_DIR_LIST is not set, using project root.\n")
        env.Replace(TEST_DIR_LIST=['./'])
    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:
            try:
                utest_fail(line + u" ✘  Unit tests failed!\n" + line)
            except UnicodeEncodeError:
                utest_fail(line + " -  Unit tests failed!\n" + line)
        else:
            try:
                utest_pass(line + u" ✓  Unit tests passed.\n" + line)
            except UnicodeEncodeError:
                utest_pass(line + " +  Unit tests passed.\n" + line)
        env.Exit(retCode)
    else:
        sys.stderr.write("*** Could not find %s command\n" % (
            env['TEST_RUNNER']))
        env.Exit(-1)

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!!!

share|improve this answer
    
Actually, this code assumes that the terminal is 80 characters long. How to make it find the actual width is left as an exercise for the reader. –  Sardathrion Nov 28 '12 at 10:09
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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