Home | History | Annotate | Download | only in assets
      1 #!/usr/bin/python
      2 
      3 """Run layout tests using Android emulator and instrumentation.
      4 
      5   First, you need to get an SD card or sdcard image that has layout tests on it.
      6   Layout tests are in following directory:
      7     /sdcard/webkit/layout_tests
      8   For example, /sdcard/webkit/layout_tests/fast
      9 
     10   Usage:
     11     Run all tests under fast/ directory:
     12       run_layout_tests.py, or
     13       run_layout_tests.py fast
     14 
     15     Run all tests under a sub directory:
     16       run_layout_tests.py fast/dom
     17 
     18     Run a single test:
     19       run_layout_tests.py fast/dom/
     20 
     21   After a merge, if there are changes of layout tests in SD card, you need to
     22   use --refresh-test-list option *once* to re-generate test list on the card.
     23 
     24   Some other options are:
     25     --rebaseline generates expected layout tests results under /sdcard/webkit/expected_result/
     26     --time-out-ms (default is 8000 millis) for each test
     27     --adb-options="-e" passes option string to adb
     28     --results-directory=..., (default is ./layout-test-results) directory name under which results are stored.
     29     --js-engine the JavaScript engine currently in use, determines which set of Android-specific expected results we should use, should be 'jsc' or 'v8'
     30 """
     31 
     32 import logging
     33 import optparse
     34 import os
     35 import subprocess
     36 import sys
     37 import time
     38 
     39 def CountLineNumber(filename):
     40   """Compute the number of lines in a given file.
     41 
     42   Args:
     43     filename: a file name related to the current directory.
     44   """
     45 
     46   fp = open(os.path.abspath(filename), "r");
     47   lines = 0
     48   for line in fp.readlines():
     49     lines = lines + 1
     50   fp.close()
     51   return lines
     52 
     53 def DumpRenderTreeFinished(adb_cmd):
     54   """ Check if DumpRenderTree finished running tests
     55 
     56   Args:
     57     output: adb_cmd string
     58   """
     59 
     60   # pull /sdcard/webkit/running_test.txt, if the content is "#DONE", it's done
     61   shell_cmd_str = adb_cmd + " shell cat /sdcard/webkit/running_test.txt"
     62   adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
     63   return adb_output.strip() == "#DONE"
     64 
     65 def DiffResults(marker, new_results, old_results, diff_results, strip_reason,
     66                 new_count_first=True):
     67    """ Given two result files, generate diff and
     68        write to diff_results file. All arguments are absolute paths
     69        to files.
     70    """
     71    old_file = open(old_results, "r")
     72    new_file = open(new_results, "r")
     73    diff_file = open(diff_results, "a")
     74 
     75    # Read lines from each file
     76    ndict = new_file.readlines()
     77    cdict = old_file.readlines()
     78 
     79    # Write marker to diff file
     80    diff_file.writelines(marker + "\n")
     81    diff_file.writelines("###############\n")
     82 
     83    # Strip reason from result lines
     84    if strip_reason is True:
     85      for i in range(0, len(ndict)):
     86        ndict[i] = ndict[i].split(' ')[0] + "\n"
     87      for i in range(0, len(cdict)):
     88        cdict[i] = cdict[i].split(' ')[0] + "\n"
     89 
     90    params = {
     91        "new": [0, ndict, cdict, "+"],
     92        "miss": [0, cdict, ndict, "-"]
     93        }
     94    if new_count_first:
     95      order = ["new", "miss"]
     96    else:
     97      order = ["miss", "new"]
     98 
     99    for key in order:
    100      for line in params[key][1]:
    101        if line not in params[key][2]:
    102          if line[-1] != "\n":
    103            line += "\n";
    104          diff_file.writelines(params[key][3] + line)
    105          params[key][0] += 1
    106 
    107    logging.info(marker + "  >>> " + str(params["new"][0]) + " new, " +
    108                 str(params["miss"][0]) + " misses")
    109 
    110    diff_file.writelines("\n\n")
    111 
    112    old_file.close()
    113    new_file.close()
    114    diff_file.close()
    115    return
    116 
    117 def CompareResults(ref_dir, results_dir):
    118   """Compare results in two directories
    119 
    120   Args:
    121     ref_dir: the reference directory having layout results as references
    122     results_dir: the results directory
    123   """
    124   logging.info("Comparing results to " + ref_dir)
    125 
    126   diff_result = os.path.join(results_dir, "layout_tests_diff.txt")
    127   if os.path.exists(diff_result):
    128     os.remove(diff_result)
    129 
    130   files=["crashed", "failed", "passed", "nontext"]
    131   for f in files:
    132     result_file_name = "layout_tests_" + f + ".txt"
    133     DiffResults(f, os.path.join(results_dir, result_file_name),
    134                 os.path.join(ref_dir, result_file_name), diff_result,
    135                 False, f != "passed")
    136   logging.info("Detailed diffs are in " + diff_result)
    137 
    138 def main(options, args):
    139   """Run the tests. Will call sys.exit when complete.
    140 
    141   Args:
    142     options: a dictionary of command line options
    143     args: a list of sub directories or files to test
    144   """
    145 
    146   # Set up logging format.
    147   log_level = logging.INFO
    148   if options.verbose:
    149     log_level = logging.DEBUG
    150   logging.basicConfig(level=log_level,
    151                       format='%(message)s')
    152 
    153   # Include all tests if none are specified.
    154   if not args:
    155     path = '/';
    156   else:
    157     path = ' '.join(args);
    158 
    159   adb_cmd = "adb ";
    160   if options.adb_options:
    161     adb_cmd += options.adb_options
    162 
    163   # Re-generate the test list if --refresh-test-list is on
    164   if options.refresh_test_list:
    165     logging.info("Generating test list.");
    166     generate_test_list_cmd_str = adb_cmd + " shell am instrument -e class com.android.dumprendertree.LayoutTestsAutoTest#generateTestList -e path \"" + path + "\" -w com.android.dumprendertree/.LayoutTestsAutoRunner"
    167     adb_output = subprocess.Popen(generate_test_list_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
    168 
    169     if adb_output.find('Process crashed') != -1:
    170        logging.info("Aborting because cannot generate test list.\n" + adb_output)
    171        sys.exit(1)
    172 
    173 
    174   logging.info("Running tests")
    175 
    176   # Count crashed tests.
    177   crashed_tests = []
    178 
    179   timeout_ms = '15000'
    180   if options.time_out_ms:
    181     timeout_ms = options.time_out_ms
    182 
    183   # Run test until it's done
    184 
    185   run_layout_test_cmd_prefix = adb_cmd + " shell am instrument"
    186 
    187   run_layout_test_cmd_postfix = " -e path \"" + path + "\" -e timeout " + timeout_ms
    188   if options.rebaseline:
    189     run_layout_test_cmd_postfix += " -e rebaseline true"
    190 
    191   # If the JS engine is not specified on the command line, try reading the
    192   # JS_ENGINE environment  variable, which is used by the build system in
    193   # external/webkit/Android.mk.
    194   js_engine = options.js_engine
    195   if not js_engine and os.environ.has_key('JS_ENGINE'):
    196     js_engine = os.environ['JS_ENGINE']
    197   if js_engine:
    198     run_layout_test_cmd_postfix += " -e jsengine " + js_engine
    199 
    200   run_layout_test_cmd_postfix += " -w com.android.dumprendertree/.LayoutTestsAutoRunner"
    201 
    202   # Call LayoutTestsAutoTest::startLayoutTests.
    203   run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#startLayoutTests" + run_layout_test_cmd_postfix
    204 
    205   adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
    206   while not DumpRenderTreeFinished(adb_cmd):
    207     # Get the running_test.txt
    208     logging.error("DumpRenderTree crashed, output:\n" + adb_output)
    209 
    210     shell_cmd_str = adb_cmd + " shell cat /sdcard/webkit/running_test.txt"
    211     crashed_test = ""
    212     while not crashed_test:
    213       (crashed_test, err) = subprocess.Popen(
    214           shell_cmd_str, shell=True, stdout=subprocess.PIPE,
    215           stderr=subprocess.PIPE).communicate()
    216       crashed_test = crashed_test.strip()
    217       if not crashed_test:
    218         logging.error('Cannot get crashed test name, device offline?')
    219         logging.error('stderr: ' + err)
    220         logging.error('retrying in 10s...')
    221         time.sleep(10)
    222 
    223     logging.info(crashed_test + " CRASHED");
    224     crashed_tests.append(crashed_test);
    225 
    226     logging.info("Resuming layout test runner...");
    227     # Call LayoutTestsAutoTest::resumeLayoutTests
    228     run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#resumeLayoutTests" + run_layout_test_cmd_postfix
    229 
    230     adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
    231 
    232   if adb_output.find('INSTRUMENTATION_FAILED') != -1:
    233     logging.error("Error happened : " + adb_output)
    234     sys.exit(1)
    235 
    236   logging.debug(adb_output);
    237   logging.info("Done\n");
    238 
    239   # Pull results from /sdcard
    240   results_dir = options.results_directory
    241   if not os.path.exists(results_dir):
    242     os.makedirs(results_dir)
    243   if not os.path.isdir(results_dir):
    244     logging.error("Cannot create results dir: " + results_dir);
    245     sys.exit(1);
    246 
    247   result_files = ["/sdcard/layout_tests_passed.txt",
    248                   "/sdcard/layout_tests_failed.txt",
    249                   "/sdcard/layout_tests_ignored.txt",
    250                   "/sdcard/layout_tests_nontext.txt"]
    251   for file in result_files:
    252     shell_cmd_str = adb_cmd + " pull " + file + " " + results_dir
    253     adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
    254     logging.debug(adb_output)
    255 
    256   # Create the crash list.
    257   fp = open(results_dir + "/layout_tests_crashed.txt", "w");
    258   for crashed_test in crashed_tests:
    259     fp.writelines(crashed_test + '\n')
    260   fp.close()
    261 
    262   # Count the number of tests in each category.
    263   passed_tests = CountLineNumber(results_dir + "/layout_tests_passed.txt")
    264   logging.info(str(passed_tests) + " passed")
    265   failed_tests = CountLineNumber(results_dir + "/layout_tests_failed.txt")
    266   logging.info(str(failed_tests) + " failed")
    267   ignored_tests = CountLineNumber(results_dir + "/layout_tests_ignored.txt")
    268   logging.info(str(ignored_tests) + " ignored results")
    269   crashed_tests = CountLineNumber(results_dir + "/layout_tests_crashed.txt")
    270   logging.info(str(crashed_tests) + " crashed")
    271   nontext_tests = CountLineNumber(results_dir + "/layout_tests_nontext.txt")
    272   logging.info(str(nontext_tests) + " no dumpAsText")
    273   logging.info(str(passed_tests + failed_tests + ignored_tests + crashed_tests + nontext_tests) + " TOTAL")
    274 
    275   logging.info("Results are stored under: " + results_dir + "\n")
    276 
    277   # Comparing results to references to find new fixes and regressions.
    278   results_dir = os.path.abspath(options.results_directory)
    279   ref_dir = options.ref_directory
    280 
    281   # if ref_dir is null, cannonify ref_dir to the script dir.
    282   if not ref_dir:
    283     script_self = sys.argv[0]
    284     script_dir = os.path.dirname(script_self)
    285     ref_dir = os.path.join(script_dir, "results")
    286 
    287   ref_dir = os.path.abspath(ref_dir)
    288 
    289   CompareResults(ref_dir, results_dir)
    290 
    291 if '__main__' == __name__:
    292   option_parser = optparse.OptionParser()
    293   option_parser.add_option("", "--rebaseline", action="store_true",
    294                            default=False,
    295                            help="generate expected results for those tests not having one")
    296   option_parser.add_option("", "--time-out-ms",
    297                            default=None,
    298                            help="set the timeout for each test")
    299   option_parser.add_option("", "--verbose", action="store_true",
    300                            default=False,
    301                            help="include debug-level logging")
    302   option_parser.add_option("", "--refresh-test-list", action="store_true",
    303                            default=False,
    304                            help="re-generate test list, it may take some time.")
    305   option_parser.add_option("", "--adb-options",
    306                            default=None,
    307                            help="pass options to adb, such as -d -e, etc");
    308   option_parser.add_option("", "--results-directory",
    309                            default="layout-test-results",
    310                            help="directory which results are stored.")
    311   option_parser.add_option("", "--ref-directory",
    312                            default=None,
    313                            dest="ref_directory",
    314                            help="directory where reference results are stored.")
    315   option_parser.add_option("", "--js-engine",
    316                            default=None,
    317                            help="The JavaScript engine currently in use, which determines which set of Android-specific expected results we should use. Should be 'jsc' or 'v8'.");
    318 
    319   options, args = option_parser.parse_args();
    320   main(options, args)
    321