| #!/usr/bin/env python |
| # Copyright 2014 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A "smart" test runner for gtest unit tests (that caches successes).""" |
| |
| import argparse |
| import logging |
| import os |
| import subprocess |
| import sys |
| |
| from mopy import gtest |
| from mopy.config import Config |
| from mopy.gn import ConfigForGNArgs, ParseGNConfig |
| from mopy.log import InitLogging |
| from mopy.paths import Paths |
| from mopy.transitive_hash import file_hash, transitive_hash |
| |
| |
| _logger = logging.getLogger() |
| _paths = Paths() |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description="A 'smart' test runner for gtest unit tests (that caches " |
| "successes).") |
| |
| parser.add_argument("--verbose", help="be verbose (multiple times for more)", |
| default=0, dest="verbose_count", action="count") |
| parser.add_argument("--successes-cache", |
| help="the file caching test results (empty to not cache)", |
| default="mojob_test_successes") |
| parser.add_argument("test_list_file", |
| help="the file containing the tests to run", type=file) |
| parser.add_argument("root_dir", help="the build directory") |
| args = parser.parse_args() |
| |
| InitLogging(args.verbose_count) |
| config = ConfigForGNArgs(ParseGNConfig(args.root_dir)) |
| |
| _logger.debug("Test list file: %s", args.test_list_file) |
| execution_globals = {"config": config} |
| exec args.test_list_file in execution_globals |
| test_list = execution_globals["tests"] |
| _logger.debug("Test list: %s" % test_list) |
| |
| print "Running tests in directory: %s" % args.root_dir |
| os.chdir(args.root_dir) |
| |
| if args.successes_cache: |
| print "Successes cache file: %s" % args.successes_cache |
| else: |
| print "No successes cache file (will run all tests unconditionally)" |
| |
| if args.successes_cache: |
| # This file simply contains a list of transitive hashes of tests that |
| # succeeded. |
| try: |
| _logger.debug("Trying to read successes cache file: %s", |
| args.successes_cache) |
| with open(args.successes_cache, 'rb') as f: |
| successes = set([x.strip() for x in f.readlines()]) |
| _logger.debug("Successes: %s", successes) |
| except IOError: |
| # Just assume that it didn't exist, or whatever. |
| print ("Failed to read successes cache file %s (will create)" % |
| args.successes_cache) |
| successes = set() |
| |
| gtest.set_color() |
| |
| exit_code = 0 |
| successes_cache_file = (open(args.successes_cache, "ab") |
| if args.successes_cache else None) |
| for test_dict in test_list: |
| test = test_dict["test"] |
| test_name = test_dict.get("name", test) |
| # TODO(vtl): Add type. |
| cacheable = test_dict.get("cacheable", True) |
| if not cacheable: |
| _logger.debug("%s is marked as non-cacheable" % test_name) |
| |
| gtest_file = test |
| if config.target_os == Config.OS_ANDROID: |
| gtest_file = test + "_apk/" + test + "-debug.apk" |
| |
| if successes_cache_file and cacheable: |
| _logger.debug("Getting transitive hash for %s ... " % test_name) |
| try: |
| if config.target_os == Config.OS_ANDROID: |
| gtest_hash = file_hash(gtest_file) |
| else: |
| gtest_hash = transitive_hash(gtest_file) |
| except subprocess.CalledProcessError: |
| print "Failed to get transitive hash for %s" % test_name |
| exit_code = 1 |
| continue |
| _logger.debug(" Transitive hash: %s" % gtest_hash) |
| |
| if gtest_hash in successes: |
| print "Skipping %s (previously succeeded)" % test_name |
| continue |
| |
| _logger.info("Will start: %s" % test_name) |
| print "Running %s...." % test_name, |
| sys.stdout.flush() |
| try: |
| if config.target_os == Config.OS_ANDROID: |
| command = [ |
| "python", |
| os.path.join(_paths.src_root, "build", "android", "test_runner.py"), |
| "gtest", |
| "--output-directory", |
| args.root_dir, |
| "-s", |
| test, |
| ] |
| else: |
| command = ["./" + test] |
| _logger.debug("Command: %s" % command) |
| subprocess.check_output(command, stderr=subprocess.STDOUT) |
| print "Succeeded" |
| # Record success. |
| if args.successes_cache and cacheable: |
| successes.add(gtest_hash) |
| successes_cache_file.write(gtest_hash + "\n") |
| successes_cache_file.flush() |
| except subprocess.CalledProcessError as e: |
| print "Failed with exit code %d and output:" % e.returncode |
| print 72 * "-" |
| print e.output |
| print 72 * "-" |
| exit_code = 1 |
| continue |
| except OSError as e: |
| print " Failed to start test" |
| exit_code = 1 |
| continue |
| _logger.info("Completed: %s" % test_name) |
| if exit_code == 0: |
| print "All tests succeeded" |
| if successes_cache_file: |
| successes_cache_file.close() |
| |
| return exit_code |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |