2009-03-03 19:31:44 -08:00
#!/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 :
2011-01-12 22:57:41 -08:00
/ sdcard / webkit / layout_tests
For example , / sdcard / webkit / layout_tests / fast
2009-03-03 19:31:44 -08:00
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 :
2011-01-12 22:57:41 -08:00
- - rebaseline generates expected layout tests results under / sdcard / webkit / expected_result /
2009-03-03 19:31:44 -08:00
- - 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 .
2010-02-25 12:50:33 +00:00
- - js - engine the JavaScript engine currently in use , determines which set of Android - specific expected results we should use , should be ' jsc ' or ' v8 '
2009-03-03 19:31:44 -08:00
"""
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
2009-08-20 18:20:46 -07:00
2009-03-03 19:31:44 -08:00
Args :
output : adb_cmd string
"""
2009-08-20 18:20:46 -07:00
2011-01-12 22:57:41 -08:00
# pull /sdcard/webkit/running_test.txt, if the content is "#DONE", it's done
shell_cmd_str = adb_cmd + " shell cat /sdcard/webkit/running_test.txt "
2009-03-03 19:31:44 -08:00
adb_output = subprocess . Popen ( shell_cmd_str , shell = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE ) . communicate ( ) [ 0 ]
return adb_output . strip ( ) == " #DONE "
2009-06-12 15:03:45 -07:00
def DiffResults ( marker , new_results , old_results , diff_results , strip_reason ,
new_count_first = True ) :
2009-03-03 19:31:44 -08:00
""" 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 " )
2009-08-20 18:20:46 -07:00
diff_file = open ( diff_results , " a " )
2009-03-03 19:31:44 -08:00
# 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 "
2009-06-12 15:03:45 -07:00
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 " )
2009-03-03 19:31:44 -08:00
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 )
2009-08-20 18:20:46 -07:00
diff_result = os . path . join ( results_dir , " layout_tests_diff.txt " )
2009-03-03 19:31:44 -08:00
if os . path . exists ( diff_result ) :
os . remove ( diff_result )
2009-06-12 15:03:45 -07:00
files = [ " crashed " , " failed " , " passed " , " nontext " ]
2009-03-03 19:31:44 -08:00
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 ,
2009-06-19 15:40:01 -07:00
False , f != " passed " )
2009-03-03 19:31:44 -08:00
logging . info ( " Detailed diffs are in " + diff_result )
def main ( options , args ) :
""" Run the tests. Will call sys.exit when complete.
2009-08-20 18:20:46 -07:00
2009-03-03 19:31:44 -08:00
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 :
2009-03-13 13:04:22 -07:00
path = ' / ' ;
2009-03-03 19:31:44 -08:00
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. " ) ;
2009-03-13 13:04:22 -07:00
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 ]
2009-03-03 19:31:44 -08:00
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 = [ ]
2010-05-03 11:49:04 -07:00
timeout_ms = ' 15000 '
2009-03-03 19:31:44 -08:00
if options . time_out_ms :
timeout_ms = options . time_out_ms
# Run test until it's done
2009-03-13 13:04:22 -07:00
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 "
2010-02-25 12:50:33 +00:00
# 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
2010-03-11 13:27:20 +00:00
if not js_engine and os . environ . has_key ( ' JS_ENGINE ' ) :
2010-02-25 12:50:33 +00:00
js_engine = os . environ [ ' JS_ENGINE ' ]
if js_engine :
run_layout_test_cmd_postfix + = " -e jsengine " + js_engine
2009-03-13 13:04:22 -07:00
run_layout_test_cmd_postfix + = " -w com.android.dumprendertree/.LayoutTestsAutoRunner "
2009-03-03 19:31:44 -08:00
# Call LayoutTestsAutoTest::startLayoutTests.
2009-03-13 13:04:22 -07:00
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 ]
2009-03-03 19:31:44 -08:00
while not DumpRenderTreeFinished ( adb_cmd ) :
# Get the running_test.txt
logging . error ( " DumpRenderTree crashed, output: \n " + adb_output )
2011-01-12 22:57:41 -08:00
shell_cmd_str = adb_cmd + " shell cat /sdcard/webkit/running_test.txt "
2009-09-14 15:20:52 -07:00
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 )
2009-08-20 18:20:46 -07:00
2009-03-03 19:31:44 -08:00
logging . info ( crashed_test + " CRASHED " ) ;
crashed_tests . append ( crashed_test ) ;
logging . info ( " Resuming layout test runner... " ) ;
# Call LayoutTestsAutoTest::resumeLayoutTests
2009-03-13 13:04:22 -07:00
run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#resumeLayoutTests " + run_layout_test_cmd_postfix
2009-03-03 19:31:44 -08:00
2009-03-13 13:04:22 -07:00
adb_output = subprocess . Popen ( run_layout_test_cmd , shell = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE ) . communicate ( ) [ 0 ]
2009-03-03 19:31:44 -08:00
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 " ,
2010-03-09 13:54:09 +00:00
" /sdcard/layout_tests_ignored.txt " ,
2009-03-03 19:31:44 -08:00
" /sdcard/layout_tests_nontext.txt " ]
2009-08-20 18:20:46 -07:00
for file in result_files :
2009-03-03 19:31:44 -08:00
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 )
2009-08-20 18:20:46 -07:00
2009-03-03 19:31:44 -08:00
# Create the crash list.
fp = open ( results_dir + " /layout_tests_crashed.txt " , " w " ) ;
2009-08-20 18:20:46 -07:00
for crashed_test in crashed_tests :
fp . writelines ( crashed_test + ' \n ' )
2009-03-03 19:31:44 -08:00
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 " )
2010-03-09 13:54:09 +00:00
ignored_tests = CountLineNumber ( results_dir + " /layout_tests_ignored.txt " )
logging . info ( str ( ignored_tests ) + " ignored results " )
2009-03-03 19:31:44 -08:00
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 " )
2010-03-09 13:54:09 +00:00
logging . info ( str ( passed_tests + failed_tests + ignored_tests + crashed_tests + nontext_tests ) + " TOTAL " )
2009-03-03 19:31:44 -08:00
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 ( )
2009-03-13 13:04:22 -07:00
option_parser . add_option ( " " , " --rebaseline " , action = " store_true " ,
default = False ,
help = " generate expected results for those tests not having one " )
2009-03-03 19:31:44 -08:00
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. " )
2010-02-25 12:50:33 +00:00
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 ' . " ) ;
2009-03-03 19:31:44 -08:00
options , args = option_parser . parse_args ( ) ;
main ( options , args )