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 /testing | |
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
Diffstat (limited to 'testing')
-rw-r--r-- | testing/android/AndroidManifest.xml | 30 | ||||
-rw-r--r-- | testing/android/OWNERS | 3 | ||||
-rwxr-xr-x | testing/android/generate_native_test.py | 194 | ||||
-rw-r--r-- | testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java | 66 | ||||
-rw-r--r-- | testing/android/native_test.gyp | 96 | ||||
-rw-r--r-- | testing/android/native_test_apk.xml | 55 | ||||
-rw-r--r-- | testing/android/native_test_launcher.cc | 190 | ||||
-rw-r--r-- | testing/android/res/values/strings.xml | 11 |
8 files changed, 645 insertions, 0 deletions
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> |