| #!/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 ast | 
 | import logging | 
 | import os | 
 | import platform | 
 | import subprocess | 
 | import sys | 
 |  | 
 | _logging = logging.getLogger() | 
 |  | 
 | import mopy.gtest | 
 | from mopy.config import Config | 
 | from mopy.gn import ConfigForGNArgs, ParseGNConfig | 
 | from mopy.paths import Paths | 
 | from mopy.transitive_hash import file_hash, transitive_hash | 
 |  | 
 | paths = Paths() | 
 |  | 
 | def main(): | 
 |   logging.basicConfig() | 
 |   # Uncomment to debug: | 
 |   # _logging.setLevel(logging.DEBUG) | 
 |  | 
 |   parser = argparse.ArgumentParser( | 
 |       description="A 'smart' test runner for gtest unit tests (that caches " | 
 |                   "successes).") | 
 |  | 
 |   parser.add_argument("gtest_list_file", | 
 |                       help="The file containing the tests to run.", | 
 |                       type=file) | 
 |   parser.add_argument("root_dir", help="The build directory.") | 
 |   parser.add_argument("successes_cache_filename", | 
 |                       help="The file caching test results.", default=None, | 
 |                       nargs='?') | 
 |   args = parser.parse_args() | 
 |  | 
 |   config = ConfigForGNArgs(ParseGNConfig(args.root_dir)) | 
 |  | 
 |   _logging.debug("Test list file: %s", args.gtest_list_file) | 
 |   execution_globals = { | 
 |       "config": config, | 
 |   } | 
 |   exec args.gtest_list_file in execution_globals | 
 |   gtest_list = execution_globals["tests"] | 
 |   _logging.debug("Test list: %s" % gtest_list) | 
 |  | 
 |   print "Running tests in directory: %s" % args.root_dir | 
 |   os.chdir(args.root_dir) | 
 |  | 
 |   if args.successes_cache_filename: | 
 |     print "Successes cache file: %s" % args.successes_cache_filename | 
 |   else: | 
 |     print "No successes cache file (will run all tests unconditionally)" | 
 |  | 
 |   if args.successes_cache_filename: | 
 |     # This file simply contains a list of transitive hashes of tests that | 
 |     # succeeded. | 
 |     try: | 
 |       _logging.debug("Trying to read successes cache file: %s", | 
 |                      args.successes_cache_filename) | 
 |       with open(args.successes_cache_filename, 'rb') as f: | 
 |         successes = set([x.strip() for x in f.readlines()]) | 
 |       _logging.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_filename) | 
 |       successes = set() | 
 |  | 
 |   mopy.gtest.set_color() | 
 |  | 
 |   # TODO(vtl): We may not close this file on failure. | 
 |   successes_cache_file = open(args.successes_cache_filename, 'ab') \ | 
 |       if args.successes_cache_filename else None | 
 |   for gtest_dict in gtest_list: | 
 |     gtest = gtest_dict["test"] | 
 |     cacheable = gtest_dict.get("cacheable", True) | 
 |     if not cacheable: | 
 |       _logging.debug("%s is marked as non-cacheable" % gtest) | 
 |  | 
 |     gtest_file = gtest | 
 |     if platform.system() == 'Windows': | 
 |       gtest_file += ".exe" | 
 |     if config.target_os == Config.OS_ANDROID: | 
 |       gtest_file = gtest + "_apk/" + gtest + "-debug.apk" | 
 |  | 
 |     if successes_cache_file and cacheable: | 
 |       _logging.debug("Getting transitive hash for %s ... " % gtest) | 
 |       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" % gtest | 
 |         return 1 | 
 |       _logging.debug("  Transitive hash: %s" % gtest_hash) | 
 |  | 
 |       if gtest_hash in successes: | 
 |         print "Skipping %s (previously succeeded)" % gtest | 
 |         continue | 
 |  | 
 |     print "Running %s...." % gtest, | 
 |     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", | 
 |             gtest, | 
 |         ] | 
 |       else: | 
 |         command = ["./" + gtest] | 
 |       subprocess.check_output(command, stderr=subprocess.STDOUT) | 
 |       print "Succeeded" | 
 |       # Record success. | 
 |       if args.successes_cache_filename 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 * '-' | 
 |       return 1 | 
 |     except OSError as e: | 
 |       print "  Failed to start test" | 
 |       return 1 | 
 |   print "All tests succeeded" | 
 |   if successes_cache_file: | 
 |     successes_cache_file.close() | 
 |  | 
 |   return 0 | 
 |  | 
 | if __name__ == '__main__': | 
 |   sys.exit(main()) |