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