Skip to content

Commit e870489

Browse files
committed
Merge branch 'main' of https://github.com/firebase/firebase-android-sdk into markduckworth/vector-type
2 parents 7baebc3 + 8c04ec2 commit e870489

File tree

8 files changed

+475
-240
lines changed

8 files changed

+475
-240
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal.concurrency;
18+
19+
import java.util.concurrent.ExecutorService;
20+
import java.util.concurrent.Executors;
21+
22+
/** Convenience methods for use in Crashlytics concurrency tests. */
23+
class ConcurrencyTesting {
24+
25+
/** Returns the current thread's name. */
26+
static String getThreadName() {
27+
return Thread.currentThread().getName();
28+
}
29+
30+
/** Creates a simple executor that runs on a single named thread. */
31+
static ExecutorService newNamedSingleThreadExecutor(String name) {
32+
return Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, name));
33+
}
34+
35+
/** Convenient sleep method that propagates the interruption, but does not throw. */
36+
static void sleep(long millis) {
37+
try {
38+
Thread.sleep(millis);
39+
} catch (InterruptedException ex) {
40+
Thread.currentThread().interrupt();
41+
}
42+
}
43+
44+
/** Helps to de-flake a test. */
45+
static void deflake() {
46+
// An easy, but ugly, way to fix a flaky test.
47+
sleep(1);
48+
}
49+
50+
private ConcurrencyTesting() {}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal.concurrency;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static com.google.firebase.crashlytics.internal.concurrency.ConcurrencyTesting.sleep;
21+
import static org.junit.Assert.assertThrows;
22+
23+
import com.google.android.gms.tasks.Task;
24+
import com.google.android.gms.tasks.TaskCompletionSource;
25+
import com.google.android.gms.tasks.Tasks;
26+
import com.google.firebase.concurrent.TestOnlyExecutors;
27+
import java.util.concurrent.CancellationException;
28+
import java.util.concurrent.ExecutionException;
29+
import java.util.concurrent.Executors;
30+
import java.util.concurrent.TimeUnit;
31+
import java.util.concurrent.TimeoutException;
32+
import org.junit.Test;
33+
34+
public class CrashlyticsTasksTest {
35+
36+
@Test
37+
public void raceReturnsFirstResult() throws Exception {
38+
// Create 2 tasks on different workers to race.
39+
Task<String> task1 =
40+
new CrashlyticsWorker(TestOnlyExecutors.background())
41+
.submit(
42+
() -> {
43+
sleep(200);
44+
return "first";
45+
});
46+
Task<String> task2 =
47+
new CrashlyticsWorker(TestOnlyExecutors.background())
48+
.submit(
49+
() -> {
50+
sleep(400);
51+
return "slow";
52+
});
53+
54+
Task<String> task = CrashlyticsTasks.race(task1, task2);
55+
String result = Tasks.await(task);
56+
57+
assertThat(result).isEqualTo("first");
58+
}
59+
60+
@Test
61+
public void raceReturnsFirstException() {
62+
// Create 2 tasks on different workers to race.
63+
Task<String> task1 =
64+
new CrashlyticsWorker(TestOnlyExecutors.background())
65+
.submitTask(
66+
() -> {
67+
sleep(200);
68+
return Tasks.forException(new ArithmeticException());
69+
});
70+
Task<String> task2 =
71+
new CrashlyticsWorker(TestOnlyExecutors.background())
72+
.submitTask(
73+
() -> {
74+
sleep(400);
75+
return Tasks.forException(new IllegalStateException());
76+
});
77+
78+
Task<String> task = CrashlyticsTasks.race(task1, task2);
79+
ExecutionException thrown = assertThrows(ExecutionException.class, () -> Tasks.await(task));
80+
81+
// The first task throws an ArithmeticException.
82+
assertThat(thrown).hasCauseThat().isInstanceOf(ArithmeticException.class);
83+
}
84+
85+
@Test
86+
public void raceFirstCancelsReturnsSecondResult() throws Exception {
87+
// Create 2 tasks on different workers to race.
88+
Task<String> task1 =
89+
new CrashlyticsWorker(TestOnlyExecutors.background())
90+
.submitTask(
91+
() -> {
92+
sleep(200);
93+
return Tasks.forCanceled();
94+
});
95+
Task<String> task2 =
96+
new CrashlyticsWorker(TestOnlyExecutors.background())
97+
.submitTask(
98+
() -> {
99+
sleep(400);
100+
return Tasks.forResult("I am slow but didn't cancel.");
101+
});
102+
103+
Task<String> task = CrashlyticsTasks.race(task1, task2);
104+
String result = Tasks.await(task);
105+
106+
assertThat(result).isEqualTo("I am slow but didn't cancel.");
107+
}
108+
109+
@Test
110+
public void raceBothCancel() {
111+
// Create 2 tasks on different workers to race.
112+
Task<String> task1 =
113+
new CrashlyticsWorker(TestOnlyExecutors.background())
114+
.submitTask(
115+
() -> {
116+
sleep(200);
117+
return Tasks.forCanceled();
118+
});
119+
Task<String> task2 =
120+
new CrashlyticsWorker(TestOnlyExecutors.background())
121+
.submitTask(
122+
() -> {
123+
sleep(400);
124+
return Tasks.forCanceled();
125+
});
126+
127+
Task<String> task = CrashlyticsTasks.race(task1, task2);
128+
129+
// Both cancelled, so cancel the race result.
130+
assertThrows(CancellationException.class, () -> Tasks.await(task));
131+
}
132+
133+
@Test
134+
public void raceTasksOnSameWorker() throws Exception {
135+
CrashlyticsWorker worker = new CrashlyticsWorker(TestOnlyExecutors.background());
136+
137+
// Create 2 tasks on the same worker to race.
138+
Task<String> task1 =
139+
worker.submit(
140+
() -> {
141+
sleep(20);
142+
return "first";
143+
});
144+
Task<String> task2 =
145+
worker.submit(
146+
() -> {
147+
sleep(30);
148+
return "second";
149+
});
150+
151+
Task<String> task = CrashlyticsTasks.race(task1, task2);
152+
String result = Tasks.await(task);
153+
154+
assertThat(result).isEqualTo("first");
155+
}
156+
157+
@Test
158+
public void raceTasksOnSameSingleThreadWorker() throws Exception {
159+
CrashlyticsWorker worker = new CrashlyticsWorker(Executors.newSingleThreadExecutor());
160+
161+
// Create 2 tasks on the same worker to race.
162+
Task<String> task1 = worker.submit(() -> "first");
163+
Task<String> task2 = worker.submit(() -> "second");
164+
165+
Task<String> task = CrashlyticsTasks.race(task1, task2);
166+
String result = Tasks.await(task);
167+
168+
// The first task is submitted to this single thread worker first, so will always be first.
169+
assertThat(result).isEqualTo("first");
170+
}
171+
172+
@Test
173+
public void raceTaskOneOnWorkerAnotherNeverCompletes() throws Exception {
174+
// Create a task on a worker, and another that never completes, to race.
175+
Task<String> task1 =
176+
new CrashlyticsWorker(TestOnlyExecutors.background()).submit(() -> "first");
177+
Task<String> task2 = new TaskCompletionSource<String>().getTask();
178+
179+
Task<String> task = CrashlyticsTasks.race(task1, task2);
180+
String result = Tasks.await(task);
181+
182+
assertThat(result).isEqualTo("first");
183+
}
184+
185+
@Test
186+
public void raceTaskOneOnWorkerAnotherOtherThatCompletesFirst() throws Exception {
187+
CrashlyticsWorker worker = new CrashlyticsWorker(TestOnlyExecutors.background());
188+
189+
// Add a decoy task to the worker to take up some time.
190+
worker.submitTask(
191+
() -> {
192+
sleep(200);
193+
return Tasks.forResult(null);
194+
});
195+
196+
// Create a task on this worker, and another, to race.
197+
Task<String> task1 = worker.submit(() -> "worker");
198+
TaskCompletionSource<String> task2 = new TaskCompletionSource<>();
199+
task2.trySetResult("other");
200+
201+
Task<String> task = CrashlyticsTasks.race(task1, task2.getTask());
202+
String result = Tasks.await(task);
203+
204+
// The other tasks completes first because the first task is queued up later on the worker.
205+
assertThat(result).isEqualTo("other");
206+
}
207+
208+
@Test
209+
public void raceNoExecutor() throws Exception {
210+
// Create tasks with no explicit executor.
211+
TaskCompletionSource<String> task1 = new TaskCompletionSource<>();
212+
TaskCompletionSource<String> task2 = new TaskCompletionSource<>();
213+
214+
Task<String> task = CrashlyticsTasks.race(task1.getTask(), task2.getTask());
215+
216+
// Set a task result from another thread.
217+
new Thread(
218+
() -> {
219+
sleep(300);
220+
task1.trySetResult("yes");
221+
})
222+
.start();
223+
224+
String result = Tasks.await(task);
225+
226+
assertThat(result).isEqualTo("yes");
227+
}
228+
229+
@Test
230+
public void raceTasksThatNeverResolve() {
231+
// Create tasks that will never resolve.
232+
Task<String> task1 = new TaskCompletionSource<String>().getTask();
233+
Task<String> task2 = new TaskCompletionSource<String>().getTask();
234+
235+
Task<String> task = CrashlyticsTasks.race(task1, task2);
236+
237+
// Since the tasks never resolve, the await will timeout.
238+
assertThrows(TimeoutException.class, () -> Tasks.await(task, 300, TimeUnit.MILLISECONDS));
239+
}
240+
}

0 commit comments

Comments
 (0)