1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/runcppunittests.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,191 @@ 1.4 +#!/usr/bin/env python 1.5 +# 1.6 +# This Source Code Form is subject to the terms of the Mozilla Public 1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.9 + 1.10 +from __future__ import with_statement 1.11 +import sys, os, tempfile, shutil 1.12 +from optparse import OptionParser 1.13 +import mozprocess, mozinfo, mozlog, mozcrash, mozfile 1.14 +from contextlib import contextmanager 1.15 + 1.16 +log = mozlog.getLogger('cppunittests') 1.17 + 1.18 +class CPPUnitTests(object): 1.19 + # Time (seconds) to wait for test process to complete 1.20 + TEST_PROC_TIMEOUT = 900 1.21 + # Time (seconds) in which process will be killed if it produces no output. 1.22 + TEST_PROC_NO_OUTPUT_TIMEOUT = 300 1.23 + 1.24 + def run_one_test(self, prog, env, symbols_path=None): 1.25 + """ 1.26 + Run a single C++ unit test program. 1.27 + 1.28 + Arguments: 1.29 + * prog: The path to the test program to run. 1.30 + * env: The environment to use for running the program. 1.31 + * symbols_path: A path to a directory containing Breakpad-formatted 1.32 + symbol files for producing stack traces on crash. 1.33 + 1.34 + Return True if the program exits with a zero status, False otherwise. 1.35 + """ 1.36 + basename = os.path.basename(prog) 1.37 + log.info("Running test %s", basename) 1.38 + with mozfile.TemporaryDirectory() as tempdir: 1.39 + proc = mozprocess.ProcessHandler([prog], 1.40 + cwd=tempdir, 1.41 + env=env) 1.42 + #TODO: After bug 811320 is fixed, don't let .run() kill the process, 1.43 + # instead use a timeout in .wait() and then kill to get a stack. 1.44 + proc.run(timeout=CPPUnitTests.TEST_PROC_TIMEOUT, 1.45 + outputTimeout=CPPUnitTests.TEST_PROC_NO_OUTPUT_TIMEOUT) 1.46 + proc.wait() 1.47 + if proc.timedOut: 1.48 + log.testFail("%s | timed out after %d seconds", 1.49 + basename, CPPUnitTests.TEST_PROC_TIMEOUT) 1.50 + return False 1.51 + if mozcrash.check_for_crashes(tempdir, symbols_path, 1.52 + test_name=basename): 1.53 + log.testFail("%s | test crashed", basename) 1.54 + return False 1.55 + result = proc.proc.returncode == 0 1.56 + if not result: 1.57 + log.testFail("%s | test failed with return code %d", 1.58 + basename, proc.proc.returncode) 1.59 + return result 1.60 + 1.61 + def build_core_environment(self, env = {}): 1.62 + """ 1.63 + Add environment variables likely to be used across all platforms, including remote systems. 1.64 + """ 1.65 + env["MOZILLA_FIVE_HOME"] = self.xre_path 1.66 + env["MOZ_XRE_DIR"] = self.xre_path 1.67 + #TODO: switch this to just abort once all C++ unit tests have 1.68 + # been fixed to enable crash reporting 1.69 + env["XPCOM_DEBUG_BREAK"] = "stack-and-abort" 1.70 + env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" 1.71 + env["MOZ_CRASHREPORTER"] = "1" 1.72 + return env 1.73 + 1.74 + def build_environment(self): 1.75 + """ 1.76 + Create and return a dictionary of all the appropriate env variables and values. 1.77 + On a remote system, we overload this to set different values and are missing things like os.environ and PATH. 1.78 + """ 1.79 + if not os.path.isdir(self.xre_path): 1.80 + raise Exception("xre_path does not exist: %s", self.xre_path) 1.81 + env = dict(os.environ) 1.82 + env = self.build_core_environment(env) 1.83 + pathvar = "" 1.84 + if mozinfo.os == "linux": 1.85 + pathvar = "LD_LIBRARY_PATH" 1.86 + elif mozinfo.os == "mac": 1.87 + pathvar = "DYLD_LIBRARY_PATH" 1.88 + elif mozinfo.os == "win": 1.89 + pathvar = "PATH" 1.90 + if pathvar: 1.91 + if pathvar in env: 1.92 + env[pathvar] = "%s%s%s" % (self.xre_path, os.pathsep, env[pathvar]) 1.93 + else: 1.94 + env[pathvar] = self.xre_path 1.95 + 1.96 + # Use llvm-symbolizer for ASan if available/required 1.97 + llvmsym = os.path.join(self.xre_path, "llvm-symbolizer") 1.98 + if os.path.isfile(llvmsym): 1.99 + env["ASAN_SYMBOLIZER_PATH"] = llvmsym 1.100 + 1.101 + return env 1.102 + 1.103 + def run_tests(self, programs, xre_path, symbols_path=None): 1.104 + """ 1.105 + Run a set of C++ unit test programs. 1.106 + 1.107 + Arguments: 1.108 + * programs: An iterable containing paths to test programs. 1.109 + * xre_path: A path to a directory containing a XUL Runtime Environment. 1.110 + * symbols_path: A path to a directory containing Breakpad-formatted 1.111 + symbol files for producing stack traces on crash. 1.112 + 1.113 + Returns True if all test programs exited with a zero status, False 1.114 + otherwise. 1.115 + """ 1.116 + self.xre_path = xre_path 1.117 + env = self.build_environment() 1.118 + pass_count = 0 1.119 + fail_count = 0 1.120 + for prog in programs: 1.121 + single_result = self.run_one_test(prog, env, symbols_path) 1.122 + if single_result: 1.123 + pass_count += 1 1.124 + else: 1.125 + fail_count += 1 1.126 + 1.127 + log.info("Result summary:") 1.128 + log.info("Passed: %d" % pass_count) 1.129 + log.info("Failed: %d" % fail_count) 1.130 + return fail_count == 0 1.131 + 1.132 +class CPPUnittestOptions(OptionParser): 1.133 + def __init__(self): 1.134 + OptionParser.__init__(self) 1.135 + self.add_option("--xre-path", 1.136 + action = "store", type = "string", dest = "xre_path", 1.137 + default = None, 1.138 + help = "absolute path to directory containing XRE (probably xulrunner)") 1.139 + self.add_option("--symbols-path", 1.140 + action = "store", type = "string", dest = "symbols_path", 1.141 + default = None, 1.142 + help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols") 1.143 + self.add_option("--skip-manifest", 1.144 + action = "store", type = "string", dest = "manifest_file", 1.145 + default = None, 1.146 + help = "absolute path to a manifest file") 1.147 + 1.148 +def extract_unittests_from_args(args, manifest_file): 1.149 + """Extract unittests from args, expanding directories as needed""" 1.150 + progs = [] 1.151 + 1.152 + # Known files commonly packaged with the cppunittests that are not tests 1.153 + skipped_progs = set(['.mkdir.done', 'remotecppunittests.py', 'runcppunittests.py', 'runcppunittests.pyc']) 1.154 + 1.155 + if manifest_file: 1.156 + skipped_progs.add(os.path.basename(manifest_file)) 1.157 + with open(manifest_file) as f: 1.158 + for line in f: 1.159 + # strip out comment, if any 1.160 + prog = line.split('#')[0] 1.161 + if prog: 1.162 + skipped_progs.add(prog.strip()) 1.163 + 1.164 + for p in args: 1.165 + if os.path.isdir(p): 1.166 + progs.extend([os.path.abspath(os.path.join(p, x)) for x in os.listdir(p) if not x in skipped_progs]) 1.167 + elif p not in skipped_progs: 1.168 + progs.append(os.path.abspath(p)) 1.169 + 1.170 + return progs 1.171 + 1.172 +def main(): 1.173 + parser = CPPUnittestOptions() 1.174 + options, args = parser.parse_args() 1.175 + if not args: 1.176 + print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0] 1.177 + sys.exit(1) 1.178 + if not options.xre_path: 1.179 + print >>sys.stderr, """Error: --xre-path is required""" 1.180 + sys.exit(1) 1.181 + 1.182 + progs = extract_unittests_from_args(args, options.manifest_file) 1.183 + options.xre_path = os.path.abspath(options.xre_path) 1.184 + tester = CPPUnitTests() 1.185 + try: 1.186 + result = tester.run_tests(progs, options.xre_path, options.symbols_path) 1.187 + except Exception, e: 1.188 + log.error(str(e)) 1.189 + result = False 1.190 + sys.exit(0 if result else 1) 1.191 + 1.192 +if __name__ == '__main__': 1.193 + main() 1.194 +