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