diff options
author | jrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-24 03:57:36 +0000 |
---|---|---|
committer | jrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-24 03:57:36 +0000 |
commit | 9713fa838364257affc9e863a4f842f044fa0c20 (patch) | |
tree | 9b80df03f33c22e2fae2eecc7051a7ac1ddffbad /base | |
parent | 27cc5a09bba00e877f8815d592d0ba955f62002b (diff) | |
download | chromium_src-9713fa838364257affc9e863a4f842f044fa0c20.zip chromium_src-9713fa838364257affc9e863a4f842f044fa0c20.tar.gz chromium_src-9713fa838364257affc9e863a4f842f044fa0c20.tar.bz2 |
apk-based test runner work. Not enabled yet. This CL is a combination of upstreaming, ndk/ant-ification, and other tweaks.
BUG=None
TEST=
Review URL: http://codereview.chromium.org/9834037
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128679 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gyp | 3 | ||||
-rw-r--r-- | base/test/android/AndroidManifest.xml | 43 | ||||
-rw-r--r-- | base/test/android/OWNERS | 2 | ||||
-rw-r--r-- | base/test/android/java/org/chromium/ChromeNativeTestActivity.java | 47 | ||||
-rw-r--r-- | base/test/android/native_test.gyp | 40 | ||||
-rw-r--r-- | base/test/android/native_test_apk.xml | 37 | ||||
-rw-r--r-- | base/test/android/native_test_launcher.cc | 190 |
7 files changed, 361 insertions, 1 deletions
diff --git a/base/base.gyp b/base/base.gyp index 08461d7..ee44677 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -9,6 +9,7 @@ 'includes': [ '../build/win_precompile.gypi', 'base.gypi', + 'test/android/native_test.gyp' ], 'targets': [ { @@ -153,7 +154,7 @@ }, { 'target_name': 'base_unittests', - 'type': 'executable', + 'type': '<(gtest_target_type)', 'sources': [ # Tests. 'android/jni_android_unittest.cc', diff --git a/base/test/android/AndroidManifest.xml b/base/test/android/AndroidManifest.xml new file mode 100644 index 0000000..5e154a0 --- /dev/null +++ b/base/test/android/AndroidManifest.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.chromium.native_test" + android:versionCode="1" + android:versionName="1.0"> + + <!-- This is the platform API where NativeActivity was introduced. --> + <uses-sdk android:minSdkVersion="14" /> + + <application android:label="ChromeNativeTest"> + <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> + + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.VIBRATE"/> + <uses-permission android:name="android.permission.GET_ACCOUNTS"/> + <uses-permission android:name="android.permission.USE_CREDENTIALS"/> + <uses-permission android:name="android.permission.READ_CONTACTS"/> + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> + <uses-permission android:name="android.permission.READ_LOGS"/> + <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/> + <uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS"/> + <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_READ"/> + <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE" /> + <uses-permission android:name="android.permission.READ_SYNC_STATS"/> + <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> + <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/> + <uses-permission android:name="android.permission.RECORD_AUDIO"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + +</manifest> diff --git a/base/test/android/OWNERS b/base/test/android/OWNERS new file mode 100644 index 0000000..2581007 --- /dev/null +++ b/base/test/android/OWNERS @@ -0,0 +1,2 @@ +jrg@chromium.org +bulach@chromium.org diff --git a/base/test/android/java/org/chromium/ChromeNativeTestActivity.java b/base/test/android/java/org/chromium/ChromeNativeTestActivity.java new file mode 100644 index 0000000..a6290b9 --- /dev/null +++ b/base/test/android/java/org/chromium/ChromeNativeTestActivity.java @@ -0,0 +1,47 @@ +// 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"; + private final String LIBRARY = "native_tests"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + loadLibrary(); + new Thread() { + @Override + public void run() { + Log.d(TAG, ">>nativeRunTests"); + nativeRunTests(getFilesDir().getAbsolutePath()); + Log.d(TAG, "<<nativeRunTests"); + } + }.start(); + } catch (UnsatisfiedLinkError e) { + Log.e(TAG, "Unable to load libnative_tests.so: " + e); + throw e; + } + } + + private void loadLibrary() throws UnsatisfiedLinkError { + Log.i(TAG, "loading: " + LIBRARY); + System.loadLibrary(LIBRARY); + Log.i(TAG, "loaded: " + LIBRARY); + } + + private native void nativeRunTests(String filesDir); +} diff --git a/base/test/android/native_test.gyp b/base/test/android/native_test.gyp new file mode 100644 index 0000000..86d3728 --- /dev/null +++ b/base/test/android/native_test.gyp @@ -0,0 +1,40 @@ +# 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 package', + 'type': 'none', + 'actions': [ + { + 'action_name': 'native_test_apk', + 'inputs': [ + '<(DEPTH)/base/test/android/native_test_apk.xml', + '<!@(find test/android/java -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. + '<(PRODUCT_DIR)/ChromeNativeTests-debug.apk', + ], + 'action': [ + 'ant', + '-DPRODUCT_DIR=<(PRODUCT_DIR)', + '-buildfile', + '<(DEPTH)/base/test/android/native_test_apk.xml', + ] + } + ], + }, + ], + }] + ] +} diff --git a/base/test/android/native_test_apk.xml b/base/test/android/native_test_apk.xml new file mode 100644 index 0000000..6f39430 --- /dev/null +++ b/base/test/android/native_test_apk.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="ChromeNativeTests" default="debug" basedir="."> + + <description> + Building native test runner ChromeNativeTests.apk + </description> + + <property environment="env"/> + <property name="sdk.dir" location="${env.ANDROID_SDK_ROOT}"/> + <property name="source.dir" location="java"/> + <property name="target" value="android-14"/> + + <!-- 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}"/> + + <!-- TODO(jrg): should I make these directories specific to the apk? --> + <property name="resource.absolute.dir" value="${out.dir}/res"/> + <property name="gen.absolute.dir" value="${out.dir}/gen"/> + <property name="jar.libs.dir" value="${out.dir}/java/libs"/> + + <import file="${sdk.dir}/tools/ant/build.xml" /> + +</project> diff --git a/base/test/android/native_test_launcher.cc b/base/test/android/native_test_launcher.cc new file mode 100644 index 0000000..ae702a2 --- /dev/null +++ b/base/test/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 "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; +} |