Skip to content

Commit 6510f67

Browse files
authored
Merge branch 'master' into rl.ftl.api33
2 parents 75508b9 + b76de4f commit 6510f67

File tree

14 files changed

+545
-45
lines changed

14 files changed

+545
-45
lines changed

firebase-crashlytics/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Unreleased
2+
* [feature] Include Firebase sessions with NDK crashes and ANRs.
23

34
# 18.4.1
45
* [changed] Updated `firebase-sessions` dependency to v1.0.2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.crashlytics.internal.common;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static com.google.firebase.crashlytics.internal.common.CrashlyticsAppQualitySessionsStore.readAqsSessionIdFile;
19+
import static org.mockito.ArgumentMatchers.anyString;
20+
import static org.mockito.Mockito.spy;
21+
import static org.mockito.Mockito.when;
22+
23+
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
24+
import com.google.firebase.crashlytics.internal.persistence.FileStore;
25+
26+
public final class CrashlyticsAppQualitySessionsStoreTest extends CrashlyticsTestCase {
27+
private static final String CLOSED_SESSION = null;
28+
29+
private static final String SESSION_ID = "64e61da7023800012303a14eecd3f58d";
30+
private static final String APP_QUALITY_SESSION_ID = "79fd5d2e08ef4ea9a4f378879e53af2e";
31+
private static final String NEW_SESSION_ID = "64e61da0007500012284a14eecd3f58d";
32+
private static final String NEW_APP_QUALITY_SESSION_ID = "f7196b60000a44a092b5cc9e624f551c";
33+
34+
private CrashlyticsAppQualitySessionsStore aqsStore;
35+
private FileStore fileStore;
36+
37+
@Override
38+
protected void setUp() throws Exception {
39+
// The files created by each test case will get cleaned up in super.tearDown().
40+
fileStore = spy(new FileStore(getContext()));
41+
aqsStore = new CrashlyticsAppQualitySessionsStore(fileStore);
42+
43+
when(fileStore.getSessionFile(anyString(), anyString()))
44+
.thenAnswer(
45+
invocation -> {
46+
// The AQS file store relies on file timestamps. This sleep ensures each new file
47+
// has a unique timestamp, so the most recent file can be found deterministically.
48+
Thread.sleep(1000L);
49+
return invocation.callRealMethod();
50+
});
51+
}
52+
53+
public void testRotateAqsId_neverRotatedSessionId_doesNotPersist() {
54+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
55+
56+
// Does not create a file because there is no session to persist in.
57+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isNull();
58+
}
59+
60+
public void testRotateSessionId_neverRotatedAqsId_doesNotPersist() {
61+
aqsStore.rotateSessionId(SESSION_ID);
62+
63+
// Does not create a file because there was no aqs id to persist.
64+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isNull();
65+
}
66+
67+
public void testRotateBothIds_persists() {
68+
aqsStore.rotateSessionId(SESSION_ID);
69+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
70+
71+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
72+
}
73+
74+
public void testRotateBothIds_storesIds() {
75+
aqsStore.rotateSessionId(SESSION_ID);
76+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
77+
78+
// Delete all session files to verify the getter is reading the locally stored ids.
79+
fileStore.deleteSessionFiles(SESSION_ID);
80+
81+
assertThat(aqsStore.getAppQualitySessionId(SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
82+
}
83+
84+
public void testRotateBothIds_thenRotateAqsId_persists() {
85+
aqsStore.rotateSessionId(SESSION_ID);
86+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
87+
88+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
89+
90+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isEqualTo(NEW_APP_QUALITY_SESSION_ID);
91+
}
92+
93+
public void testRotateBothIds_thenRotateSessionId_persistsInNewSession() {
94+
aqsStore.rotateSessionId(SESSION_ID);
95+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
96+
97+
aqsStore.rotateSessionId(NEW_SESSION_ID);
98+
99+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
100+
assertThat(readAqsSessionIdFile(fileStore, NEW_SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
101+
}
102+
103+
public void testRotateBothIds_thenSessionId_thenAqsId_persistsInNewSessionOnly() {
104+
aqsStore.rotateSessionId(SESSION_ID);
105+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
106+
107+
aqsStore.rotateSessionId(NEW_SESSION_ID);
108+
109+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
110+
111+
// Old session still contains the old aqs id.
112+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
113+
114+
// New sessions contains the new aqs id.
115+
assertThat(readAqsSessionIdFile(fileStore, NEW_SESSION_ID))
116+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
117+
}
118+
119+
public void testRotateBothIds_thenAqsId_thenSessionId_persistsInBothSessions() {
120+
aqsStore.rotateSessionId(SESSION_ID);
121+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
122+
123+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
124+
125+
aqsStore.rotateSessionId(NEW_SESSION_ID);
126+
127+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isEqualTo(NEW_APP_QUALITY_SESSION_ID);
128+
assertThat(readAqsSessionIdFile(fileStore, NEW_SESSION_ID))
129+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
130+
}
131+
132+
public void testRotateBothIds_thenReadInvalidSessionId_returnsNull() {
133+
aqsStore.rotateSessionId(SESSION_ID);
134+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
135+
136+
assertThat(readAqsSessionIdFile(fileStore, "sessionDoesNotExist")).isNull();
137+
}
138+
139+
public void testRotateAqsIdWhileSessionClosed_persistsAqsIdInNewSessionOnly() {
140+
// Setup first session.
141+
aqsStore.rotateSessionId(SESSION_ID);
142+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
143+
144+
// Close the first session.
145+
aqsStore.rotateSessionId(CLOSED_SESSION);
146+
147+
// Rotate the aqs session id while Crashlytics session is closed.
148+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
149+
150+
// Start a new Crashlytics session.
151+
aqsStore.rotateSessionId(NEW_SESSION_ID);
152+
153+
// Verify the old session has the old aqs id
154+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
155+
156+
// Verify the new session has the updated aqs id.
157+
assertThat(readAqsSessionIdFile(fileStore, NEW_SESSION_ID))
158+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
159+
}
160+
161+
public void testUpdateAqsIdWhileSessionFailedToClosed_persistsNewAqsIdInBothSessions() {
162+
// Setup first session.
163+
aqsStore.rotateSessionId(SESSION_ID);
164+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
165+
166+
// Simulate failing to close the first session by not closing it.
167+
168+
// Update the aqs session id while Crashlytics session failed to closed.
169+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
170+
171+
// Start a new Crashlytics session.
172+
aqsStore.rotateSessionId(NEW_SESSION_ID);
173+
174+
// Verify the old session has the new aqs id since it failed to close.
175+
assertThat(readAqsSessionIdFile(fileStore, SESSION_ID)).isEqualTo(NEW_APP_QUALITY_SESSION_ID);
176+
177+
// Verify the new session has the updated aqs id.
178+
assertThat(readAqsSessionIdFile(fileStore, NEW_SESSION_ID))
179+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
180+
}
181+
182+
public void testGetAppQualitySessionId_manyAqsIdRotations_returnsLatestAqsIdPerSession() {
183+
// Open the first Crashlytics session.
184+
aqsStore.rotateSessionId(SESSION_ID);
185+
186+
// Rotate the aqs id several times.
187+
aqsStore.rotateAppQualitySessionId("aqs id 1");
188+
aqsStore.rotateAppQualitySessionId("aqs id 2");
189+
aqsStore.rotateAppQualitySessionId("aqs id 3");
190+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
191+
192+
// Open a new Crashlytics session.
193+
aqsStore.rotateSessionId(NEW_SESSION_ID);
194+
195+
// Rotate the aqs id several times.
196+
aqsStore.rotateAppQualitySessionId("new aqs id 1");
197+
aqsStore.rotateAppQualitySessionId("new aqs id 2");
198+
aqsStore.rotateAppQualitySessionId("new aqs id 3");
199+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
200+
201+
// Verify the latest aqs id per session is returned.
202+
assertThat(aqsStore.getAppQualitySessionId(SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
203+
assertThat(aqsStore.getAppQualitySessionId(NEW_SESSION_ID))
204+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
205+
}
206+
207+
public void testGetAppQualitySessionId_manySessionIdRotations_returnsProperAqsIdForEachSession() {
208+
// Rotate the aqs id with no Crashlytics session.
209+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
210+
211+
// Rotate the Crashlytics id several times.
212+
aqsStore.rotateSessionId("session id 1");
213+
aqsStore.rotateSessionId("session id 2");
214+
aqsStore.rotateSessionId("session id 3");
215+
aqsStore.rotateSessionId(SESSION_ID);
216+
217+
// Rotate the aqs session id.
218+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
219+
220+
// Update the aqs id several times.
221+
aqsStore.rotateSessionId("new session id 1");
222+
aqsStore.rotateSessionId("new session id 2");
223+
aqsStore.rotateSessionId("new session id 3");
224+
aqsStore.rotateSessionId(NEW_SESSION_ID);
225+
226+
// Verify the correct aqs id for each session is returned.
227+
assertThat(aqsStore.getAppQualitySessionId(SESSION_ID)).isEqualTo(NEW_APP_QUALITY_SESSION_ID);
228+
assertThat(aqsStore.getAppQualitySessionId(NEW_SESSION_ID))
229+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
230+
}
231+
232+
public void testGetAppQualitySessionId_afterRelaunch_returnsPersistedAqsId() {
233+
// Setup first session.
234+
aqsStore.rotateSessionId(SESSION_ID);
235+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
236+
237+
// Rotate the aqs id during the Crashlytics session
238+
aqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
239+
240+
// Simulate a native crash and relaunch by making a new aqs store instance.
241+
CrashlyticsAppQualitySessionsStore newAqsStore =
242+
new CrashlyticsAppQualitySessionsStore(new FileStore(getContext()));
243+
244+
assertThat(newAqsStore.getAppQualitySessionId(SESSION_ID))
245+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
246+
}
247+
248+
public void testGetAppQualitySessionId_afterRelaunch_afterRotate_returnsPersistedAqsId() {
249+
// Setup first session.
250+
aqsStore.rotateSessionId(SESSION_ID);
251+
aqsStore.rotateAppQualitySessionId(APP_QUALITY_SESSION_ID);
252+
253+
// Simulate a native crash and relaunch by making a new aqs store instance.
254+
CrashlyticsAppQualitySessionsStore newAqsStore =
255+
new CrashlyticsAppQualitySessionsStore(new FileStore(getContext()));
256+
257+
// Rotate the ids in the new launch.
258+
newAqsStore.rotateSessionId(NEW_SESSION_ID);
259+
newAqsStore.rotateAppQualitySessionId(NEW_APP_QUALITY_SESSION_ID);
260+
261+
// Verify the old session still persisted the old aqs id.
262+
assertThat(newAqsStore.getAppQualitySessionId(SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
263+
}
264+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.crashlytics.internal.common;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static org.mockito.Mockito.mock;
19+
20+
import androidx.annotation.NonNull;
21+
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
22+
import com.google.firebase.crashlytics.internal.persistence.FileStore;
23+
import com.google.firebase.sessions.api.SessionSubscriber.SessionDetails;
24+
25+
public final class CrashlyticsAppQualitySessionsSubscriberTest extends CrashlyticsTestCase {
26+
private static final String SESSION_ID = "64e61da7023800012303a14eecd3f58d";
27+
private static final String APP_QUALITY_SESSION_ID = "79fd5d2e08ef4ea9a4f378879e53af2e";
28+
private static final String NEW_SESSION_ID = "64e61da0007500012284a14eecd3f58d";
29+
private static final String NEW_APP_QUALITY_SESSION_ID = "f7196b60000a44a092b5cc9e624f551c";
30+
31+
private CrashlyticsAppQualitySessionsSubscriber aqsSubscriber;
32+
33+
@Override
34+
protected void setUp() throws Exception {
35+
// The files created by each test case will get cleaned up in super.tearDown().
36+
aqsSubscriber =
37+
new CrashlyticsAppQualitySessionsSubscriber(
38+
mock(DataCollectionArbiter.class), new FileStore(getContext()));
39+
}
40+
41+
public void testGetAppQualitySessionId_returnsLatestAqsIdForSession() {
42+
aqsSubscriber.setSessionId(SESSION_ID);
43+
44+
aqsSubscriber.onSessionChanged(createSessionDetails("aqs id 1"));
45+
aqsSubscriber.onSessionChanged(createSessionDetails("aqs id 2"));
46+
aqsSubscriber.onSessionChanged(createSessionDetails("aqs id 3"));
47+
aqsSubscriber.onSessionChanged(createSessionDetails(APP_QUALITY_SESSION_ID));
48+
49+
assertThat(aqsSubscriber.getAppQualitySessionId(SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
50+
}
51+
52+
public void testGetAppQualitySessionId_returnsCorrectAqsIdForEachSession() {
53+
String session_id_1 = "session id 1";
54+
String session_id_2 = "session id 2";
55+
String session_id_3 = "session id 3";
56+
String new_session_id_1 = "new session id 1";
57+
String new_session_id_2 = "new session id 2";
58+
String new_session_id_3 = "new session id 3";
59+
60+
aqsSubscriber.onSessionChanged(createSessionDetails(APP_QUALITY_SESSION_ID));
61+
62+
// Rotate the session id multiple times for a single aqs id.
63+
aqsSubscriber.setSessionId(session_id_1);
64+
aqsSubscriber.setSessionId(session_id_2);
65+
aqsSubscriber.setSessionId(session_id_3);
66+
aqsSubscriber.setSessionId(SESSION_ID);
67+
68+
// Close the session.
69+
aqsSubscriber.setSessionId(null);
70+
71+
// Rotate the aqs id.
72+
aqsSubscriber.onSessionChanged(createSessionDetails(NEW_APP_QUALITY_SESSION_ID));
73+
74+
// Rotate the session id multiple times again for the rotated aqs id.
75+
aqsSubscriber.setSessionId(new_session_id_1);
76+
aqsSubscriber.setSessionId(new_session_id_2);
77+
aqsSubscriber.setSessionId(new_session_id_3);
78+
aqsSubscriber.setSessionId(NEW_SESSION_ID);
79+
80+
assertThat(aqsSubscriber.getAppQualitySessionId(session_id_1))
81+
.isEqualTo(APP_QUALITY_SESSION_ID);
82+
assertThat(aqsSubscriber.getAppQualitySessionId(session_id_2))
83+
.isEqualTo(APP_QUALITY_SESSION_ID);
84+
assertThat(aqsSubscriber.getAppQualitySessionId(session_id_3))
85+
.isEqualTo(APP_QUALITY_SESSION_ID);
86+
87+
assertThat(aqsSubscriber.getAppQualitySessionId(SESSION_ID)).isEqualTo(APP_QUALITY_SESSION_ID);
88+
89+
assertThat(aqsSubscriber.getAppQualitySessionId(new_session_id_1))
90+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
91+
assertThat(aqsSubscriber.getAppQualitySessionId(new_session_id_2))
92+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
93+
assertThat(aqsSubscriber.getAppQualitySessionId(new_session_id_3))
94+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
95+
96+
assertThat(aqsSubscriber.getAppQualitySessionId(NEW_SESSION_ID))
97+
.isEqualTo(NEW_APP_QUALITY_SESSION_ID);
98+
}
99+
100+
private static SessionDetails createSessionDetails(@NonNull String appQualitySessionId) {
101+
return new SessionDetails(appQualitySessionId);
102+
}
103+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ public CrashlyticsController build() {
163163
logFileManager,
164164
sessionReportingCoordinator,
165165
nativeComponent,
166-
analyticsEventLogger);
166+
analyticsEventLogger,
167+
mock(CrashlyticsAppQualitySessionsSubscriber.class));
167168
return controller;
168169
}
169170
}

0 commit comments

Comments
 (0)