Source code
Revision control
Copy as Markdown
Other Tools
/*
* Copyright 2024 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
// Based on Chromium's https://source.chromium.org/chromium/chromium/src/+/main:testing/android/native_test/java/src/org/chromium/native_test/NativeTest.java
package org.webrtc.native_test;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Process;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;
import org.webrtc.native_test.StrictModeContext;
import org.chromium.build.gtest_apk.NativeTestIntent;
import org.chromium.test.reporter.TestStatusReporter;
import java.io.File;
/** Helper to run tests inside Activity or NativeActivity. */
@JNINamespace("webrtc::test::android")
public class NativeTestWebrtc {
private static final String TAG = "NativeTestWebrtc";
private String mCommandLineFilePath;
private StringBuilder mCommandLineFlags = new StringBuilder();
private TestStatusReporter mReporter;
private boolean mRunInSubThread;
private String mStdoutFilePath;
private static class ReportingUncaughtExceptionHandler
implements Thread.UncaughtExceptionHandler {
private TestStatusReporter mReporter;
private Thread.UncaughtExceptionHandler mWrappedHandler;
public ReportingUncaughtExceptionHandler(
TestStatusReporter reporter, Thread.UncaughtExceptionHandler wrappedHandler) {
mReporter = reporter;
mWrappedHandler = wrappedHandler;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
mReporter.uncaughtException(Process.myPid(), ex);
if (mWrappedHandler != null) mWrappedHandler.uncaughtException(thread, ex);
}
}
public void preCreate(Activity activity) {
String coverageDeviceFile =
activity.getIntent().getStringExtra(NativeTestIntent.EXTRA_COVERAGE_DEVICE_FILE);
if (coverageDeviceFile != null) {
try {
Os.setenv("LLVM_PROFILE_FILE", coverageDeviceFile, true);
} catch (ErrnoException e) {
Log.w(TAG, "failed to set LLVM_PROFILE_FILE" + e.toString());
}
}
// Set TMPDIR to make perfetto_unittests not to use /data/local/tmp as a tmp directory.
try {
Os.setenv("TMPDIR", activity.getApplicationContext().getCacheDir().getPath(), false);
} catch (ErrnoException e) {
Log.w(TAG, "failed to set TMPDIR" + e.toString());
}
}
public void postCreate(Activity activity) {
parseArgumentsFromIntent(activity, activity.getIntent());
mReporter = new TestStatusReporter(activity);
mReporter.testRunStarted(Process.myPid());
Thread.setDefaultUncaughtExceptionHandler(
new ReportingUncaughtExceptionHandler(
mReporter, Thread.getDefaultUncaughtExceptionHandler()));
}
private void parseArgumentsFromIntent(Activity activity, Intent intent) {
Log.i(TAG, "Extras:");
Bundle extras = intent.getExtras();
if (extras != null) {
for (String s : extras.keySet()) {
Log.i(TAG, " " + s);
}
}
mCommandLineFilePath = intent.getStringExtra(NativeTestIntent.EXTRA_COMMAND_LINE_FILE);
if (mCommandLineFilePath == null) {
mCommandLineFilePath = "";
} else {
File commandLineFile = new File(mCommandLineFilePath);
if (!commandLineFile.isAbsolute()) {
mCommandLineFilePath =
Environment.getExternalStorageDirectory() + "/" + mCommandLineFilePath;
}
Log.i(TAG, "command line file path: " + mCommandLineFilePath);
}
String commandLineFlags = intent.getStringExtra(NativeTestIntent.EXTRA_COMMAND_LINE_FLAGS);
if (commandLineFlags != null) mCommandLineFlags.append(commandLineFlags);
mRunInSubThread = intent.hasExtra(NativeTestIntent.EXTRA_RUN_IN_SUB_THREAD);
String gtestFilter = intent.getStringExtra(NativeTestIntent.EXTRA_GTEST_FILTER);
if (gtestFilter != null) {
appendCommandLineFlags("--gtest_filter=" + gtestFilter);
}
mStdoutFilePath = intent.getStringExtra(NativeTestIntent.EXTRA_STDOUT_FILE);
}
public void appendCommandLineFlags(String flags) {
mCommandLineFlags.append(" ").append(flags);
}
public void postStart(final Activity activity, boolean forceRunInSubThread) {
final Runnable runTestsTask =
new Runnable() {
@Override
public void run() {
runTests(activity);
}
};
if (mRunInSubThread || forceRunInSubThread) {
// Post a task that posts a task that creates a new thread and runs tests on it.
// On L and M, the system posts a task to the main thread that prints to stdout
// through multiple tasks executed on the main thread ensures that this task
// runs before we start running tests s.t. its output doesn't interfere with
// the test output. See crbug.com/678146 for additional context.
final Handler handler = new Handler();
final Runnable startTestThreadTask =
new Runnable() {
@Override
public void run() {
new Thread(runTestsTask).start();
}
};
final Runnable postTestStarterTask =
new Runnable() {
@Override
public void run() {
handler.post(startTestThreadTask);
}
};
handler.post(postTestStarterTask);
} else {
// Post a task to run the tests. This allows us to not block
// onCreate and still run tests on the main thread.
new Handler().post(runTestsTask);
}
}
private void runTests(Activity activity) {
Natives jni = NativeTestWebrtcJni.get();
String isolated_test_root = NativeTestWebrtc.getIsolatedTestRoot();
Log.i(TAG, "Calling into native code");
jni.runTests(
mCommandLineFlags.toString(),
mCommandLineFilePath,
mStdoutFilePath,
isolated_test_root);
Log.i(TAG, "Call into native code returned");
activity.finish();
mReporter.testRunFinished(Process.myPid());
}
// @CalledByNative
public static String getIsolatedTestRoot() {
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
return Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/chromium_tests_root";
}
}
// 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.
@SuppressWarnings("UnusedMethod")
private void nativeTestFailed() {
Log.e(TAG, "[ RUNNER_FAILED ] could not load native library");
}
@NativeMethods
interface Natives {
void runTests(
@JniType("std::string") String commandLineFlags,
@JniType("std::string") String commandLineFilePath,
@JniType("std::string") String stdoutFilePath,
@JniType("std::string") String testDataDir);
}
}