3 # runtest.py - wrapper script for running SCons tests
5 # This script mainly exists to set PYTHONPATH to the right list of
6 # directories to test the SCons modules.
8 # By default, it directly uses the modules in the local tree:
9 # ./src/ (source files we ship) and ./etc/ (other modules we don't).
11 # HOWEVER, now that SCons has Repository support, we don't have
12 # Aegis copy all of the files into the local tree. So if you're
13 # using Aegis and want to run tests by hand using this script, you
14 # must "aecp ." the entire source tree into your local directory
15 # structure. When you're done with your change, you can then
16 # "aecpu -unch ." to un-copy any files that you haven't changed.
18 # When any -p option is specified, this script assumes it's in a
19 # directory in which a build has been performed, and sets PYTHONPATH
20 # so that it *only* references the modules that have unpacked from
21 # the specified built package, to test whether the packages are good.
25 # -a Run all tests; does a virtual 'find' for
26 # all SCons tests under the current directory.
28 # -d Debug. Runs the script under the Python
29 # debugger (pdb.py) so you don't have to
30 # muck with PYTHONPATH yourself.
32 # -h Print the help and exit.
34 # -o file Print test results to the specified file
35 # in the format expected by aetest(5). This
36 # is intended for use in the batch_test_command
37 # field in the Aegis project config file.
39 # -P Python Use the specified Python interpreter.
41 # -p package Test against the specified package.
43 # -q Quiet. By default, runtest.py prints the
44 # command line it will execute before
45 # executing it. This suppresses that print.
47 # -X The scons "script" is an executable; don't
50 # -x scons The scons script to use for tests.
52 # (Note: There used to be a -v option that specified the SCons
53 # version to be tested, when we were installing in a version-specific
54 # library directory. If we ever resurrect that as the default, then
55 # you can find the appropriate code in the 0.04 version of this script,
56 # rather than reinventing that wheel.)
79 python = os.path.join(sys.prefix, 'jython')
81 python = sys.executable
85 if sys.platform == 'win32' or os.name == 'java':
86 lib_dir = os.path.join(sys.exec_prefix, "Lib")
88 # The hard-coded "python" here is the directory name,
89 # not an executable, so it's all right.
90 lib_dir = os.path.join(sys.exec_prefix, "lib", "python" + sys.version[0:3])
93 Usage: runtest.py [OPTIONS] [TEST ...]
95 -a, --all Run all tests.
96 -d, --debug Run test scripts under the Python debugger.
97 -h, --help Print this message and exit.
98 -o FILE, --output FILE Print test results to FILE (Aegis format).
99 -P Python Use the specified Python interpreter.
100 -p PACKAGE, --package PACKAGE
101 Test against the specified PACKAGE:
103 local-tar-gz .tar.gz standalone package
104 local-zip .zip standalone package
106 src-tar-gz .tar.gz source package
107 src-zip .zip source package
108 tar-gz .tar.gz distribution
109 zip .zip distribution
110 -q, --quiet Don't print the test being executed.
111 -v version Specify the SCons version.
112 -X Test script is executable, don't feed to Python.
113 -x SCRIPT, --exec SCRIPT Test SCRIPT.
116 opts, args = getopt.getopt(sys.argv[1:], "adho:P:p:qv:Xx:",
117 ['all', 'debug', 'help', 'output=',
118 'package=', 'python=', 'quiet',
119 'version=', 'exec='])
122 if o == '-a' or o == '--all':
124 elif o == '-d' or o == '--debug':
125 debug = os.path.join(lib_dir, "pdb.py")
126 elif o == '-h' or o == '--help':
129 elif o == '-o' or o == '--output':
130 if not os.path.isabs(a):
131 a = os.path.join(cwd, a)
133 elif o == '-P' or o == '--python':
135 elif o == '-p' or o == '--package':
137 elif o == '-q' or o == '--quiet':
139 elif o == '-v' or o == '--version':
143 elif o == '-x' or o == '--exec':
147 for dir in string.split(os.environ['PATH'], os.pathsep):
148 f = os.path.join(dir, file)
149 if os.path.isfile(f):
154 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
158 aegis = whereis('aegis')
163 paths = os.popen("aesub '$sp' 2>/dev/null", "r").read()[:-1]
164 sp.extend(string.split(paths, os.pathsep))
165 spe = os.popen("aesub '$spe' 2>/dev/null", "r").read()[:-1]
166 spe = string.split(spe, os.pathsep)
173 def __init__(self, path, spe=None):
175 self.abspath = os.path.abspath(path)
178 f = os.path.join(dir, path)
179 if os.path.isfile(f):
188 for g in glob.glob(a):
189 tests.append(Test(g))
192 x = os.path.join(dir, a)
196 tests.append(Test(g))
200 for g in glob.glob(a):
201 tests.append(Test(g))
205 def find_Test_py(arg, dirname, names, tdict=tdict):
206 for n in filter(lambda n: n[-8:] == "Tests.py", names):
207 t = os.path.join(dirname, n)
208 if not tdict.has_key(t):
210 os.path.walk('src', find_Test_py, 0)
212 def find_py(arg, dirname, names, tdict=tdict):
213 for n in filter(lambda n: n[-3:] == ".py", names):
214 t = os.path.join(dirname, n)
215 if not tdict.has_key(t):
217 os.path.walk('test', find_py, 0)
220 cmd = "aegis -list -unf pf 2>/dev/null"
221 for line in os.popen(cmd, "r").readlines():
222 a = string.split(line)
223 if a[0] == "test" and not tdict.has_key(a[-1]):
224 tdict[a[-1]] = Test(a[-1], spe)
225 cmd = "aegis -list -unf cf 2>/dev/null"
226 for line in os.popen(cmd, "r").readlines():
227 a = string.split(line)
231 elif not tdict.has_key(a[-1]):
232 tdict[a[-1]] = Test(a[-1], spe)
236 tests = map(tdict.get, keys)
238 sys.stderr.write("""\
239 runtest.py: No tests were specified on the command line.
240 List one or more tests, or use the -a option
241 to find and run all tests.
249 'local-tar-gz' : None,
258 # The hard-coded "python2.1" here is the library directory
259 # name on Debian systems, not an executable, so it's all right.
261 'deb' : os.path.join('python2.1', 'site-packages')
264 if not dir.has_key(package):
265 sys.stderr.write("Unknown package '%s'\n" % package)
268 test_dir = os.path.join(cwd, 'build', 'test-%s' % package)
270 if dir[package] is None:
271 scons_script_dir = test_dir
272 globs = glob.glob(os.path.join(test_dir, 'scons-local-*'))
274 sys.stderr.write("No `scons-local-*' dir in `%s'\n" % test_dir)
277 pythonpath_dir = globs[len(globs)-1]
278 elif sys.platform == 'win32':
279 scons_script_dir = os.path.join(test_dir, dir[package], 'Scripts')
280 scons_lib_dir = os.path.join(test_dir, dir[package])
281 pythonpath_dir = scons_lib_dir
283 scons_script_dir = os.path.join(test_dir, dir[package], 'bin')
284 l = lib.get(package, 'scons')
285 scons_lib_dir = os.path.join(test_dir, dir[package], 'lib', l)
286 pythonpath_dir = scons_lib_dir
292 # XXX: Logic like the following will be necessary once
293 # we fix runtest.py to run tests within an Aegis change
294 # without symlinks back to the baseline(s).
299 # d = os.path.join(dir, 'src', 'script')
300 # f = os.path.join(d, 'scons.py')
301 # if os.path.isfile(f):
304 # spe = map(lambda x: os.path.join(x, 'src', 'engine'), spe)
305 # ld = string.join(spe, os.pathsep)
307 scons_script_dir = sd or os.path.join(cwd, 'src', 'script')
309 scons_lib_dir = ld or os.path.join(cwd, 'src', 'engine')
311 pythonpath_dir = scons_lib_dir
314 # Let the version of SCons that the -x option pointed to find
316 os.environ['SCONS'] = scons
318 # Because SCons is really aggressive about finding its modules,
319 # it sometimes finds SCons modules elsewhere on the system.
320 # This forces SCons to use the modules that are being tested.
321 os.environ['SCONS_LIB_DIR'] = scons_lib_dir
324 os.environ['SCONS_EXEC'] = '1'
326 os.environ['SCONS_SCRIPT_DIR'] = scons_script_dir
327 os.environ['SCONS_CWD'] = cwd
329 os.environ['SCONS_VERSION'] = version
331 old_pythonpath = os.environ.get('PYTHONPATH')
333 pythonpaths = [ pythonpath_dir ]
335 pythonpaths.append(os.path.join(p, 'build', 'etc'))
336 pythonpaths.append(os.path.join(p, 'etc'))
337 os.environ['PYTHONPATH'] = string.join(pythonpaths, os.pathsep)
340 os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + \
344 os.chdir(scons_script_dir)
347 def __init__(self, file):
349 def write(self, arg):
352 def __getattr__(self, attr):
353 return getattr(self.file, attr)
355 sys.stdout = Unbuffered(sys.stdout)
358 cmd = string.join([python, debug, t.abspath], " ")
360 sys.stdout.write(cmd + "\n")
366 sys.stdout.write("Unexpected exit status %d\n" % s)
368 fail = filter(lambda t: t.status == 1, tests)
369 no_result = filter(lambda t: t.status == 2, tests)
374 sys.stdout.write("\nFailed the following test:\n")
376 sys.stdout.write("\nFailed the following %d tests:\n" % len(fail))
377 paths = map(lambda x: x.path, fail)
378 sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
380 if len(no_result) == 1:
381 sys.stdout.write("\nNO RESULT from the following test:\n")
383 sys.stdout.write("\nNO RESULT from the following %d tests:\n" % len(no_result))
384 paths = map(lambda x: x.path, no_result)
385 sys.stdout.write("\t" + string.join(paths, "\n\t") + "\n")
388 f = open(output, 'w')
389 f.write("test_result = [\n")
391 f.write(' { file_name = "%s";\n' % t.path)
392 f.write(' exit_status = %d; },\n' % t.status)