Skip to content

Commit 2d869e5

Browse files
Allow failing of individual transactions
1 parent a57dac5 commit 2d869e5

File tree

4 files changed

+115
-34
lines changed

4 files changed

+115
-34
lines changed

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

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
3434
.client(1)
3535
.expectPrimaryState(false)
3636
.userSets('collection/a', { v: 1 })
37-
.failDatabase()
37+
.failDatabaseTransactions({
38+
'Locally write mutations': true,
39+
'Synchronize last document change read time': true,
40+
'Lookup mutation documents': true
41+
})
3842
.client(0)
3943
.writeAcks('collection/a', 1, { expectUserCallback: false })
4044
.client(1)
@@ -64,7 +68,11 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
6468
.client(1)
6569
.expectPrimaryState(false)
6670
.userListens(query)
67-
.failDatabase()
71+
.failDatabaseTransactions({
72+
'Allocate target': true,
73+
'Lookup mutation documents': true,
74+
'Get new document changes': true
75+
})
6876
.client(0)
6977
.expectListen(query)
7078
.watchAcksFull(query, 1000)
@@ -84,7 +92,10 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
8492
return (
8593
client(0)
8694
.expectPrimaryState(true)
87-
.failDatabase()
95+
.failDatabaseTransactions({
96+
'Allocate target': true,
97+
'Get target data': true
98+
})
8899
.client(1)
89100
.userListens(query)
90101
.client(0)
@@ -94,7 +105,10 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
94105
.recoverDatabase()
95106
.runTimer(TimerId.AsyncQueueRetry)
96107
.expectListen(query)
97-
.failDatabase()
108+
.failDatabaseTransactions({
109+
'Allocate target': true,
110+
'Release target': true
111+
})
98112
.client(1)
99113
.userUnlistens(query)
100114
.client(0)
@@ -113,7 +127,12 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
113127
return spec()
114128
.userSets('collection/key1', { foo: 'a' })
115129
.expectNumOutstandingWrites(1)
116-
.failDatabase()
130+
.failDatabaseTransactions({
131+
'Locally write mutations': true,
132+
notifyLocalViewChanges: true,
133+
'Get next mutation batch': true,
134+
'Get last stream token': true
135+
})
117136
.userSets('collection/key2', { bar: 'b' })
118137
.expectUserCallbacks({ rejected: ['collection/key2'] })
119138
.recoverDatabase()
@@ -149,7 +168,11 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
149168
fromCache: true,
150169
hasPendingWrites: true
151170
})
152-
.failDatabase()
171+
.failDatabaseTransactions({
172+
'Locally write mutations': true,
173+
notifyLocalViewChanges: true,
174+
'Get next mutation batch': true
175+
})
153176
.userSets('collection/key2', { foo: 'b' })
154177
.expectUserCallbacks({ rejected: ['collection/key2'] })
155178
.recoverDatabase()
@@ -173,7 +196,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
173196
.userListens(query1)
174197
.watchAcksFull(query1, 1)
175198
.expectEvents(query1, {})
176-
.failDatabase()
199+
.failDatabaseTransactions({ 'Allocate target': true })
177200
.userListens(query2)
178201
.expectEvents(query2, { errorCode: Code.UNAVAILABLE })
179202
.recoverDatabase()
@@ -189,7 +212,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
189212
.userListens(query1)
190213
.watchAcksFull(query1, 1)
191214
.expectEvents(query1, {})
192-
.failDatabase()
215+
.failDatabaseTransactions({ 'Allocate target': true })
193216
.userListens(query2)
194217
.expectEvents(query2, { errorCode: Code.UNAVAILABLE })
195218
.recoverDatabase()
@@ -213,7 +236,10 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
213236
added: [doc1]
214237
})
215238
.watchSends({ affects: [query] }, doc2)
216-
.failDatabase()
239+
.failDatabaseTransactions({
240+
'Get last remote snapshot version': true,
241+
'Release target': true
242+
})
217243
.watchSnapshots(1500)
218244
// `failDatabase()` causes us to go offline.
219245
.expectActiveTargets()
@@ -250,7 +276,10 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
250276
.expectEvents(doc2Query, {
251277
added: [doc2]
252278
})
253-
.failDatabase()
279+
.failDatabaseTransactions({
280+
'Get last remote snapshot version': true,
281+
'Release target': true
282+
})
254283
.watchRemoves(
255284
doc1Query,
256285
new RpcError(Code.PERMISSION_DENIED, 'Simulated target error')

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
parseQuery,
4646
runSpec,
4747
SpecConfig,
48+
SpecDatabaseFailures,
4849
SpecDocument,
4950
SpecQuery,
5051
SpecQueryFilter,
@@ -435,12 +436,15 @@ export class SpecBuilder {
435436
return this;
436437
}
437438

438-
/** Fails all database operations until `recoverDatabase()` is called. */
439-
failDatabase(): this {
439+
/**
440+
* Fails the specified database transaction until `recoverDatabase()` is
441+
* called.
442+
*/
443+
failDatabaseTransactions(failureMode: SpecDatabaseFailures): this {
440444
this.nextStep();
441445
this.injectFailures = true;
442446
this.currentStep = {
443-
failDatabase: true
447+
failDatabase: failureMode
444448
};
445449
return this;
446450
}

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +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';
3940
import { Connection, Stream } from '../../../src/remote/connection';
4041
import { StreamBridge } from '../../../src/remote/stream_bridge';
4142
import * as api from '../../../src/protos/firestore_proto_api';
@@ -56,7 +57,7 @@ import { expect } from 'chai';
5657
* transaction failures.
5758
*/
5859
export class MockMemoryPersistence extends MemoryPersistence {
59-
injectFailures = false;
60+
injectFailures?: SpecDatabaseFailures;
6061

6162
runTransaction<T>(
6263
action: string,
@@ -66,12 +67,17 @@ export class MockMemoryPersistence extends MemoryPersistence {
6667
) => PersistencePromise<T>
6768
): Promise<T> {
6869
if (this.injectFailures) {
69-
return Promise.reject(
70-
new IndexedDbTransactionError(new Error('Simulated retryable error'))
71-
);
72-
} else {
73-
return super.runTransaction(action, mode, transactionOperation);
70+
if (this.injectFailures[action as PersistenceAction] === undefined) {
71+
throw fail('Failure mode not specified for action: ' + action);
72+
}
73+
if (this.injectFailures[action as PersistenceAction]) {
74+
return Promise.reject(
75+
new IndexedDbTransactionError(new Error('Simulated retryable error'))
76+
);
77+
}
7478
}
79+
80+
return super.runTransaction(action, mode, transactionOperation);
7581
}
7682
}
7783

@@ -80,7 +86,7 @@ export class MockMemoryPersistence extends MemoryPersistence {
8086
* transaction failures.
8187
*/
8288
export class MockIndexedDbPersistence extends IndexedDbPersistence {
83-
injectFailures = false;
89+
injectFailures?: SpecDatabaseFailures;
8490

8591
runTransaction<T>(
8692
action: string,
@@ -90,12 +96,16 @@ export class MockIndexedDbPersistence extends IndexedDbPersistence {
9096
) => PersistencePromise<T>
9197
): Promise<T> {
9298
if (this.injectFailures) {
93-
return Promise.reject(
94-
new IndexedDbTransactionError(new Error('Simulated retryable error'))
95-
);
96-
} else {
97-
return super.runTransaction(action, mode, transactionOperation);
99+
if (this.injectFailures[action as PersistenceAction] === undefined) {
100+
throw fail('Failure mode not specified for action: ' + action);
101+
} else if (this.injectFailures[action as PersistenceAction]) {
102+
return Promise.reject(
103+
new IndexedDbTransactionError(new Error('Simulated retryable error'))
104+
);
105+
}
98106
}
107+
108+
return super.runTransaction(action, mode, transactionOperation);
99109
}
100110
}
101111

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

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,8 @@ abstract class TestRunner {
341341
} else if ('changeUser' in step) {
342342
return this.doChangeUser(step.changeUser!);
343343
} else if ('failDatabase' in step) {
344-
return step.failDatabase!
345-
? this.doFailDatabase()
344+
return step.failDatabase
345+
? this.doFailDatabase(step.failDatabase!)
346346
: this.doRecoverDatabase();
347347
} else {
348348
return fail('Unknown step: ' + JSON.stringify(step));
@@ -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).to.be.true;
374+
expect(this.persistence.injectFailures?.['Allocate target']).to.be.true;
375375
} else {
376376
// Skip the backoff that may have been triggered by a previous call to
377377
// `watchStreamCloses()`.
@@ -446,7 +446,7 @@ abstract class TestRunner {
446446
() => this.rejectedDocs.push(...documentKeys)
447447
);
448448

449-
if (!this.persistence.injectFailures) {
449+
if (this.persistence.injectFailures?.['Locally write mutations'] !== true) {
450450
this.sharedWrites.push(mutations);
451451
}
452452

@@ -717,12 +717,14 @@ abstract class TestRunner {
717717
);
718718
}
719719

720-
private async doFailDatabase(): Promise<void> {
721-
this.persistence.injectFailures = true;
720+
private async doFailDatabase(
721+
failActions: SpecDatabaseFailures
722+
): Promise<void> {
723+
this.persistence.injectFailures = failActions;
722724
}
723725

724726
private async doRecoverDatabase(): Promise<void> {
725-
this.persistence.injectFailures = false;
727+
this.persistence.injectFailures = undefined;
726728
}
727729

728730
private validateExpectedSnapshotEvents(
@@ -1181,6 +1183,42 @@ export interface SpecConfig {
11811183
maxConcurrentLimboResolutions?: number;
11821184
}
11831185

1186+
/**
1187+
* The cumulative list of actions run against Persistence. This is used by the
1188+
* Spec tests to fail specific types of actions.
1189+
*/
1190+
export type PersistenceAction =
1191+
| 'Get next mutation batch'
1192+
| 'read document'
1193+
| 'Allocate target'
1194+
| 'Release target'
1195+
| 'Execute query'
1196+
| 'Handle user change'
1197+
| 'Locally write mutations'
1198+
| 'Acknowledge batch'
1199+
| 'Reject batch'
1200+
| 'Get highest unacknowledged batch id'
1201+
| 'Get last stream token'
1202+
| 'Set last stream token'
1203+
| 'Get last remote snapshot version'
1204+
| 'Set last remote snapshot version'
1205+
| 'Apply remote event'
1206+
| 'notifyLocalViewChanges'
1207+
| 'Remote document keys'
1208+
| 'Collect garbage'
1209+
| 'maybeGarbageCollectMultiClientState'
1210+
| 'Lookup mutation documents'
1211+
| 'Get target data'
1212+
| 'Get new document changes'
1213+
| 'Synchronize last document change read time';
1214+
1215+
/** Specifies failure or success for a list of database actions. */
1216+
export type SpecDatabaseFailures = Partial<
1217+
{
1218+
readonly [key in PersistenceAction]: boolean;
1219+
}
1220+
>;
1221+
11841222
/**
11851223
* Union type for each step. The step consists of exactly one `field`
11861224
* set and optionally expected events in the `expect` field.
@@ -1225,8 +1263,8 @@ export interface SpecStep {
12251263
/** Fail a write */
12261264
failWrite?: SpecWriteFailure;
12271265

1228-
/** Fail all database transactions. */
1229-
failDatabase?: boolean;
1266+
/** Fails the listed database actions. */
1267+
failDatabase?: false | SpecDatabaseFailures;
12301268

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

0 commit comments

Comments
 (0)