Skip to content

Commit

Permalink
feat: module crash tests
Browse files Browse the repository at this point in the history
  • Loading branch information
arifBurakDemiray committed Nov 21, 2023
1 parent e1cf869 commit d77ef73
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
## XX.XX.XX

* Fixed a bug where setting custom user properties would not work.

* Crash Reporting interface added and accessible through "Countly::instance()::crash()" call.

* Deprecated "Countly::backendMode()" call, use "Countly::backendM" instead via "instance()" call.
* Deprecated "Usage::addCrashReport" call, use "Countly::crash" instead via "instance()" call.

## 23.10.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public CrashImpl setName(String name) {

@Override
public CrashImpl setSegments(Map<String, Object> segments) {
if (segments != null && segments.size() > 0) {
if (segments != null && !segments.isEmpty()) {
return add("_custom", new JSONObject(segments));
}
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public void init(InternalConfig config) {
Class cls = Class.forName(config.getCrashProcessorClass());
crashProcessor = (CrashProcessor) cls.getConstructors()[0].newInstance();
} catch (Throwable t) {
t.printStackTrace();
L.e("[ModuleCrash] Cannot instantiate CrashProcessor" + t);
}
}
Expand Down Expand Up @@ -117,10 +118,6 @@ public CrashImpl onCrash(InternalConfig config, CrashImpl crash) {
return crash;
}

public static void putCrashIntoParams(CrashImpl crash, Params params) {
params.add("crash", crash.getJSON());
}

public class Crashes {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ private boolean processCrash(InternalConfig config, Long id) {
}

Request request = ModuleRequests.nonSessionRequest(config);
ModuleCrash.putCrashIntoParams(crash, request.params);
request.params.add("crash", crash.getJSON());

ModuleRequests.addRequiredParametersToParams(config, request.params);
ModuleRequests.addRequiredTimeParametersToParams(request.params);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package ly.count.sdk.java.internal;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import ly.count.sdk.java.Config;
import ly.count.sdk.java.Countly;
import ly.count.sdk.java.Crash;
import ly.count.sdk.java.CrashProcessor;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class ModuleCrashTests {

@Before
public void beforeTest() {
TestUtils.createCleanTestState();
}

@After
public void stop() {
Countly.instance().halt();
}

/**
* "recordHandledException"
* Validating that handled exception is recorded correctly to request queue
* Request queue should contain one request with crash object
*/
@Test
public void recordHandledException() throws InterruptedException {
Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.CrashReporting));
TestUtils.setAdditionalDeviceMetrics();
Throwable testThrowable = new Exception("test");
Thread.sleep(200);
Countly.instance().crash().recordHandledException(testThrowable);
validateCrashInRQ(testThrowable, false, null);
}

/**
* "recordHandledException" with custom crash processor
* Validating that crash processor is called and request queue is empty because it returns null
* Request queue should be empty
*/
@Test
public void recordHandledException_customCrashProcessor() throws InterruptedException {
Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.CrashReporting)
.setCrashProcessorClass(TestCrashProcessor.class));
TestUtils.setAdditionalDeviceMetrics();
Throwable testThrowable = new Exception("test");
Thread.sleep(200);
Countly.instance().crash().recordHandledException(testThrowable);
Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
}

/**
* "recordHandledException"
* Validating that handled exception is recorded correctly to request queue with custom segment
* Request queue should contain one request with crash object
*/
@Test
public void recordHandledException_withSegment() throws InterruptedException {
recordExceptionWithSegment_base(false, (throwable, customSegment) ->
Countly.instance().crash().recordHandledException(throwable, customSegment));
}

/**
* "recordHandledException"
* Validating that request queue is empty because null is passed as throwable
* Request queue should not contain one request with crash object
*/
@Test
public void recordHandledException_null() {
Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.CrashReporting));
TestUtils.setAdditionalDeviceMetrics();
Countly.instance().crash().recordHandledException(null);
Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
}

/**
* "recordUnhandledException"
* Validating that request queue is empty because null is passed as throwable
* Request queue should not contain one request with crash object
*/
@Test
public void recordUnhandledException_null() {
Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.CrashReporting));
TestUtils.setAdditionalDeviceMetrics();
Countly.instance().crash().recordUnhandledException(null);
Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
}

/**
* "recordUnhandledException"
* Validating that handled exception is recorded correctly to request queue,
* Request queue should contain one request with crash object
*/
@Test
public void recordUnhandledException() {
Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.CrashReporting));
TestUtils.setAdditionalDeviceMetrics();
Throwable testThrowable = new Exception("test");
Countly.instance().crash().recordUnhandledException(testThrowable);
validateCrashInRQ(testThrowable, true, null);
}

/**
* "recordUnhandledException"
* Validating that handled exception is recorded correctly to request queue with custom segment
* Request queue should contain one request with crash object
*/
@Test
public void recordUnhandledException_withSegment() throws InterruptedException {
recordExceptionWithSegment_base(true, (throwable, customSegment) ->
Countly.instance().crash().recordUnhandledException(throwable, customSegment));
}

/**
* Validate crash in request queue and has expected required parameters
*
* @param expectedError throwable to validate
* @param fatal is fatal
* @param customSegment custom segment to validate
*/
private void validateCrashInRQ(Throwable expectedError, boolean fatal, JSONObject customSegment) {
Map<String, String>[] rq = TestUtils.getCurrentRQ();
Assert.assertEquals(1, rq.length);
Assert.assertEquals(10, rq[0].size());
TestUtils.validateRequiredParams(rq[0]);
JSONObject crashObj = new JSONObject(rq[0].get("crash"));
Assert.assertEquals(customSegment == null ? 19 : 20, crashObj.length());

Assert.assertTrue(crashObj.getDouble("_run") > 0);
Assert.assertTrue(crashObj.getInt("_disk_total") > 0);
Assert.assertTrue(crashObj.getInt("_disk_current") > 0);
Assert.assertTrue(crashObj.getInt("_ram_current") > 0);
Assert.assertTrue(crashObj.getInt("_ram_total") > 0);
Assert.assertEquals("Device", crashObj.get("_device"));
Assert.assertEquals("Manufacturer", crashObj.get("_manufacture"));
Assert.assertEquals("CPU1.2", crashObj.get("_cpu"));
Assert.assertEquals("OpenGL2.3.1", crashObj.get("_opengl"));
Assert.assertEquals("portrait", crashObj.get("_orientation"));
Assert.assertEquals("100x100", crashObj.get("_resolution"));
Assert.assertEquals(true, crashObj.get("_online"));
Assert.assertEquals(true, crashObj.get("_muted"));
Assert.assertEquals(0.52f, crashObj.getFloat("_bat"), 0.0001);
Assert.assertEquals(TestUtils.APPLICATION_VERSION, crashObj.getString("_app_version"));
Assert.assertEquals(System.getProperty("os.version", "n/a"), crashObj.get("_os_version"));
Assert.assertEquals(System.getProperty("os.name"), crashObj.get("_os"));

StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
expectedError.printStackTrace(pw);
Assert.assertEquals(sw.toString(), crashObj.get("_error"));
Assert.assertEquals(!fatal, crashObj.get("_nonfatal"));
if (customSegment != null) {
Assert.assertEquals(customSegment.toString(), crashObj.getJSONObject("_custom").toString());
}
}

private void recordExceptionWithSegment_base(boolean fatal, BiConsumer<Throwable, Map<String, Object>> crashReporter) throws InterruptedException {
Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.CrashReporting));
TestUtils.setAdditionalDeviceMetrics();
Throwable testThrowable = new RuntimeException("Someting Happened");
Map<String, Object> customSegment = new ConcurrentHashMap<>();
customSegment.put("test", "test");
customSegment.put("test2", 2);
customSegment.put("test3", 3.0);
customSegment.put("test4", true);
customSegment.put("test5", new JSONObject());
customSegment.put("test6", new String[] { "test" });
customSegment.put("test7", new int[] { 1 });
customSegment.put("test8", new double[] { 1.0 });
customSegment.put("test9", new boolean[] { true });
customSegment.put("test10", new JSONObject[] { new JSONObject() });
customSegment.put("test11", new String[][] { { "test" } });
Thread.sleep(200); // wait for a time for the throwable
crashReporter.accept(testThrowable, customSegment);

validateCrashInRQ(testThrowable, fatal, new JSONObject(customSegment));
}

public static class TestCrashProcessor implements CrashProcessor {

public TestCrashProcessor() {
}

@Override
public Crash process(Crash crash) {
return null;
}
}
}
12 changes: 12 additions & 0 deletions sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ static Config getBaseConfig(String deviceID) {
return config;
}

static void setAdditionalDeviceMetrics() {
Device.dev.setCpu("CPU1.2");
Device.dev.setBatteryLevel(0.52f);
Device.dev.setManufacturer("Manufacturer");
Device.dev.setMuted(true);
Device.dev.setOpenGL("OpenGL2.3.1");
Device.dev.setOnline(true);
Device.dev.setOrientation("portrait");
Device.dev.setResolution("100x100");
Device.dev.setDevice("Device");
}

static Config getConfigRemoteConfigs() {
return getBaseConfig().enableFeatures(Config.Feature.RemoteConfig);
}
Expand Down

0 comments on commit d77ef73

Please sign in to comment.