Skip to content

Commit

Permalink
Added DetoxJUnitRunner for Android, it enables ability instruments ar…
Browse files Browse the repository at this point in the history
…tifact recording for android
  • Loading branch information
valentynberehovyi committed Feb 12, 2020
1 parent 6b765fb commit 4eb9955
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ class InstrumentsRecordingStateActionHandler(
) : DetoxActionHandler {

override fun handle(params: String, messageId: Long) {
val recordingPath = JSONObject(params).optString("recordingPath", null)
val recordingPath = JSONObject(params).opt("recordingPath")
if (recordingPath != null) {
instrumentsManager.startRecordingAtLocalPath(recordingPath)
instrumentsManager.startRecordingAtLocalPath(recordingPath as String)
} else {
instrumentsManager.stopRecording()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.wix.detox;

import android.app.Application;
import android.os.Bundle;

import androidx.test.runner.AndroidJUnitRunner;
import androidx.test.runner.lifecycle.ApplicationLifecycleCallback;
import androidx.test.runner.lifecycle.ApplicationLifecycleMonitor;
import androidx.test.runner.lifecycle.ApplicationLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.ApplicationStage;

import com.wix.detox.instruments.DetoxInstrumentsManager;


public class DetoxJUnitRunner extends AndroidJUnitRunner {
private DetoxInstrumentsManager instrumentsManager;

@Override
public void onCreate(final Bundle arguments) {
super.onCreate(arguments);

final ApplicationLifecycleMonitor monitor = ApplicationLifecycleMonitorRegistry.getInstance();
monitor.addLifecycleCallback(new ApplicationLifecycleCallback() {
@Override
public void onApplicationLifecycleChanged(Application app, ApplicationStage stage) {
if (stage == ApplicationStage.PRE_ON_CREATE) {
onBeforeAppOnCreate(app, arguments);
} else if (stage == ApplicationStage.CREATED) {
onAfterAppOnCreate();
}
}
});
}

private void onBeforeAppOnCreate(Application app, Bundle arguments) {
if (DetoxInstrumentsManager.supports()) {
final String recordingPath = arguments.getString("detoxInstrumRecPath");
if (recordingPath != null) {
instrumentsManager = new DetoxInstrumentsManager(app);
instrumentsManager.startRecordingAtLocalPath(recordingPath);
}
}
}

private void onAfterAppOnCreate() {
if (instrumentsManager != null) {
instrumentsManager.tryInstallJsi();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class DetoxManager implements WebSocketClient.ActionHandler {
private ReadyActionHandler readyActionHandler = null;

private Context reactNativeHostHolder;
private DetoxInstrumentsManager instrumentsManager;

DetoxManager(@NonNull Context context) {
this.reactNativeHostHolder = context;
Expand All @@ -60,13 +59,6 @@ class DetoxManager implements WebSocketClient.ActionHandler {
stop();
return;
}
if (DetoxInstrumentsManager.supports()) {
final String recordingPath = arguments.getString(DETOX_RECORDING_PATH_ARG_KEY);
if (recordingPath != null) {
instrumentsManager = new DetoxInstrumentsManager(reactNativeHostHolder);
instrumentsManager.startRecordingAtLocalPath(recordingPath);
}
}

Log.i(LOG_TAG, "DetoxServerUrl: " + detoxServerUrl);
Log.i(LOG_TAG, "DetoxSessionId: " + detoxSessionId);
Expand Down Expand Up @@ -162,9 +154,7 @@ public Unit invoke() {
}));

if (DetoxInstrumentsManager.supports()) {
if (instrumentsManager == null) {
instrumentsManager = new DetoxInstrumentsManager(reactNativeHostHolder);
}
final DetoxInstrumentsManager instrumentsManager = new DetoxInstrumentsManager(reactNativeHostHolder);
actionHandlers.put("setRecordingState", new InstrumentsRecordingStateActionHandler(instrumentsManager, wsClient));
actionHandlers.put("event", new InstrumentsEventsActionsHandler(instrumentsManager, wsClient));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ public DetoxInstrumentsManager(Context context) {
public DetoxInstrumentsManager(Context context, Instruments instruments) {
this.context = context;
this.instruments = instruments;
this.recording = instruments.getActiveRecording();
}

public void tryInstallJsi() {
if (!instruments.installed()) {
return;
}
instruments.tryInstallJsiHook(context);
}

public void startRecordingAtLocalPath(String path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
public interface Instruments {
boolean installed();

void tryInstallJsiHook(Context context);

InstrumentsRecording getActiveRecording();

InstrumentsRecording startRecording(
Context context,
boolean recordPerformance,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ public class InstrumentsRecordingReflected implements InstrumentsRecording {
}

private final Object profilerInstance;
private final InstrumentsReflected instruments;

public InstrumentsRecordingReflected(Object profilerInstance) {
public InstrumentsRecordingReflected(Object profilerInstance, InstrumentsReflected instruments) {
this.profilerInstance = profilerInstance;
this.instruments = instruments;
}

@Override
Expand All @@ -57,6 +59,7 @@ public void stop() {
} catch (Exception e) {
throw new DetoxInstrumentsException(e);
}
instruments.resetActiveRecording();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ public class InstrumentsReflected implements Instruments {
private static Constructor constructorDtxProfilingConfiguration;
private static Method methodGetInstanceOfProfiler;
private static Method methodStartRecording;
private static Method methodTryInstallJsiHook;
private static final boolean hasProfiler;
private InstrumentsRecording activeRecording;

static {
try {
final String basePackageName = "com.wix.detoxprofiler";
final Class<?> profilerClass = Class.forName(basePackageName + ".DTXProfiler");
final Class<?> configurationClass = Class.forName(basePackageName + ".DTXProfilingConfiguration");
final Class<?> jsiHookClass = Class.forName(basePackageName + ".JsiHook");

constructorDtxProfilingConfiguration = configurationClass.getConstructor(
boolean.class,//recordPerformance
Expand All @@ -32,6 +35,8 @@ public class InstrumentsReflected implements Instruments {
);
methodGetInstanceOfProfiler = profilerClass.getDeclaredMethod("getInstance", Context.class);
methodStartRecording = profilerClass.getDeclaredMethod("startProfiling", Context.class, configurationClass);
methodTryInstallJsiHook = jsiHookClass.getDeclaredMethod("tryInstall", profilerClass, Context.class);
methodTryInstallJsiHook.setAccessible(true);
} catch (ClassNotFoundException e) {
methodGetInstanceOfProfiler = null;
} catch (NoSuchMethodException e) {
Expand All @@ -48,6 +53,24 @@ public boolean installed() {
return hasProfiler;
}

void resetActiveRecording() {
activeRecording = null;
}

public InstrumentsRecording getActiveRecording() {
return activeRecording;
}

@Override
public void tryInstallJsiHook(Context context) {
try {
final Object profilerInstance = methodGetInstanceOfProfiler.invoke(null, context);
methodTryInstallJsiHook.invoke(null, profilerInstance, context);
} catch (Exception e) {
throw new DetoxInstrumentsException(e);
}
}

@Override
public InstrumentsRecording startRecording(
Context context,
Expand All @@ -65,7 +88,8 @@ public InstrumentsRecording startRecording(
);
final Object profilerInstance = methodGetInstanceOfProfiler.invoke(null, context);
methodStartRecording.invoke(profilerInstance, context, configurationInstance);
return new InstrumentsRecordingReflected(profilerInstance);
activeRecording = new InstrumentsRecordingReflected(profilerInstance, this);
return activeRecording;
} catch (Exception e) {
throw new DetoxInstrumentsException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@ package com.wix.detox.instruments

import android.content.Context
import com.nhaarman.mockitokotlin2.*
import com.wix.detox.TestEngineFacade
import com.wix.detox.UTHelpers.yieldToOtherThreads
import com.wix.detox.instruments.DetoxInstrumentsManager
import com.wix.invoke.MethodInvocation
import org.json.JSONObject
import org.assertj.core.api.Assertions
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.io.File
import java.lang.reflect.InvocationTargetException
import java.util.*
import java.util.concurrent.Executors

object DetoxInstrumentsManagerSpec : Spek({
describe("Instruments manager") {
Expand Down Expand Up @@ -92,6 +85,19 @@ object DetoxInstrumentsManagerSpec : Spek({
verify(instruments, never()).startRecording(any(), any(), any(), any(), any())
}

describe("tryInstallJsi") {
it("proxy when instruments installed") {
whenever(instruments.installed()).thenReturn(true)
instrumentsManager.tryInstallJsi()
verify(instruments).tryInstallJsiHook(appContext)
}
it("skipping when instruments not installed") {
whenever(instruments.installed()).thenReturn(false)
instrumentsManager.tryInstallJsi()
verify(instruments, never()).tryInstallJsiHook(any())
}
}

describe("recording") {
lateinit var recording: InstrumentsRecording

Expand Down

0 comments on commit 4eb9955

Please sign in to comment.