Skip to content

Commit cd15480

Browse files
authored
Firestore: Disable bloom filter integration tests when using Firestore emulator (#7463)
1 parent c5518c8 commit cd15480

File tree

1 file changed

+116
-117
lines changed

1 file changed

+116
-117
lines changed

packages/firestore/test/integration/api/query.test.ts

Lines changed: 116 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2075,129 +2075,128 @@ apiDescribe('Queries', persistence => {
20752075
});
20762076
});
20772077

2078-
it('resuming a query should use bloom filter to avoid full requery', async () => {
2079-
// Prepare the names and contents of the 100 documents to create.
2080-
const testDocs: { [key: string]: object } = {};
2081-
for (let i = 0; i < 100; i++) {
2082-
testDocs['doc' + (1000 + i)] = { key: 42 };
2083-
}
2084-
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();
2089-
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. Use a WriteBatch, rather than
2099-
// deleteDoc(), to avoid affecting the local cache.
2100-
const deletedDocumentIds = new Set<string>();
2101-
const writeBatchForDocumentDeletes = writeBatch(db);
2102-
for (let i = 0; i < createdDocuments.length; i += 2) {
2103-
const documentToDelete = createdDocuments[i];
2104-
writeBatchForDocumentDeletes.delete(documentToDelete);
2105-
deletedDocumentIds.add(documentToDelete.id);
2106-
}
2107-
await writeBatchForDocumentDeletes.commit();
2108-
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-
}
2078+
// TODO(b/291365820): Stop skipping this test when running against the
2079+
// Firestore emulator once the emulator is improved to include a bloom filter
2080+
// in the existence filter messages that it sends.
2081+
// eslint-disable-next-line no-restricted-properties
2082+
(USE_EMULATOR ? it.skip : it)(
2083+
'resuming a query should use bloom filter to avoid full requery',
2084+
async () => {
2085+
// Prepare the names and contents of the 100 documents to create.
2086+
const testDocs: { [key: string]: object } = {};
2087+
for (let i = 0; i < 100; i++) {
2088+
testDocs['doc' + (1000 + i)] = { key: 42 };
2089+
}
21412090

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-
}
2091+
// Ensure that the local cache is configured to use LRU garbage
2092+
// collection (rather than eager garbage collection) so that the resume
2093+
// token and document data does not get prematurely evicted.
2094+
const lruPersistence = persistence.toLruGc();
21512095

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-
}
2096+
return withRetry(async attemptNumber => {
2097+
return withTestCollection(
2098+
lruPersistence,
2099+
testDocs,
2100+
async (coll, db) => {
2101+
// Run a query to populate the local cache with the 100 documents
2102+
// and a resume token.
2103+
const snapshot1 = await getDocs(coll);
2104+
expect(snapshot1.size, 'snapshot1.size').to.equal(100);
2105+
const createdDocuments = snapshot1.docs.map(
2106+
snapshot => snapshot.ref
2107+
);
21712108

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-
}
2109+
// Delete 50 of the 100 documents. Use a WriteBatch, rather than
2110+
// deleteDoc(), to avoid affecting the local cache.
2111+
const deletedDocumentIds = new Set<string>();
2112+
const writeBatchForDocumentDeletes = writeBatch(db);
2113+
for (let i = 0; i < createdDocuments.length; i += 2) {
2114+
const documentToDelete = createdDocuments[i];
2115+
writeBatchForDocumentDeletes.delete(documentToDelete);
2116+
deletedDocumentIds.add(documentToDelete.id);
2117+
}
2118+
await writeBatchForDocumentDeletes.commit();
2119+
2120+
// Wait for 10 seconds, during which Watch will stop tracking the
2121+
// query and will send an existence filter rather than "delete"
2122+
// events when the query is resumed.
2123+
await new Promise(resolve => setTimeout(resolve, 10000));
2124+
2125+
// Resume the query and save the resulting snapshot for
2126+
// verification. Use some internal testing hooks to "capture" the
2127+
// existence filter mismatches to verify that Watch sent a bloom
2128+
// filter, and it was used to avert a full requery.
2129+
const [existenceFilterMismatches, snapshot2] =
2130+
await captureExistenceFilterMismatches(() => getDocs(coll));
2131+
2132+
// Verify that the snapshot from the resumed query contains the
2133+
// expected documents; that is, that it contains the 50 documents
2134+
// that were _not_ deleted.
2135+
const actualDocumentIds = snapshot2.docs
2136+
.map(documentSnapshot => documentSnapshot.ref.id)
2137+
.sort();
2138+
const expectedDocumentIds = createdDocuments
2139+
.filter(documentRef => !deletedDocumentIds.has(documentRef.id))
2140+
.map(documentRef => documentRef.id)
2141+
.sort();
2142+
expect(actualDocumentIds, 'snapshot2.docs').to.deep.equal(
2143+
expectedDocumentIds
2144+
);
21902145

2191-
expect(
2192-
bloomFilter.applied,
2193-
`bloomFilter.applied with attemptNumber=${attemptNumber}`
2194-
).to.be.true;
2146+
// Verify that Watch sent an existence filter with the correct
2147+
// counts when the query was resumed.
2148+
expect(
2149+
existenceFilterMismatches,
2150+
'existenceFilterMismatches'
2151+
).to.have.length(1);
2152+
const { localCacheCount, existenceFilterCount, bloomFilter } =
2153+
existenceFilterMismatches[0];
2154+
expect(localCacheCount, 'localCacheCount').to.equal(100);
2155+
expect(existenceFilterCount, 'existenceFilterCount').to.equal(50);
2156+
2157+
// Verify that Watch sent a valid bloom filter.
2158+
if (!bloomFilter) {
2159+
expect.fail(
2160+
'The existence filter should have specified a bloom filter ' +
2161+
'in its `unchanged_names` field.'
2162+
);
2163+
throw new Error('should never get here');
2164+
}
2165+
2166+
expect(bloomFilter.hashCount, 'bloomFilter.hashCount').to.be.above(
2167+
0
2168+
);
2169+
expect(
2170+
bloomFilter.bitmapLength,
2171+
'bloomFilter.bitmapLength'
2172+
).to.be.above(0);
2173+
expect(bloomFilter.padding, 'bloomFilterPadding').to.be.above(0);
2174+
expect(bloomFilter.padding, 'bloomFilterPadding').to.be.below(8);
2175+
2176+
// Verify that the bloom filter was successfully used to avert a
2177+
// full requery. If a false positive occurred then retry the entire
2178+
// test. Although statistically rare, false positives are expected
2179+
// to happen occasionally. When a false positive _does_ happen, just
2180+
// retry the test with a different set of documents. If that retry
2181+
// also_ experiences a false positive, then fail the test because
2182+
// that is so improbable that something must have gone wrong.
2183+
if (attemptNumber === 1 && !bloomFilter.applied) {
2184+
throw new RetryError();
2185+
}
2186+
2187+
expect(
2188+
bloomFilter.applied,
2189+
`bloomFilter.applied with attemptNumber=${attemptNumber}`
2190+
).to.be.true;
2191+
}
2192+
);
21952193
});
2196-
});
2197-
}).timeout('90s');
2194+
}
2195+
).timeout('90s');
21982196

2199-
// TODO(b/270731363): Re-enable this test once the Firestore emulator is fixed
2200-
// to send an existence filter.
2197+
// TODO(b/291365820): Stop skipping this test when running against the
2198+
// Firestore emulator once the emulator is improved to include a bloom filter
2199+
// in the existence filter messages that it sends.
22012200
// eslint-disable-next-line no-restricted-properties
22022201
(USE_EMULATOR ? it.skip : it)(
22032202
'bloom filter should correctly encode complex Unicode characters',

0 commit comments

Comments
 (0)