| /* | |
| * 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 | |
| // and Angle's https://source.chromium.org/chromium/chromium/src/+/main:third_party/angle/src/tests/test_utils/runner/android/java/src/com/android/angle/test/AngleNativeTest.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.webrtc.native_test.TestStatusReporter; | |
| import org.chromium.build.gtest_apk.NativeTestIntent; | |
| 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 final StringBuilder mCommandLineFlags = new StringBuilder(); | |
| private TestStatusReporter mReporter; | |
| private boolean mRunInSubThread; | |
| private String mStdoutFilePath; | |
| private static class ReportingUncaughtExceptionHandler | |
| implements Thread.UncaughtExceptionHandler { | |
| private final TestStatusReporter mReporter; | |
| private final 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 | |
| // from android::Layout (https://goo.gl/vZA38p). Chaining the subthread creation | |
| // 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( | |
| String commandLineFlags, | |
| String commandLineFilePath, | |
| String stdoutFilePath, | |
| String testDataDir); | |
| } | |
| } |