Skip to content

Commit 9d87d70

Browse files
Clean up recovery spec tests (#3085)
1 parent 4a70e17 commit 9d87d70

File tree

5 files changed

+50
-99
lines changed

5 files changed

+50
-99
lines changed

packages/firestore/test/unit/local/indexeddb_persistence.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ describe('IndexedDb: allowTabSynchronization', () => {
11441144
'clientA',
11451145
/* multiClient= */ false,
11461146
async db => {
1147-
db.injectFailures = { updateClientMetadataAndTryBecomePrimary: true };
1147+
db.injectFailures = ['updateClientMetadataAndTryBecomePrimary'];
11481148
await expect(db.start()).to.eventually.be.rejectedWith(
11491149
'Failed to obtain exclusive access to the persistence layer.'
11501150
);
@@ -1158,7 +1158,7 @@ describe('IndexedDb: allowTabSynchronization', () => {
11581158
'clientA',
11591159
/* multiClient= */ true,
11601160
async db => {
1161-
db.injectFailures = { updateClientMetadataAndTryBecomePrimary: true };
1161+
db.injectFailures = ['updateClientMetadataAndTryBecomePrimary'];
11621162
await db.start();
11631163
await db.shutdown();
11641164
}
@@ -1167,10 +1167,10 @@ describe('IndexedDb: allowTabSynchronization', () => {
11671167

11681168
it('ignores intermittent IndexedDbTransactionError during lease refresh', async () => {
11691169
await withPersistence('clientA', async (db, _, queue) => {
1170-
db.injectFailures = { updateClientMetadataAndTryBecomePrimary: true };
1170+
db.injectFailures = ['updateClientMetadataAndTryBecomePrimary'];
11711171
await queue.runDelayedOperationsEarly(TimerId.ClientMetadataRefresh);
11721172
await queue.enqueue(() => {
1173-
db.injectFailures = undefined;
1173+
db.injectFailures = [];
11741174
return db.runTransaction('check success', 'readwrite-primary', () =>
11751175
PersistencePromise.resolve()
11761176
);

packages/firestore/test/unit/specs/recovery_spec.test.ts

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import { Code } from '../../../src/util/error';
2323
import { deletedDoc, doc, filter, path } from '../../util/helpers';
2424
import { RpcError } from './spec_rpc_error';
2525

26+
// The IndexedDB action that the Watch stream uses to detect if IndexedDB access
27+
// is available again.
28+
const ASYNC_QUEUE_PROBER = 'Get last remote snapshot version';
29+
2630
describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
2731
specTest(
2832
'Write is acknowledged by primary client (with recovery)',
@@ -34,11 +38,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
3438
.client(1)
3539
.expectPrimaryState(false)
3640
.userSets('collection/a', { v: 1 })
37-
.failDatabaseTransactions({
38-
'Locally write mutations': true,
39-
'Synchronize last document change read time': true,
40-
'Lookup mutation documents': true
41-
})
41+
.failDatabaseTransactions('Lookup mutation documents')
4242
.client(0)
4343
.writeAcks('collection/a', 1, { expectUserCallback: false })
4444
.client(1)
@@ -62,24 +62,21 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
6262
['multi-client'],
6363
() => {
6464
const query = Query.atPath(path('collection'));
65+
const doc1 = doc('collection/doc', 1, { foo: 'a' });
6566

6667
return client(0)
6768
.expectPrimaryState(true)
6869
.client(1)
6970
.expectPrimaryState(false)
7071
.userListens(query)
71-
.failDatabaseTransactions({
72-
'Allocate target': true,
73-
'Lookup mutation documents': true,
74-
'Get new document changes': true
75-
})
72+
.failDatabaseTransactions('Get new document changes')
7673
.client(0)
7774
.expectListen(query)
78-
.watchAcksFull(query, 1000)
75+
.watchAcksFull(query, 1000, doc1)
7976
.client(1)
8077
.recoverDatabase()
8178
.runTimer(TimerId.AsyncQueueRetry)
82-
.expectEvents(query, {});
79+
.expectEvents(query, { added: [doc1] });
8380
}
8481
);
8582

@@ -92,10 +89,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
9289
return (
9390
client(0)
9491
.expectPrimaryState(true)
95-
.failDatabaseTransactions({
96-
'Allocate target': true,
97-
'Get target data': true
98-
})
92+
.failDatabaseTransactions('Allocate target', 'Get target data')
9993
.client(1)
10094
.userListens(query)
10195
.client(0)
@@ -105,10 +99,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
10599
.recoverDatabase()
106100
.runTimer(TimerId.AsyncQueueRetry)
107101
.expectListen(query)
108-
.failDatabaseTransactions({
109-
'Allocate target': true,
110-
'Release target': true
111-
})
102+
.failDatabaseTransactions('Release target')
112103
.client(1)
113104
.userUnlistens(query)
114105
.client(0)
@@ -130,18 +121,12 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
130121
.expectNumOutstandingWrites(1)
131122
// We fail the write if we cannot persist the local mutation (via
132123
// 'Locally write mutations').
133-
.failDatabaseTransactions({
134-
'Locally write mutations': true
135-
})
124+
.failDatabaseTransactions('Locally write mutations')
136125
.userSets('collection/key2', { bar: 'b' })
137126
.expectUserCallbacks({ rejected: ['collection/key2'] })
138127
// The write is considered successful if we can persist the local mutation
139128
// but fail to update view assignments (via 'notifyLocalViewChanges').
140-
.failDatabaseTransactions({
141-
'Locally write mutations': false,
142-
notifyLocalViewChanges: true,
143-
'Get next mutation batch': false
144-
})
129+
.failDatabaseTransactions('notifyLocalViewChanges')
145130
.userSets('collection/key3', { bar: 'b' })
146131
.recoverDatabase()
147132
.expectNumOutstandingWrites(2)
@@ -178,11 +163,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
178163
fromCache: true,
179164
hasPendingWrites: true
180165
})
181-
.failDatabaseTransactions({
182-
'Locally write mutations': true,
183-
notifyLocalViewChanges: true,
184-
'Get next mutation batch': true
185-
})
166+
.failDatabaseTransactions('Locally write mutations')
186167
.userSets('collection/key2', { foo: 'b' })
187168
.expectUserCallbacks({ rejected: ['collection/key2'] })
188169
.recoverDatabase()
@@ -213,12 +194,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
213194
const doc2 = doc('collection/key2', 2, { foo: 'b' });
214195
return spec()
215196
.userListens(query)
216-
.failDatabaseTransactions({
217-
'Locally write mutations': false,
218-
notifyLocalViewChanges: true,
219-
'Get next mutation batch': false,
220-
'Set last stream token': false
221-
})
197+
.failDatabaseTransactions('notifyLocalViewChanges')
222198
.userSets('collection/key1', { foo: 'a' })
223199
.expectEvents(query, {
224200
added: [doc1Local],
@@ -228,11 +204,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
228204
.recoverDatabase()
229205
.runTimer(TimerId.AsyncQueueRetry)
230206
.writeAcks('collection/key1', 1)
231-
.failDatabaseTransactions({
232-
'Apply remote event': false,
233-
notifyLocalViewChanges: true,
234-
'Get last remote snapshot version': false
235-
})
207+
.failDatabaseTransactions('notifyLocalViewChanges')
236208
.watchAcksFull(query, 1000, doc1, doc2)
237209
.expectEvents(query, {
238210
metadata: [doc1],
@@ -256,11 +228,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
256228
.expectEvents(query, {
257229
added: [doc1]
258230
})
259-
.failDatabaseTransactions({
260-
'Apply remote event': false,
261-
notifyLocalViewChanges: true,
262-
'Get last remote snapshot version': false
263-
})
231+
.failDatabaseTransactions('notifyLocalViewChanges')
264232
.watchSends({ removed: [query] }, deletedDoc1)
265233
.watchSnapshots(2000)
266234
.expectEvents(query, {
@@ -282,7 +250,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
282250
.userListens(query1)
283251
.watchAcksFull(query1, 1)
284252
.expectEvents(query1, {})
285-
.failDatabaseTransactions({ 'Allocate target': true })
253+
.failDatabaseTransactions('Allocate target')
286254
.userListens(query2)
287255
.expectEvents(query2, { errorCode: Code.UNAVAILABLE })
288256
.recoverDatabase()
@@ -298,7 +266,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
298266
.userListens(query1)
299267
.watchAcksFull(query1, 1)
300268
.expectEvents(query1, {})
301-
.failDatabaseTransactions({ 'Allocate target': true })
269+
.failDatabaseTransactions('Allocate target')
302270
.userListens(query2)
303271
.expectEvents(query2, { errorCode: Code.UNAVAILABLE })
304272
.recoverDatabase()
@@ -320,12 +288,9 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
320288
added: [doc1]
321289
})
322290
.watchSends({ affects: [query] }, doc2)
323-
.failDatabaseTransactions({
324-
'Get last remote snapshot version': true,
325-
'Release target': true
326-
})
291+
.failDatabaseTransactions('Get last remote snapshot version')
327292
.watchSnapshots(1500)
328-
// `failDatabase()` causes us to go offline.
293+
// `failDatabaseTransactions()` causes us to go offline.
329294
.expectActiveTargets()
330295
.expectEvents(query, { fromCache: true })
331296
.recoverDatabase()
@@ -357,15 +322,12 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
357322
.expectEvents(doc2Query, {
358323
added: [doc2]
359324
})
360-
.failDatabaseTransactions({
361-
'Get last remote snapshot version': true,
362-
'Release target': true
363-
})
325+
.failDatabaseTransactions('Release target', ASYNC_QUEUE_PROBER)
364326
.watchRemoves(
365327
doc1Query,
366328
new RpcError(Code.PERMISSION_DENIED, 'Simulated target error')
367329
)
368-
// `failDatabase()` causes us to go offline.
330+
// `failDatabaseTransactions()` causes us to go offline.
369331
.expectActiveTargets()
370332
.expectEvents(doc1Query, { fromCache: true })
371333
.expectEvents(doc2Query, { fromCache: true })
@@ -416,7 +378,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
416378
})
417379
.watchAcksFull(filteredQuery, 2000)
418380
.expectLimboDocs(doc1a.key)
419-
.failDatabaseTransactions({ 'Get last remote snapshot version': true })
381+
.failDatabaseTransactions('Get last remote snapshot version')
420382
.watchAcksFull(limboQuery, 3000, doc1b)
421383
.expectActiveTargets()
422384
.recoverDatabase()
@@ -459,10 +421,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
459421
})
460422
.watchAcksFull(filteredQuery, 2000)
461423
.expectLimboDocs(doc1.key)
462-
.failDatabaseTransactions({
463-
'Apply remote event': true,
464-
'Get last remote snapshot version': true
465-
})
424+
.failDatabaseTransactions('Apply remote event', ASYNC_QUEUE_PROBER)
466425
.watchRemoves(
467426
limboQuery,
468427
new RpcError(Code.PERMISSION_DENIED, 'Test error')

packages/firestore/test/unit/specs/spec_builder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ import { RpcError } from './spec_rpc_error';
4343
import { ObjectMap } from '../../../src/util/obj_map';
4444
import {
4545
parseQuery,
46+
PersistenceAction,
4647
runSpec,
4748
SpecConfig,
48-
SpecDatabaseFailures,
4949
SpecDocument,
5050
SpecQuery,
5151
SpecQueryFilter,
@@ -440,11 +440,11 @@ export class SpecBuilder {
440440
* Fails the specified database transaction until `recoverDatabase()` is
441441
* called.
442442
*/
443-
failDatabaseTransactions(failureMode: SpecDatabaseFailures): this {
443+
failDatabaseTransactions(...actions: PersistenceAction[]): this {
444444
this.nextStep();
445445
this.injectFailures = true;
446446
this.currentStep = {
447-
failDatabase: failureMode
447+
failDatabase: actions
448448
};
449449
return this;
450450
}

packages/firestore/test/unit/specs/spec_test_components.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
MemoryPersistence
3737
} from '../../../src/local/memory_persistence';
3838
import { LruParams } from '../../../src/local/lru_garbage_collector';
39-
import { PersistenceAction, SpecDatabaseFailures } from './spec_test_runner';
39+
import { PersistenceAction } from './spec_test_runner';
4040
import { Connection, Stream } from '../../../src/remote/connection';
4141
import { StreamBridge } from '../../../src/remote/stream_bridge';
4242
import * as api from '../../../src/protos/firestore_proto_api';
@@ -57,7 +57,7 @@ import { expect } from 'chai';
5757
* transaction failures.
5858
*/
5959
export class MockMemoryPersistence extends MemoryPersistence {
60-
injectFailures?: SpecDatabaseFailures;
60+
injectFailures: PersistenceAction[] = [];
6161

6262
async runTransaction<T>(
6363
action: string,
@@ -76,7 +76,7 @@ export class MockMemoryPersistence extends MemoryPersistence {
7676
* transaction failures.
7777
*/
7878
export class MockIndexedDbPersistence extends IndexedDbPersistence {
79-
injectFailures?: SpecDatabaseFailures;
79+
injectFailures: PersistenceAction[] = [];
8080

8181
async runTransaction<T>(
8282
action: string,
@@ -95,18 +95,15 @@ export class MockIndexedDbPersistence extends IndexedDbPersistence {
9595
* MockMemoryPersistence that can inject transaction failures.
9696
*/
9797
function failTransactionIfNeeded(
98-
config: SpecDatabaseFailures | undefined,
99-
action: string
98+
failActions: PersistenceAction[],
99+
actionName: string
100100
): void {
101-
if (config) {
102-
const shouldFail = config[action as PersistenceAction];
103-
if (shouldFail === undefined) {
104-
throw fail('Failure mode not specified for action: ' + action);
105-
} else if (shouldFail) {
106-
throw new IndexedDbTransactionError(
107-
new Error('Simulated retryable error: ' + action)
108-
);
109-
}
101+
const shouldFail =
102+
failActions.indexOf(actionName as PersistenceAction) !== -1;
103+
if (shouldFail) {
104+
throw new IndexedDbTransactionError(
105+
new Error('Simulated retryable error: ' + actionName)
106+
);
110107
}
111108
}
112109

packages/firestore/test/unit/specs/spec_test_runner.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ abstract class TestRunner {
371371
await this.queue.enqueue(() => this.eventManager.listen(queryListener));
372372

373373
if (targetFailed) {
374-
expect(this.persistence.injectFailures?.['Allocate target']).to.be.true;
374+
expect(this.persistence.injectFailures).contains('Allocate target');
375375
} else {
376376
// Skip the backoff that may have been triggered by a previous call to
377377
// `watchStreamCloses()`.
@@ -446,7 +446,9 @@ abstract class TestRunner {
446446
() => this.rejectedDocs.push(...documentKeys)
447447
);
448448

449-
if (this.persistence.injectFailures?.['Locally write mutations'] !== true) {
449+
if (
450+
this.persistence.injectFailures.indexOf('Locally write mutations') === -1
451+
) {
450452
this.sharedWrites.push(mutations);
451453
}
452454

@@ -726,13 +728,13 @@ abstract class TestRunner {
726728
}
727729

728730
private async doFailDatabase(
729-
failActions: SpecDatabaseFailures
731+
failActions: PersistenceAction[]
730732
): Promise<void> {
731733
this.persistence.injectFailures = failActions;
732734
}
733735

734736
private async doRecoverDatabase(): Promise<void> {
735-
this.persistence.injectFailures = undefined;
737+
this.persistence.injectFailures = [];
736738
}
737739

738740
private validateExpectedSnapshotEvents(
@@ -1225,13 +1227,6 @@ export type PersistenceAction =
12251227
| 'Synchronize last document change read time'
12261228
| 'updateClientMetadataAndTryBecomePrimary';
12271229

1228-
/** Specifies failure or success for a list of database actions. */
1229-
export type SpecDatabaseFailures = Partial<
1230-
{
1231-
readonly [key in PersistenceAction]: boolean;
1232-
}
1233-
>;
1234-
12351230
/**
12361231
* Union type for each step. The step consists of exactly one `field`
12371232
* set and optionally expected events in the `expect` field.
@@ -1277,7 +1272,7 @@ export interface SpecStep {
12771272
failWrite?: SpecWriteFailure;
12781273

12791274
/** Fails the listed database actions. */
1280-
failDatabase?: false | SpecDatabaseFailures;
1275+
failDatabase?: false | PersistenceAction[];
12811276

12821277
/**
12831278
* Run a queued timer task (without waiting for the delay to expire). See

0 commit comments

Comments
 (0)