Skip to content

Commit a1e1191

Browse files
committed
feat: add a retry barrier test
1 parent 1d15b6d commit a1e1191

File tree

5 files changed

+138
-33
lines changed

5 files changed

+138
-33
lines changed

sdk/src/main/java/com/deploygate/sdk/CustomLog.java

+13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.deploygate.sdk;
22

3+
import android.os.Bundle;
34
import android.os.Parcel;
45
import android.os.Parcelable;
56

7+
import com.deploygate.service.DeployGateEvent;
8+
69
class CustomLog implements Parcelable {
710
public final String type;
811
public final String body;
@@ -30,6 +33,16 @@ int getAndIncrementRetryCount() {
3033
return retryCount++;
3134
}
3235

36+
/**
37+
* @return a bundle to send to the client service
38+
*/
39+
Bundle toExtras() {
40+
Bundle extras = new Bundle();
41+
extras.putSerializable(DeployGateEvent.EXTRA_LOG, body);
42+
extras.putSerializable(DeployGateEvent.EXTRA_LOG_TYPE, type);
43+
return extras;
44+
}
45+
3346
public static final Creator<CustomLog> CREATOR = new Creator<CustomLog>() {
3447
@Override
3548
public CustomLog createFromParcel(Parcel in) {

sdk/src/main/java/com/deploygate/sdk/CustomLogTransmitter.java

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.deploygate.sdk;
22

3-
import android.os.Bundle;
43
import android.os.Handler;
54
import android.os.HandlerThread;
65
import android.os.Looper;
@@ -29,14 +28,17 @@
2928
*/
3029
class CustomLogTransmitter {
3130
private static final int MAX_RETRY_COUNT = 3;
31+
static final int SEND_LOG_RESULT_SUCCESS = 0;
32+
static final int SEND_LOG_RESULT_FAILURE_RETRIABLE = -1;
33+
static final int SEND_LOG_RESULT_FAILURE_RETRY_EXCEEDED = -2;
3234

3335
private final String packageName;
3436
private final CustomLogConfiguration configuration;
3537

3638
@SuppressWarnings("FieldCanBeLocal")
3739
private final HandlerThread thread;
3840
private CustomLogHandler handler;
39-
private boolean isDisabled;
41+
private boolean isDisabledTransmission;
4042

4143
private volatile IDeployGateSdkService service;
4244

@@ -54,7 +56,7 @@ class CustomLogTransmitter {
5456

5557
this.packageName = packageName;
5658
this.configuration = configuration;
57-
this.isDisabled = true;
59+
this.isDisabledTransmission = false;
5860

5961
this.thread = new HandlerThread("deploygate-sdk-event-log");
6062
this.thread.start();
@@ -100,7 +102,7 @@ public final synchronized void transmit(
100102
String type,
101103
String body
102104
) {
103-
if (isDisabled) {
105+
if (isDisabledTransmission) {
104106
return;
105107
}
106108

@@ -113,13 +115,13 @@ public final synchronized void transmit(
113115
/**
114116
* Disable transmissions.
115117
*
116-
* @param disabled
118+
* @param disabledTransmission
117119
* specify true if wanna disable the transmitter, otherwise false.
118120
*/
119-
public final void setDisabled(boolean disabled) {
120-
isDisabled = disabled;
121+
public final void setDisabledTransmission(boolean disabledTransmission) {
122+
isDisabledTransmission = disabledTransmission;
121123

122-
if (disabled) {
124+
if (disabledTransmission) {
123125
Logger.d("Disabled custom log transmitter");
124126
} else {
125127
Logger.d("Enabled custom log transmitter");
@@ -162,30 +164,28 @@ int getPendingCount() {
162164
*
163165
* @return true if this can transmit the custom log, otherwise false.
164166
*/
165-
boolean sendLog(CustomLog log) {
167+
int sendLog(CustomLog log) {
166168
IDeployGateSdkService service = this.service;
167169

168170
if (service == null) {
169-
return false;
171+
// Don't increment retry count
172+
return SEND_LOG_RESULT_FAILURE_RETRIABLE;
170173
}
171174

172175
try {
173-
Bundle extras = new Bundle();
174-
extras.putSerializable(DeployGateEvent.EXTRA_LOG, log.body);
175-
extras.putSerializable(DeployGateEvent.EXTRA_LOG_TYPE, log.type);
176-
177-
service.sendEvent(packageName, DeployGateEvent.ACTION_SEND_CUSTOM_LOG, extras);
178-
return true;
176+
service.sendEvent(packageName, DeployGateEvent.ACTION_SEND_CUSTOM_LOG, log.toExtras());
177+
return SEND_LOG_RESULT_SUCCESS;
179178
} catch (RemoteException e) {
180179
int currentAttempts = log.getAndIncrementRetryCount();
181180

182181
if (currentAttempts >= MAX_RETRY_COUNT) {
183182
Logger.e("failed to send custom log and exceeded the max retry count: %s", e.getMessage());
183+
return SEND_LOG_RESULT_FAILURE_RETRY_EXCEEDED;
184184
} else {
185185
Logger.w("failed to send custom log %d times: %s", currentAttempts + 1, e.getMessage());
186186
}
187187

188-
return false;
188+
return SEND_LOG_RESULT_FAILURE_RETRIABLE;
189189
}
190190
}
191191

@@ -307,7 +307,7 @@ void sendAllInBuffer() {
307307
while (!customLogs.isEmpty()) {
308308
CustomLog log = customLogs.poll();
309309

310-
if (!transmitter.sendLog(log)) {
310+
if (transmitter.sendLog(log) == SEND_LOG_RESULT_FAILURE_RETRIABLE) {
311311
// Don't lost the failed log
312312
customLogs.addFirst(log);
313313
sendEmptyMessageDelayed(WHAT_SEND_LOGS, 1000L);

sdk/src/main/java/com/deploygate/sdk/DeployGate.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,12 @@ private DeployGate(
246246
private boolean initService(boolean isBoot) {
247247
if (isDeployGateAvailable()) {
248248
Log.v(TAG, "DeployGate installation detected. Initializing.");
249-
mCustomLogTransmitter.setDisabled(false);
249+
mCustomLogTransmitter.setDisabledTransmission(false);
250250
bindToService(isBoot);
251251
return true;
252252
} else {
253253
Log.v(TAG, "DeployGate is not available on this device.");
254-
mCustomLogTransmitter.setDisabled(true);
254+
mCustomLogTransmitter.setDisabledTransmission(true);
255255
mInitializedLatch.countDown();
256256
mIsDeployGateAvailable = false;
257257
callbackDeployGateUnavailable();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.deploygate.sdk;
2+
3+
import android.os.Bundle;
4+
5+
import androidx.test.ext.junit.runners.AndroidJUnit4;
6+
7+
import com.deploygate.sdk.truth.BundleSubject;
8+
9+
import org.junit.Test;
10+
import org.junit.runner.RunWith;
11+
12+
@RunWith(AndroidJUnit4.class)
13+
public class CustomLogTest {
14+
15+
@Test
16+
public void CustomLog_toExtras_must_be_valid_format() {
17+
CustomLog log = new CustomLog("error", "yes");
18+
19+
BundleSubject.assertThat(log.toExtras()).isEqualTo(createLogExtra("error", "yes"));
20+
}
21+
22+
private static Bundle createLogExtra(
23+
String type,
24+
String body
25+
) {
26+
Bundle bundle = new Bundle();
27+
bundle.putString("logType", type);
28+
bundle.putString("log", body);
29+
return bundle;
30+
}
31+
}

sdk/src/test/java/com/deploygate/sdk/CustomLogTransmitterTest.java

+74-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.deploygate.sdk;
22

33
import android.os.Bundle;
4+
import android.os.DeadObjectException;
45
import android.os.RemoteException;
6+
import android.os.TransactionTooLargeException;
57

68
import androidx.test.ext.junit.runners.AndroidJUnit4;
79

@@ -15,10 +17,20 @@
1517
import org.mockito.Mock;
1618
import org.mockito.Mockito;
1719
import org.mockito.MockitoAnnotations;
20+
import org.mockito.invocation.InvocationOnMock;
21+
import org.mockito.stubbing.Answer;
1822
import org.mockito.verification.VerificationMode;
1923
import org.robolectric.Shadows;
2024
import org.robolectric.annotation.LooperMode;
2125

26+
import java.util.ArrayList;
27+
import java.util.List;
28+
29+
import static com.deploygate.sdk.mockito.BundleMatcher.eq;
30+
import static org.mockito.ArgumentMatchers.eq;
31+
import static org.mockito.Mockito.doAnswer;
32+
import static org.mockito.Mockito.doNothing;
33+
import static org.mockito.Mockito.doThrow;
2234
import static org.robolectric.annotation.LooperMode.Mode.PAUSED;
2335

2436
@RunWith(AndroidJUnit4.class)
@@ -57,13 +69,15 @@ public void check_buffer_size_works_with_drop_by_oldest() throws RemoteException
5769
Shadows.shadowOf(customLogTransmitter.getLooper()).idle();
5870

5971
for (int i = 0; i < 5; i++) {
60-
Mockito.verify(service, Mockito.never()).sendEvent(Mockito.eq(PACKAGE_NAME), Mockito.eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), BundleMatcher.eq(createLogExtra("type", String.valueOf(i))));
72+
CustomLog log = new CustomLog("type", String.valueOf(i));
73+
Mockito.verify(service, Mockito.never()).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(log.toExtras()));
6174
}
6275

6376
VerificationMode once = Mockito.times(1);
6477

6578
for (int i = 5; i < 10; i++) {
66-
Mockito.verify(service, once).sendEvent(Mockito.eq(PACKAGE_NAME), Mockito.eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), BundleMatcher.eq(createLogExtra("type", String.valueOf(i))));
79+
CustomLog log = new CustomLog("type", String.valueOf(i));
80+
Mockito.verify(service, once).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(log.toExtras()));
6781
}
6882
}
6983

@@ -85,16 +99,40 @@ public void check_buffer_size_works_with_preserve_buffer() throws RemoteExceptio
8599
VerificationMode once = Mockito.times(1);
86100

87101
for (int i = 0; i < 5; i++) {
88-
Mockito.verify(service, once).sendEvent(Mockito.eq(PACKAGE_NAME), Mockito.eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), BundleMatcher.eq(createLogExtra("type", String.valueOf(i))));
102+
CustomLog log = new CustomLog("type", String.valueOf(i));
103+
Mockito.verify(service, once).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(log.toExtras()));
89104
}
90105

91106
for (int i = 5; i < 10; i++) {
92-
Mockito.verify(service, Mockito.never()).sendEvent(Mockito.eq(PACKAGE_NAME), Mockito.eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), BundleMatcher.eq(createLogExtra("type", String.valueOf(i))));
107+
CustomLog log = new CustomLog("type", String.valueOf(i));
108+
Mockito.verify(service, Mockito.never()).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(log.toExtras()));
93109
}
94110
}
95111

96112
@Test(timeout = 3000L)
97-
public void transmit_works_regardless_of_service() {
113+
public void transmit_works_regardless_of_service() throws RemoteException {
114+
customLogTransmitter.connect(service);
115+
116+
CustomLog noIssue = new CustomLog("type", "noIssue");
117+
CustomLog successAfterRetries = new CustomLog("type", "successAfterRetries");
118+
CustomLog retryExceeded = new CustomLog("type", "retryExceeded");
119+
120+
doNothing().when(service).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(noIssue.toExtras()));
121+
doThrow(TransactionTooLargeException.class).doThrow(DeadObjectException.class).doNothing().when(service).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(successAfterRetries.toExtras()));
122+
doThrow(RemoteException.class).when(service).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(retryExceeded.toExtras()));
123+
124+
Truth.assertThat(customLogTransmitter.sendLog(noIssue)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_SUCCESS);
125+
Truth.assertThat(customLogTransmitter.sendLog(successAfterRetries)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_FAILURE_RETRIABLE);
126+
Truth.assertThat(customLogTransmitter.sendLog(successAfterRetries)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_FAILURE_RETRIABLE);
127+
Truth.assertThat(customLogTransmitter.sendLog(successAfterRetries)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_SUCCESS);
128+
Truth.assertThat(customLogTransmitter.sendLog(retryExceeded)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_FAILURE_RETRIABLE);
129+
Truth.assertThat(customLogTransmitter.sendLog(retryExceeded)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_FAILURE_RETRIABLE);
130+
Truth.assertThat(customLogTransmitter.sendLog(retryExceeded)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_FAILURE_RETRIABLE);
131+
Truth.assertThat(customLogTransmitter.sendLog(retryExceeded)).isEqualTo(CustomLogTransmitter.SEND_LOG_RESULT_FAILURE_RETRY_EXCEEDED);
132+
}
133+
134+
@Test(timeout = 3000L)
135+
public void retry_barrier_can_prevent_holding_logs_that_always_fail() {
98136
CustomLogConfiguration configuration = new CustomLogConfiguration.Builder().setBufferSize(8).build();
99137
CustomLogTransmitter customLogTransmitter = new CustomLogTransmitter(PACKAGE_NAME, configuration);
100138

@@ -107,13 +145,36 @@ public void transmit_works_regardless_of_service() {
107145
Truth.assertThat(customLogTransmitter.getPendingCount()).isEqualTo(8);
108146
}
109147

110-
private static Bundle createLogExtra(
111-
String type,
112-
String body
113-
) {
114-
Bundle bundle = new Bundle();
115-
bundle.putString("logType", type);
116-
bundle.putString("log", body);
117-
return bundle;
148+
@Test(timeout = 3000L)
149+
public void transmit_works_as_expected_with_retry_barrier() throws RemoteException {
150+
List<Bundle> extras = new ArrayList<>();
151+
Answer capture = new Answer() {
152+
@Override
153+
public Object answer(InvocationOnMock invocation) throws Throwable {
154+
Bundle extra = invocation.getArgument(2);
155+
extras.add(extra);
156+
return null;
157+
}
158+
};
159+
160+
CustomLog noIssue = new CustomLog("type", "noIssue");
161+
CustomLog successAfterRetries = new CustomLog("type", "successAfterRetries");
162+
CustomLog retryExceeded = new CustomLog("type", "retryExceeded");
163+
164+
doAnswer(capture).when(service).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(noIssue.toExtras()));
165+
doThrow(TransactionTooLargeException.class).doThrow(DeadObjectException.class).doAnswer(capture).when(service).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(successAfterRetries.toExtras()));
166+
doThrow(RemoteException.class).when(service).sendEvent(eq(PACKAGE_NAME), eq(DeployGateEvent.ACTION_SEND_CUSTOM_LOG), eq(retryExceeded.toExtras()));
167+
168+
customLogTransmitter.connect(service);
169+
170+
customLogTransmitter.transmit(successAfterRetries.type, successAfterRetries.body);
171+
customLogTransmitter.transmit(noIssue.type, noIssue.body);
172+
customLogTransmitter.transmit(retryExceeded.type, retryExceeded.body);
173+
174+
Shadows.shadowOf(customLogTransmitter.getLooper()).idle();
175+
176+
Truth.assertThat(extras).hasSize(2);
177+
Truth.assertThat(extras.get(0).getString(DeployGateEvent.EXTRA_LOG)).isEqualTo(successAfterRetries.body);
178+
Truth.assertThat(extras.get(1).getString(DeployGateEvent.EXTRA_LOG)).isEqualTo(noIssue.body);
118179
}
119180
}

0 commit comments

Comments
 (0)