Skip to content

Commit 33f2413

Browse files
authored
Merge branch 'main' into gsakakihara/deduplicate_by_message_id
2 parents fa9e4a0 + 9ed73c4 commit 33f2413

File tree

45 files changed

+2019
-516
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2019
-516
lines changed

.github/workflows/check_format.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Check Format
2+
concurrency:
3+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
4+
cancel-in-progress: true
5+
on:
6+
pull_request:
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
determine_changed:
13+
name: "Determine changed modules"
14+
runs-on: ubuntu-22.04
15+
if: (github.repository == 'Firebase/firebase-android-sdk' && github.event_name == 'push') || github.event_name == 'pull_request'
16+
outputs:
17+
modules: ${{ steps.changed-modules.outputs.modules }}
18+
steps:
19+
- uses: actions/[email protected]
20+
with:
21+
fetch-depth: 2
22+
submodules: true
23+
24+
- name: Set up JDK 17
25+
uses: actions/[email protected]
26+
with:
27+
java-version: 17
28+
distribution: temurin
29+
cache: gradle
30+
31+
- id: changed-modules
32+
run: |
33+
git diff --name-only HEAD~1 | xargs printf -- '--changed-git-paths %s\n' | xargs ./gradlew writeChangedProjects --output-file-path=modules.json
34+
echo modules=$(cat modules.json) >> $GITHUB_OUTPUT
35+
36+
check_format:
37+
name: "Check Format"
38+
runs-on: ubuntu-22.04
39+
needs:
40+
- determine_changed
41+
strategy:
42+
fail-fast: false
43+
matrix:
44+
module: ${{ fromJSON(needs.determine_changed.outputs.modules) }}
45+
46+
steps:
47+
- uses: actions/[email protected]
48+
with:
49+
fetch-depth: 2
50+
submodules: true
51+
52+
- name: Set up JDK 17
53+
uses: actions/[email protected]
54+
with:
55+
java-version: 17
56+
distribution: temurin
57+
cache: gradle
58+
59+
- name: ${{ matrix.module }} Check Format
60+
run: |
61+
./gradlew ${{matrix.module}}:spotlessCheck
62+
63+
# A job that fails if any job in the check_format matrix fails,
64+
# to be used as a required check for merging.
65+
check_all:
66+
runs-on: ubuntu-22.04
67+
if: always()
68+
name: Check Format (matrix)
69+
needs: check_format
70+
steps:
71+
- name: Check matrix
72+
if: needs.check_format.result != 'success'
73+
run: exit 1

firebase-crashlytics/CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Unreleased
2+
* [feature] Added the `isCrashlyticsCollectionEnabled` API to check if Crashlytics collection is enabled.
3+
([Github #5919](//github.com/firebase/firebase-android-sdk/issues/5919))
4+
* [fixed] Ensure that on-demand fatal events are never processed on the main thread.
25

36

47
# 19.0.3
58
* [changed] Update the internal file system to handle long file names.
6-
* [feature] Added the `isCrashlyticsCollectionEnabled` API to check if Crashlytics collection is enabled.
7-
([Github #5919](//github.com/firebase/firebase-android-sdk/issues/5919))
8-
99

1010
## Kotlin
1111
The Kotlin extensions library transitively includes the updated

firebase-crashlytics/firebase-crashlytics.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ dependencies {
9898
testImplementation(libs.mockito.core)
9999
testImplementation(libs.robolectric)
100100
testImplementation(libs.truth)
101+
testImplementation(project(":integ-testing"))
101102

102103
androidTestImplementation(libs.androidx.test.core)
103104
androidTestImplementation(libs.androidx.test.runner)
@@ -111,4 +112,6 @@ dependencies {
111112
androidTestImplementation(libs.androidx.test.junit)
112113
androidTestImplementation(libs.androidx.test.runner)
113114
androidTestImplementation(libs.truth)
115+
androidTestImplementation(libs.playservices.tasks)
116+
androidTestImplementation(project(":integ-testing"))
114117
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import androidx.test.core.app.ApplicationProvider;
22+
import com.google.firebase.FirebaseApp;
23+
import com.google.firebase.FirebaseOptions;
24+
import com.google.firebase.crashlytics.internal.common.CrashlyticsCore;
25+
import com.google.firebase.crashlytics.internal.common.DataCollectionArbiter;
26+
import java.lang.reflect.Field;
27+
import org.junit.After;
28+
import org.junit.Before;
29+
import org.junit.Test;
30+
31+
/** Tests for the internal apis that development platform plugins call. */
32+
public class CrashlyticsPluginsTest {
33+
private static final String APP_ID = "1:1:android:1a";
34+
private static final String API_KEY = "API-KEY-API-KEY-API-KEY-API-KEY-API-KEY";
35+
private static final String PROJECT_ID = "PROJECT-ID";
36+
37+
@Before
38+
public void setUp() {
39+
FirebaseApp.initializeApp(
40+
ApplicationProvider.getApplicationContext(),
41+
new FirebaseOptions.Builder()
42+
.setApplicationId(APP_ID)
43+
.setApiKey(API_KEY)
44+
.setProjectId(PROJECT_ID)
45+
.build());
46+
}
47+
48+
@After
49+
public void tearDown() {
50+
FirebaseApp.clearInstancesForTest();
51+
}
52+
53+
@Test
54+
public void accessCrashlyticsCore() {
55+
// Both Flutter and Unity plugins access CrashlyticsCore from FirebaseCrashlytics.core field.
56+
CrashlyticsCore core = FirebaseCrashlytics.getInstance().core;
57+
assertThat(core).isNotNull();
58+
59+
// Verify the internal method logFatalException exists without reflection.
60+
Runnable logFatalException = () -> core.logFatalException(new Throwable());
61+
assertThat(logFatalException).isNotNull();
62+
63+
// Verify the internal method setInternalKey exists without reflection.
64+
Runnable setInternalKey = () -> core.setInternalKey("", "");
65+
assertThat(setInternalKey).isNotNull();
66+
}
67+
68+
@Test
69+
public void accessDataCollection() throws Exception {
70+
// The Unity plugin accesses CrashlyticsCore.dataCollectionArbiter this via reflection.
71+
CrashlyticsCore core = FirebaseCrashlytics.getInstance().core;
72+
Field field = core.getClass().getDeclaredField("dataCollectionArbiter");
73+
field.setAccessible(true); // The dataCollectionArbiter field is private in CrashlyticsCore.
74+
DataCollectionArbiter dataCollectionArbiter = (DataCollectionArbiter) field.get(core);
75+
76+
assertThat(dataCollectionArbiter).isNotNull();
77+
}
78+
}

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@
3434
import com.google.android.gms.tasks.TaskCompletionSource;
3535
import com.google.android.gms.tasks.Tasks;
3636
import com.google.firebase.FirebaseApp;
37+
import com.google.firebase.concurrent.TestOnlyExecutors;
3738
import com.google.firebase.crashlytics.internal.CrashlyticsNativeComponent;
3839
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
3940
import com.google.firebase.crashlytics.internal.DevelopmentPlatformProvider;
4041
import com.google.firebase.crashlytics.internal.NativeSessionFileProvider;
4142
import com.google.firebase.crashlytics.internal.analytics.AnalyticsEventLogger;
43+
import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorker;
4244
import com.google.firebase.crashlytics.internal.metadata.LogFileManager;
4345
import com.google.firebase.crashlytics.internal.metadata.UserMetadata;
4446
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
@@ -55,13 +57,15 @@
5557
import java.util.TreeSet;
5658
import java.util.concurrent.Executor;
5759
import java.util.concurrent.TimeUnit;
58-
import org.junit.Test;
5960
import org.mockito.ArgumentCaptor;
6061

6162
public class CrashlyticsControllerTest extends CrashlyticsTestCase {
6263
private static final String GOOGLE_APP_ID = "google:app:id";
6364
private static final String SESSION_ID = "session_id";
6465

66+
private final CrashlyticsWorker commonWorker =
67+
new CrashlyticsWorker(TestOnlyExecutors.background());
68+
6569
private Context testContext;
6670
private IdManager idManager;
6771
private SettingsProvider testSettingsProvider;
@@ -70,6 +74,8 @@ public class CrashlyticsControllerTest extends CrashlyticsTestCase {
7074
private DataCollectionArbiter mockDataCollectionArbiter;
7175
private CrashlyticsNativeComponent mockNativeComponent = mock(CrashlyticsNativeComponent.class);
7276

77+
private CrashlyticsWorker diskWriteWorker = new CrashlyticsWorker(TestOnlyExecutors.background());
78+
7379
@Override
7480
protected void setUp() throws Exception {
7581
super.setUp();
@@ -99,14 +105,19 @@ protected void setUp() throws Exception {
99105
when(testSettingsProvider.getSettingsAsync()).thenReturn(Tasks.forResult(testSettings));
100106
}
101107

108+
@Override
109+
protected void tearDown() throws Exception {
110+
super.tearDown();
111+
commonWorker.await();
112+
}
113+
102114
/** A convenience class for building CrashlyticsController instances for testing. */
103115
private class ControllerBuilder {
104116
private DataCollectionArbiter dataCollectionArbiter;
105117
private CrashlyticsNativeComponent nativeComponent = null;
106118
private AnalyticsEventLogger analyticsEventLogger;
107119
private SessionReportingCoordinator sessionReportingCoordinator;
108120

109-
private CrashlyticsBackgroundWorker backgroundWorker;
110121
private LogFileManager logFileManager = null;
111122

112123
private UserMetadata userMetadata = null;
@@ -118,8 +129,6 @@ private class ControllerBuilder {
118129
analyticsEventLogger = mock(AnalyticsEventLogger.class);
119130

120131
sessionReportingCoordinator = mockSessionReportingCoordinator;
121-
122-
backgroundWorker = new CrashlyticsBackgroundWorker(new SameThreadExecutorService());
123132
}
124133

125134
ControllerBuilder setDataCollectionArbiter(DataCollectionArbiter arbiter) {
@@ -168,7 +177,7 @@ public CrashlyticsController build() {
168177
final CrashlyticsController controller =
169178
new CrashlyticsController(
170179
testContext.getApplicationContext(),
171-
backgroundWorker,
180+
commonWorker,
172181
idManager,
173182
dataCollectionArbiter,
174183
testFileStore,
@@ -179,7 +188,8 @@ public CrashlyticsController build() {
179188
sessionReportingCoordinator,
180189
nativeComponent,
181190
analyticsEventLogger,
182-
mock(CrashlyticsAppQualitySessionsSubscriber.class));
191+
mock(CrashlyticsAppQualitySessionsSubscriber.class),
192+
diskWriteWorker);
183193
return controller;
184194
}
185195
}
@@ -208,6 +218,8 @@ public void testWriteNonFatal_callsSessionReportingCoordinatorPersistNonFatal()
208218
controller.writeNonFatalException(thread, nonFatal);
209219
controller.doCloseSessions(testSettingsProvider);
210220

221+
commonWorker.await();
222+
211223
verify(mockSessionReportingCoordinator)
212224
.persistNonFatalEvent(eq(nonFatal), eq(thread), eq(sessionId), anyLong());
213225
}
@@ -228,9 +240,8 @@ public void testFatalException_callsSessionReportingCoordinatorPersistFatal() th
228240
.persistFatalEvent(eq(fatal), eq(thread), eq(sessionId), anyLong());
229241
}
230242

231-
@Test
232243
@SdkSuppress(minSdkVersion = 30) // ApplicationExitInfo
233-
public void testOnDemandFatal_callLogFatalException() {
244+
public void testOnDemandFatal_callLogFatalException() throws Exception {
234245
Thread thread = Thread.currentThread();
235246
Exception fatal = new RuntimeException("Fatal");
236247
Thread.UncaughtExceptionHandler exceptionHandler = mock(Thread.UncaughtExceptionHandler.class);
@@ -246,6 +257,8 @@ public void testOnDemandFatal_callLogFatalException() {
246257
controller.enableExceptionHandling(SESSION_ID, exceptionHandler, testSettingsProvider);
247258
controller.logFatalException(thread, fatal);
248259

260+
commonWorker.await();
261+
249262
verify(mockUserMetadata).setNewSession(not(eq(SESSION_ID)));
250263
}
251264

@@ -323,17 +336,20 @@ public File getOsFile() {
323336
final CrashlyticsController controller =
324337
builder().setNativeComponent(mockNativeComponent).setLogFileManager(logFileManager).build();
325338

326-
controller.finalizeSessions(testSettingsProvider);
339+
commonWorker.submit(() -> controller.finalizeSessions(testSettingsProvider));
340+
commonWorker.await();
341+
327342
verify(mockSessionReportingCoordinator)
328343
.finalizeSessionWithNativeEvent(eq(previousSessionId), any(), any());
329344
verify(mockSessionReportingCoordinator, never())
330345
.finalizeSessionWithNativeEvent(eq(sessionId), any(), any());
331346
}
332347

333348
@SdkSuppress(minSdkVersion = 30) // ApplicationExitInfo
334-
public void testMissingNativeComponentCausesNoReports() {
349+
public void testMissingNativeComponentCausesNoReports() throws Exception {
335350
final CrashlyticsController controller = createController();
336-
controller.finalizeSessions(testSettingsProvider);
351+
commonWorker.submit(() -> controller.finalizeSessions(testSettingsProvider));
352+
commonWorker.await();
337353

338354
List<String> sessions = testFileStore.getAllOpenSessionIds();
339355
for (String sessionId : sessions) {
@@ -363,13 +379,14 @@ public void testLoggedExceptionsAfterCrashOk() {
363379
*/
364380
// FIXME: Validate this test works as intended
365381
@SdkSuppress(minSdkVersion = 30) // ApplicationExitInfo
366-
public void testLogStringAfterCrashOk() {
382+
public void testLogStringAfterCrashOk() throws Exception {
367383
final CrashlyticsController controller = builder().build();
368384
controller.handleUncaughtException(
369385
testSettingsProvider, Thread.currentThread(), new RuntimeException());
370386

371387
// This should not throw.
372-
controller.writeToLog(System.currentTimeMillis(), "Hi");
388+
diskWriteWorker.submit(() -> controller.writeToLog(System.currentTimeMillis(), "Hi"));
389+
diskWriteWorker.await();
373390
}
374391

375392
/**
@@ -384,7 +401,8 @@ public void testFinalizeSessionAfterCrashOk() throws Exception {
384401
testSettingsProvider, Thread.currentThread(), new RuntimeException());
385402

386403
// This should not throw.
387-
controller.finalizeSessions(testSettingsProvider);
404+
commonWorker.submit(() -> controller.finalizeSessions(testSettingsProvider));
405+
commonWorker.await();
388406
}
389407

390408
@SdkSuppress(minSdkVersion = 30) // ApplicationExitInfo
@@ -535,7 +553,7 @@ public void testUploadDisabledThenEnabled() throws Exception {
535553
}
536554

537555
@SdkSuppress(minSdkVersion = 30) // ApplicationExitInfo
538-
public void testFatalEvent_sendsAppExceptionEvent() {
556+
public void testFatalEvent_sendsAppExceptionEvent() throws Exception {
539557
final String sessionId = "sessionId";
540558
final LogFileManager logFileManager = new LogFileManager(testFileStore);
541559
final AnalyticsEventLogger mockFirebaseAnalyticsLogger = mock(AnalyticsEventLogger.class);
@@ -548,10 +566,14 @@ public void testFatalEvent_sendsAppExceptionEvent() {
548566
when(mockSessionReportingCoordinator.listSortedOpenSessionIds())
549567
.thenReturn(new TreeSet<>(Collections.singleton(sessionId)));
550568

551-
controller.openSession(SESSION_ID);
552-
controller.handleUncaughtException(
553-
testSettingsProvider, Thread.currentThread(), new RuntimeException("Fatal"));
554-
controller.finalizeSessions(testSettingsProvider);
569+
commonWorker.submit(
570+
() -> {
571+
controller.openSession(SESSION_ID);
572+
controller.handleUncaughtException(
573+
testSettingsProvider, Thread.currentThread(), new RuntimeException("Fatal"));
574+
controller.finalizeSessions(testSettingsProvider);
575+
});
576+
commonWorker.await();
555577

556578
assertFirebaseAnalyticsCrashEvent(mockFirebaseAnalyticsLogger);
557579
}

0 commit comments

Comments
 (0)