Skip to content

Commit e950372

Browse files
Merge pull request #172 from backtrace-labs/BT-5584/checkForComodification
Bt 5584/check for comodification
2 parents 99559f3 + cff7c5a commit e950372

File tree

2 files changed

+232
-52
lines changed

2 files changed

+232
-52
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package backtraceio.library.database;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import androidx.annotation.NonNull;
6+
7+
import org.junit.Before;
8+
import org.junit.Test;
9+
10+
import java.util.ArrayList;
11+
import java.util.HashMap;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.concurrent.CountDownLatch;
15+
16+
import backtraceio.library.models.BacktraceData;
17+
import backtraceio.library.models.database.BacktraceDatabaseRecord;
18+
import backtraceio.library.models.database.BacktraceDatabaseSettings;
19+
import backtraceio.library.models.json.BacktraceReport;
20+
import backtraceio.library.services.BacktraceDatabaseContext;
21+
22+
public class BacktraceDatabaseContextMultithreadedTest {
23+
private static class TestConfig {
24+
final int recordsState;
25+
final int recordsToAdd;
26+
final int recordsToDelete;
27+
final int threadWaitTimeMs;
28+
29+
TestConfig(int recordsState, int recordsToAdd, int recordsToDelete, int threadWaitTimeMs) {
30+
this.recordsState = recordsState;
31+
this.recordsToAdd = recordsToAdd;
32+
this.recordsToDelete = recordsToDelete;
33+
this.threadWaitTimeMs = threadWaitTimeMs;
34+
}
35+
}
36+
37+
private static class ConcurrentTestState {
38+
final List<Exception> caughtExceptions = new ArrayList<>();
39+
final List<Integer> deletedRecords = new ArrayList<>();
40+
final List<Integer> addedRecords = new ArrayList<>();
41+
42+
synchronized void handleException(Exception e) {
43+
caughtExceptions.add(e);
44+
}
45+
46+
void printExceptions() {
47+
for (Exception e : caughtExceptions) {
48+
e.printStackTrace();
49+
}
50+
}
51+
}
52+
private BacktraceDatabaseContext databaseContext;
53+
54+
@Before
55+
public void setUp() {
56+
BacktraceDatabaseSettings settings = new BacktraceDatabaseSettings("test-path");
57+
settings.setRetryLimit(3);
58+
databaseContext = new BacktraceDatabaseContext(settings);
59+
}
60+
61+
@Test
62+
public void testConcurrentModification() throws InterruptedException {
63+
// GIVEN
64+
final TestConfig config = new TestConfig(500, 250, 150, 30000); // 30s
65+
final List<BacktraceDatabaseRecord> initialRecords = generateMockRecords(config.recordsState);
66+
67+
final CountDownLatch startLatch = new CountDownLatch(1);
68+
final ConcurrentTestState testState = new ConcurrentTestState();
69+
70+
// Create and start test threads
71+
Thread addThread = createAddThread(startLatch, config.recordsToAdd, testState);
72+
Thread deleteThread = createDeleteThread(startLatch, initialRecords, config.recordsToDelete, testState);
73+
Thread readThread = createReadThread(startLatch, testState);
74+
75+
// WHEN
76+
startThreads(deleteThread, addThread, readThread);
77+
startLatch.countDown();
78+
waitForThreads(deleteThread, addThread, readThread, config.threadWaitTimeMs);
79+
80+
// Print any exceptions that occurred
81+
testState.printExceptions();
82+
83+
// THEN
84+
assertTestResults(config, testState);
85+
}
86+
87+
private Thread createDeleteThread(CountDownLatch latch, List<BacktraceDatabaseRecord> records,
88+
int recordsToDelete, ConcurrentTestState state) {
89+
return new Thread(() -> {
90+
try {
91+
latch.await();
92+
for (int i = 0; i < recordsToDelete; i++) {
93+
databaseContext.delete(records.get(i));
94+
state.deletedRecords.add(1);
95+
}
96+
} catch (Exception e) {
97+
state.handleException(e);
98+
}
99+
});
100+
}
101+
102+
private Thread createAddThread(CountDownLatch latch, int recordsToAdd, ConcurrentTestState state) {
103+
return new Thread(() -> {
104+
try {
105+
latch.await();
106+
for (int i = 0; i < recordsToAdd; i++) {
107+
BacktraceData data = createMockBacktraceData();
108+
databaseContext.add(data);
109+
state.addedRecords.add(1);
110+
}
111+
} catch (Exception e) {
112+
state.handleException(e);
113+
}
114+
});
115+
}
116+
117+
private Thread createReadThread(CountDownLatch latch, ConcurrentTestState state) {
118+
return new Thread(() -> {
119+
try {
120+
latch.await();
121+
String result;
122+
while (true) {
123+
for (BacktraceDatabaseRecord record : databaseContext.get()) {
124+
result = record.toString();
125+
}
126+
}
127+
} catch (Exception e) {
128+
state.handleException(e);
129+
}
130+
});
131+
}
132+
133+
private void startThreads(Thread... threads) {
134+
for (Thread thread : threads) {
135+
thread.start();
136+
}
137+
}
138+
139+
private void waitForThreads(Thread deleteThread, Thread addThread, Thread readThread, int waitTimeMs)
140+
throws InterruptedException {
141+
deleteThread.join(waitTimeMs);
142+
addThread.join(waitTimeMs);
143+
readThread.join(waitTimeMs);
144+
}
145+
146+
private void assertTestResults(TestConfig config, ConcurrentTestState state) {
147+
assertEquals(0, state.caughtExceptions.size());
148+
assertEquals(
149+
config.recordsState + config.recordsToAdd - config.recordsToDelete,
150+
config.recordsState + state.addedRecords.size() - state.deletedRecords.size()
151+
);
152+
}
153+
154+
@NonNull
155+
private List<BacktraceDatabaseRecord> generateMockRecords(int recordCount) {
156+
final List<BacktraceDatabaseRecord> records = new ArrayList<>();
157+
for (int i = 0; i < recordCount; i++) {
158+
BacktraceData data = createMockBacktraceData();
159+
BacktraceDatabaseRecord record = databaseContext.add(data);
160+
records.add(record);
161+
}
162+
return records;
163+
}
164+
165+
private BacktraceData createMockBacktraceData() {
166+
final Exception testException = new Exception("Test exception");
167+
168+
final Map<String, Object> attributes = new HashMap<String, Object>() {{
169+
put("test_attribute", "test_value");
170+
}};
171+
172+
return new BacktraceData.Builder(new BacktraceReport(testException, attributes)).build();
173+
}
174+
}

0 commit comments

Comments
 (0)