run_layout_tests.py revision 36fd6d243ee5414618e8bc2cbdc6c0a6f311157f
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/android/layout_tests
8  For example, /sdcard/android/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/android/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"""
30
31import logging
32import optparse
33import os
34import subprocess
35import sys
36import time
37
38def CountLineNumber(filename):
39  """Compute the number of lines in a given file.
40
41  Args:
42    filename: a file name related to the current directory.
43  """
44
45  fp = open(os.path.abspath(filename), "r");
46  lines = 0
47  for line in fp.readlines():
48    lines = lines + 1
49  fp.close()
50  return lines
51
52def DumpRenderTreeFinished(adb_cmd):
53  """ Check if DumpRenderTree finished running tests
54
55  Args:
56    output: adb_cmd string
57  """
58
59  # pull /sdcard/android/running_test.txt, if the content is "#DONE", it's done
60  shell_cmd_str = adb_cmd + " shell cat /sdcard/android/running_test.txt"
61  adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
62  return adb_output.strip() == "#DONE"
63
64def DiffResults(marker, new_results, old_results, diff_results, strip_reason):
65   """ Given two result files, generate diff and
66       write to diff_results file. All arguments are absolute paths
67       to files.
68   """
69   old_file = open(old_results, "r")
70   new_file = open(new_results, "r")
71   diff_file = open(diff_results, "a")
72
73   # Read lines from each file
74   ndict = new_file.readlines()
75   cdict = old_file.readlines()
76
77   # Write marker to diff file
78   diff_file.writelines(marker + "\n")
79   diff_file.writelines("###############\n")
80
81   # Strip reason from result lines
82   if strip_reason is True:
83     for i in range(0, len(ndict)):
84       ndict[i] = ndict[i].split(' ')[0] + "\n"
85     for i in range(0, len(cdict)):
86       cdict[i] = cdict[i].split(' ')[0] + "\n"
87
88   # Find results in new_results missing in old_results
89   new_count=0
90   for line in ndict:
91     if line not in cdict:
92       diff_file.writelines("+ " + line)
93       new_count += 1
94
95   # Find results in old_results missing in new_results
96   missing_count=0
97   for line in cdict:
98     if line not in ndict:
99       diff_file.writelines("- " + line)
100       missing_count += 1
101
102   logging.info(marker + "  >>> " + str(new_count) + " new, " + str(missing_count) + " misses")
103
104   diff_file.writelines("\n\n")
105
106   old_file.close()
107   new_file.close()
108   diff_file.close()
109   return
110
111def CompareResults(ref_dir, results_dir):
112  """Compare results in two directories
113
114  Args:
115    ref_dir: the reference directory having layout results as references
116    results_dir: the results directory
117  """
118  logging.info("Comparing results to " + ref_dir)
119
120  diff_result = os.path.join(results_dir, "layout_tests_diff.txt")
121  if os.path.exists(diff_result):
122    os.remove(diff_result)
123
124  files=["passed", "failed", "nontext", "crashed"]
125  for f in files:
126    result_file_name = "layout_tests_" + f + ".txt"
127    DiffResults(f, os.path.join(results_dir, result_file_name),
128                os.path.join(ref_dir, result_file_name), diff_result,
129                f == "failed")
130  logging.info("Detailed diffs are in " + diff_result)
131
132def main(options, args):
133  """Run the tests. Will call sys.exit when complete.
134
135  Args:
136    options: a dictionary of command line options
137    args: a list of sub directories or files to test
138  """
139
140  # Set up logging format.
141  log_level = logging.INFO
142  if options.verbose:
143    log_level = logging.DEBUG
144  logging.basicConfig(level=log_level,
145                      format='%(message)s')
146
147  # Include all tests if none are specified.
148  if not args:
149    path = '/';
150  else:
151    path = ' '.join(args);
152
153  adb_cmd = "adb ";
154  if options.adb_options:
155    adb_cmd += options.adb_options
156
157  # Re-generate the test list if --refresh-test-list is on
158  if options.refresh_test_list:
159    logging.info("Generating test list.");
160    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"
161    adb_output = subprocess.Popen(generate_test_list_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
162
163    if adb_output.find('Process crashed') != -1:
164       logging.info("Aborting because cannot generate test list.\n" + adb_output)
165       sys.exit(1)
166
167
168  logging.info("Running tests")
169
170  # Count crashed tests.
171  crashed_tests = []
172
173  timeout_ms = '5000'
174  if options.time_out_ms:
175    timeout_ms = options.time_out_ms
176
177  # Run test until it's done
178
179  run_layout_test_cmd_prefix = adb_cmd + " shell am instrument"
180
181  run_layout_test_cmd_postfix = " -e path \"" + path + "\" -e timeout " + timeout_ms
182  if options.rebaseline:
183    run_layout_test_cmd_postfix += " -e rebaseline true"
184  run_layout_test_cmd_postfix += " -w com.android.dumprendertree/.LayoutTestsAutoRunner"
185
186  # Call LayoutTestsAutoTest::startLayoutTests.
187  run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#startLayoutTests" + run_layout_test_cmd_postfix
188
189  adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
190  while not DumpRenderTreeFinished(adb_cmd):
191    # Get the running_test.txt
192    logging.error("DumpRenderTree crashed, output:\n" + adb_output)
193
194    shell_cmd_str = adb_cmd + " shell cat /sdcard/android/running_test.txt"
195    crashed_test = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE).communicate()[0]
196
197    logging.info(crashed_test + " CRASHED");
198    crashed_tests.append(crashed_test);
199
200    logging.info("Resuming layout test runner...");
201    # Call LayoutTestsAutoTest::resumeLayoutTests
202    run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#resumeLayoutTests" + run_layout_test_cmd_postfix
203
204    adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
205
206  if adb_output.find('INSTRUMENTATION_FAILED') != -1:
207    logging.error("Error happened : " + adb_output)
208    sys.exit(1)
209
210  logging.debug(adb_output);
211  logging.info("Done\n");
212
213  # Pull results from /sdcard
214  results_dir = options.results_directory
215  if not os.path.exists(results_dir):
216    os.makedirs(results_dir)
217  if not os.path.isdir(results_dir):
218    logging.error("Cannot create results dir: " + results_dir);
219    sys.exit(1);
220
221  result_files = ["/sdcard/layout_tests_passed.txt",
222                  "/sdcard/layout_tests_failed.txt",
223                  "/sdcard/layout_tests_nontext.txt"]
224  for file in result_files:
225    shell_cmd_str = adb_cmd + " pull " + file + " " + results_dir
226    adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
227    logging.debug(adb_output)
228
229  # Create the crash list.
230  fp = open(results_dir + "/layout_tests_crashed.txt", "w");
231  fp.writelines('\n'.join(crashed_tests))
232  fp.close()
233
234  # Count the number of tests in each category.
235  passed_tests = CountLineNumber(results_dir + "/layout_tests_passed.txt")
236  logging.info(str(passed_tests) + " passed")
237  failed_tests = CountLineNumber(results_dir + "/layout_tests_failed.txt")
238  logging.info(str(failed_tests) + " failed")
239  crashed_tests = CountLineNumber(results_dir + "/layout_tests_crashed.txt")
240  logging.info(str(crashed_tests) + " crashed")
241  nontext_tests = CountLineNumber(results_dir + "/layout_tests_nontext.txt")
242  logging.info(str(nontext_tests) + " no dumpAsText")
243
244  logging.info("Results are stored under: " + results_dir + "\n")
245
246  # Comparing results to references to find new fixes and regressions.
247  results_dir = os.path.abspath(options.results_directory)
248  ref_dir = options.ref_directory
249
250  # if ref_dir is null, cannonify ref_dir to the script dir.
251  if not ref_dir:
252    script_self = sys.argv[0]
253    script_dir = os.path.dirname(script_self)
254    ref_dir = os.path.join(script_dir, "results")
255
256  ref_dir = os.path.abspath(ref_dir)
257
258  CompareResults(ref_dir, results_dir)
259
260if '__main__' == __name__:
261  option_parser = optparse.OptionParser()
262  option_parser.add_option("", "--rebaseline", action="store_true",
263                           default=False,
264                           help="generate expected results for those tests not having one")
265  option_parser.add_option("", "--time-out-ms",
266                           default=None,
267                           help="set the timeout for each test")
268  option_parser.add_option("", "--verbose", action="store_true",
269                           default=False,
270                           help="include debug-level logging")
271  option_parser.add_option("", "--refresh-test-list", action="store_true",
272                           default=False,
273                           help="re-generate test list, it may take some time.")
274  option_parser.add_option("", "--adb-options",
275                           default=None,
276                           help="pass options to adb, such as -d -e, etc");
277  option_parser.add_option("", "--results-directory",
278                           default="layout-test-results",
279                           help="directory which results are stored.")
280  option_parser.add_option("", "--ref-directory",
281                           default=None,
282                           dest="ref_directory",
283                           help="directory where reference results are stored.")
284
285  options, args = option_parser.parse_args();
286  main(options, args)
287