summaryrefslogtreecommitdiffstats
path: root/tests/DumpRenderTree/assets/run_layout_tests.py
blob: 7f3ef2d07c887221a29b2b7569b40b4cc842e782 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#!/usr/bin/python

"""Run layout tests using Android emulator and instrumentation.

  First, you need to get an SD card or sdcard image that has layout tests on it.
  Layout tests are in following directory:
    /sdcard/android/layout_tests
  For example, /sdcard/android/layout_tests/fast

  Usage:
    Run all tests under fast/ directory:
      run_layout_tests.py, or
      run_layout_tests.py fast

    Run all tests under a sub directory:
      run_layout_tests.py fast/dom

    Run a single test:
      run_layout_tests.py fast/dom/

  After a merge, if there are changes of layout tests in SD card, you need to
  use --refresh-test-list option *once* to re-generate test list on the card.

  Some other options are:
    --rebaseline generates expected layout tests results under /sdcard/android/expected_result/
    --time-out-ms (default is 8000 millis) for each test
    --adb-options="-e" passes option string to adb
    --results-directory=..., (default is ./layout-test-results) directory name under which results are stored.
    --js-engine the JavaScript engine currently in use, determines which set of Android-specific expected results we should use, should be 'jsc' or 'v8'
"""

import logging
import optparse
import os
import subprocess
import sys
import time

def CountLineNumber(filename):
  """Compute the number of lines in a given file.

  Args:
    filename: a file name related to the current directory.
  """

  fp = open(os.path.abspath(filename), "r");
  lines = 0
  for line in fp.readlines():
    lines = lines + 1
  fp.close()
  return lines

def DumpRenderTreeFinished(adb_cmd):
  """ Check if DumpRenderTree finished running tests

  Args:
    output: adb_cmd string
  """

  # pull /sdcard/android/running_test.txt, if the content is "#DONE", it's done
  shell_cmd_str = adb_cmd + " shell cat /sdcard/android/running_test.txt"
  adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
  return adb_output.strip() == "#DONE"

def DiffResults(marker, new_results, old_results, diff_results, strip_reason,
                new_count_first=True):
   """ Given two result files, generate diff and
       write to diff_results file. All arguments are absolute paths
       to files.
   """
   old_file = open(old_results, "r")
   new_file = open(new_results, "r")
   diff_file = open(diff_results, "a")

   # Read lines from each file
   ndict = new_file.readlines()
   cdict = old_file.readlines()

   # Write marker to diff file
   diff_file.writelines(marker + "\n")
   diff_file.writelines("###############\n")

   # Strip reason from result lines
   if strip_reason is True:
     for i in range(0, len(ndict)):
       ndict[i] = ndict[i].split(' ')[0] + "\n"
     for i in range(0, len(cdict)):
       cdict[i] = cdict[i].split(' ')[0] + "\n"

   params = {
       "new": [0, ndict, cdict, "+"],
       "miss": [0, cdict, ndict, "-"]
       }
   if new_count_first:
     order = ["new", "miss"]
   else:
     order = ["miss", "new"]

   for key in order:
     for line in params[key][1]:
       if line not in params[key][2]:
         if line[-1] != "\n":
           line += "\n";
         diff_file.writelines(params[key][3] + line)
         params[key][0] += 1

   logging.info(marker + "  >>> " + str(params["new"][0]) + " new, " +
                str(params["miss"][0]) + " misses")

   diff_file.writelines("\n\n")

   old_file.close()
   new_file.close()
   diff_file.close()
   return

def CompareResults(ref_dir, results_dir):
  """Compare results in two directories

  Args:
    ref_dir: the reference directory having layout results as references
    results_dir: the results directory
  """
  logging.info("Comparing results to " + ref_dir)

  diff_result = os.path.join(results_dir, "layout_tests_diff.txt")
  if os.path.exists(diff_result):
    os.remove(diff_result)

  files=["crashed", "failed", "passed", "nontext"]
  for f in files:
    result_file_name = "layout_tests_" + f + ".txt"
    DiffResults(f, os.path.join(results_dir, result_file_name),
                os.path.join(ref_dir, result_file_name), diff_result,
                False, f != "passed")
  logging.info("Detailed diffs are in " + diff_result)

def main(options, args):
  """Run the tests. Will call sys.exit when complete.

  Args:
    options: a dictionary of command line options
    args: a list of sub directories or files to test
  """

  # Set up logging format.
  log_level = logging.INFO
  if options.verbose:
    log_level = logging.DEBUG
  logging.basicConfig(level=log_level,
                      format='%(message)s')

  # Include all tests if none are specified.
  if not args:
    path = '/';
  else:
    path = ' '.join(args);

  adb_cmd = "adb ";
  if options.adb_options:
    adb_cmd += options.adb_options

  # Re-generate the test list if --refresh-test-list is on
  if options.refresh_test_list:
    logging.info("Generating test list.");
    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"
    adb_output = subprocess.Popen(generate_test_list_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]

    if adb_output.find('Process crashed') != -1:
       logging.info("Aborting because cannot generate test list.\n" + adb_output)
       sys.exit(1)


  logging.info("Running tests")

  # Count crashed tests.
  crashed_tests = []

  timeout_ms = '30000'
  if options.time_out_ms:
    timeout_ms = options.time_out_ms

  # Run test until it's done

  run_layout_test_cmd_prefix = adb_cmd + " shell am instrument"

  run_layout_test_cmd_postfix = " -e path \"" + path + "\" -e timeout " + timeout_ms
  if options.rebaseline:
    run_layout_test_cmd_postfix += " -e rebaseline true"

  # If the JS engine is not specified on the command line, try reading the
  # JS_ENGINE environment  variable, which is used by the build system in
  # external/webkit/Android.mk.
  js_engine = options.js_engine
  if not js_engine:
    js_engine = os.environ['JS_ENGINE']
  if js_engine:
    run_layout_test_cmd_postfix += " -e jsengine " + js_engine

  run_layout_test_cmd_postfix += " -w com.android.dumprendertree/.LayoutTestsAutoRunner"

  # Call LayoutTestsAutoTest::startLayoutTests.
  run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#startLayoutTests" + run_layout_test_cmd_postfix

  adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
  while not DumpRenderTreeFinished(adb_cmd):
    # Get the running_test.txt
    logging.error("DumpRenderTree crashed, output:\n" + adb_output)

    shell_cmd_str = adb_cmd + " shell cat /sdcard/android/running_test.txt"
    crashed_test = ""
    while not crashed_test:
      (crashed_test, err) = subprocess.Popen(
          shell_cmd_str, shell=True, stdout=subprocess.PIPE,
          stderr=subprocess.PIPE).communicate()
      crashed_test = crashed_test.strip()
      if not crashed_test:
        logging.error('Cannot get crashed test name, device offline?')
        logging.error('stderr: ' + err)
        logging.error('retrying in 10s...')
        time.sleep(10)

    logging.info(crashed_test + " CRASHED");
    crashed_tests.append(crashed_test);

    logging.info("Resuming layout test runner...");
    # Call LayoutTestsAutoTest::resumeLayoutTests
    run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#resumeLayoutTests" + run_layout_test_cmd_postfix

    adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]

  if adb_output.find('INSTRUMENTATION_FAILED') != -1:
    logging.error("Error happened : " + adb_output)
    sys.exit(1)

  logging.debug(adb_output);
  logging.info("Done\n");

  # Pull results from /sdcard
  results_dir = options.results_directory
  if not os.path.exists(results_dir):
    os.makedirs(results_dir)
  if not os.path.isdir(results_dir):
    logging.error("Cannot create results dir: " + results_dir);
    sys.exit(1);

  result_files = ["/sdcard/layout_tests_passed.txt",
                  "/sdcard/layout_tests_failed.txt",
                  "/sdcard/layout_tests_nontext.txt"]
  for file in result_files:
    shell_cmd_str = adb_cmd + " pull " + file + " " + results_dir
    adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
    logging.debug(adb_output)

  # Create the crash list.
  fp = open(results_dir + "/layout_tests_crashed.txt", "w");
  for crashed_test in crashed_tests:
    fp.writelines(crashed_test + '\n')
  fp.close()

  # Count the number of tests in each category.
  passed_tests = CountLineNumber(results_dir + "/layout_tests_passed.txt")
  logging.info(str(passed_tests) + " passed")
  failed_tests = CountLineNumber(results_dir + "/layout_tests_failed.txt")
  logging.info(str(failed_tests) + " failed")
  crashed_tests = CountLineNumber(results_dir + "/layout_tests_crashed.txt")
  logging.info(str(crashed_tests) + " crashed")
  nontext_tests = CountLineNumber(results_dir + "/layout_tests_nontext.txt")
  logging.info(str(nontext_tests) + " no dumpAsText")

  logging.info("Results are stored under: " + results_dir + "\n")

  # Comparing results to references to find new fixes and regressions.
  results_dir = os.path.abspath(options.results_directory)
  ref_dir = options.ref_directory

  # if ref_dir is null, cannonify ref_dir to the script dir.
  if not ref_dir:
    script_self = sys.argv[0]
    script_dir = os.path.dirname(script_self)
    ref_dir = os.path.join(script_dir, "results")

  ref_dir = os.path.abspath(ref_dir)

  CompareResults(ref_dir, results_dir)

if '__main__' == __name__:
  option_parser = optparse.OptionParser()
  option_parser.add_option("", "--rebaseline", action="store_true",
                           default=False,
                           help="generate expected results for those tests not having one")
  option_parser.add_option("", "--time-out-ms",
                           default=None,
                           help="set the timeout for each test")
  option_parser.add_option("", "--verbose", action="store_true",
                           default=False,
                           help="include debug-level logging")
  option_parser.add_option("", "--refresh-test-list", action="store_true",
                           default=False,
                           help="re-generate test list, it may take some time.")
  option_parser.add_option("", "--adb-options",
                           default=None,
                           help="pass options to adb, such as -d -e, etc");
  option_parser.add_option("", "--results-directory",
                           default="layout-test-results",
                           help="directory which results are stored.")
  option_parser.add_option("", "--ref-directory",
                           default=None,
                           dest="ref_directory",
                           help="directory where reference results are stored.")
  option_parser.add_option("", "--js-engine",
                           default=None,
                           help="The JavaScript engine currently in use, which determines which set of Android-specific expected results we should use. Should be 'jsc' or 'v8'.");

  options, args = option_parser.parse_args();
  main(options, args)