Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | #!/usr/bin/env python |
michael@0 | 2 | # |
michael@0 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 6 | |
michael@0 | 7 | from __future__ import with_statement |
michael@0 | 8 | import sys, os, tempfile, shutil |
michael@0 | 9 | from optparse import OptionParser |
michael@0 | 10 | import mozprocess, mozinfo, mozlog, mozcrash, mozfile |
michael@0 | 11 | from contextlib import contextmanager |
michael@0 | 12 | |
michael@0 | 13 | log = mozlog.getLogger('cppunittests') |
michael@0 | 14 | |
michael@0 | 15 | class CPPUnitTests(object): |
michael@0 | 16 | # Time (seconds) to wait for test process to complete |
michael@0 | 17 | TEST_PROC_TIMEOUT = 900 |
michael@0 | 18 | # Time (seconds) in which process will be killed if it produces no output. |
michael@0 | 19 | TEST_PROC_NO_OUTPUT_TIMEOUT = 300 |
michael@0 | 20 | |
michael@0 | 21 | def run_one_test(self, prog, env, symbols_path=None): |
michael@0 | 22 | """ |
michael@0 | 23 | Run a single C++ unit test program. |
michael@0 | 24 | |
michael@0 | 25 | Arguments: |
michael@0 | 26 | * prog: The path to the test program to run. |
michael@0 | 27 | * env: The environment to use for running the program. |
michael@0 | 28 | * symbols_path: A path to a directory containing Breakpad-formatted |
michael@0 | 29 | symbol files for producing stack traces on crash. |
michael@0 | 30 | |
michael@0 | 31 | Return True if the program exits with a zero status, False otherwise. |
michael@0 | 32 | """ |
michael@0 | 33 | basename = os.path.basename(prog) |
michael@0 | 34 | log.info("Running test %s", basename) |
michael@0 | 35 | with mozfile.TemporaryDirectory() as tempdir: |
michael@0 | 36 | proc = mozprocess.ProcessHandler([prog], |
michael@0 | 37 | cwd=tempdir, |
michael@0 | 38 | env=env) |
michael@0 | 39 | #TODO: After bug 811320 is fixed, don't let .run() kill the process, |
michael@0 | 40 | # instead use a timeout in .wait() and then kill to get a stack. |
michael@0 | 41 | proc.run(timeout=CPPUnitTests.TEST_PROC_TIMEOUT, |
michael@0 | 42 | outputTimeout=CPPUnitTests.TEST_PROC_NO_OUTPUT_TIMEOUT) |
michael@0 | 43 | proc.wait() |
michael@0 | 44 | if proc.timedOut: |
michael@0 | 45 | log.testFail("%s | timed out after %d seconds", |
michael@0 | 46 | basename, CPPUnitTests.TEST_PROC_TIMEOUT) |
michael@0 | 47 | return False |
michael@0 | 48 | if mozcrash.check_for_crashes(tempdir, symbols_path, |
michael@0 | 49 | test_name=basename): |
michael@0 | 50 | log.testFail("%s | test crashed", basename) |
michael@0 | 51 | return False |
michael@0 | 52 | result = proc.proc.returncode == 0 |
michael@0 | 53 | if not result: |
michael@0 | 54 | log.testFail("%s | test failed with return code %d", |
michael@0 | 55 | basename, proc.proc.returncode) |
michael@0 | 56 | return result |
michael@0 | 57 | |
michael@0 | 58 | def build_core_environment(self, env = {}): |
michael@0 | 59 | """ |
michael@0 | 60 | Add environment variables likely to be used across all platforms, including remote systems. |
michael@0 | 61 | """ |
michael@0 | 62 | env["MOZILLA_FIVE_HOME"] = self.xre_path |
michael@0 | 63 | env["MOZ_XRE_DIR"] = self.xre_path |
michael@0 | 64 | #TODO: switch this to just abort once all C++ unit tests have |
michael@0 | 65 | # been fixed to enable crash reporting |
michael@0 | 66 | env["XPCOM_DEBUG_BREAK"] = "stack-and-abort" |
michael@0 | 67 | env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" |
michael@0 | 68 | env["MOZ_CRASHREPORTER"] = "1" |
michael@0 | 69 | return env |
michael@0 | 70 | |
michael@0 | 71 | def build_environment(self): |
michael@0 | 72 | """ |
michael@0 | 73 | Create and return a dictionary of all the appropriate env variables and values. |
michael@0 | 74 | On a remote system, we overload this to set different values and are missing things like os.environ and PATH. |
michael@0 | 75 | """ |
michael@0 | 76 | if not os.path.isdir(self.xre_path): |
michael@0 | 77 | raise Exception("xre_path does not exist: %s", self.xre_path) |
michael@0 | 78 | env = dict(os.environ) |
michael@0 | 79 | env = self.build_core_environment(env) |
michael@0 | 80 | pathvar = "" |
michael@0 | 81 | if mozinfo.os == "linux": |
michael@0 | 82 | pathvar = "LD_LIBRARY_PATH" |
michael@0 | 83 | elif mozinfo.os == "mac": |
michael@0 | 84 | pathvar = "DYLD_LIBRARY_PATH" |
michael@0 | 85 | elif mozinfo.os == "win": |
michael@0 | 86 | pathvar = "PATH" |
michael@0 | 87 | if pathvar: |
michael@0 | 88 | if pathvar in env: |
michael@0 | 89 | env[pathvar] = "%s%s%s" % (self.xre_path, os.pathsep, env[pathvar]) |
michael@0 | 90 | else: |
michael@0 | 91 | env[pathvar] = self.xre_path |
michael@0 | 92 | |
michael@0 | 93 | # Use llvm-symbolizer for ASan if available/required |
michael@0 | 94 | llvmsym = os.path.join(self.xre_path, "llvm-symbolizer") |
michael@0 | 95 | if os.path.isfile(llvmsym): |
michael@0 | 96 | env["ASAN_SYMBOLIZER_PATH"] = llvmsym |
michael@0 | 97 | |
michael@0 | 98 | return env |
michael@0 | 99 | |
michael@0 | 100 | def run_tests(self, programs, xre_path, symbols_path=None): |
michael@0 | 101 | """ |
michael@0 | 102 | Run a set of C++ unit test programs. |
michael@0 | 103 | |
michael@0 | 104 | Arguments: |
michael@0 | 105 | * programs: An iterable containing paths to test programs. |
michael@0 | 106 | * xre_path: A path to a directory containing a XUL Runtime Environment. |
michael@0 | 107 | * symbols_path: A path to a directory containing Breakpad-formatted |
michael@0 | 108 | symbol files for producing stack traces on crash. |
michael@0 | 109 | |
michael@0 | 110 | Returns True if all test programs exited with a zero status, False |
michael@0 | 111 | otherwise. |
michael@0 | 112 | """ |
michael@0 | 113 | self.xre_path = xre_path |
michael@0 | 114 | env = self.build_environment() |
michael@0 | 115 | pass_count = 0 |
michael@0 | 116 | fail_count = 0 |
michael@0 | 117 | for prog in programs: |
michael@0 | 118 | single_result = self.run_one_test(prog, env, symbols_path) |
michael@0 | 119 | if single_result: |
michael@0 | 120 | pass_count += 1 |
michael@0 | 121 | else: |
michael@0 | 122 | fail_count += 1 |
michael@0 | 123 | |
michael@0 | 124 | log.info("Result summary:") |
michael@0 | 125 | log.info("Passed: %d" % pass_count) |
michael@0 | 126 | log.info("Failed: %d" % fail_count) |
michael@0 | 127 | return fail_count == 0 |
michael@0 | 128 | |
michael@0 | 129 | class CPPUnittestOptions(OptionParser): |
michael@0 | 130 | def __init__(self): |
michael@0 | 131 | OptionParser.__init__(self) |
michael@0 | 132 | self.add_option("--xre-path", |
michael@0 | 133 | action = "store", type = "string", dest = "xre_path", |
michael@0 | 134 | default = None, |
michael@0 | 135 | help = "absolute path to directory containing XRE (probably xulrunner)") |
michael@0 | 136 | self.add_option("--symbols-path", |
michael@0 | 137 | action = "store", type = "string", dest = "symbols_path", |
michael@0 | 138 | default = None, |
michael@0 | 139 | help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols") |
michael@0 | 140 | self.add_option("--skip-manifest", |
michael@0 | 141 | action = "store", type = "string", dest = "manifest_file", |
michael@0 | 142 | default = None, |
michael@0 | 143 | help = "absolute path to a manifest file") |
michael@0 | 144 | |
michael@0 | 145 | def extract_unittests_from_args(args, manifest_file): |
michael@0 | 146 | """Extract unittests from args, expanding directories as needed""" |
michael@0 | 147 | progs = [] |
michael@0 | 148 | |
michael@0 | 149 | # Known files commonly packaged with the cppunittests that are not tests |
michael@0 | 150 | skipped_progs = set(['.mkdir.done', 'remotecppunittests.py', 'runcppunittests.py', 'runcppunittests.pyc']) |
michael@0 | 151 | |
michael@0 | 152 | if manifest_file: |
michael@0 | 153 | skipped_progs.add(os.path.basename(manifest_file)) |
michael@0 | 154 | with open(manifest_file) as f: |
michael@0 | 155 | for line in f: |
michael@0 | 156 | # strip out comment, if any |
michael@0 | 157 | prog = line.split('#')[0] |
michael@0 | 158 | if prog: |
michael@0 | 159 | skipped_progs.add(prog.strip()) |
michael@0 | 160 | |
michael@0 | 161 | for p in args: |
michael@0 | 162 | if os.path.isdir(p): |
michael@0 | 163 | progs.extend([os.path.abspath(os.path.join(p, x)) for x in os.listdir(p) if not x in skipped_progs]) |
michael@0 | 164 | elif p not in skipped_progs: |
michael@0 | 165 | progs.append(os.path.abspath(p)) |
michael@0 | 166 | |
michael@0 | 167 | return progs |
michael@0 | 168 | |
michael@0 | 169 | def main(): |
michael@0 | 170 | parser = CPPUnittestOptions() |
michael@0 | 171 | options, args = parser.parse_args() |
michael@0 | 172 | if not args: |
michael@0 | 173 | print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0] |
michael@0 | 174 | sys.exit(1) |
michael@0 | 175 | if not options.xre_path: |
michael@0 | 176 | print >>sys.stderr, """Error: --xre-path is required""" |
michael@0 | 177 | sys.exit(1) |
michael@0 | 178 | |
michael@0 | 179 | progs = extract_unittests_from_args(args, options.manifest_file) |
michael@0 | 180 | options.xre_path = os.path.abspath(options.xre_path) |
michael@0 | 181 | tester = CPPUnitTests() |
michael@0 | 182 | try: |
michael@0 | 183 | result = tester.run_tests(progs, options.xre_path, options.symbols_path) |
michael@0 | 184 | except Exception, e: |
michael@0 | 185 | log.error(str(e)) |
michael@0 | 186 | result = False |
michael@0 | 187 | sys.exit(0 if result else 1) |
michael@0 | 188 | |
michael@0 | 189 | if __name__ == '__main__': |
michael@0 | 190 | main() |
michael@0 | 191 |