Skip to content

Commit 17827f9

Browse files
committed
Move unit test under AndroidTest so that it can be run on simulator
1 parent d542173 commit 17827f9

File tree

8 files changed

+263
-22
lines changed

8 files changed

+263
-22
lines changed

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/AutoIndexingExperiment.java

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,50 @@
33
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollection;
44
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.toDataMap;
55
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor;
6+
import static com.google.firebase.firestore.testutil.TestUtil.doc;
7+
import static com.google.firebase.firestore.testutil.TestUtil.docMap;
8+
import static com.google.firebase.firestore.testutil.TestUtil.filter;
69
import static com.google.firebase.firestore.testutil.TestUtil.map;
10+
import static com.google.firebase.firestore.testutil.TestUtil.query;
711
import static org.junit.Assert.assertEquals;
812

13+
import android.content.Context;
14+
import androidx.test.core.app.ApplicationProvider;
915
import androidx.test.ext.junit.runners.AndroidJUnit4;
16+
import com.google.android.gms.common.internal.Preconditions;
1017
import com.google.android.gms.tasks.Task;
18+
import com.google.firebase.database.collection.ImmutableSortedMap;
19+
import com.google.firebase.database.collection.ImmutableSortedSet;
20+
import com.google.firebase.firestore.auth.User;
21+
import com.google.firebase.firestore.core.Query;
22+
import com.google.firebase.firestore.core.View;
23+
import com.google.firebase.firestore.local.DocumentOverlayCache;
24+
import com.google.firebase.firestore.local.IndexManager;
25+
import com.google.firebase.firestore.local.LocalDocumentsView;
26+
import com.google.firebase.firestore.local.LocalSerializer;
27+
import com.google.firebase.firestore.local.LruGarbageCollector;
28+
import com.google.firebase.firestore.local.MutationQueue;
29+
import com.google.firebase.firestore.local.Persistence;
30+
import com.google.firebase.firestore.local.QueryContext;
31+
import com.google.firebase.firestore.local.QueryEngine;
32+
import com.google.firebase.firestore.local.RemoteDocumentCache;
33+
import com.google.firebase.firestore.local.SQLitePersistence;
34+
import com.google.firebase.firestore.model.DatabaseId;
35+
import com.google.firebase.firestore.model.Document;
36+
import com.google.firebase.firestore.model.DocumentKey;
37+
import com.google.firebase.firestore.model.DocumentSet;
38+
import com.google.firebase.firestore.model.FieldIndex;
39+
import com.google.firebase.firestore.model.MutableDocument;
40+
import com.google.firebase.firestore.remote.RemoteSerializer;
1141
import java.util.ArrayList;
1242
import java.util.Arrays;
1343
import java.util.Collections;
1444
import java.util.List;
1545
import java.util.Map;
46+
import java.util.concurrent.Callable;
47+
import java.util.concurrent.TimeUnit;
48+
import javax.annotation.Nullable;
49+
import org.junit.Before;
1650
import org.junit.Test;
1751
import org.junit.runner.RunWith;
1852

@@ -127,4 +161,211 @@ public void testIndexedWithNonIndexedTime() {
127161
}
128162
}
129163
}
164+
165+
private Persistence persistence;
166+
private RemoteDocumentCache remoteDocumentCache;
167+
private MutationQueue mutationQueue;
168+
private DocumentOverlayCache documentOverlayCache;
169+
170+
protected IndexManager indexManager;
171+
protected QueryEngine queryEngine;
172+
173+
private @Nullable Boolean expectFullCollectionScan;
174+
175+
public static SQLitePersistence createSQLitePersistence() {
176+
DatabaseId databaseId = DatabaseId.forProject("projectId");
177+
LocalSerializer serializer = new LocalSerializer(new RemoteSerializer(databaseId));
178+
Context context = ApplicationProvider.getApplicationContext();
179+
SQLitePersistence persistence =
180+
new SQLitePersistence(
181+
context, "test", databaseId, serializer, LruGarbageCollector.Params.Default());
182+
persistence.start();
183+
return persistence;
184+
}
185+
186+
@Before
187+
public void setUp() {
188+
expectFullCollectionScan = null;
189+
190+
persistence = createSQLitePersistence();
191+
192+
indexManager = persistence.getIndexManager(User.UNAUTHENTICATED);
193+
mutationQueue = persistence.getMutationQueue(User.UNAUTHENTICATED, indexManager);
194+
documentOverlayCache = persistence.getDocumentOverlayCache(User.UNAUTHENTICATED);
195+
remoteDocumentCache = persistence.getRemoteDocumentCache();
196+
queryEngine = new QueryEngine();
197+
198+
indexManager.start();
199+
mutationQueue.start();
200+
201+
remoteDocumentCache.setIndexManager(indexManager);
202+
203+
LocalDocumentsView localDocuments =
204+
new LocalDocumentsView(
205+
remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager) {
206+
@Override
207+
public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
208+
com.google.firebase.firestore.core.Query query, FieldIndex.IndexOffset offset) {
209+
assertEquals(
210+
"Observed query execution mode did not match expectation",
211+
expectFullCollectionScan,
212+
FieldIndex.IndexOffset.NONE.equals(offset));
213+
return super.getDocumentsMatchingQuery(query, offset);
214+
}
215+
};
216+
queryEngine.initialize(localDocuments, indexManager, false);
217+
}
218+
219+
/** Adds the provided documents to the remote document cache. */
220+
protected void addDocument(MutableDocument... docs) {
221+
persistence.runTransaction(
222+
"addDocument",
223+
() -> {
224+
for (MutableDocument doc : docs) {
225+
remoteDocumentCache.add(doc, doc.getVersion());
226+
}
227+
});
228+
}
229+
230+
protected <T> T expectOptimizedCollectionScan(Callable<T> c) throws Exception {
231+
try {
232+
expectFullCollectionScan = false;
233+
return c.call();
234+
} finally {
235+
expectFullCollectionScan = null;
236+
}
237+
}
238+
239+
private <T> T expectFullCollectionScan(Callable<T> c) throws Exception {
240+
try {
241+
expectFullCollectionScan = true;
242+
return c.call();
243+
} finally {
244+
expectFullCollectionScan = null;
245+
}
246+
}
247+
248+
protected DocumentSet runQuery(
249+
com.google.firebase.firestore.core.Query query, boolean autoIndexing, QueryContext counter) {
250+
Preconditions.checkNotNull(
251+
expectFullCollectionScan,
252+
"Encountered runQuery() call not wrapped in expectOptimizedCollectionQuery()/expectFullCollectionQuery()");
253+
ImmutableSortedMap<DocumentKey, Document> docs =
254+
queryEngine.getDocumentsMatchingQueryTest(query, autoIndexing, counter);
255+
View view =
256+
new View(query, new ImmutableSortedSet<>(Collections.emptyList(), DocumentKey::compareTo));
257+
View.DocumentChanges viewDocChanges = view.computeDocChanges(docs);
258+
return view.applyChanges(viewDocChanges).getSnapshot().getDocuments();
259+
}
260+
261+
private void createTestingDocument(
262+
String basePath, int documentID, boolean isMatched, int numOfFields) {
263+
Map<String, Object> fields = map("match", isMatched);
264+
265+
// Randomly generate the rest of fields
266+
for (int i = 2; i <= numOfFields; i++) {
267+
int valueIndex = (int) (Math.random() * values.size()) % values.size();
268+
fields.put("field" + i, values.get(valueIndex));
269+
}
270+
271+
MutableDocument doc = doc(basePath + "/" + documentID, 1, fields);
272+
addDocument(doc);
273+
274+
indexManager.updateIndexEntries(docMap(doc));
275+
indexManager.updateCollectionGroup(basePath, FieldIndex.IndexOffset.fromDocument(doc));
276+
}
277+
278+
private void createTestingCollection(
279+
String basePath, int totalSetCount, int portion /*0 - 10*/, int numOfFields /* 1 - 30*/) {
280+
int documentCounter = 0;
281+
for (int i = 1; i <= totalSetCount; i++) {
282+
// Generate a random order list of 0 ... 9
283+
ArrayList<Integer> indexes = new ArrayList<>();
284+
for (int index = 0; index < 10; index++) {
285+
indexes.add(index);
286+
}
287+
Collections.shuffle(indexes);
288+
289+
for (int match = 0; match < portion; match++) {
290+
int currentID = documentCounter + indexes.get(match);
291+
createTestingDocument(basePath, currentID, true, numOfFields);
292+
}
293+
for (int unmatch = portion; unmatch < 10; unmatch++) {
294+
int currentID = documentCounter + indexes.get(unmatch);
295+
createTestingDocument(basePath, currentID, false, numOfFields);
296+
}
297+
documentCounter += 10;
298+
}
299+
}
300+
301+
@Test
302+
public void testCombinesIndexedWithNonIndexedResults() throws Exception {
303+
// Every set contains 10 documents
304+
final int numOfSet = 100;
305+
// could overflow. Currently it is safe when numOfSet set to 1000 and running on macbook M1
306+
long totalBeforeIndex = 0;
307+
long totalAfterIndex = 0;
308+
long totalDocumentCount = 0;
309+
long totalResultCount = 0;
310+
311+
// Temperate heuristic
312+
double without = 3.7;
313+
double with = 4.9;
314+
315+
for (int totalSetCount = 1; totalSetCount <= numOfSet; totalSetCount *= 10) {
316+
// portion stands for the percentage of documents matching query
317+
for (int portion = 0; portion <= 10; portion++) {
318+
for (int numOfFields = 1; numOfFields <= 31; numOfFields += 10) {
319+
String basePath = "documentCount" + totalSetCount;
320+
// Auto indexing
321+
Query query = query(basePath).filter(filter("match", "==", true));
322+
indexManager.createTargetIndices(query.toTarget());
323+
createTestingCollection(basePath, totalSetCount, portion, numOfFields);
324+
325+
QueryContext counterWithoutIndex = new QueryContext();
326+
long beforeAutoStart = System.nanoTime();
327+
DocumentSet results =
328+
expectFullCollectionScan(() -> runQuery(query, false, counterWithoutIndex));
329+
long beforeAutoEnd = System.nanoTime();
330+
long millisecondsBeforeAuto =
331+
TimeUnit.MILLISECONDS.convert(
332+
(beforeAutoEnd - beforeAutoStart), TimeUnit.NANOSECONDS);
333+
totalBeforeIndex += (beforeAutoEnd - beforeAutoStart);
334+
totalDocumentCount += counterWithoutIndex.fullScanCount;
335+
assertEquals(portion * totalSetCount, results.size());
336+
337+
QueryContext counterWithIndex = new QueryContext();
338+
long autoStart = System.nanoTime();
339+
results = expectOptimizedCollectionScan(() -> runQuery(query, true, counterWithIndex));
340+
long autoEnd = System.nanoTime();
341+
long millisecondsAfterAuto =
342+
TimeUnit.MILLISECONDS.convert((autoEnd - autoStart), TimeUnit.NANOSECONDS);
343+
totalAfterIndex += (autoEnd - autoStart);
344+
assertEquals(portion * totalSetCount, results.size());
345+
totalResultCount += results.size();
346+
if (millisecondsBeforeAuto > millisecondsAfterAuto) {
347+
System.out.println(
348+
"Auto Indexing saves time when total of documents inside collection is "
349+
+ totalSetCount * 10
350+
+ ". The matching percentage is "
351+
+ portion
352+
+ "0%. And each document contains "
353+
+ numOfFields
354+
+ " fields.\n"
355+
+ "Weight result for without auto indexing is "
356+
+ without * counterWithoutIndex.fullScanCount
357+
+ ". And weight result for auto indexing is "
358+
+ with * results.size());
359+
}
360+
}
361+
}
362+
}
363+
364+
System.out.println(
365+
"The time heuristic is "
366+
+ (totalBeforeIndex / totalDocumentCount)
367+
+ " before auto indexing");
368+
System.out.println(
369+
"The time heuristic is " + (totalAfterIndex / totalResultCount) + " after auto indexing");
370+
}
130371
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalDocumentsView.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@
4848
* in remoteDocumentCache or local mutations for the document). The view is computed by applying the
4949
* mutations in the MutationQueue to the RemoteDocumentCache.
5050
*/
51-
class LocalDocumentsView {
51+
public class LocalDocumentsView {
5252

5353
private final RemoteDocumentCache remoteDocumentCache;
5454
private final MutationQueue mutationQueue;
5555
private final DocumentOverlayCache documentOverlayCache;
5656
private final IndexManager indexManager;
5757

58-
LocalDocumentsView(
58+
protected LocalDocumentsView(
5959
RemoteDocumentCache remoteDocumentCache,
6060
MutationQueue mutationQueue,
6161
DocumentOverlayCache documentOverlayCache,
@@ -276,7 +276,7 @@ ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
276276
}
277277
}
278278

279-
ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
279+
protected ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
280280
Query query, IndexOffset offset) {
281281
return getDocumentsMatchingQuery(query, offset, new QueryContext());
282282
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/MemoryPersistence.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private void setReferenceDelegate(ReferenceDelegate delegate) {
9494
}
9595

9696
@Override
97-
MutationQueue getMutationQueue(User user, IndexManager indexManager) {
97+
public MutationQueue getMutationQueue(User user, IndexManager indexManager) {
9898
MemoryMutationQueue queue = mutationQueues.get(user);
9999
if (queue == null) {
100100
queue = new MemoryMutationQueue(this, user);
@@ -113,12 +113,12 @@ MemoryTargetCache getTargetCache() {
113113
}
114114

115115
@Override
116-
MemoryRemoteDocumentCache getRemoteDocumentCache() {
116+
public MemoryRemoteDocumentCache getRemoteDocumentCache() {
117117
return remoteDocumentCache;
118118
}
119119

120120
@Override
121-
MemoryIndexManager getIndexManager(User user) {
121+
public MemoryIndexManager getIndexManager(User user) {
122122
// We do not currently support indices for memory persistence, so we can return the same shared
123123
// instance of the memory index manager.
124124
return indexManager;
@@ -130,7 +130,7 @@ BundleCache getBundleCache() {
130130
}
131131

132132
@Override
133-
DocumentOverlayCache getDocumentOverlayCache(User user) {
133+
public DocumentOverlayCache getDocumentOverlayCache(User user) {
134134
MemoryDocumentOverlayCache overlay = overlays.get(user);
135135
if (overlay == null) {
136136
overlay = new MemoryDocumentOverlayCache();
@@ -145,7 +145,7 @@ OverlayMigrationManager getOverlayMigrationManager() {
145145
}
146146

147147
@Override
148-
void runTransaction(String action, Runnable operation) {
148+
public void runTransaction(String action, Runnable operation) {
149149
referenceDelegate.onTransactionStarted();
150150
try {
151151
operation.run();

firebase-firestore/src/main/java/com/google/firebase/firestore/local/MutationQueue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import java.util.List;
2525

2626
/** A queue of mutations to apply to the remote store. */
27-
interface MutationQueue {
27+
public interface MutationQueue {
2828
/**
2929
* Starts the mutation queue, performing any initial reads that might be required to establish
3030
* invariants, etc.

firebase-firestore/src/main/java/com/google/firebase/firestore/local/Persistence.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,22 +78,22 @@ public abstract class Persistence {
7878
* implementation to the extent possible (e.g. in the case of uid switching from
7979
* sally=>jack=>sally, sally's mutation queue will be preserved).
8080
*/
81-
abstract MutationQueue getMutationQueue(User user, IndexManager indexManager);
81+
public abstract MutationQueue getMutationQueue(User user, IndexManager indexManager);
8282

8383
/** Creates a TargetCache representing the persisted cache of queries. */
8484
abstract TargetCache getTargetCache();
8585

8686
/** Creates a RemoteDocumentCache representing the persisted cache of remote documents. */
87-
abstract RemoteDocumentCache getRemoteDocumentCache();
87+
public abstract RemoteDocumentCache getRemoteDocumentCache();
8888

8989
/** Creates an IndexManager that manages our persisted query indexes. */
90-
abstract IndexManager getIndexManager(User user);
90+
public abstract IndexManager getIndexManager(User user);
9191

9292
/** Returns a BundleCache representing the persisted cache of loaded bundles. */
9393
abstract BundleCache getBundleCache();
9494

9595
/** Returns a DocumentOverlayCache representing the documents that are mutated locally. */
96-
abstract DocumentOverlayCache getDocumentOverlayCache(User user);
96+
public abstract DocumentOverlayCache getDocumentOverlayCache(User user);
9797

9898
/** Returns a OverlayMigrationManager that runs any pending data migration required by SDK. */
9999
abstract OverlayMigrationManager getOverlayMigrationManager();
@@ -106,7 +106,7 @@ public abstract class Persistence {
106106
* @param action A description of the action performed by this transaction, used for logging.
107107
* @param operation The operation to run inside a transaction.
108108
*/
109-
abstract void runTransaction(String action, Runnable operation);
109+
public abstract void runTransaction(String action, Runnable operation);
110110

111111
/**
112112
* Performs an operation inside a persistence transaction. Any reads or writes against persistence

firebase-firestore/src/main/java/com/google/firebase/firestore/local/QueryContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
package com.google.firebase.firestore.local;
1616

1717
public class QueryContext {
18-
QueryContext() {}
18+
public QueryContext() {}
1919

20-
int fullScanCount = 0;
20+
public int fullScanCount = 0;
2121
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/RemoteDocumentCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* meaning we can cache both Document instances (an actual document with data) as well as NoDocument
3232
* instances (indicating that the document is known to not exist).
3333
*/
34-
interface RemoteDocumentCache {
34+
public interface RemoteDocumentCache {
3535

3636
/** Sets the index manager to use for managing the collectionGroup index. */
3737
void setIndexManager(IndexManager indexManager);

0 commit comments

Comments
 (0)