Skip to content

Commit e1744b0

Browse files
authored
Merge pull request #646 from callstack-internal/bugfix/reassure-flakiness
[NoQA] Investigating Reassure flakiness
2 parents b58e02f + d43972e commit e1744b0

5 files changed

Lines changed: 66 additions & 21 deletions

File tree

.github/workflows/reassurePerfTests.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
# Note: We run baseline and delta performance checks in the same runner to reduce hardware variance across machines
1111
perf-tests:
1212
if: ${{ github.actor != 'OSBotify' }}
13-
runs-on: ubuntu-latest
13+
runs-on: ubuntu-24.04-v4
1414
steps:
1515
# v4
1616
- name: Checkout
@@ -28,7 +28,7 @@ jobs:
2828
run: npm install
2929

3030
- name: Run Reassure baseline tests
31-
run: npx reassure --baseline
31+
run: npx reassure --baseline --verbose
3232

3333
- name: Checkout PR head SHA
3434
run: |
@@ -39,7 +39,15 @@ jobs:
3939
run: npm install --force
4040

4141
- name: Run Reassure delta tests
42-
run: npx reassure --branch
42+
run: npx reassure --branch --verbose
43+
44+
- name: Upload Reassure results
45+
uses: actions/upload-artifact@v4
46+
with:
47+
name: results
48+
path: .reassure/
49+
if-no-files-found: ignore
50+
include-hidden-files: true
4351

4452
- name: Validate output.json
4553
id: validateReassureOutput

jest-sequencer.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const Sequencer = require('@jest/test-sequencer').default;
2+
3+
/**
4+
* Makes all unit tests run ordered by their path, reducing flakiness on Reassure.
5+
*/
6+
class TestSequencer extends Sequencer {
7+
shard(tests, {shardIndex, shardCount}) {
8+
const shardSize = Math.ceil(tests.length / shardCount);
9+
const shardStart = shardSize * (shardIndex - 1);
10+
const shardEnd = shardSize * shardIndex;
11+
12+
return [...tests].sort((a, b) => (a.path > b.path ? 1 : -1)).slice(shardStart, shardEnd);
13+
}
14+
15+
sort(tests) {
16+
const copyTests = [...tests];
17+
return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1));
18+
}
19+
}
20+
21+
module.exports = TestSequencer;

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ module.exports = {
1414
setupFilesAfterEnv: ['./jestSetup.js'],
1515
testTimeout: 60000,
1616
transformIgnorePatterns: ['node_modules/(?!((@)?react-native|@ngneat/falso|uuid)/)'],
17+
testSequencer: './jest-sequencer.js',
1718
};

lib/OnyxUtils.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ const METHOD = {
4949
type OnyxMethod = ValueOf<typeof METHOD>;
5050

5151
// Key/value store of Onyx key and arrays of values to merge
52-
const mergeQueue: Record<OnyxKey, Array<OnyxValue<OnyxKey>>> = {};
53-
const mergeQueuePromise: Record<OnyxKey, Promise<void>> = {};
52+
let mergeQueue: Record<OnyxKey, Array<OnyxValue<OnyxKey>>> = {};
53+
let mergeQueuePromise: Record<OnyxKey, Promise<void>> = {};
5454

5555
// Holds a mapping of all the React components that want their state subscribed to a store key
56-
const callbackToStateMapping: Record<string, Mapping<OnyxKey>> = {};
56+
let callbackToStateMapping: Record<string, Mapping<OnyxKey>> = {};
5757

5858
// Keeps a copy of the values of the onyx collection keys as a map for faster lookups
5959
let onyxCollectionKeySet = new Set<OnyxKey>();
6060

6161
// Holds a mapping of the connected key to the subscriptionID for faster lookups
62-
const onyxKeyToSubscriptionIDs = new Map();
62+
let onyxKeyToSubscriptionIDs = new Map();
6363

6464
// Optional user-provided key value states set when Onyx initializes or clears
6565
let defaultKeyStates: Record<OnyxKey, OnyxValue<OnyxKey>> = {};
@@ -68,7 +68,7 @@ let batchUpdatesPromise: Promise<void> | null = null;
6868
let batchUpdatesQueue: Array<() => void> = [];
6969

7070
// Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data.
71-
const lastConnectionCallbackData = new Map<number, OnyxValue<OnyxKey>>();
71+
let lastConnectionCallbackData = new Map<number, OnyxValue<OnyxKey>>();
7272

7373
let snapshotKey: OnyxKey | null = null;
7474

@@ -1450,6 +1450,18 @@ function updateSnapshots(data: OnyxUpdate[], mergeFn: typeof Onyx.merge): Array<
14501450
return promises;
14511451
}
14521452

1453+
/**
1454+
* Clear internal variables used in this file, useful in test environments.
1455+
*/
1456+
function clearOnyxUtilsInternals() {
1457+
mergeQueue = {};
1458+
mergeQueuePromise = {};
1459+
callbackToStateMapping = {};
1460+
onyxKeyToSubscriptionIDs = new Map();
1461+
batchUpdatesQueue = [];
1462+
lastConnectionCallbackData = new Map();
1463+
}
1464+
14531465
const OnyxUtils = {
14541466
METHOD,
14551467
getMergeQueue,
@@ -1551,3 +1563,4 @@ GlobalSettings.addGlobalSettingsChangeListener(({enablePerformanceMetrics}) => {
15511563

15521564
export type {OnyxMethod};
15531565
export default OnyxUtils;
1566+
export {clearOnyxUtilsInternals};

tests/perf-test/OnyxUtils.perf-test.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {OnyxKey, Selector} from '../../lib';
44
import Onyx from '../../lib';
55
import StorageMock from '../../lib/storage';
66
import OnyxCache from '../../lib/OnyxCache';
7-
import OnyxUtils from '../../lib/OnyxUtils';
7+
import OnyxUtils, {clearOnyxUtilsInternals} from '../../lib/OnyxUtils';
88
import type GenericCollection from '../utils/GenericCollection';
99
import type {Mapping, OnyxUpdate} from '../../lib/Onyx';
1010
import createDeferredTask from '../../lib/createDeferredTask';
@@ -44,6 +44,7 @@ const mockedReportActionsMap = getRandomReportActions(collectionKey);
4444
const mockedReportActionsKeys = Object.keys(mockedReportActionsMap);
4545

4646
const clearOnyxAfterEachMeasure = async () => {
47+
clearOnyxUtilsInternals();
4748
await Onyx.clear();
4849
};
4950

@@ -59,6 +60,7 @@ describe('OnyxUtils', () => {
5960
});
6061

6162
afterEach(async () => {
63+
clearOnyxUtilsInternals();
6264
await Onyx.clear();
6365
});
6466

@@ -321,14 +323,14 @@ describe('OnyxUtils', () => {
321323
});
322324
},
323325
afterEach: async () => {
324-
await clearOnyxAfterEachMeasure();
325326
mockedReportActionsKeys.forEach((key) => {
326327
const id = subscriptionMap.get(key);
327328
if (id) {
328329
OnyxUtils.unsubscribeFromKey(id);
329330
}
330331
});
331332
subscriptionMap.clear();
333+
await clearOnyxAfterEachMeasure();
332334
},
333335
});
334336
});
@@ -351,11 +353,11 @@ describe('OnyxUtils', () => {
351353
}
352354
},
353355
afterEach: async () => {
354-
await clearOnyxAfterEachMeasure();
355356
subscriptionIDs.forEach((id) => {
356357
OnyxUtils.unsubscribeFromKey(id);
357358
});
358359
subscriptionIDs.clear();
360+
await clearOnyxAfterEachMeasure();
359361
},
360362
});
361363
});
@@ -384,10 +386,10 @@ describe('OnyxUtils', () => {
384386
subscriptionID = OnyxUtils.subscribeToKey({key: collectionKey, callback: jest.fn(), initWithStoredValues: false});
385387
},
386388
afterEach: async () => {
387-
await clearOnyxAfterEachMeasure();
388389
if (subscriptionID) {
389390
OnyxUtils.unsubscribeFromKey(subscriptionID);
390391
}
392+
await clearOnyxAfterEachMeasure();
391393
},
392394
},
393395
);
@@ -417,10 +419,10 @@ describe('OnyxUtils', () => {
417419
subscriptionID = OnyxUtils.subscribeToKey({key: collectionKey, callback: jest.fn(), initWithStoredValues: false});
418420
},
419421
afterEach: async () => {
420-
await clearOnyxAfterEachMeasure();
421422
if (subscriptionID) {
422423
OnyxUtils.unsubscribeFromKey(subscriptionID);
423424
}
425+
await clearOnyxAfterEachMeasure();
424426
},
425427
},
426428
);
@@ -466,10 +468,10 @@ describe('OnyxUtils', () => {
466468
await Onyx.multiSet(mockedReportActionsMap);
467469
},
468470
afterEach: async () => {
469-
await clearOnyxAfterEachMeasure();
470471
if (subscriptionID) {
471472
OnyxUtils.unsubscribeFromKey(subscriptionID);
472473
}
474+
await clearOnyxAfterEachMeasure();
473475
},
474476
},
475477
);
@@ -495,14 +497,14 @@ describe('OnyxUtils', () => {
495497
});
496498
},
497499
afterEach: async () => {
498-
await clearOnyxAfterEachMeasure();
499500
mockedReportActionsKeys.forEach((key) => {
500501
const id = subscriptionMap.get(key);
501502
if (id) {
502503
OnyxUtils.unsubscribeFromKey(id);
503504
}
504505
});
505506
subscriptionMap.clear();
507+
await clearOnyxAfterEachMeasure();
506508
},
507509
},
508510
);
@@ -526,14 +528,14 @@ describe('OnyxUtils', () => {
526528
});
527529
},
528530
afterEach: async () => {
529-
await clearOnyxAfterEachMeasure();
530531
mockedReportActionsKeys.forEach((key) => {
531532
const id = subscriptionMap.get(key);
532533
if (id) {
533534
OnyxUtils.unsubscribeFromKey(id);
534535
}
535536
});
536537
subscriptionMap.clear();
538+
await clearOnyxAfterEachMeasure();
537539
},
538540
});
539541
});
@@ -726,8 +728,8 @@ describe('OnyxUtils', () => {
726728
await StorageMock.multiSet(Object.entries(mockedReportActionsMap).map(([k, v]) => [k, v]));
727729
},
728730
afterEach: async () => {
729-
await clearOnyxAfterEachMeasure();
730731
OnyxUtils.unsubscribeFromKey(subscriptionID);
732+
await clearOnyxAfterEachMeasure();
731733
},
732734
},
733735
);
@@ -753,8 +755,8 @@ describe('OnyxUtils', () => {
753755
await StorageMock.multiSet(Object.entries(mockedReportActionsMap).map(([k, v]) => [k, v]));
754756
},
755757
afterEach: async () => {
756-
await clearOnyxAfterEachMeasure();
757758
OnyxUtils.unsubscribeFromKey(subscriptionID);
759+
await clearOnyxAfterEachMeasure();
758760
},
759761
},
760762
);
@@ -822,9 +824,9 @@ describe('OnyxUtils', () => {
822824
});
823825
},
824826
afterEach: async () => {
825-
await clearOnyxAfterEachMeasure();
826827
OnyxUtils.deleteKeyBySubscriptions(subscriptionID);
827828
OnyxUtils.unsubscribeFromKey(subscriptionID);
829+
await clearOnyxAfterEachMeasure();
828830
},
829831
});
830832
});
@@ -844,8 +846,8 @@ describe('OnyxUtils', () => {
844846
OnyxUtils.storeKeyBySubscriptions(key, subscriptionID);
845847
},
846848
afterEach: async () => {
847-
await clearOnyxAfterEachMeasure();
848849
OnyxUtils.unsubscribeFromKey(subscriptionID);
850+
await clearOnyxAfterEachMeasure();
849851
},
850852
});
851853
});
@@ -865,8 +867,8 @@ describe('OnyxUtils', () => {
865867
}),
866868
{
867869
afterEach: async () => {
868-
await clearOnyxAfterEachMeasure();
869870
OnyxCache.removeLastAccessedKey(key);
871+
await clearOnyxAfterEachMeasure();
870872
},
871873
},
872874
);

0 commit comments

Comments
 (0)