2024-12-04 15:54:49 +01:00
/ *
* 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.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
// 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.
2024-12-09 11:14:47 +01:00
@SuppressWarnings ( " UnusedMethod " )
2024-12-04 15:54:49 +01:00
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 ) ;
}
}