diff options
author | jrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-19 21:02:09 +0000 |
---|---|---|
committer | jrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-19 21:02:09 +0000 |
commit | d9f9695633f8ea3dd1d4d02136c12cf2ffe429b8 (patch) | |
tree | 18b8981c285c249d714b079cafce988ad66d5f8d | |
parent | d9426956e7653f57361544bfa9ff7cfd84590a5a (diff) | |
download | chromium_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
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> |