source: trunk/anuga_core/runtests.py @ 9680

Last change on this file since 9680 was 9649, checked in by steve, 10 years ago

Change setup.py so that on windows it used setuptools to setup the VC++ compiler

File size: 13.0 KB
Line 
1#!/usr/bin/env python
2"""
3runtests.py [OPTIONS] [-- ARGS]
4
5Run tests, building the project first.
6
7Examples::
8
9    $ python runtests.py
10    $ python runtests.py -s {SAMPLE_SUBMODULE}
11    $ python runtests.py -t {SAMPLE_TEST}
12    $ python runtests.py --ipython
13    $ python runtests.py --python somescript.py
14    $ python runtests.py --bench
15
16Run a debugger:
17
18    $ gdb --args python runtests.py [...other args...]
19
20Generate C code coverage listing under build/lcov/:
21(requires http://ltp.sourceforge.net/coverage/lcov.php)
22
23    $ python runtests.py --gcov [...other args...]
24    $ python runtests.py --lcov-html
25
26"""
27
28#
29# This is a generic test runner script for projects using Numpy's test
30# framework. Change the following values to adapt to your project:
31#
32
33PROJECT_MODULE = "anuga"
34PROJECT_ROOT_FILES = ['anuga', 'LICENCE.txt', 'setup.py']
35SAMPLE_TEST = "anuga/file/tests/test_basic.py:test_xlogy"
36SAMPLE_SUBMODULE = "file"
37
38EXTRA_PATH = []
39
40# ---------------------------------------------------------------------
41
42
43if __doc__ is None:
44    __doc__ = "Run without -OO if you want usage info"
45else:
46    __doc__ = __doc__.format(**globals())
47
48
49import sys
50import os
51
52# In case we are run from the source directory, we don't want to import the
53# project from there:
54sys.path.pop(0)
55
56import shutil
57import subprocess
58import time
59import imp
60from argparse import ArgumentParser, REMAINDER
61
62ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__)))
63
64def main(argv):
65    parser = ArgumentParser(usage=__doc__.lstrip())
66    parser.add_argument("--verbose", "-v", action="count", default=1,
67                        help="more verbosity")
68    parser.add_argument("--no-build", "-n", action="store_true", default=False,
69                        help="do not build the project (use system installed version)")
70    parser.add_argument("--build-only", "-b", action="store_true", default=False,
71                        help="just build, do not run any tests")
72    parser.add_argument("--doctests", action="store_true", default=False,
73                        help="Run doctests in module")
74    parser.add_argument("--coverage", action="store_true", default=False,
75                        help=("report coverage of project code. HTML output goes "
76                              "under build/coverage"))
77    parser.add_argument("--gcov", action="store_true", default=False,
78                        help=("enable C code coverage via gcov (requires GCC). "
79                              "gcov output goes to build/**/*.gc*"))
80    parser.add_argument("--lcov-html", action="store_true", default=False,
81                        help=("produce HTML for C code coverage information "
82                              "from a previous run with --gcov. "
83                              "HTML output goes to build/lcov/"))
84    parser.add_argument("--mode", "-m", default="fast",
85                        help="'fast', 'full', or something that could be "
86                             "passed to nosetests -A [default: fast]")
87    parser.add_argument("--submodule", "-s", default=None,
88                        help="Submodule whose tests to run (cluster, constants, ...)")
89    parser.add_argument("--pythonpath", "-p", default=None,
90                        help="Paths to prepend to PYTHONPATH")
91    parser.add_argument("--tests", "-t", action='append',
92                        help="Specify tests to run")
93    parser.add_argument("--python", action="store_true",
94                        help="Start a Python shell with PYTHONPATH set")
95    parser.add_argument("--ipython", "-i", action="store_true",
96                        help="Start IPython shell with PYTHONPATH set")
97    parser.add_argument("--shell", action="store_true",
98                        help="Start Unix shell with PYTHONPATH set")
99    parser.add_argument("--debug", "-g", action="store_true",
100                        help="Debug build")
101    parser.add_argument("--show-build-log", action="store_true",
102                        help="Show build output rather than using a log file")
103    parser.add_argument("--bench", action="store_true",
104                        help="Run benchmark suite instead of test suite")
105    parser.add_argument("args", metavar="ARGS", default=[], nargs=REMAINDER,
106                        help="Arguments to pass to Nose, Python or shell")
107    args = parser.parse_args(argv)
108
109    if args.lcov_html:
110        # generate C code coverage output
111        lcov_generate()
112        sys.exit(0)
113
114    if args.pythonpath:
115        for p in reversed(args.pythonpath.split(os.pathsep)):
116            sys.path.insert(0, p)
117
118    if args.gcov:
119        gcov_reset_counters()
120
121    if not args.no_build:
122        site_dir = build_project(args)
123        sys.path.insert(0, site_dir)
124        os.environ['PYTHONPATH'] = site_dir
125
126    extra_argv = args.args[:]
127    if extra_argv and extra_argv[0] == '--':
128        extra_argv = extra_argv[1:]
129
130    if args.python:
131        if extra_argv:
132            # Don't use subprocess, since we don't want to include the
133            # current path in PYTHONPATH.
134            sys.argv = extra_argv
135            with open(extra_argv[0], 'r') as f:
136                script = f.read()
137            sys.modules['__main__'] = imp.new_module('__main__')
138            ns = dict(__name__='__main__',
139                      __file__=extra_argv[0])
140            exec_(script, ns)
141            sys.exit(0)
142        else:
143            import code
144            code.interact()
145            sys.exit(0)
146
147    if args.ipython:
148        import IPython
149        IPython.embed(user_ns={})
150        sys.exit(0)
151
152    if args.shell:
153        shell = os.environ.get('SHELL', 'sh')
154        print("Spawning a Unix shell...")
155        os.execv(shell, [shell] + extra_argv)
156        sys.exit(1)
157
158    if args.coverage:
159        dst_dir = os.path.join(ROOT_DIR, 'build', 'coverage')
160        fn = os.path.join(dst_dir, 'coverage_html.js')
161        if os.path.isdir(dst_dir) and os.path.isfile(fn):
162            shutil.rmtree(dst_dir)
163        extra_argv += ['--cover-html',
164                       '--cover-html-dir='+dst_dir]
165
166    test_dir = os.path.join(ROOT_DIR, 'build', 'test')
167
168    if args.build_only:
169        sys.exit(0)
170    elif args.submodule:
171        modname = PROJECT_MODULE + '.' + args.submodule
172        try:
173            __import__(modname)
174            if args.bench:
175                test = sys.modules[modname].bench
176            else:
177                test = sys.modules[modname].test
178        except (ImportError, KeyError, AttributeError) as e:
179            print("Cannot run tests for %s (%s)" % (modname, e))
180            sys.exit(2)
181    elif args.tests:
182        def fix_test_path(x):
183            # fix up test path
184            p = x.split(':')
185            p[0] = os.path.relpath(os.path.abspath(p[0]),
186                                   test_dir)
187            return ':'.join(p)
188
189        tests = [fix_test_path(x) for x in args.tests]
190
191        def test(*a, **kw):
192            extra_argv = kw.pop('extra_argv', ())
193            extra_argv = extra_argv + tests[1:]
194            kw['extra_argv'] = extra_argv
195            from numpy.testing import Tester
196            if args.bench:
197                return Tester(tests[0]).bench(*a, **kw)
198            else:
199                return Tester(tests[0]).test(*a, **kw)
200    else:
201        __import__(PROJECT_MODULE)
202        if args.bench:
203            test = sys.modules[PROJECT_MODULE].bench
204        else:
205            test = sys.modules[PROJECT_MODULE].test
206
207    # Run the tests under build/test
208    try:
209        shutil.rmtree(test_dir)
210    except OSError:
211        pass
212    try:
213        os.makedirs(test_dir)
214    except OSError:
215        pass
216
217    shutil.copyfile(os.path.join(ROOT_DIR, '.coveragerc'),
218                    os.path.join(test_dir, '.coveragerc'))
219
220    cwd = os.getcwd()
221    try:
222        os.chdir(test_dir)
223        if args.bench:
224            result = test(args.mode,
225                          verbose=args.verbose,
226                          extra_argv=extra_argv)
227        else:
228            result = test(args.mode,
229                          verbose=args.verbose,
230                          extra_argv=extra_argv,
231                          doctests=args.doctests,
232                          coverage=args.coverage)
233    finally:
234        os.chdir(cwd)
235
236    if isinstance(result, bool):
237        sys.exit(0 if result else 1)
238    elif result.wasSuccessful():
239        sys.exit(0)
240    else:
241        sys.exit(1)
242
243
244def build_project(args):
245    """
246    Build a dev version of the project.
247
248    Returns
249    -------
250    site_dir
251        site-packages directory where it was installed
252
253    """
254
255    root_ok = [os.path.exists(os.path.join(ROOT_DIR, fn))
256               for fn in PROJECT_ROOT_FILES]
257    if not all(root_ok):
258        print("To build the project, run runtests.py in "
259              "git checkout or unpacked source")
260        sys.exit(1)
261
262    dst_dir = os.path.join(ROOT_DIR, 'build', 'testenv')
263
264    env = dict(os.environ)
265    cmd = [sys.executable, 'setup.py']
266
267    # Always use ccache, if installed
268    env['PATH'] = os.pathsep.join(EXTRA_PATH + env.get('PATH', '').split(os.pathsep))
269
270    if args.debug or args.gcov:
271        # assume everyone uses gcc/gfortran
272        env['OPT'] = '-O0 -ggdb'
273        env['FOPT'] = '-O0 -ggdb'
274        if args.gcov:
275            import distutils.sysconfig
276            cvars = distutils.sysconfig.get_config_vars()
277            env['OPT'] = '-O0 -ggdb'
278            env['FOPT'] = '-O0 -ggdb'
279            env['CC'] = cvars['CC'] + ' --coverage'
280            env['CXX'] = cvars['CXX'] + ' --coverage'
281            env['F77'] = 'gfortran --coverage '
282            env['F90'] = 'gfortran --coverage '
283            env['LDSHARED'] = cvars['LDSHARED'] + ' --coverage'
284            env['LDFLAGS'] = " ".join(cvars['LDSHARED'].split()[1:]) + ' --coverage'
285        cmd += ["build"]
286
287    cmd += ['install', '--prefix=' + dst_dir]
288   
289#     local_site_packages_dir = os.path.join(dst_dir, 'lib','python2.7','site-packages')
290#     env['PYTHONPATH'] = local_site_packages_dir
291#     import os
292#     os.system('mkdir -p %s' % local_site_packages_dir)
293
294    log_filename = os.path.join(ROOT_DIR, 'build.log')
295
296    if args.show_build_log:
297        ret = subprocess.call(cmd, env=env, cwd=ROOT_DIR)
298    else:
299        log_filename = os.path.join(ROOT_DIR, 'build.log')
300        print("Building, see build.log...")
301        with open(log_filename, 'w') as log:
302            p = subprocess.Popen(cmd, env=env, stdout=log, stderr=log,
303                                 cwd=ROOT_DIR)
304
305        # Wait for it to finish, and print something to indicate the
306        # process is alive, but only if the log file has grown (to
307        # allow continuous integration environments kill a hanging
308        # process accurately if it produces no output)
309        last_blip = time.time()
310        last_log_size = os.stat(log_filename).st_size
311        while p.poll() is None:
312            time.sleep(0.5)
313            if time.time() - last_blip > 60:
314                log_size = os.stat(log_filename).st_size
315                if log_size > last_log_size:
316                    print("    ... build in progress")
317                    last_blip = time.time()
318                    last_log_size = log_size
319
320        ret = p.wait()
321
322    if ret == 0:
323        print("Build OK")
324    else:
325        if not args.show_build_log:
326            with open(log_filename, 'r') as f:
327                print(f.read())
328            print("Build failed!")
329        sys.exit(1)
330
331    from distutils.sysconfig import get_python_lib
332    site_dir = get_python_lib(prefix=dst_dir, plat_specific=True)
333
334    return site_dir
335
336
337#
338# GCOV support
339#
340def gcov_reset_counters():
341    print("Removing previous GCOV .gcda files...")
342    build_dir = os.path.join(ROOT_DIR, 'build')
343    for dirpath, dirnames, filenames in os.walk(build_dir):
344        for fn in filenames:
345            if fn.endswith('.gcda') or fn.endswith('.da'):
346                pth = os.path.join(dirpath, fn)
347                os.unlink(pth)
348
349#
350# LCOV support
351#
352
353LCOV_OUTPUT_FILE = os.path.join(ROOT_DIR, 'build', 'lcov.out')
354LCOV_HTML_DIR = os.path.join(ROOT_DIR, 'build', 'lcov')
355
356def lcov_generate():
357    try: os.unlink(LCOV_OUTPUT_FILE)
358    except OSError: pass
359    try: shutil.rmtree(LCOV_HTML_DIR)
360    except OSError: pass
361
362    print("Capturing lcov info...")
363    subprocess.call(['lcov', '-q', '-c',
364                     '-d', os.path.join(ROOT_DIR, 'build'),
365                     '-b', ROOT_DIR,
366                     '--output-file', LCOV_OUTPUT_FILE])
367
368    print("Generating lcov HTML output...")
369    ret = subprocess.call(['genhtml', '-q', LCOV_OUTPUT_FILE, 
370                           '--output-directory', LCOV_HTML_DIR, 
371                           '--legend', '--highlight'])
372    if ret != 0:
373        print("genhtml failed!")
374    else:
375        print("HTML output generated under build/lcov/")
376
377
378#
379# Python 3 support
380#
381
382if sys.version_info[0] >= 3:
383    import builtins
384    exec_ = getattr(builtins, "exec")
385else:
386    def exec_(code, globs=None, locs=None):
387        """Execute code in a namespace."""
388        if globs is None:
389            frame = sys._getframe(1)
390            globs = frame.f_globals
391            if locs is None:
392                locs = frame.f_locals
393            del frame
394        elif locs is None:
395            locs = globs
396        exec("""exec code in globs, locs""")
397
398if __name__ == "__main__":
399    main(argv=sys.argv[1:])
Note: See TracBrowser for help on using the repository browser.