summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-19 21:02:09 +0000
committerjrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-19 21:02:09 +0000
commitd9f9695633f8ea3dd1d4d02136c12cf2ffe429b8 (patch)
tree18b8981c285c249d714b079cafce988ad66d5f8d
parentd9426956e7653f57361544bfa9ff7cfd84590a5a (diff)
downloadchromium_src-d9f9695633f8ea3dd1d4d02136c12cf2ffe429b8.zip
chromium_src-d9f9695633f8ea3dd1d4d02136c12cf2ffe429b8.tar.gz
chromium_src-d9f9695633f8ea3dd1d4d02136c12cf2ffe429b8.tar.bz2
apk-based test runner work for android. 2 unit test bundles converted over (ipc, base).
OFF by default; enable with a gyp var. E.g. GYP_DEFINES="$GYP_DEFINES gtest_target_type=shared_library" android_gyp Some useful commands: adb uninstall org.chromium.native_test adb install -r out/Release/base_unittests_apk/ChromeNativeTests-debug.apk adb shell am start -n org.chromium.native_test/org.chromium.native_test.ChromeNativeTestActivity For the moment, all apks can be built simultaneously but use the same activity name. Thus you cannot have more than one installed at the same time. BUG=None TEST= Review URL: http://codereview.chromium.org/10051021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@133053 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/android/java/base.xml24
-rw-r--r--base/android/jni_android.h3
-rw-r--r--base/base.gyp56
-rw-r--r--build/all_android.gyp14
-rwxr-xr-xbuild/android/buildbot_functions.sh22
-rwxr-xr-xbuild/android/buildbot_fyi.sh2
-rwxr-xr-xbuild/android/buildbot_try_compile_test.sh1
-rwxr-xr-xbuild/android/envsetup.sh9
-rwxr-xr-xbuild/android/run_tests.py36
-rw-r--r--build/android/single_test_runner.py17
-rw-r--r--build/android/test_package.py15
-rw-r--r--build/android/test_package_apk.py91
-rw-r--r--build/android/test_result.py8
-rw-r--r--build/common.gypi12
-rw-r--r--ipc/ipc.gyp53
-rw-r--r--media/base/android/OWNERS3
-rw-r--r--media/base/android/java/java.gyp3
-rw-r--r--testing/android/AndroidManifest.xml30
-rw-r--r--testing/android/OWNERS3
-rwxr-xr-xtesting/android/generate_native_test.py194
-rw-r--r--testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java66
-rw-r--r--testing/android/native_test.gyp96
-rw-r--r--testing/android/native_test_apk.xml55
-rw-r--r--testing/android/native_test_launcher.cc190
-rw-r--r--testing/android/res/values/strings.xml11
25 files changed, 979 insertions, 35 deletions
diff --git a/base/android/java/base.xml b/base/android/java/base.xml
index 36afff4..2da1441 100644
--- a/base/android/java/base.xml
+++ b/base/android/java/base.xml
@@ -9,8 +9,12 @@
two should be unified. -->
<property name="sdk.version" value="${env.ANDROID_SDK_VERSION}"/>
<property name="src" location="."/>
- <property name="build" location="build"/>
<property name="dist" location="dist"/>
+ <property name="out.dir" location="${PRODUCT_DIR}"/>
+ <!-- TODO(jrg): establish a standard for the intermediate java
+ directories. Settle on a standard once ant/jar build files
+ like this are androidified -->
+ <property name="dest.dir" location="${PRODUCT_DIR}/java/base"/>
<!-- Set path depending on the type of repository. If ANDROID_BUILD_TOP is
set then build using the provided location. Otherwise, assume the build
@@ -25,13 +29,20 @@
<!-- Create the time stamp -->
<tstamp/>
<!-- Create the build directory structure used by compile -->
- <mkdir dir="${build}"/>
+ <mkdir dir="${out.dir}"/>
+ <mkdir dir="${dest.dir}"/>
</target>
<target name="compile" depends="init"
description="compile the source " >
<!-- Compile the java code from ${src} into ${build} -->
- <javac srcdir="${src}" destdir="${build}">
+ <!-- TODO(jrg): adapting this to a proper android antfile will
+ remove warnings like this:
+ base.xml:23: warning: 'includeantruntime' was not set,
+ defaulting to build.sysclasspath=last;
+ set to false for repeatable builds
+ -->
+ <javac srcdir="${src}" destdir="${dest.dir}">
<classpath>
<path location="${location.base}/android.jar"/>
</classpath>
@@ -43,14 +54,13 @@
<!-- Create the distribution directory -->
<mkdir dir="${dist}/lib"/>
- <!-- Put everything in ${build} into the chromium_base.jar file -->
- <jar jarfile="${dist}/lib/chromium_base.jar" basedir="${build}"/>
+ <jar jarfile="${out.dir}/chromium_base.jar" basedir="${dest.dir}"/>
</target>
<target name="clean"
description="clean up" >
- <!-- Delete the ${build} and ${dist} directory trees -->
- <delete dir="${build}"/>
+ <!-- Delete the appropriate directory trees -->
+ <delete dir="${dest.dir}"/>
<delete dir="${dist}"/>
</target>
</project>
diff --git a/base/android/jni_android.h b/base/android/jni_android.h
index e483281..878798e 100644
--- a/base/android/jni_android.h
+++ b/base/android/jni_android.h
@@ -14,6 +14,9 @@
namespace base {
namespace android {
+// Used to mark symbols to be exported in a shared library's symbol table.
+#define JNI_EXPORT __attribute__ ((visibility("default")))
+
// Attach the current thread to the VM (if necessary) and return the JNIEnv*.
JNIEnv* AttachCurrentThread();
diff --git a/base/base.gyp b/base/base.gyp
index 4d8a7d9..be281dc 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -128,7 +128,7 @@
},
{
'target_name': 'base_unittests',
- 'type': 'executable',
+ 'type': '<(gtest_target_type)',
'sources': [
# Tests.
'android/jni_android_unittest.cc',
@@ -313,6 +313,11 @@
'android/jni_generator/jni_generator.gyp:jni_generator_tests',
],
}],
+ ['OS=="android" and "<(gtest_target_type)"=="shared_library"', {
+ 'dependencies': [
+ '../testing/android/native_test.gyp:native_test_native_code',
+ ],
+ }],
['use_glib==1', {
'sources!': [
'file_version_info_unittest.cc',
@@ -627,5 +632,54 @@
},
],
}],
+ # Special target to wrap a <(gtest_target_type)==shared_library
+ # base_unittests into an android apk for execution.
+ # TODO(jrg): lib.target comes from _InstallableTargetInstallPath()
+ # in the gyp make generator. What is the correct way to extract
+ # this path from gyp and into 'raw' for input to antfiles?
+ # Hard-coding in the gypfile seems a poor choice.
+ # TODO(jrg): there has to be a shorter way to do all this. Try
+ # and convert this entire target cluster into ~5 lines that can be
+ # trivially copied to other projects with only a deps change, such
+ # as with a new gtest_target_type called
+ # 'shared_library_apk_wrapper' that does a lot of this magically.
+ ['OS=="android" and "<(gtest_target_type)"=="shared_library"', {
+ 'targets': [
+ {
+ 'target_name': 'base_unittests_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'base', # So that android/java/java.gyp:base_java is built
+ 'base_unittests',
+ ],
+ 'actions': [
+ {
+ # Generate apk files (including source and antfile) from
+ # a template, and builds them.
+ 'action_name': 'generate_and_build',
+ 'inputs': [
+ '../testing/android/generate_native_test.py',
+ '<(PRODUCT_DIR)/lib.target/libbase_unittests.so',
+ '<(PRODUCT_DIR)/chromium_base.jar'
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/ChromeNativeTests_base_unittests-debug.apk',
+ ],
+ 'action': [
+ '../testing/android/generate_native_test.py',
+ '--native_library',
+ '<(PRODUCT_DIR)/lib.target/libbase_unittests.so',
+ '--jar',
+ '<(PRODUCT_DIR)/chromium_base.jar',
+ '--output',
+ '<(PRODUCT_DIR)/base_unittests_apk',
+ '--ant-args',
+ '-DPRODUCT_DIR=<(PRODUCT_DIR)',
+ '--ant-compile'
+ ],
+ },
+ ]
+ }],
+ }],
],
}
diff --git a/build/all_android.gyp b/build/all_android.gyp
index d7c534cb..759a6c1 100644
--- a/build/all_android.gyp
+++ b/build/all_android.gyp
@@ -49,6 +49,20 @@
# gyps to resolve.
'../chrome/chrome_resources.gyp:packed_resources',
],
+ 'conditions': [
+ ['"<(gtest_target_type)"=="shared_library"', {
+ 'dependencies': [
+ # The first item is simply the template. We add as a dep
+ # to make sure it builds in ungenerated form. TODO(jrg):
+ # once stable, transition to a test-only (optional)
+ # target.
+ '../testing/android/native_test.gyp:native_test_apk',
+ # Unit test bundles packaged as an apk.
+ '../base/base.gyp:base_unittests_apk',
+ '../ipc/ipc.gyp:ipc_tests_apk',
+ ],
+ }]
+ ],
},
{
# Experimental / in-progress targets that are expected to fail
diff --git a/build/android/buildbot_functions.sh b/build/android/buildbot_functions.sh
index c830e58..20dba61 100755
--- a/build/android/buildbot_functions.sh
+++ b/build/android/buildbot_functions.sh
@@ -189,6 +189,28 @@ function bb_compile {
bb_goma_make
}
+# Re-gyp and compile with unit test bundles configured as shlibs for
+# the native test runner. Experimental for now. Once the native test
+# loader is on by default, this entire function becomes obsolete.
+function bb_native_test_compile_run_tests {
+ echo "@@@BUILD_STEP Re-gyp for the native test runner@@@"
+ GYP_DEFINES="$GYP_DEFINES gtest_target_type=shared_library" android_gyp
+
+ echo "@@@BUILD_STEP Native test runner compile@@@"
+ bb_goma_make
+
+ # Make sure running the template prints an expected failure.
+ echo "@@@BUILD_STEP Native test runner template test@@@"
+ tempfile=/tmp/tempfile-$$.txt
+ build/android/run_tests.py --xvfb --verbose \
+ -s out/Release/replaceme_apk/ChromeNativeTests-debug.apk \
+ | sed 's/@@@STEP_FAILURE@@@//g' | tee $tempfile
+ happy_failure=$(cat $tempfile | grep RUNNER_FAILED | wc -l)
+ if [[ $happy_failure -eq 0 ]] ; then
+ echo "@@@STEP_FAILURE@@@"
+ fi
+}
+
# Experimental compile step; does not turn the tree red if it fails.
function bb_compile_experimental {
# Linking DumpRenderTree appears to hang forever?
diff --git a/build/android/buildbot_fyi.sh b/build/android/buildbot_fyi.sh
index 1670a3c..f0eb7dd 100755
--- a/build/android/buildbot_fyi.sh
+++ b/build/android/buildbot_fyi.sh
@@ -17,5 +17,3 @@ bb_install_build_deps "${ROOT}"/../..
bb_compile
bb_compile_experimental
bb_run_tests
-
-
diff --git a/build/android/buildbot_try_compile_test.sh b/build/android/buildbot_try_compile_test.sh
index 827c3e3..e48ed1b 100755
--- a/build/android/buildbot_try_compile_test.sh
+++ b/build/android/buildbot_try_compile_test.sh
@@ -16,3 +16,4 @@ bb_baseline_setup "${ROOT}"/../..
bb_install_build_deps "${ROOT}"/../..
bb_compile
bb_run_tests
+bb_native_test_compile_run_tests
diff --git a/build/android/envsetup.sh b/build/android/envsetup.sh
index 6c4c563..1628dbd 100755
--- a/build/android/envsetup.sh
+++ b/build/android/envsetup.sh
@@ -49,8 +49,13 @@ export ANDROID_TOOLCHAIN="${ANDROID_NDK_ROOT}/toolchains/arm-linux-androideabi-4
export ANDROID_SDK_VERSION="15"
-# Add Android SDK's platform-tools to system path.
-export PATH="${PATH}:${ANDROID_SDK_ROOT}/platform-tools/"
+# Needed by android antfiles when creating apks.
+export ANDROID_SDK_HOME=${ANDROID_SDK_ROOT}
+
+# Add Android SDK/NDK tools to system path.
+export PATH=$PATH:${ANDROID_NDK_ROOT}
+export PATH=$PATH:${ANDROID_SDK_ROOT}/tools
+export PATH=$PATH:${ANDROID_SDK_ROOT}/platform-tools
if [ ! -d "${ANDROID_TOOLCHAIN}" ]; then
echo "Can not find Android toolchain in ${ANDROID_TOOLCHAIN}." >& 2
diff --git a/build/android/run_tests.py b/build/android/run_tests.py
index 995d950..587a0c1 100755
--- a/build/android/run_tests.py
+++ b/build/android/run_tests.py
@@ -77,12 +77,23 @@ _TEST_SUITES = ['base_unittests',
]
-def FullyQualifiedTestSuites():
- """Return a fully qualified list that represents all known suites."""
+def FullyQualifiedTestSuites(apk):
+ """Return a fully qualified list that represents all known suites.
+
+ Args:
+ apk: if True, use the apk-based test runner"""
# If not specified, assume the test suites are in out/Release
test_suite_dir = os.path.abspath(os.path.join(run_tests_helper.CHROME_DIR,
'out', 'Release'))
- return [os.path.join(test_suite_dir, t) for t in _TEST_SUITES]
+ if apk:
+ # out/Release/$SUITE_apk/ChromeNativeTests-debug.apk
+ suites = [os.path.join(test_suite_dir,
+ t + '_apk',
+ 'ChromeNativeTests-debug.apk')
+ for t in _TEST_SUITES]
+ else:
+ suites = [os.path.join(test_suite_dir, t) for t in _TEST_SUITES]
+ return suites
class TimeProfile(object):
@@ -151,7 +162,7 @@ class Xvfb(object):
def RunTests(device, test_suite, gtest_filter, test_arguments, rebaseline,
timeout, performance_test, cleanup_test_files, tool,
- log_dump_name, annotate=False):
+ log_dump_name, apk, annotate=False):
"""Runs the tests.
Args:
@@ -165,6 +176,7 @@ def RunTests(device, test_suite, gtest_filter, test_arguments, rebaseline,
cleanup_test_files: Whether or not to cleanup test files on device.
tool: Name of the Valgrind tool.
log_dump_name: Name of log dump file.
+ apk: boolean to state if we are using the apk based test runner
annotate: should we print buildbot-style annotations?
Returns:
@@ -174,16 +186,18 @@ def RunTests(device, test_suite, gtest_filter, test_arguments, rebaseline,
if test_suite:
global _TEST_SUITES
- if not os.path.exists(test_suite):
+ if (not os.path.exists(test_suite) and
+ not os.path.splitext(test_suite)[1] == '.apk'):
logging.critical('Unrecognized test suite %s, supported: %s' %
(test_suite, _TEST_SUITES))
if test_suite in _TEST_SUITES:
logging.critical('(Remember to include the path: out/Release/%s)',
test_suite)
- return TestResults.FromOkAndFailed([], [BaseTestResult(test_suite, '')])
+ return TestResults.FromOkAndFailed([], [BaseTestResult(test_suite, '')],
+ False, False)
fully_qualified_test_suites = [test_suite]
else:
- fully_qualified_test_suites = FullyQualifiedTestSuites()
+ fully_qualified_test_suites = FullyQualifiedTestSuites(apk)
debug_info_list = []
print 'Known suites: ' + str(_TEST_SUITES)
print 'Running these: ' + str(fully_qualified_test_suites)
@@ -213,6 +227,8 @@ def RunTests(device, test_suite, gtest_filter, test_arguments, rebaseline,
print '@@@STEP_WARNINGS@@@'
elif test.test_results.failed:
print '@@@STEP_FAILURE@@@'
+ elif test.test_results.overall_fail:
+ print '@@@STEP_FAILURE@@@'
else:
print 'Step success!' # No annotation needed
@@ -323,6 +339,7 @@ def _RunATestSuite(options):
options.performance_test,
options.cleanup_test_files, options.tool,
options.log_dump,
+ options.apk,
annotate=options.annotate)
for buildbot_emulator in buildbot_emulators:
@@ -363,7 +380,7 @@ def Dispatch(options):
if options.test_suite:
all_test_suites = [options.test_suite]
else:
- all_test_suites = FullyQualifiedTestSuites()
+ all_test_suites = FullyQualifiedTestSuites(options.apk)
failures = 0
for suite in all_test_suites:
options.test_suite = suite
@@ -427,6 +444,9 @@ def main(argv):
option_parser.add_option('--annotate', default=True,
help='Print buildbot-style annotate messages '
'for each test suite. Default=True')
+ option_parser.add_option('--apk', default=False,
+ help='Use the apk test runner '
+ '(off by default for now)')
options, args = option_parser.parse_args(argv)
if len(args) > 1:
print 'Unknown argument:', args[1:]
diff --git a/build/android/single_test_runner.py b/build/android/single_test_runner.py
index bb79352..fe59cb6 100644
--- a/build/android/single_test_runner.py
+++ b/build/android/single_test_runner.py
@@ -9,6 +9,7 @@ import sys
from base_test_runner import BaseTestRunner
import debug_info
import run_tests_helper
+from test_package_apk import TestPackageApk
from test_package_executable import TestPackageExecutable
from test_result import TestResults
@@ -46,9 +47,16 @@ class SingleTestRunner(BaseTestRunner):
self.dump_debug_info = None
self.fast_and_loose = fast_and_loose
- self.test_package = TestPackageExecutable(self.adb, device,
- test_suite, timeout, rebaseline, performance_test, cleanup_test_files,
- tool, self.dump_debug_info)
+ if os.path.splitext(test_suite)[1] == '.apk':
+ self.test_package = TestPackageApk(
+ self.adb, device,
+ test_suite, timeout, rebaseline, performance_test, cleanup_test_files,
+ tool, self.dump_debug_info)
+ else:
+ self.test_package = TestPackageExecutable(
+ self.adb, device,
+ test_suite, timeout, rebaseline, performance_test, cleanup_test_files,
+ tool, self.dump_debug_info)
def _GetHttpServerDocumentRootForTestSuite(self):
"""Returns the document root needed by the test suite."""
@@ -265,7 +273,8 @@ class SingleTestRunner(BaseTestRunner):
break
self.test_results = TestResults.FromOkAndFailed(list(executed_results -
failed_results),
- list(failed_results))
+ list(failed_results),
+ False, False)
def RunTests(self):
"""Runs all tests (in rebaseline mode, runs each test in isolation).
diff --git a/build/android/test_package.py b/build/android/test_package.py
index 16b834c..0c04377 100644
--- a/build/android/test_package.py
+++ b/build/android/test_package.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Copyright (c) 2012 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.
@@ -35,6 +35,7 @@ class TestPackage(object):
performance_test, cleanup_test_files, tool, dump_debug_info):
self.adb = adb
self.device = device
+ self.test_suite_full = test_suite
self.test_suite = os.path.splitext(test_suite)[0]
self.test_suite_basename = os.path.basename(self.test_suite)
self.test_suite_dirname = os.path.dirname(self.test_suite)
@@ -129,14 +130,21 @@ class TestPackage(object):
ok_tests = []
failed_tests = []
timed_out = False
+ overall_fail = False
re_run = re.compile('\[ RUN \] ?(.*)\r\n')
re_fail = re.compile('\[ FAILED \] ?(.*)\r\n')
+ re_runner_fail = re.compile('\[ RUNNER_FAILED \] ?(.*)\r\n')
re_ok = re.compile('\[ OK \] ?(.*)\r\n')
(io_stats_before, ready_to_continue) = self._BeginGetIOStats()
while ready_to_continue:
- found = p.expect([re_run, pexpect.EOF], timeout=self.timeout)
+ found = p.expect([re_run, pexpect.EOF, re_runner_fail],
+ timeout=self.timeout)
if found == 1: # matched pexpect.EOF
break
+ if found == 2: # RUNNER_FAILED
+ logging.error('RUNNER_FAILED')
+ overall_fail = True
+ break
if self.dump_debug_info:
self.dump_debug_info.TakeScreenshot('_Test_Start_Run_')
full_test_name = p.match.group(1)
@@ -165,4 +173,5 @@ class TestPackage(object):
'\npexpect.after: %s'
% (p.before,
p.after))]
- return TestResults.FromOkAndFailed(ok_tests, failed_tests, timed_out)
+ return TestResults.FromOkAndFailed(ok_tests, failed_tests,
+ timed_out, overall_fail)
diff --git a/build/android/test_package_apk.py b/build/android/test_package_apk.py
new file mode 100644
index 0000000..144c429
--- /dev/null
+++ b/build/android/test_package_apk.py
@@ -0,0 +1,91 @@
+#!/usr/bin/python
+# Copyright (c) 2012 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 sys
+
+import cmd_helper
+import logging
+import shutil
+import tempfile
+from test_package import TestPackage
+
+
+class TestPackageApk(TestPackage):
+ """A helper class for running APK-based native tests.
+
+ Args:
+ adb: ADB interface the tests are using.
+ device: Device to run the tests.
+ test_suite: A specific test suite to run, empty to run all.
+ timeout: Timeout for each test.
+ rebaseline: Whether or not to run tests in isolation and update the filter.
+ performance_test: Whether or not performance test(s).
+ cleanup_test_files: Whether or not to cleanup test files on device.
+ tool: Name of the Valgrind tool.
+ dump_debug_info: A debug_info object.
+ """
+
+ APK_DATA_DIR = '/data/user/0/com.android.chrome.native_tests/files/'
+
+ def __init__(self, adb, device, test_suite, timeout, rebaseline,
+ performance_test, cleanup_test_files, tool,
+ dump_debug_info):
+ TestPackage.__init__(self, adb, device, test_suite, timeout,
+ rebaseline, performance_test, cleanup_test_files,
+ tool, dump_debug_info)
+
+ def _CreateTestRunnerScript(self, options):
+ tool_wrapper = self.tool.GetTestWrapper()
+ if tool_wrapper:
+ raise RuntimeError("TestPackageApk does not support custom wrappers.")
+ command_line_file = tempfile.NamedTemporaryFile()
+ # GTest expects argv[0] to be the executable path.
+ command_line_file.write(self.test_suite_basename + ' ' + options)
+ command_line_file.flush()
+ self.adb.PushIfNeeded(command_line_file.name,
+ TestPackageApk.APK_DATA_DIR +
+ 'chrome-native-tests-command-line')
+
+ def _GetGTestReturnCode(self):
+ return None
+
+ def GetAllTests(self):
+ """Returns a list of all tests available in the test suite."""
+ self._CreateTestRunnerScript('--gtest_list_tests')
+ self.adb.RunShellCommand(
+ 'am start -n '
+ 'com.android.chrome.native_tests/'
+ 'android.app.NativeActivity')
+ stdout_file = tempfile.NamedTemporaryFile()
+ ret = []
+ self.adb.Adb().Pull(TestPackageApk.APK_DATA_DIR + 'stdout.txt',
+ stdout_file.name)
+ ret = self._ParseGTestListTests(stdout_file)
+ return ret
+
+ def CreateTestRunnerScript(self, gtest_filter, test_arguments):
+ self._CreateTestRunnerScript('--gtest_filter=%s %s' % (gtest_filter,
+ test_arguments))
+
+ def RunTestsAndListResults(self):
+ self.adb.StartMonitoringLogcat(clear=True, logfile=sys.stdout)
+ self.adb.RunShellCommand(
+ 'am start -n '
+ 'org.chromium.native_test/'
+ 'org.chromium.native_test.ChromeNativeTestActivity')
+ return self._WatchTestOutput(self.adb.GetMonitoredLogCat())
+
+ def StripAndCopyExecutable(self):
+ # Always uninstall the previous one (by activity name); we don't
+ # know what was embedded in it.
+ logging.info('Uninstalling any activity with the test name')
+ self.adb.Adb().SendCommand('uninstall org.chromium.native_test',
+ timeout_time=60*5)
+ logging.info('Installing new apk')
+ self.adb.Adb().SendCommand('install -r ' + self.test_suite_full,
+ timeout_time=60*5)
+ logging.info('Install has completed.')
diff --git a/build/android/test_result.py b/build/android/test_result.py
index 0c56436..5f1a3e2 100644
--- a/build/android/test_result.py
+++ b/build/android/test_result.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Copyright (c) 2012 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.
@@ -58,13 +58,15 @@ class TestResults(object):
self.disabled = []
self.unexpected_pass = []
self.timed_out = False
+ self.overall_fail = False
@staticmethod
- def FromOkAndFailed(ok, failed, timed_out=False):
+ def FromOkAndFailed(ok, failed, timed_out, overall_fail):
ret = TestResults()
ret.ok = ok
ret.failed = failed
ret.timed_out = timed_out
+ ret.overall_fail = overall_fail
return ret
@staticmethod
@@ -80,6 +82,8 @@ class TestResults(object):
ret.unexpected_pass += t.unexpected_pass
if t.timed_out:
ret.timed_out = True
+ if t.overall_fail:
+ ret.overall_fail = True
return ret
def _Log(self, sorted_list):
diff --git a/build/common.gypi b/build/common.gypi
index 9c6a9bf..429438e 100644
--- a/build/common.gypi
+++ b/build/common.gypi
@@ -836,10 +836,11 @@
# http://crbug.com/115320
'notifications%': 0,
- # Builds the gtest targets as a shared_library.
- # TODO(michaelbai): Use the fixed value 'shared_library' once it
- # is fully supported.
'gtest_target_type%': '<(gtest_target_type)',
+ # TODO(jrg): when 'gtest_target_type'=='shared_libary' and
+ # OS==android, make all gtest_targets depend on
+ # testing/android/native_test.gyp:native_test_apk.
+ ### 'gtest_target_type': 'shared_libary',
# Uses system APIs for decoding audio and video.
'use_libffmpeg%': '0',
@@ -2427,6 +2428,11 @@
'ldflags': [
'-Wl,-shared,-Bsymbolic',
],
+ # Use of -nostdlib prevents the compiler from bringing
+ # in crtbegin_dynamic.o et al, so we get an undefined
+ # reference to ___dso_handle when building
+ # gtest_target_type==shared_library.
+ 'ldflags!': [ '-nostdlib' ],
}],
],
}],
diff --git a/ipc/ipc.gyp b/ipc/ipc.gyp
index 9b38f77..e6b5116 100644
--- a/ipc/ipc.gyp
+++ b/ipc/ipc.gyp
@@ -1,4 +1,4 @@
-# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Copyright (c) 2012 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.
@@ -32,7 +32,7 @@
'targets': [
{
'target_name': 'ipc_tests',
- 'type': 'executable',
+ 'type': '<(gtest_target_type)',
'dependencies': [
'ipc',
'../base/base.gyp:base',
@@ -63,6 +63,11 @@
'../build/linux/system.gyp:gtk',
],
}],
+ ['OS=="android" and "<(gtest_target_type)"=="shared_library"', {
+ 'dependencies': [
+ '../testing/android/native_test.gyp:native_test_native_code',
+ ],
+ }],
['os_posix == 1 and OS != "mac" and OS != "android"', {
'conditions': [
['linux_use_tcmalloc==1', {
@@ -87,4 +92,48 @@
],
},
],
+ 'conditions': [
+ # Special target to wrap a <(gtest_target_type)==shared_library
+ # ipc_tests into an android apk for execution.
+ # See base.gyp for TODO(jrg)s about this strategy.
+ ['OS=="android" and "<(gtest_target_type)"=="shared_library"', {
+ 'targets': [
+ {
+ 'target_name': 'ipc_tests_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'ipc_tests',
+ ],
+ 'actions': [
+ {
+ # Generate apk files (including source and antfile) from
+ # a template, and builds them.
+ 'action_name': 'generate_and_build',
+ 'inputs': [
+ '../testing/android/generate_native_test.py',
+ '<(PRODUCT_DIR)/lib.target/libipc_tests.so',
+ '<(PRODUCT_DIR)/chromium_base.jar'
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/ChromeNativeTests_ipc_tests-debug.apk',
+ ],
+ 'action': [
+ '../testing/android/generate_native_test.py',
+ '--native_library',
+ '<(PRODUCT_DIR)/lib.target/libipc_tests.so',
+ # TODO(jrg): find a better way to specify jar
+ # dependencies. Hard coding seems fragile.
+ '--jar',
+ '<(PRODUCT_DIR)/chromium_base.jar',
+ '--output',
+ '<(PRODUCT_DIR)/ipc_tests_apk',
+ '--ant-args',
+ '-DPRODUCT_DIR=<(PRODUCT_DIR)',
+ '--ant-compile'
+ ],
+ },
+ ],
+ }],
+ }],
+ ],
}
diff --git a/media/base/android/OWNERS b/media/base/android/OWNERS
new file mode 100644
index 0000000..b071eb0
--- /dev/null
+++ b/media/base/android/OWNERS
@@ -0,0 +1,3 @@
+bulach@chromium.org
+jcivelli@chromium.org
+jrg@chromium.org
diff --git a/media/base/android/java/java.gyp b/media/base/android/java/java.gyp
index 14717f13..d59aec4 100644
--- a/media/base/android/java/java.gyp
+++ b/media/base/android/java/java.gyp
@@ -19,10 +19,11 @@
'<(DEPTH)/base/android/java/java.gyp:base_java',
],
'outputs': [
- 'dist/lib/chromium_media.jar',
+ '$(PRODUCT_DIR)/chromium_media.jar',
],
'action': [
'ant',
+ '-DPRODUCT_DIR=<(PRODUCT_DIR)',
'-buildfile',
'media.xml',
]
diff --git a/testing/android/AndroidManifest.xml b/testing/android/AndroidManifest.xml
new file mode 100644
index 0000000..de98aec
--- /dev/null
+++ b/testing/android/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2012 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.chromium.native_test"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="15" />
+
+ <application android:label="ChromeNativeTests">
+ <activity android:name=".ChromeNativeTestActivity"
+ android:label="ChromeNativeTest"
+ android:configChanges="orientation|keyboardHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!-- TODO(jrg): add more permissions as needed by unit tests. -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+</manifest>
diff --git a/testing/android/OWNERS b/testing/android/OWNERS
new file mode 100644
index 0000000..da07f99
--- /dev/null
+++ b/testing/android/OWNERS
@@ -0,0 +1,3 @@
+bulach@chromium.org
+jrg@chromium.org
+
diff --git a/testing/android/generate_native_test.py b/testing/android/generate_native_test.py
new file mode 100755
index 0000000..7b7853d
--- /dev/null
+++ b/testing/android/generate_native_test.py
@@ -0,0 +1,194 @@
+#!/usr/bin/python
+# Copyright (c) 2012 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.
+
+# On Android we build unit test bundles as shared libraries. To run
+# tests, we launch a special "test runner" apk which loads the library
+# then jumps into it. Since java is required for many tests
+# (e.g. PathUtils.java), a "pure native" test bundle is inadequate.
+#
+# This script, generate_native_test.py, is used to generate the source
+# for an apk that wraps a unit test shared library bundle. That
+# allows us to have a single boiler-plate application be used across
+# all unit test bundles.
+
+import logging
+import optparse
+import os
+import re
+import shutil
+import subprocess
+import sys
+
+
+class NativeTestApkGenerator(object):
+ """Generate a native test apk source tree.
+
+ TODO(jrg): develop this more so the activity name is replaced as
+ well. That will allow multiple test runners to be installed at the
+ same time. (The complication is that it involves renaming a java
+ class, which implies regeneration of a jni header, and on and on...)
+ """
+
+ # Files or directories we need to copy to create a complete apk test shell.
+ _SOURCE_FILES = ['AndroidManifest.xml',
+ 'native_test_apk.xml',
+ 'res', # res/values/strings.xml
+ 'java', # .../ChromeNativeTestActivity.java
+ ]
+
+ # Files in the destion directory that have a "replaceme" string
+ # which should be replaced by the basename of the shared library.
+ # Note we also update the filename if 'replaceme' is itself found in
+ # the filename.
+ _REPLACEME_FILES = ['AndroidManifest.xml',
+ 'native_test_apk.xml',
+ 'res/values/strings.xml']
+
+ def __init__(self, native_library, jars, output_directory):
+ self._native_library = native_library
+ self._jars = jars
+ self._output_directory = output_directory
+ self._root_name = None
+ if self._native_library:
+ self._root_name = self._LibraryRoot()
+ logging.warn('root name: %s' % self._root_name)
+
+
+ def _LibraryRoot(self):
+ """Return a root name for a shared library.
+
+ The root name should be suitable for substitution in apk files
+ like the manifest. For example, blah/foo/libbase_unittests.so
+ becomes base_unittests.
+ """
+ rootfinder = re.match('.?lib(.+).so',
+ os.path.basename(self._native_library))
+ if rootfinder:
+ return rootfinder.group(1)
+ else:
+ return None
+
+ def _CopyTemplateFiles(self):
+ """Copy files needed to build a new apk.
+
+ TODO(jrg): add more smarts so we don't change file timestamps if
+ the files don't change?
+ """
+ srcdir = os.path.dirname(os.path.realpath( __file__))
+ if not os.path.exists(self._output_directory):
+ os.makedirs(self._output_directory)
+ for f in self._SOURCE_FILES:
+ src = os.path.join(srcdir, f)
+ dest = os.path.join(self._output_directory, f)
+ if os.path.isfile(src):
+ if os.path.exists(dest):
+ os.remove(dest)
+ logging.warn('%s --> %s' % (src, dest))
+ shutil.copyfile(src, dest)
+ else: # directory
+ if os.path.exists(dest):
+ # One more sanity check since we're deleting a directory...
+ if not '/out/' in dest:
+ raise Exception('Unbelievable output directory; bailing for safety')
+ shutil.rmtree(dest)
+ logging.warn('%s --> %s' % (src, dest))
+ shutil.copytree(src, dest)
+
+ def _ReplaceStrings(self):
+ """Replace 'replaceme' strings in generated files with a root libname.
+
+ If we have no root libname (e.g. no shlib was specified), do nothing.
+ """
+ if not self._root_name:
+ return
+ logging.warn('Replacing "replaceme" with ' + self._root_name)
+ for f in self._REPLACEME_FILES:
+ dest = os.path.join(self._output_directory, f)
+ contents = open(dest).read()
+ contents = contents.replace('replaceme', self._root_name)
+ dest = dest.replace('replaceme', self._root_name) # update the filename!
+ open(dest, "w").write(contents)
+
+ def _CopyLibraryAndJars(self):
+ """Copy the shlib and jars into the apk source tree (if relevant)"""
+ if self._native_library:
+ destdir = os.path.join(self._output_directory, 'libs/armeabi')
+ if not os.path.exists(destdir):
+ os.makedirs(destdir)
+ dest = os.path.join(destdir, os.path.basename(self._native_library))
+ logging.warn('%s --> %s' % (self._native_library, dest))
+ shutil.copyfile(self._native_library, dest)
+ if self._jars:
+ destdir = os.path.join(self._output_directory, 'libs')
+ if not os.path.exists(destdir):
+ os.makedirs(destdir)
+ for jar in self._jars:
+ dest = os.path.join(destdir, os.path.basename(jar))
+ logging.warn('%s --> %s' % (jar, dest))
+ shutil.copyfile(jar, dest)
+
+ def CreateBundle(self):
+ """Create the apk bundle source and assemble components."""
+ self._CopyTemplateFiles()
+ self._ReplaceStrings()
+ self._CopyLibraryAndJars()
+
+ def Compile(self, ant_args):
+ """Build the generated apk with ant.
+
+ Args:
+ ant_args: extra args to pass to ant
+ """
+ cmd = ['ant']
+ if ant_args:
+ cmd.append(ant_args)
+ cmd.extend(['-buildfile',
+ os.path.join(self._output_directory, 'native_test_apk.xml')])
+ logging.warn(cmd)
+ p = subprocess.Popen(cmd, stderr=subprocess.STDOUT)
+ (stdout, _) = p.communicate()
+ logging.warn(stdout)
+ if p.returncode != 0:
+ logging.error('Ant return code %d' % p.returncode)
+ sys.exit(p.returncode)
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('--verbose',
+ help='Be verbose')
+ parser.add_option('--native_library',
+ help='Full name of native shared library test bundle')
+ parser.add_option('--jar', action='append',
+ help='Include this jar; can be specified multiple times')
+ parser.add_option('--output',
+ help='Output directory for generated files.')
+ parser.add_option('--ant-compile', action='store_true',
+ help='If specified, build the generated apk with ant')
+ parser.add_option('--ant-args',
+ help='extra args for ant')
+
+ options, _ = parser.parse_args(argv)
+
+ # It is not an error to specify no native library; the apk should
+ # still be generated and build. It will, however, print
+ # NATIVE_LOADER_FAILED when run.
+ if not options.output:
+ raise Exception('No output directory specified for generated files')
+
+ if options.verbose:
+ logging.basicConfig(level=logging.DEBUG, format=' %(message)s')
+
+ ntag = NativeTestApkGenerator(native_library=options.native_library,
+ jars=options.jar,
+ output_directory=options.output)
+ ntag.CreateBundle()
+ if options.ant_compile:
+ ntag.Compile(options.ant_args)
+
+ logging.warn('COMPLETE.')
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java b/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java
new file mode 100644
index 0000000..b4bb6ad
--- /dev/null
+++ b/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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.
+
+package org.chromium.native_test;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+// Android's NativeActivity is mostly useful for pure-native code.
+// Our tests need to go up to our own java classes, which is not possible using
+// the native activity class loader.
+// We start a background thread in here to run the tests and avoid an ANR.
+// TODO(bulach): watch out for tests that implicitly assume they run on the main
+// thread.
+public class ChromeNativeTestActivity extends Activity {
+ private final String TAG = "ChromeNativeTestActivity";
+
+ // Name of our shlib as obtained from a string resource.
+ private String mLibrary;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mLibrary = getResources().getString(R.string.native_library);
+ if ((mLibrary == null) || mLibrary.startsWith("replace")) {
+ nativeTestFailed();
+ return;
+ }
+
+ try {
+ loadLibrary();
+ new Thread() {
+ @Override
+ public void run() {
+ Log.d(TAG, ">>nativeRunTests");
+ nativeRunTests(getFilesDir().getAbsolutePath());
+ // TODO(jrg): make sure a crash in native code
+ // triggers nativeTestFailed().
+ Log.d(TAG, "<<nativeRunTests");
+ }
+ }.start();
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "Unable to load lib" + mLibrary + ".so: " + e);
+ nativeTestFailed();
+ throw e;
+ }
+ }
+
+ // Signal a failure of the native test loader to python scripts
+ // which run tests. For example, we look for
+ // RUNNER_FAILED build/android/test_package.py.
+ private void nativeTestFailed() {
+ Log.e(TAG, "[ RUNNER_FAILED ] could not load native library");
+ }
+
+ private void loadLibrary() throws UnsatisfiedLinkError {
+ Log.i(TAG, "loading: " + mLibrary);
+ System.loadLibrary(mLibrary);
+ Log.i(TAG, "loaded: " + mLibrary);
+ }
+
+ private native void nativeRunTests(String filesDir);
+}
diff --git a/testing/android/native_test.gyp b/testing/android/native_test.gyp
new file mode 100644
index 0000000..1b60cbf
--- /dev/null
+++ b/testing/android/native_test.gyp
@@ -0,0 +1,96 @@
+# Copyright (c) 2012 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.
+
+{
+ 'conditions': [
+ ['OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'native_test_apk',
+ 'message': 'building native test apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'native_test_native_code',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'native_test_apk',
+ 'inputs': [
+ '<(DEPTH)/testing/android/native_test_apk.xml',
+ '<!@(find <(DEPTH)/testing/android -name "*.java")',
+ 'native_test_launcher.cc'
+ ],
+ 'outputs': [
+ # Awkwardly, we build a Debug APK even when gyp is in
+ # Release mode. I don't think it matters (e.g. we're
+ # probably happy to not codesign) but naming should be
+ # fixed. The -debug name is an aspect of the android
+ # SDK antfiles (e.g. ${sdk.dir}/tools/ant/build.xml)
+ '<(PRODUCT_DIR)/ChromeNativeTests-debug.apk',
+ ],
+ 'action': [
+ 'ant',
+ '-DPRODUCT_DIR=<(PRODUCT_DIR)',
+ '-buildfile',
+ '<(DEPTH)/testing/android/native_test_apk.xml',
+ ]
+ }
+ ],
+ },
+ {
+ 'target_name': 'native_test_native_code',
+ 'message': 'building native pieces of native test package',
+ 'type': 'static_library',
+ 'sources': [
+ 'native_test_launcher.cc',
+ ],
+ 'direct_dependent_settings': {
+ 'ldflags!': [
+ # JNI_OnLoad is implemented in a .a and we need to
+ # re-export in the .so.
+ '-Wl,--exclude-libs=ALL',
+ ],
+ },
+ 'dependencies': [
+ 'jni_headers',
+ '../../base/base.gyp:base',
+ '../../base/base.gyp:test_support_base',
+ '../gtest.gyp:gtest',
+ ],
+ },
+ {
+ 'target_name': 'jni_headers',
+ 'type': 'none',
+ 'actions': [
+ {
+ 'action_name': 'generate_jni_headers',
+ 'inputs': [
+ '../../base/android/jni_generator/jni_generator.py',
+ 'java/src/org/chromium/native_test/ChromeNativeTestActivity.java'
+ ],
+ 'outputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/testing/android/jni/'
+ 'chrome_native_test_activity_jni.h',
+ ],
+ 'action': [
+ 'python',
+ '../../base/android/jni_generator/jni_generator.py',
+ '-o',
+ '<@(_inputs)',
+ '<@(_outputs)',
+ ],
+ }
+ ],
+ # So generated jni headers can be found by targets that
+ # depend on this.
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)',
+ ],
+ },
+ },
+ ],
+ }]
+ ],
+}
diff --git a/testing/android/native_test_apk.xml b/testing/android/native_test_apk.xml
new file mode 100644
index 0000000..bae45ce
--- /dev/null
+++ b/testing/android/native_test_apk.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2012 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.
+-->
+
+<project name="ChromeNativeTests" default="debug" basedir=".">
+
+ <description>
+ Building native test runner ChromeNativeTests_replaceme.apk
+ </description>
+
+ <property environment="env"/>
+ <property name="sdk.dir" location="${env.ANDROID_SDK_ROOT}"/>
+ <property name="sdk.version" value="${env.ANDROID_SDK_VERSION}"/>
+ <property name="src" location="."/>
+
+ <!-- TODO(jrg): although the source.dir setting points the android
+ antfiles to the correct location for finding our java, and our jars
+ are rebuilt when the java changes, a re-javac does not trigger a
+ re-package of our apk. Fix. -->
+ <property name="source.dir" location="java"/>
+
+ <property name="target" value="android-${env.ANDROID_SDK_VERSION}"/>
+
+ <condition property="location.base"
+ value="${sdk.dir}"
+ else="${sdk.dir}/platforms/android-${sdk.version}">
+ <isset property="env.ANDROID_BUILD_TOP"/>
+ </condition>
+
+ <!-- We expect PRODUCT_DIR to be set like the gyp var
+ (e.g. $ROOT/out/Debug) -->
+ <!-- TODO(jrg): ideally we need this to run before -build-setup, where
+ directories are made based on this variable. -->
+ <target name="-pre-build">
+ <if>
+ <condition>
+ <isset property="PRODUCT_DIR" />
+ </condition>
+ <else>
+ <fail message="PRODUCT_DIR env var not set?" />
+ </else>
+ </if>
+ </target>
+
+ <property name="out.dir" location="${PRODUCT_DIR}/replaceme_apk"/>
+
+ <!-- TODO(jrg): should I make this directory specific to the apk? -->
+ <property name="gen.absolute.dir" value="${out.dir}/gen"/>
+
+ <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>
diff --git a/testing/android/native_test_launcher.cc b/testing/android/native_test_launcher.cc
new file mode 100644
index 0000000..bc5d47f
--- /dev/null
+++ b/testing/android/native_test_launcher.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 2012 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.
+
+#include <stdio.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/path_utils.h"
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "base/test/test_suite.h"
+#include "testing/android/jni/chrome_native_test_activity_jni.h"
+#include "gtest/gtest.h"
+
+// GTest's main function.
+extern int main(int argc, char** argv);
+
+namespace {
+
+void ParseArgsFromString(const std::string& command_line,
+ std::vector<std::string>* args) {
+ StringTokenizer tokenizer(command_line, kWhitespaceASCII);
+ tokenizer.set_quote_chars("\"");
+ while (tokenizer.GetNext()) {
+ std::string token;
+ RemoveChars(tokenizer.token(), "\"", &token);
+ args->push_back(token);
+ }
+}
+
+void ParseArgsFromCommandLineFile(const FilePath& internal_data_path,
+ std::vector<std::string>* args) {
+ static const char kCommandLineFile[] = "chrome-native-tests-command-line";
+ FilePath command_line(internal_data_path.Append(FilePath(kCommandLineFile)));
+ std::string command_line_string;
+ if (file_util::ReadFileToString(command_line, &command_line_string)) {
+ ParseArgsFromString(command_line_string, args);
+ }
+}
+
+void ArgsToArgv(const std::vector<std::string>& args,
+ std::vector<char*>* argv) {
+ // We need to pass in a non-const char**.
+ int argc = args.size();
+ argv->resize(argc);
+ for (int i = 0; i < argc; ++i)
+ (*argv)[i] = const_cast<char*>(args[i].c_str());
+}
+
+// As we are the native side of an Android app, we don't have any 'console', so
+// gtest's standard output goes nowhere.
+// Instead, we inject an "EventListener" in gtest and then we print the results
+// using LOG, which goes to adb logcat.
+class AndroidLogPrinter : public ::testing::EmptyTestEventListener {
+ public:
+ void Init(int* argc, char** argv);
+
+ // EmptyTestEventListener
+ virtual void OnTestProgramStart(
+ const ::testing::UnitTest& unit_test) OVERRIDE;
+ virtual void OnTestStart(const ::testing::TestInfo& test_info) OVERRIDE;
+ virtual void OnTestPartResult(
+ const ::testing::TestPartResult& test_part_result) OVERRIDE;
+ virtual void OnTestEnd(const ::testing::TestInfo& test_info) OVERRIDE;
+ virtual void OnTestProgramEnd(const ::testing::UnitTest& unit_test) OVERRIDE;
+};
+
+void AndroidLogPrinter::Init(int* argc, char** argv) {
+ // InitGoogleTest must be called befure we add ourselves as a listener.
+ ::testing::InitGoogleTest(argc, argv);
+ ::testing::TestEventListeners& listeners =
+ ::testing::UnitTest::GetInstance()->listeners();
+ // Adds a listener to the end. Google Test takes the ownership.
+ listeners.Append(this);
+}
+
+void AndroidLogPrinter::OnTestProgramStart(
+ const ::testing::UnitTest& unit_test) {
+ std::string msg = StringPrintf("[ START ] %d",
+ unit_test.test_to_run_count());
+ LOG(ERROR) << msg;
+}
+
+void AndroidLogPrinter::OnTestStart(const ::testing::TestInfo& test_info) {
+ std::string msg = StringPrintf("[ RUN ] %s.%s",
+ test_info.test_case_name(), test_info.name());
+ LOG(ERROR) << msg;
+}
+
+void AndroidLogPrinter::OnTestPartResult(
+ const ::testing::TestPartResult& test_part_result) {
+ std::string msg = StringPrintf(
+ "%s in %s:%d\n%s\n",
+ test_part_result.failed() ? "*** Failure" : "Success",
+ test_part_result.file_name(),
+ test_part_result.line_number(),
+ test_part_result.summary());
+ LOG(ERROR) << msg;
+}
+
+void AndroidLogPrinter::OnTestEnd(const ::testing::TestInfo& test_info) {
+ std::string msg = StringPrintf("%s %s.%s",
+ test_info.result()->Failed() ? "[ FAILED ]" : "[ OK ]",
+ test_info.test_case_name(), test_info.name());
+ LOG(ERROR) << msg;
+}
+
+void AndroidLogPrinter::OnTestProgramEnd(
+ const ::testing::UnitTest& unit_test) {
+ std::string msg = StringPrintf("[ END ] %d",
+ unit_test.successful_test_count());
+ LOG(ERROR) << msg;
+}
+
+void LibraryLoadedOnMainThread(JNIEnv* env) {
+ static const char* const kInitialArgv[] = { "ChromeTestActivity" };
+
+ {
+ // We need a test suite to be created before we do any tracing or
+ // logging: it creates a global at_exit_manager and initializes
+ // internal gtest data structures based on the command line.
+ // It needs to be scoped as it also resets the CommandLine.
+ std::vector<std::string> args;
+ FilePath path("/data/user/0/org.chromium.native_tests/files/");
+ ParseArgsFromCommandLineFile(path, &args);
+ std::vector<char*> argv;
+ ArgsToArgv(args, &argv);
+ base::TestSuite test_suite(argv.size(), &argv[0]);
+ }
+
+ CommandLine::Init(arraysize(kInitialArgv), kInitialArgv);
+
+ logging::InitLogging(NULL,
+ logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
+ logging::DONT_LOCK_LOG_FILE,
+ logging::DELETE_OLD_LOG_FILE,
+ logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS);
+ // To view log output with IDs and timestamps use "adb logcat -v threadtime".
+ logging::SetLogItems(false, // Process ID
+ false, // Thread ID
+ false, // Timestamp
+ false); // Tick count
+ VLOG(0) << "Chromium logging enabled: level = " << logging::GetMinLogLevel()
+ << ", default verbosity = " << logging::GetVlogVerbosity();
+ base::android::RegisterPathUtils(env);
+}
+
+} // namespace
+
+// This method is called on a separate java thread so that we won't trigger
+// an ANR.
+static void RunTests(JNIEnv* env, jobject obj, jstring jfiles_dir) {
+ FilePath files_dir(base::android::ConvertJavaStringToUTF8(env, jfiles_dir));
+ // A few options, such "--gtest_list_tests", will just use printf directly
+ // and won't use the "AndroidLogPrinter". Redirect stdout to a known file.
+ FilePath stdout_path(files_dir.Append(FilePath("stdout.txt")));
+ freopen(stdout_path.value().c_str(), "w", stdout);
+
+ std::vector<std::string> args;
+ ParseArgsFromCommandLineFile(files_dir, &args);
+
+ // We need to pass in a non-const char**.
+ std::vector<char*> argv;
+ ArgsToArgv(args, &argv);
+
+ int argc = argv.size();
+ // This object is owned by gtest.
+ AndroidLogPrinter* log = new AndroidLogPrinter();
+ log->Init(&argc, &argv[0]);
+
+ main(argc, &argv[0]);
+}
+
+// This is called by the VM when the shared library is first loaded.
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ base::android::InitVM(vm);
+ JNIEnv* env = base::android::AttachCurrentThread();
+ if (!RegisterNativesImpl(env)) {
+ return -1;
+ }
+ LibraryLoadedOnMainThread(env);
+ return JNI_VERSION_1_4;
+}
diff --git a/testing/android/res/values/strings.xml b/testing/android/res/values/strings.xml
new file mode 100644
index 0000000..1597eb69
--- /dev/null
+++ b/testing/android/res/values/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2012 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.
+-->
+<resources>
+ <!-- argument to pass to System.loadLibrary(). E.g. base_unittests
+ for loading libbase_unittests.so. -->
+ <string name="native_library">replaceme</string>
+</resources>