1 | #!/usr/bin/env python |
---|
2 | """ |
---|
3 | runtests.py [OPTIONS] [-- ARGS] |
---|
4 | |
---|
5 | Run tests, building the project first. |
---|
6 | |
---|
7 | Examples:: |
---|
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 | |
---|
16 | Run a debugger: |
---|
17 | |
---|
18 | $ gdb --args python runtests.py [...other args...] |
---|
19 | |
---|
20 | Generate 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 | |
---|
33 | PROJECT_MODULE = "anuga" |
---|
34 | PROJECT_ROOT_FILES = ['anuga', 'LICENCE.txt', 'setup.py'] |
---|
35 | SAMPLE_TEST = "anuga/file/tests/test_basic.py:test_xlogy" |
---|
36 | SAMPLE_SUBMODULE = "file" |
---|
37 | |
---|
38 | EXTRA_PATH = [] |
---|
39 | |
---|
40 | # --------------------------------------------------------------------- |
---|
41 | |
---|
42 | |
---|
43 | if __doc__ is None: |
---|
44 | __doc__ = "Run without -OO if you want usage info" |
---|
45 | else: |
---|
46 | __doc__ = __doc__.format(**globals()) |
---|
47 | |
---|
48 | |
---|
49 | import sys |
---|
50 | import os |
---|
51 | |
---|
52 | # In case we are run from the source directory, we don't want to import the |
---|
53 | # project from there: |
---|
54 | sys.path.pop(0) |
---|
55 | |
---|
56 | import shutil |
---|
57 | import subprocess |
---|
58 | import time |
---|
59 | import imp |
---|
60 | from argparse import ArgumentParser, REMAINDER |
---|
61 | |
---|
62 | ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__))) |
---|
63 | |
---|
64 | def 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 | |
---|
244 | def 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 | # |
---|
340 | def 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 | |
---|
353 | LCOV_OUTPUT_FILE = os.path.join(ROOT_DIR, 'build', 'lcov.out') |
---|
354 | LCOV_HTML_DIR = os.path.join(ROOT_DIR, 'build', 'lcov') |
---|
355 | |
---|
356 | def 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 | |
---|
382 | if sys.version_info[0] >= 3: |
---|
383 | import builtins |
---|
384 | exec_ = getattr(builtins, "exec") |
---|
385 | else: |
---|
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 | |
---|
398 | if __name__ == "__main__": |
---|
399 | main(argv=sys.argv[1:]) |
---|