blob: 896973de365b7a4da82033d834321a0ad0b80f7a [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import re
import shutil
import stat
import subprocess
import time
import traceback
import build_common
import prep_launch_chrome
import staging
import toolchain
from build_options import OPTIONS
from util import file_util
from util import platform_util
from util.test import suite_runner
from util.test import suite_runner_config_flags as flags
from util.test import system_mode
from util.test import test_method_result
_NACL_FILTER_PATTERN = re.compile('|'.join([
r'^WARNING: SIGPIPE not blocked$',
r'^linker:']))
_BARE_METAL_FILTER_PATTERN = re.compile('|'.join([
r'^WARNING: SIGPIPE not blocked$',
r'^WARNING: linker: libdvm.so has text relocations.',
r'^bm_loader:',
# TODO(crbug.com/406226): r'^libcxx: DANGER' should be removed once
# ARC is rebased to L.
r'^libcxx: DANGER',
r'^linker:',
# TODO(crbug.com/266627): This filter is
# for temporary messages. Remove this.
r'^nacl_irt_']))
def _cleanup_output(raw):
if platform_util.is_running_on_cygwin():
# TODO(crbug.com/355468): Normalize the newline characters.
# If the root cause is inside ARC, text mode handling should be killed
# in posix translation or dalvik runtime to be compatible with Linux.
raw = raw.replace('\r\n', '\n')
filter_pattern = None
if OPTIONS.is_nacl_build():
filter_pattern = _NACL_FILTER_PATTERN
elif OPTIONS.is_bare_metal_build():
filter_pattern = _BARE_METAL_FILTER_PATTERN
if not filter_pattern:
return raw
lines = [line for line in raw.split('\n') if not filter_pattern.match(line)]
return '\n'.join(lines)
class DalvikVMTestRunner(suite_runner.SuiteRunnerBase):
"""A test runner for running Dalvik VM tests."""
DALVIK_TESTS_DIR = staging.as_staging('android/dalvik/tests')
def __init__(self, test_name, **kwargs):
test_source_dir = os.path.join(
DalvikVMTestRunner.DALVIK_TESTS_DIR, test_name)
# TODO(crbug.com/354354): Currently we cannot write an expectation
# for a test which does not have a third level name, and need to
# store the expectation manually.
test_cases_file = os.path.join(test_source_dir, 'test_cases')
if os.path.exists(test_cases_file):
# If we have testcase file, it contains, "Java arguments" and "dalvik
# VM options". We use "Java arguments" part as its name as is, although
# it sometimes contains white spaces.
with open(test_cases_file) as stream:
test_arg_map = dict(line.split(':', 1) for line in stream)
else:
# There is no individual test case. So we name whole the test case as
# "Main" here.
test_arg_map = {'Main': ''}
super(DalvikVMTestRunner, self).__init__(
'dalvik.' + test_name,
dict.fromkeys(test_arg_map.iterkeys(), flags.PASS),
**kwargs)
self._dalvik_test_name = test_name
self._test_source_dir = test_source_dir
self._test_dir = os.path.join(
build_common.get_target_common_dir(), 'dalvik_tests', test_name)
self._is_benchmark = os.path.exists(
os.path.join(test_source_dir, 'README.benchmark'))
self._test_arg_map = test_arg_map
@staticmethod
def setup_work_root():
test_root = os.path.join(
build_common.get_target_common_dir(), 'dalvik_tests')
file_util.makedirs_safely(test_root)
def _shell_args(self, class_extra_args, dalvik_extra_args):
jars = ['/system/framework/' + jar for jar in ['core.jar',
'ext.jar',
'framework.jar']]
execution_mode = 'jit' if OPTIONS.enable_dalvik_jit() else 'fast'
classpath = ':'.join(jars)
class_args = ['-cp', 'test.jar'] + class_extra_args.split()
dalvik_args = ['-Xbootclasspath:' + classpath,
'-Xgc:precise',
'-Xgenregmap',
'-Xint:' + execution_mode,
'-ea'] + dalvik_extra_args.split() + class_args
return ['shell', 'cd', '/data', ';', 'dalvikvm'] + dalvik_args
def prepare(self, unused_test_methods_to_run):
"""Builds test jar files for a test."""
test_dir = self._test_dir
if os.path.exists(test_dir):
shutil.rmtree(test_dir)
# Copy the source directory to the working directory.
# Note that we must not copy the files by python's internal utilities
# here, such as shutil.copy, or loops written manually, etc., because it
# would cause ETXTBSY in run_subprocess called below if we run this
# on multi-threading. Here is the scenario:
# Let there are two cases A, and B, and, to simplify, let what we do here
# are 1) copying the "{A_src,B_src}/build" files to "{A,B}/build", and then
# 2) fork() and execute() "{A,B}/build". Each will run on a different
# threads, named thread-A and thread-B.
# 1) on thread-A, "A_src/build" is copied to "A/build".
# 2) on thread-B, "B_src/build" starts to be copied to "B/build". For that
# purpose, "B/build" is opened with "write" flag.
# 3) on thread-A, the process is fork()'ed, *before the copy of "B/build"
# is completed. So, subprocess-A keeps the FD of "B/build" with "write".
# 4) on thread-B, "B/build" is copied, and close()'ed, then fork()'ed.
# 5) on subprocess-B, it tries to exec "B/build". However, the file is
# still kept opened by subprocess-A. As a result, ETXTBSY is reported.
# Probably, the ideal solution would be that such an issue should be
# handled by the framework (crbug.com/345667), but it seems to need some
# more investigation. So, instead, we copy the files in another process.
subprocess.check_call(['cp', '-Lr', self._test_source_dir, test_dir])
build_script = os.path.abspath(os.path.join(test_dir, 'build'))
if not os.path.isfile(build_script):
# If not found, use the default-build script.
# Note: do not use a python function here, such as shutil.copy directly.
# See above comment for details.
subprocess.check_call(
['cp',
os.path.join(DalvikVMTestRunner.DALVIK_TESTS_DIR,
'etc', 'default-build'),
build_script])
# Ensure that the executable bit is set.
os.chmod(build_script, stat.S_IRWXU)
env = {
'JAVAC': toolchain.get_tool('java', 'javac'),
'PATH': ':'.join([
os.environ['PATH'],
# TODO(crbug.com/378196): We should remove canned/host/android/bin
# from PATH when we remove the canned directory.
os.path.join(build_common.get_arc_root(),
'canned', 'host', 'android', 'bin', 'linux-i686'),
os.path.join(build_common.get_arc_root(),
toolchain.get_android_sdk_build_tools_dir())
])
}
self.run_subprocess([build_script], env=env, cwd=test_dir)
args = self.get_system_mode_launch_chrome_command(self._name)
prep_launch_chrome.prepare_crx_with_raw_args(args)
def run(self, test_methods_to_run):
results = {}
raw_output = []
for test_name in test_methods_to_run:
dalvik_args = self._test_arg_map[test_name]
with system_mode.SystemMode(self) as arc:
try:
# Mark incomplete at beginning. Will be overwrite on completion.
test_status = test_method_result.TestMethodResult.INCOMPLETE
test_file = os.path.join(self._test_dir, 'test.jar')
assert os.access(test_file, os.R_OK), ('can not read a test file ' +
test_file)
arc.run_adb(['push', test_file, '/data'])
test_ex_file = os.path.join(self._test_dir, 'test-ex.jar')
if os.access(test_ex_file, os.R_OK):
arc.run_adb(['push', test_ex_file, '/data'])
begin_time = time.time()
output = arc.run_adb(self._shell_args(test_name, dalvik_args))
elapsed_time = time.time() - begin_time
if self._is_benchmark:
output = 'Benchmark %s: %d ms' % (test_name, elapsed_time * 1000)
if self._args.output == 'verbose':
print output
raw_output.append(output)
else:
output_file_path = os.path.join(self._test_dir, 'output.txt')
with open(output_file_path, 'w') as output_file:
output_file.write(_cleanup_output(output))
expected_file_path = os.path.join(
self._test_source_dir, 'expected.txt')
# We diff the output against expected, ignoring whitespace,
# as the output file comes from running adb in ARC and so
# has \r\n instead of just \n.
output = self.run_subprocess(
['diff', '-b', output_file_path, expected_file_path],
omit_xvfb=True)
raw_output.append(output)
test_status = test_method_result.TestMethodResult.PASS
except subprocess.CalledProcessError as e:
if hasattr(e, 'output') and e.output:
raw_output.append(e.output)
result = test_method_result.TestMethodResult.FAIL
except system_mode.SystemModeError:
raw_output.append(arc.get_log())
result = test_method_result.TestMethodResult.FAIL
except Exception:
raw_output.append(traceback.format_exc())
raw_output.append(self._get_subprocess_output())
result = test_method_result.TestMethodResult.FAIL
result = test_method_result.TestMethodResult(test_name, test_status)
self.get_scoreboard().update([result])
results[test_name] = result
raw_output.append(arc.get_log())
return '\n'.join(raw_output), results