@@ -26,7 +26,6 @@ import {
26
26
Bytes ,
27
27
collection ,
28
28
collectionGroup ,
29
- CollectionReference ,
30
29
deleteDoc ,
31
30
disableNetwork ,
32
31
doc ,
@@ -36,7 +35,6 @@ import {
36
35
enableNetwork ,
37
36
endAt ,
38
37
endBefore ,
39
- Firestore ,
40
38
GeoPoint ,
41
39
getDocs ,
42
40
getDocsFromCache ,
@@ -60,10 +58,12 @@ import {
60
58
} from '../util/firebase_export' ;
61
59
import {
62
60
apiDescribe ,
61
+ RetryError ,
63
62
toChangesArray ,
64
63
toDataArray ,
65
64
toIds ,
66
65
withEmptyTestCollection ,
66
+ withRetry ,
67
67
withTestCollection ,
68
68
withTestDb
69
69
} from '../util/helpers' ;
@@ -2082,134 +2082,118 @@ apiDescribe('Queries', persistence => {
2082
2082
testDocs [ 'doc' + ( 1000 + i ) ] = { key : 42 } ;
2083
2083
}
2084
2084
2085
- // The function that runs a single iteration of the test.
2086
- // Below this definition, there is a "while" loop that calls this function
2087
- // potentially multiple times.
2088
- const runTestIteration = async (
2089
- coll : CollectionReference ,
2090
- db : Firestore
2091
- ) : Promise < 'retry' | 'passed' > => {
2092
- // Run a query to populate the local cache with the 100 documents and a
2093
- // resume token.
2094
- const snapshot1 = await getDocs ( coll ) ;
2095
- expect ( snapshot1 . size , 'snapshot1.size' ) . to . equal ( 100 ) ;
2096
- const createdDocuments = snapshot1 . docs . map ( snapshot => snapshot . ref ) ;
2097
-
2098
- // Delete 50 of the 100 documents. Do this in a transaction, rather than
2099
- // deleteDoc(), to avoid affecting the local cache.
2100
- const deletedDocumentIds = new Set < string > ( ) ;
2101
- await runTransaction ( db , async txn => {
2102
- for ( let i = 0 ; i < createdDocuments . length ; i += 2 ) {
2103
- const documentToDelete = createdDocuments [ i ] ;
2104
- txn . delete ( documentToDelete ) ;
2105
- deletedDocumentIds . add ( documentToDelete . id ) ;
2106
- }
2107
- } ) ;
2085
+ // Ensure that the local cache is configured to use LRU garbage
2086
+ // collection (rather than eager garbage collection) so that the resume
2087
+ // token and document data does not get prematurely evicted.
2088
+ const lruPersistence = persistence . toLruGc ( ) ;
2108
2089
2109
- // Wait for 10 seconds, during which Watch will stop tracking the query
2110
- // and will send an existence filter rather than "delete" events when the
2111
- // query is resumed.
2112
- await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2113
-
2114
- // Resume the query and save the resulting snapshot for verification.
2115
- // Use some internal testing hooks to "capture" the existence filter
2116
- // mismatches to verify that Watch sent a bloom filter, and it was used to
2117
- // avert a full requery.
2118
- const [ existenceFilterMismatches , snapshot2 ] =
2119
- await captureExistenceFilterMismatches ( ( ) => getDocs ( coll ) ) ;
2120
-
2121
- // Verify that the snapshot from the resumed query contains the expected
2122
- // documents; that is, that it contains the 50 documents that were _not_
2123
- // deleted.
2124
- // TODO(b/270731363): Remove the "if" condition below once the
2125
- // Firestore Emulator is fixed to send an existence filter. At the time of
2126
- // writing, the Firestore emulator fails to send an existence filter,
2127
- // resulting in the client including the deleted documents in the snapshot
2128
- // of the resumed query.
2129
- if ( ! ( USE_EMULATOR && snapshot2 . size === 100 ) ) {
2130
- const actualDocumentIds = snapshot2 . docs
2131
- . map ( documentSnapshot => documentSnapshot . ref . id )
2132
- . sort ( ) ;
2133
- const expectedDocumentIds = createdDocuments
2134
- . filter ( documentRef => ! deletedDocumentIds . has ( documentRef . id ) )
2135
- . map ( documentRef => documentRef . id )
2136
- . sort ( ) ;
2137
- expect ( actualDocumentIds , 'snapshot2.docs' ) . to . deep . equal (
2138
- expectedDocumentIds
2139
- ) ;
2140
- }
2090
+ return withRetry ( async attemptNumber => {
2091
+ return withTestCollection ( lruPersistence , testDocs , async ( coll , db ) => {
2092
+ // Run a query to populate the local cache with the 100 documents and a
2093
+ // resume token.
2094
+ const snapshot1 = await getDocs ( coll ) ;
2095
+ expect ( snapshot1 . size , 'snapshot1.size' ) . to . equal ( 100 ) ;
2096
+ const createdDocuments = snapshot1 . docs . map ( snapshot => snapshot . ref ) ;
2097
+
2098
+ // Delete 50 of the 100 documents. Do this in a transaction, rather than
2099
+ // deleteDoc(), to avoid affecting the local cache.
2100
+ const deletedDocumentIds = new Set < string > ( ) ;
2101
+ await runTransaction ( db , async txn => {
2102
+ for ( let i = 0 ; i < createdDocuments . length ; i += 2 ) {
2103
+ const documentToDelete = createdDocuments [ i ] ;
2104
+ txn . delete ( documentToDelete ) ;
2105
+ deletedDocumentIds . add ( documentToDelete . id ) ;
2106
+ }
2107
+ } ) ;
2141
2108
2142
- // Skip the verification of the existence filter mismatch when testing
2143
- // against the Firestore emulator because the Firestore emulator fails to
2144
- // to send an existence filter at all.
2145
- // TODO(b/270731363): Enable the verification of the existence filter
2146
- // mismatch once the Firestore emulator is fixed to send an existence
2147
- // filter.
2148
- if ( USE_EMULATOR ) {
2149
- return 'passed' ;
2150
- }
2109
+ // Wait for 10 seconds, during which Watch will stop tracking the query
2110
+ // and will send an existence filter rather than "delete" events when
2111
+ // the query is resumed.
2112
+ await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2113
+
2114
+ // Resume the query and save the resulting snapshot for verification.
2115
+ // Use some internal testing hooks to "capture" the existence filter
2116
+ // mismatches to verify that Watch sent a bloom filter, and it was used
2117
+ // to avert a full requery.
2118
+ const [ existenceFilterMismatches , snapshot2 ] =
2119
+ await captureExistenceFilterMismatches ( ( ) => getDocs ( coll ) ) ;
2120
+
2121
+ // Verify that the snapshot from the resumed query contains the expected
2122
+ // documents; that is, that it contains the 50 documents that were _not_
2123
+ // deleted.
2124
+ // TODO(b/270731363): Remove the "if" condition below once the
2125
+ // Firestore Emulator is fixed to send an existence filter. At the time
2126
+ // of writing, the Firestore emulator fails to send an existence filter,
2127
+ // resulting in the client including the deleted documents in the
2128
+ // snapshot of the resumed query.
2129
+ if ( ! ( USE_EMULATOR && snapshot2 . size === 100 ) ) {
2130
+ const actualDocumentIds = snapshot2 . docs
2131
+ . map ( documentSnapshot => documentSnapshot . ref . id )
2132
+ . sort ( ) ;
2133
+ const expectedDocumentIds = createdDocuments
2134
+ . filter ( documentRef => ! deletedDocumentIds . has ( documentRef . id ) )
2135
+ . map ( documentRef => documentRef . id )
2136
+ . sort ( ) ;
2137
+ expect ( actualDocumentIds , 'snapshot2.docs' ) . to . deep . equal (
2138
+ expectedDocumentIds
2139
+ ) ;
2140
+ }
2151
2141
2152
- // Verify that Watch sent an existence filter with the correct counts when
2153
- // the query was resumed.
2154
- expect (
2155
- existenceFilterMismatches ,
2156
- 'existenceFilterMismatches'
2157
- ) . to . have . length ( 1 ) ;
2158
- const { localCacheCount, existenceFilterCount, bloomFilter } =
2159
- existenceFilterMismatches [ 0 ] ;
2160
- expect ( localCacheCount , 'localCacheCount' ) . to . equal ( 100 ) ;
2161
- expect ( existenceFilterCount , 'existenceFilterCount' ) . to . equal ( 50 ) ;
2162
-
2163
- // Verify that Watch sent a valid bloom filter.
2164
- if ( ! bloomFilter ) {
2165
- expect . fail (
2166
- 'The existence filter should have specified a bloom filter in its ' +
2167
- '`unchanged_names` field.'
2168
- ) ;
2169
- throw new Error ( 'should never get here' ) ;
2170
- }
2142
+ // Skip the verification of the existence filter mismatch when testing
2143
+ // against the Firestore emulator because the Firestore emulator fails
2144
+ // to to send an existence filter at all.
2145
+ // TODO(b/270731363): Enable the verification of the existence filter
2146
+ // mismatch once the Firestore emulator is fixed to send an existence
2147
+ // filter.
2148
+ if ( USE_EMULATOR ) {
2149
+ return ;
2150
+ }
2171
2151
2172
- expect ( bloomFilter . hashCount , 'bloomFilter.hashCount' ) . to . be . above ( 0 ) ;
2173
- expect ( bloomFilter . bitmapLength , 'bloomFilter.bitmapLength' ) . to . be . above (
2174
- 0
2175
- ) ;
2176
- expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . above ( 0 ) ;
2177
- expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . below ( 8 ) ;
2178
-
2179
- // Verify that the bloom filter was successfully used to avert a full
2180
- // requery. If a false positive occurred then retry the entire test.
2181
- // Although statistically rare, false positives are expected to happen
2182
- // occasionally. When a false positive _does_ happen, just retry the test
2183
- // with a different set of documents. If that retry _also_ experiences a
2184
- // false positive, then fail the test because that is so improbable that
2185
- // something must have gone wrong.
2186
- if ( attemptNumber === 1 && ! bloomFilter . applied ) {
2187
- return 'retry' ;
2188
- }
2189
- expect (
2190
- bloomFilter . applied ,
2191
- `bloomFilter.applied with attemptNumber=${ attemptNumber } `
2192
- ) . to . be . true ;
2152
+ // Verify that Watch sent an existence filter with the correct counts
2153
+ // when the query was resumed.
2154
+ expect (
2155
+ existenceFilterMismatches ,
2156
+ 'existenceFilterMismatches'
2157
+ ) . to . have . length ( 1 ) ;
2158
+ const { localCacheCount, existenceFilterCount, bloomFilter } =
2159
+ existenceFilterMismatches [ 0 ] ;
2160
+ expect ( localCacheCount , 'localCacheCount' ) . to . equal ( 100 ) ;
2161
+ expect ( existenceFilterCount , 'existenceFilterCount' ) . to . equal ( 50 ) ;
2162
+
2163
+ // Verify that Watch sent a valid bloom filter.
2164
+ if ( ! bloomFilter ) {
2165
+ expect . fail (
2166
+ 'The existence filter should have specified a bloom filter in its ' +
2167
+ '`unchanged_names` field.'
2168
+ ) ;
2169
+ throw new Error ( 'should never get here' ) ;
2170
+ }
2193
2171
2194
- return 'passed' ;
2195
- } ;
2172
+ expect ( bloomFilter . hashCount , 'bloomFilter.hashCount' ) . to . be . above ( 0 ) ;
2173
+ expect (
2174
+ bloomFilter . bitmapLength ,
2175
+ 'bloomFilter.bitmapLength'
2176
+ ) . to . be . above ( 0 ) ;
2177
+ expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . above ( 0 ) ;
2178
+ expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . below ( 8 ) ;
2179
+
2180
+ // Verify that the bloom filter was successfully used to avert a full
2181
+ // requery. If a false positive occurred then retry the entire test.
2182
+ // Although statistically rare, false positives are expected to happen
2183
+ // occasionally. When a false positive _does_ happen, just retry the
2184
+ // test with a different set of documents. If that retry _also_
2185
+ // experiences a false positive, then fail the test because that is so
2186
+ // improbable that something must have gone wrong.
2187
+ if ( attemptNumber === 1 && ! bloomFilter . applied ) {
2188
+ throw new RetryError ( ) ;
2189
+ }
2196
2190
2197
- // Run the test
2198
- let attemptNumber = 0 ;
2199
- while ( true ) {
2200
- attemptNumber ++ ;
2201
- const iterationResult = await withTestCollection (
2202
- // Ensure that the local cache is configured to use LRU garbage
2203
- // collection (rather than eager garbage collection) so that the resume
2204
- // token and document data does not get prematurely evicted.
2205
- persistence . toLruGc ( ) ,
2206
- testDocs ,
2207
- runTestIteration
2208
- ) ;
2209
- if ( iterationResult === 'passed' ) {
2210
- break ;
2211
- }
2212
- }
2191
+ expect (
2192
+ bloomFilter . applied ,
2193
+ `bloomFilter.applied with attemptNumber=${ attemptNumber } `
2194
+ ) . to . be . true ;
2195
+ } ) ;
2196
+ } ) ;
2213
2197
} ) . timeout ( '90s' ) ;
2214
2198
} ) ;
2215
2199
0 commit comments