Bumping manifests a=b2g-bump
[gecko.git] / testing / runcppunittests.py
blob0a1916fda85e2b2e5acac8ecfc4941c3bea91b5d
1 #!/usr/bin/env python
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 from __future__ import with_statement
8 import sys, os, tempfile, shutil
9 from optparse import OptionParser
10 import manifestparser
11 import mozprocess
12 import mozinfo
13 import mozcrash
14 import mozfile
15 from contextlib import contextmanager
16 from mozlog import structured
17 from subprocess import PIPE
19 class CPPUnitTests(object):
20 # Time (seconds) to wait for test process to complete
21 TEST_PROC_TIMEOUT = 900
22 # Time (seconds) in which process will be killed if it produces no output.
23 TEST_PROC_NO_OUTPUT_TIMEOUT = 300
25 def run_one_test(self, prog, env, symbols_path=None, interactive=False):
26 """
27 Run a single C++ unit test program.
29 Arguments:
30 * prog: The path to the test program to run.
31 * env: The environment to use for running the program.
32 * symbols_path: A path to a directory containing Breakpad-formatted
33 symbol files for producing stack traces on crash.
35 Return True if the program exits with a zero status, False otherwise.
36 """
37 basename = os.path.basename(prog)
38 self.log.test_start(basename)
39 with mozfile.TemporaryDirectory() as tempdir:
40 if interactive:
41 # For tests run locally, via mach, print output directly
42 proc = mozprocess.ProcessHandler([prog],
43 cwd=tempdir,
44 env=env,
45 storeOutput=False)
46 else:
47 proc = mozprocess.ProcessHandler([prog],
48 cwd=tempdir,
49 env=env,
50 storeOutput=True,
51 processOutputLine=lambda _: None)
52 #TODO: After bug 811320 is fixed, don't let .run() kill the process,
53 # instead use a timeout in .wait() and then kill to get a stack.
54 proc.run(timeout=CPPUnitTests.TEST_PROC_TIMEOUT,
55 outputTimeout=CPPUnitTests.TEST_PROC_NO_OUTPUT_TIMEOUT)
56 proc.wait()
57 if proc.output:
58 output = "\n%s" % "\n".join(proc.output)
59 self.log.process_output(proc.pid, output, command=[prog])
60 if proc.timedOut:
61 message = "timed out after %d seconds" % CPPUnitTests.TEST_PROC_TIMEOUT
62 self.log.test_end(basename, status='TIMEOUT', expected='PASS',
63 message=message)
64 return False
65 if mozcrash.check_for_crashes(tempdir, symbols_path,
66 test_name=basename):
67 self.log.test_end(basename, status='CRASH', expected='PASS')
68 return False
69 result = proc.proc.returncode == 0
70 if not result:
71 self.log.test_end(basename, status='FAIL', expected='PASS',
72 message=("test failed with return code %d" %
73 proc.proc.returncode))
74 else:
75 self.log.test_end(basename, status='PASS', expected='PASS')
76 return result
78 def build_core_environment(self, env = {}):
79 """
80 Add environment variables likely to be used across all platforms, including remote systems.
81 """
82 env["MOZILLA_FIVE_HOME"] = self.xre_path
83 env["MOZ_XRE_DIR"] = self.xre_path
84 #TODO: switch this to just abort once all C++ unit tests have
85 # been fixed to enable crash reporting
86 env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
87 env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
88 env["MOZ_CRASHREPORTER"] = "1"
89 return env
91 def build_environment(self):
92 """
93 Create and return a dictionary of all the appropriate env variables and values.
94 On a remote system, we overload this to set different values and are missing things like os.environ and PATH.
95 """
96 if not os.path.isdir(self.xre_path):
97 raise Exception("xre_path does not exist: %s", self.xre_path)
98 env = dict(os.environ)
99 env = self.build_core_environment(env)
100 pathvar = ""
101 libpath = self.xre_path
102 if mozinfo.os == "linux":
103 pathvar = "LD_LIBRARY_PATH"
104 elif mozinfo.os == "mac":
105 applibpath = os.path.join(os.path.dirname(libpath), 'MacOS')
106 if os.path.exists(applibpath):
107 # Set the library load path to Contents/MacOS if we're run from
108 # the app bundle.
109 libpath = applibpath
110 pathvar = "DYLD_LIBRARY_PATH"
111 elif mozinfo.os == "win":
112 pathvar = "PATH"
113 if pathvar:
114 if pathvar in env:
115 env[pathvar] = "%s%s%s" % (libpath, os.pathsep, env[pathvar])
116 else:
117 env[pathvar] = libpath
119 # Use llvm-symbolizer for ASan if available/required
120 llvmsym = os.path.join(self.xre_path, "llvm-symbolizer")
121 if os.path.isfile(llvmsym):
122 env["ASAN_SYMBOLIZER_PATH"] = llvmsym
123 self.log.info("ASan using symbolizer at %s" % llvmsym)
124 else:
125 self.log.info("Failed to find ASan symbolizer at %s" % llvmsym)
127 return env
129 def run_tests(self, programs, xre_path, symbols_path=None, interactive=False):
131 Run a set of C++ unit test programs.
133 Arguments:
134 * programs: An iterable containing paths to test programs.
135 * xre_path: A path to a directory containing a XUL Runtime Environment.
136 * symbols_path: A path to a directory containing Breakpad-formatted
137 symbol files for producing stack traces on crash.
139 Returns True if all test programs exited with a zero status, False
140 otherwise.
142 self.xre_path = xre_path
143 self.log = structured.structuredlog.get_default_logger()
144 self.log.suite_start(programs)
145 env = self.build_environment()
146 pass_count = 0
147 fail_count = 0
148 for prog in programs:
149 single_result = self.run_one_test(prog, env, symbols_path, interactive)
150 if single_result:
151 pass_count += 1
152 else:
153 fail_count += 1
154 self.log.suite_end()
156 # Mozharness-parseable summary formatting.
157 self.log.info("Result summary:")
158 self.log.info("cppunittests INFO | Passed: %d" % pass_count)
159 self.log.info("cppunittests INFO | Failed: %d" % fail_count)
160 return fail_count == 0
162 class CPPUnittestOptions(OptionParser):
163 def __init__(self):
164 OptionParser.__init__(self)
165 self.add_option("--xre-path",
166 action = "store", type = "string", dest = "xre_path",
167 default = None,
168 help = "absolute path to directory containing XRE (probably xulrunner)")
169 self.add_option("--symbols-path",
170 action = "store", type = "string", dest = "symbols_path",
171 default = None,
172 help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
173 self.add_option("--skip-manifest",
174 action = "store", type = "string", dest = "manifest_file",
175 default = None,
176 help = "absolute path to a manifest file")
178 def extract_unittests_from_args(args, environ):
179 """Extract unittests from args, expanding directories as needed"""
180 mp = manifestparser.TestManifest(strict=True)
181 tests = []
182 for p in args:
183 if os.path.isdir(p):
184 try:
185 mp.read(os.path.join(p, 'cppunittest.ini'))
186 except IOError:
187 tests.extend([os.path.abspath(os.path.join(p, x)) for x in os.listdir(p)])
188 else:
189 tests.append(os.path.abspath(p))
191 # we skip the existence check here because not all tests are built
192 # for all platforms (and it will fail on Windows anyway)
193 if mozinfo.isWin:
194 tests.extend([test['path'] + '.exe' for test in mp.active_tests(exists=False, disabled=False, **environ)])
195 else:
196 tests.extend([test['path'] for test in mp.active_tests(exists=False, disabled=False, **environ)])
198 # skip non-existing tests
199 tests = [test for test in tests if os.path.isfile(test)]
201 return tests
203 def main():
204 parser = CPPUnittestOptions()
205 structured.commandline.add_logging_group(parser)
206 options, args = parser.parse_args()
207 if not args:
208 print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0]
209 sys.exit(1)
210 if not options.xre_path:
211 print >>sys.stderr, """Error: --xre-path is required"""
212 sys.exit(1)
214 log = structured.commandline.setup_logging("cppunittests",
215 options,
216 {"tbpl": sys.stdout})
218 progs = extract_unittests_from_args(args, mozinfo.info)
219 options.xre_path = os.path.abspath(options.xre_path)
220 if mozinfo.isMac:
221 options.xre_path = os.path.join(os.path.dirname(options.xre_path), 'Resources')
222 tester = CPPUnitTests()
224 try:
225 result = tester.run_tests(progs, options.xre_path, options.symbols_path)
226 except Exception as e:
227 log.error(str(e))
228 result = False
230 sys.exit(0 if result else 1)
232 if __name__ == '__main__':
233 main()