Skip to content

Commit a301b0d

Browse files
committed
react-native: fix breadcrumb file rotation never triggering on exact chunk boundaries
1 parent 9c33420 commit a301b0d

3 files changed

Lines changed: 58 additions & 10 deletions

File tree

‎packages/react-native/src/storage/combinedChunkSplitter.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export function combinedChunkSplitter<W extends Chunk>(
2121
for (const splitter of splitters) {
2222
const [c1, c2] = splitter(chunk);
2323
chunk = c1;
24-
if (c2) {
24+
// An empty second chunk still means "split here"; dropping it loses the split.
25+
if (c2 !== undefined) {
2526
// Prepend second chunk to the rest
2627
rest.unshift(c2);
2728
}

‎packages/react-native/tests/nativeBreadcrumbPropagationTests.spec.ts‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ function breadcrumbPathsSentToNative(): string[] {
5353
.filter((p: string) => p.includes('breadcrumb'));
5454
}
5555

56-
function attributesSentToNative(): Record<string, string> {
57-
return Object.assign({}, ...nativeMock.useAttributes.mock.calls.map((call) => call[0]));
56+
async function settle() {
57+
for (let i = 0; i < 10; i++) {
58+
await nextTick();
59+
}
5860
}
5961

6062
describe('BacktraceClient native breadcrumb propagation', () => {
@@ -67,21 +69,24 @@ describe('BacktraceClient native breadcrumb propagation', () => {
6769
it('Should tell the native crash reporter about the breadcrumb files created after rotation', async () => {
6870
const client = createClient();
6971
client.initialize();
70-
71-
const pathsAtInit = nativeMock.initialize.mock.calls[0][3].filter((p: string) => p.includes('breadcrumb'));
7272
nativeMock.useAttachments.mockClear();
7373

7474
for (let i = 0; i < 20; i++) {
7575
client.breadcrumbs?.info(`breadcrumb-${i}`);
7676
await nextTick();
7777
}
78+
await settle();
7879

7980
const sentLater = breadcrumbPathsSentToNative();
80-
expect(sentLater.length).toBeGreaterThan(0);
81-
expect(sentLater.some((p) => !pathsAtInit.includes(p))).toBe(true);
81+
expect(sentLater.some((p) => /bt-breadcrumbs-[1-9]/.test(p))).toBe(true);
82+
83+
const calls = nativeMock.useAttachments.mock.calls;
84+
const lastPaths = calls[calls.length - 1][0].filter((p: string) => p.includes('breadcrumb'));
85+
expect(lastPaths.length).toBeGreaterThan(0);
86+
expect(lastPaths.some((p: string) => p.includes('bt-breadcrumbs-0_'))).toBe(false);
8287
});
8388

84-
it('Should send the last breadcrumb id to the native crash reporter', async () => {
89+
it('Should send a fresh last breadcrumb id to the native crash reporter on every rotation', async () => {
8590
const client = createClient();
8691
client.initialize();
8792
nativeMock.useAttributes.mockClear();
@@ -90,7 +95,13 @@ describe('BacktraceClient native breadcrumb propagation', () => {
9095
client.breadcrumbs?.info(`breadcrumb-${i}`);
9196
await nextTick();
9297
}
93-
94-
expect(attributesSentToNative()['breadcrumbs.lastId']).toBeDefined();
98+
await settle();
99+
100+
const pushed = nativeMock.useAttributes.mock.calls
101+
.map((call) => call[0]['breadcrumbs.lastId'])
102+
.filter((value) => value !== undefined)
103+
.map(Number);
104+
expect(pushed.length).toBeGreaterThan(1);
105+
expect(pushed[pushed.length - 1]).toBeGreaterThan(pushed[0]);
95106
});
96107
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { combinedChunkSplitter } from '../../src/storage/combinedChunkSplitter';
2+
import { lengthChunkSplitter } from '../../src/storage/lengthChunkSplitter';
3+
import { lineChunkSplitter } from '../../src/storage/lineChunkSplitter';
4+
5+
const join = (chunks: string[]) => chunks.join('');
6+
7+
describe('combinedChunkSplitter', () => {
8+
it('should split when any splitter splits', () => {
9+
const splitter = combinedChunkSplitter(join, lengthChunkSplitter(4), lineChunkSplitter(10));
10+
11+
const [c1, c2] = splitter('abcdefgh');
12+
13+
expect(c1).toEqual('abcd');
14+
expect(c2).toEqual('efgh');
15+
});
16+
17+
it('should not split when no splitter splits', () => {
18+
const splitter = combinedChunkSplitter(join, lineChunkSplitter(10), lengthChunkSplitter(1000));
19+
20+
const [c1, c2] = splitter('line-1\n');
21+
22+
expect(c1).toEqual('line-1\n');
23+
expect(c2).toBeUndefined();
24+
});
25+
26+
it('should keep the split when the second chunk is empty', () => {
27+
const splitter = combinedChunkSplitter(join, lineChunkSplitter(2), lengthChunkSplitter(1000, 'skip'));
28+
29+
splitter('line-1\n');
30+
const [c1, c2] = splitter('line-2\n');
31+
32+
expect(c1).toEqual('line-2\n');
33+
expect(c2).toBeDefined();
34+
expect(c2?.length).toEqual(0);
35+
});
36+
});

0 commit comments

Comments
 (0)