Skip to content

feat: add setNDKCrashesEnabled API #1132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased](https://github.com/Instabug/Instabug-React-Native/compare/v12.7.1...dev)

### Added

- Support enabling NDK crash capturing on Android ([#1132](https://github.com/Instabug/Instabug-React-Native/pull/1132)).

## [12.7.1](https://github.com/Instabug/Instabug-React-Native/compare/v12.7.0...v12.7.1) (February 15, 2024)

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,28 @@ public void run() {
}
}
});
}

/**
* Enables and disables capturing native C++ NDK crash reporting.
*
* @param isEnabled boolean indicating enabled or disabled.
*/
@ReactMethod
public void setNDKCrashesEnabled(final boolean isEnabled) {
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
try {
if (isEnabled) {
CrashReporting.setNDKCrashesState(Feature.State.ENABLED);
} else {
CrashReporting.setNDKCrashesState(Feature.State.DISABLED);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,85 @@
package com.instabug.reactlibrary;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;

import android.os.Looper;

import com.instabug.crash.CrashReporting;
import com.instabug.library.Feature;
import com.instabug.reactlibrary.util.GlobalMocks;
import com.instabug.reactlibrary.utils.MainThreadHandler;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;


public class RNInstabugCrashReportingModuleTest {
private final RNInstabugCrashReportingModule rnModule = new RNInstabugCrashReportingModule(null);

// Mock Objects
private MockedStatic<Looper> mockLooper;
private MockedStatic<MainThreadHandler> mockMainThreadHandler;
private MockedStatic<CrashReporting> mockCrashReporting;


@Before
public void mockMainThreadHandler() throws Exception {
// Mock static functions
mockLooper = mockStatic(Looper.class);
mockMainThreadHandler = mockStatic(MainThreadHandler.class);
mockCrashReporting = mockStatic(CrashReporting.class);
// Mock Looper class
Looper mockMainThreadLooper = mock(Looper.class);
Mockito.when(Looper.getMainLooper()).thenReturn(mockMainThreadLooper);


// Override runOnMainThread
Answer<Boolean> handlerPostAnswer = new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
invocation.getArgument(0, Runnable.class).run();
return true;
}
};
Mockito.doAnswer(handlerPostAnswer).when(MainThreadHandler.class);
MainThreadHandler.runOnMainThread(any(Runnable.class));
}

@After
public void tearDown() {
// Remove static mocks
mockLooper.close();
mockMainThreadHandler.close();
mockCrashReporting.close();
}

/********Crashes*********/

@Test
public void testSetNDKCrashesEnabledGivenTrue() {
// when
rnModule.setNDKCrashesEnabled(true);

//then
mockCrashReporting.verify(() -> CrashReporting.setNDKCrashesState(Feature.State.ENABLED));
}

@Test
public void testSetNDKCrashesEnabledGivenFalse() {
// when
rnModule.setNDKCrashesEnabled(false);

//then
mockCrashReporting.verify(() -> CrashReporting.setNDKCrashesState(Feature.State.DISABLED));
}

@Test
public void givenString$sendHandledJSCrash_whenQuery_thenShouldCallNativeApiWithArgs() throws Exception {
// JSONObject json = mock(JSONObject.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.facebook.react.bridge.WritableMap;
import com.instabug.chat.Replies;
import com.instabug.library.Feature;
import com.instabug.reactlibrary.utils.InstabugUtil;
import com.instabug.reactlibrary.utils.MapUtil;
import com.instabug.reactlibrary.utils.MainThreadHandler;

Expand Down Expand Up @@ -42,13 +43,15 @@ public class RNInstabugRepliesModuleTest {
private MockedStatic<Looper> mockLooper;
private MockedStatic <MainThreadHandler> mockMainThreadHandler;
private MockedStatic <Replies> mockReplies;
private MockedStatic <InstabugUtil> mockInstabugUtil;

@Before
public void mockMainThreadHandler() throws Exception {
// Mock static functions
mockReplies = mockStatic(Replies.class);
mockLooper = mockStatic(Looper.class);
mockMainThreadHandler = mockStatic(MainThreadHandler.class);
mockInstabugUtil = mockStatic(InstabugUtil.class);

// Mock Looper class
Looper mockMainThreadLooper = mock(Looper.class);
Expand All @@ -71,6 +74,7 @@ public void tearDown() {
mockLooper.close();
mockMainThreadHandler.close();
mockReplies.close();
mockInstabugUtil.close();
}

/********Replies*********/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ public boolean getUseDeveloperSupport() {

@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new RNInstabugExampleReactnativePackage());
return packages;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.instabug.react.example;

import static com.instabug.reactlibrary.utils.InstabugUtil.getMethod;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.instabug.crash.CrashReporting;
import com.instabug.crash.models.IBGNonFatalException;
import com.instabug.library.Feature;
import com.instabug.reactlibrary.RNInstabugReactnativeModule;
import com.instabug.reactlibrary.utils.MainThreadHandler;

import org.json.JSONObject;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class RNInstabugExampleCrashReportingModule extends ReactContextBaseJavaModule {

public RNInstabugExampleCrashReportingModule(ReactApplicationContext reactApplicationContext) {
super(reactApplicationContext);
}

@Nonnull
@Override
public String getName() {
return "CrashReportingExampleModule";
}

@ReactMethod
public void sendNativeNonFatal(final String exceptionObject) {
final IBGNonFatalException exception = new IBGNonFatalException.Builder(new IllegalStateException("Test exception"))
.build();
CrashReporting.report(exception);

}

@ReactMethod
public void sendNativeFatalCrash() {
throw new IllegalStateException("Unhandled IllegalStateException from Instabug Test App");
}

@ReactMethod
public void sendANR() {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

@ReactMethod
public void sendFatalHang() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

@ReactMethod
public void sendOOM() {
oomCrash();
}

private void oomCrash() {
new Thread(() -> {
List<String> stringList = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
stringList.add(getRandomString(10_000));
}
}).start();
}

private String getRandomString(int length) {
List<Character> charset = new ArrayList<>();
for (char ch = 'a'; ch <= 'z'; ch++) {
charset.add(ch);
}
for (char ch = 'A'; ch <= 'Z'; ch++) {
charset.add(ch);
}
for (char ch = '0'; ch <= '9'; ch++) {
charset.add(ch);
}

StringBuilder randomString = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
char randomChar = charset.get(random.nextInt(charset.size()));
randomString.append(randomChar);
}

return randomString.toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.instabug.react.example;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.instabug.reactlibrary.RNInstabugAPMModule;
import com.instabug.reactlibrary.RNInstabugBugReportingModule;
import com.instabug.reactlibrary.RNInstabugCrashReportingModule;
import com.instabug.reactlibrary.RNInstabugFeatureRequestsModule;
import com.instabug.reactlibrary.RNInstabugReactnativeModule;
import com.instabug.reactlibrary.RNInstabugRepliesModule;
import com.instabug.reactlibrary.RNInstabugSessionReplayModule;
import com.instabug.reactlibrary.RNInstabugSurveysModule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class RNInstabugExampleReactnativePackage implements ReactPackage {

private static final String TAG = RNInstabugExampleReactnativePackage.class.getSimpleName();

public RNInstabugExampleReactnativePackage() {}

@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNInstabugExampleCrashReportingModule(reactContext));
return modules;
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
14 changes: 14 additions & 0 deletions examples/default/ios/InstabugExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
20E556262AC55766007416B1 /* InstabugSessionReplayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E556252AC55766007416B1 /* InstabugSessionReplayTests.m */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
C3C8CCF379347A4DF9D2A39D /* CrashReportingExampleModule.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C8C24386310A3120006604 /* CrashReportingExampleModule.m */; };
CC3DF88E2A1DFC9A003E9914 /* InstabugCrashReportingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF8852A1DFC99003E9914 /* InstabugCrashReportingTests.m */; };
CC3DF88F2A1DFC9A003E9914 /* InstabugBugReportingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF8862A1DFC99003E9914 /* InstabugBugReportingTests.m */; };
CC3DF8902A1DFC9A003E9914 /* InstabugSampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3DF8872A1DFC99003E9914 /* InstabugSampleTests.m */; };
Expand Down Expand Up @@ -51,6 +52,8 @@
9A3D962AB03F97E25566779F /* Pods-InstabugExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample.debug.xcconfig"; path = "Target Support Files/Pods-InstabugExample/Pods-InstabugExample.debug.xcconfig"; sourceTree = "<group>"; };
BAED0D0441A708AE2390E153 /* libPods-InstabugExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InstabugExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
BD54B44E2DF85672BB2D4DEE /* Pods-InstabugExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample.release.xcconfig"; path = "Target Support Files/Pods-InstabugExample/Pods-InstabugExample.release.xcconfig"; sourceTree = "<group>"; };
C3C8C24386310A3120006604 /* CrashReportingExampleModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CrashReportingExampleModule.m; sourceTree = "<group>"; };
C3C8C784EADC037C5A752B94 /* CrashReportingExampleModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrashReportingExampleModule.h; sourceTree = "<group>"; };
CC3DF8852A1DFC99003E9914 /* InstabugCrashReportingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugCrashReportingTests.m; sourceTree = "<group>"; };
CC3DF8862A1DFC99003E9914 /* InstabugBugReportingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugBugReportingTests.m; sourceTree = "<group>"; };
CC3DF8872A1DFC99003E9914 /* InstabugSampleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugSampleTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -121,6 +124,7 @@
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
C3C8C1DDCEA91410F27A3683 /* native */,
);
name = InstabugExample;
sourceTree = "<group>";
Expand Down Expand Up @@ -177,6 +181,15 @@
path = Pods;
sourceTree = "<group>";
};
C3C8C1DDCEA91410F27A3683 /* native */ = {
isa = PBXGroup;
children = (
C3C8C784EADC037C5A752B94 /* CrashReportingExampleModule.h */,
C3C8C24386310A3120006604 /* CrashReportingExampleModule.m */,
);
path = native;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -463,6 +476,7 @@
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
C3C8CCF379347A4DF9D2A39D /* CrashReportingExampleModule.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
12 changes: 12 additions & 0 deletions examples/default/ios/native/CrashReportingExampleModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface CrashReportingExampleModule : RCTEventEmitter <RCTBridgeModule>

- (void)sendNativeNonFatal;
- (void)sendNativeFatalCrash;
- (void)sendFatalHang;
- (void)sendOOM;

@end
Loading