1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.native_test; 6 7 import android.app.Activity; 8 import android.content.Context; 9 import android.content.Intent; 10 import android.os.Build; 11 import android.os.Bundle; 12 import android.os.Environment; 13 import android.os.Handler; 14 import android.os.Process; 15 import android.system.Os; 16 17 import org.chromium.base.Log; 18 import org.chromium.base.annotations.JNINamespace; 19 import org.chromium.base.test.util.UrlUtils; 20 import org.chromium.test.reporter.TestStatusReporter; 21 22 import java.io.File; 23 import java.util.ArrayList; 24 import java.util.Iterator; 25 26 /** 27 * Helper to run tests inside Activity or NativeActivity. 28 */ 29 @JNINamespace("testing::android") 30 public class NativeTest { 31 public static final String EXTRA_COMMAND_LINE_FILE = 32 "org.chromium.native_test.NativeTest.CommandLineFile"; 33 public static final String EXTRA_COMMAND_LINE_FLAGS = 34 "org.chromium.native_test.NativeTest.CommandLineFlags"; 35 public static final String EXTRA_RUN_IN_SUB_THREAD = 36 "org.chromium.native_test.NativeTest.RunInSubThread"; 37 public static final String EXTRA_SHARD = 38 "org.chromium.native_test.NativeTest.Shard"; 39 public static final String EXTRA_STDOUT_FILE = 40 "org.chromium.native_test.NativeTest.StdoutFile"; 41 public static final String EXTRA_COVERAGE_DEVICE_FILE = 42 "org.chromium.native_test.NativeTest.CoverageDeviceFile"; 43 44 private static final String TAG = "NativeTest"; 45 46 private String mCommandLineFilePath; 47 private StringBuilder mCommandLineFlags = new StringBuilder(); 48 private TestStatusReporter mReporter; 49 private boolean mRunInSubThread; 50 private String mStdoutFilePath; 51 52 private static class ReportingUncaughtExceptionHandler 53 implements Thread.UncaughtExceptionHandler { 54 55 private TestStatusReporter mReporter; 56 private Thread.UncaughtExceptionHandler mWrappedHandler; 57 ReportingUncaughtExceptionHandler(TestStatusReporter reporter, Thread.UncaughtExceptionHandler wrappedHandler)58 public ReportingUncaughtExceptionHandler(TestStatusReporter reporter, 59 Thread.UncaughtExceptionHandler wrappedHandler) { 60 mReporter = reporter; 61 mWrappedHandler = wrappedHandler; 62 } 63 64 @Override uncaughtException(Thread thread, Throwable ex)65 public void uncaughtException(Thread thread, Throwable ex) { 66 mReporter.uncaughtException(Process.myPid(), ex); 67 if (mWrappedHandler != null) mWrappedHandler.uncaughtException(thread, ex); 68 } 69 } 70 preCreate(Activity activity)71 public void preCreate(Activity activity) { 72 String coverageDeviceFile = activity.getIntent().getStringExtra(EXTRA_COVERAGE_DEVICE_FILE); 73 if (coverageDeviceFile != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 74 try { 75 Os.setenv("LLVM_PROFILE_FILE", coverageDeviceFile, true); 76 } catch (Exception e) { 77 Log.w(TAG, "failed to set LLVM_PROFILE_FILE", e); 78 } 79 } 80 } 81 postCreate(Activity activity)82 public void postCreate(Activity activity) { 83 parseArgumentsFromIntent(activity, activity.getIntent()); 84 mReporter = new TestStatusReporter(activity); 85 mReporter.testRunStarted(Process.myPid()); 86 Thread.setDefaultUncaughtExceptionHandler( 87 new ReportingUncaughtExceptionHandler(mReporter, 88 Thread.getDefaultUncaughtExceptionHandler())); 89 } 90 parseArgumentsFromIntent(Activity activity, Intent intent)91 private void parseArgumentsFromIntent(Activity activity, Intent intent) { 92 Log.i(TAG, "Extras:"); 93 Bundle extras = intent.getExtras(); 94 if (extras != null) { 95 for (String s : extras.keySet()) { 96 Log.i(TAG, " %s", s); 97 } 98 } 99 100 mCommandLineFilePath = intent.getStringExtra(EXTRA_COMMAND_LINE_FILE); 101 if (mCommandLineFilePath == null) { 102 mCommandLineFilePath = ""; 103 } else { 104 File commandLineFile = new File(mCommandLineFilePath); 105 if (!commandLineFile.isAbsolute()) { 106 mCommandLineFilePath = Environment.getExternalStorageDirectory() + "/" 107 + mCommandLineFilePath; 108 } 109 Log.i(TAG, "command line file path: %s", mCommandLineFilePath); 110 } 111 112 String commandLineFlags = intent.getStringExtra(EXTRA_COMMAND_LINE_FLAGS); 113 if (commandLineFlags != null) mCommandLineFlags.append(commandLineFlags); 114 115 mRunInSubThread = intent.hasExtra(EXTRA_RUN_IN_SUB_THREAD); 116 117 ArrayList<String> shard = intent.getStringArrayListExtra(EXTRA_SHARD); 118 if (shard != null) { 119 StringBuilder filterFlag = new StringBuilder(); 120 filterFlag.append("--gtest_filter="); 121 for (Iterator<String> test_iter = shard.iterator(); test_iter.hasNext();) { 122 filterFlag.append(test_iter.next()); 123 if (test_iter.hasNext()) { 124 filterFlag.append(":"); 125 } 126 } 127 appendCommandLineFlags(filterFlag.toString()); 128 } 129 130 mStdoutFilePath = intent.getStringExtra(EXTRA_STDOUT_FILE); 131 } 132 appendCommandLineFlags(String flags)133 public void appendCommandLineFlags(String flags) { 134 mCommandLineFlags.append(" ").append(flags); 135 } 136 postStart(final Activity activity, boolean forceRunInSubThread)137 public void postStart(final Activity activity, boolean forceRunInSubThread) { 138 final Runnable runTestsTask = new Runnable() { 139 @Override 140 public void run() { 141 runTests(activity); 142 } 143 }; 144 145 if (mRunInSubThread || forceRunInSubThread) { 146 // Post a task that posts a task that creates a new thread and runs tests on it. 147 148 // On L and M, the system posts a task to the main thread that prints to stdout 149 // from android::Layout (https://goo.gl/vZA38p). Chaining the subthread creation 150 // through multiple tasks executed on the main thread ensures that this task 151 // runs before we start running tests s.t. its output doesn't interfere with 152 // the test output. See crbug.com/678146 for additional context. 153 154 final Handler handler = new Handler(); 155 final Runnable startTestThreadTask = new Runnable() { 156 @Override 157 public void run() { 158 new Thread(runTestsTask).start(); 159 } 160 }; 161 final Runnable postTestStarterTask = new Runnable() { 162 @Override 163 public void run() { 164 handler.post(startTestThreadTask); 165 } 166 }; 167 handler.post(postTestStarterTask); 168 } else { 169 // Post a task to run the tests. This allows us to not block 170 // onCreate and still run tests on the main thread. 171 new Handler().post(runTestsTask); 172 } 173 } 174 runTests(Activity activity)175 private void runTests(Activity activity) { 176 nativeRunTests(mCommandLineFlags.toString(), mCommandLineFilePath, mStdoutFilePath, 177 activity.getApplicationContext(), UrlUtils.getIsolatedTestRoot()); 178 activity.finish(); 179 mReporter.testRunFinished(Process.myPid()); 180 } 181 182 // Signal a failure of the native test loader to python scripts 183 // which run tests. For example, we look for 184 // RUNNER_FAILED build/android/test_package.py. nativeTestFailed()185 private void nativeTestFailed() { 186 Log.e(TAG, "[ RUNNER_FAILED ] could not load native library"); 187 } 188 nativeRunTests(String commandLineFlags, String commandLineFilePath, String stdoutFilePath, Context appContext, String testDataDir)189 private native void nativeRunTests(String commandLineFlags, String commandLineFilePath, 190 String stdoutFilePath, Context appContext, String testDataDir); 191 } 192