• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.android.icing;
16 
17 import static com.google.common.truth.Truth.assertThat;
18 import static com.google.common.truth.Truth.assertWithMessage;
19 
20 import com.google.android.icing.IcingSearchEngine;
21 import com.google.android.icing.proto.BatchGetResultProto;
22 import com.google.android.icing.proto.BatchPutResultProto;
23 import com.google.android.icing.proto.BlobProto;
24 import com.google.android.icing.proto.DebugInfoResultProto;
25 import com.google.android.icing.proto.DebugInfoVerbosity;
26 import com.google.android.icing.proto.DeleteByNamespaceResultProto;
27 import com.google.android.icing.proto.DeleteByQueryResultProto;
28 import com.google.android.icing.proto.DeleteBySchemaTypeResultProto;
29 import com.google.android.icing.proto.DeleteResultProto;
30 import com.google.android.icing.proto.DocumentProto;
31 import com.google.android.icing.proto.GetAllNamespacesResultProto;
32 import com.google.android.icing.proto.GetOptimizeInfoResultProto;
33 import com.google.android.icing.proto.GetResultProto;
34 import com.google.android.icing.proto.GetResultSpecProto;
35 import com.google.android.icing.proto.GetSchemaResultProto;
36 import com.google.android.icing.proto.GetSchemaTypeResultProto;
37 import com.google.android.icing.proto.IcingSearchEngineOptions;
38 import com.google.android.icing.proto.InitializeResultProto;
39 import com.google.android.icing.proto.LogSeverity;
40 import com.google.android.icing.proto.OptimizeResultProto;
41 import com.google.android.icing.proto.PersistToDiskResultProto;
42 import com.google.android.icing.proto.PersistType;
43 import com.google.android.icing.proto.PropertyConfigProto;
44 import com.google.android.icing.proto.PropertyProto;
45 import com.google.android.icing.proto.PutDocumentRequest;
46 import com.google.android.icing.proto.PutResultProto;
47 import com.google.android.icing.proto.ReportUsageResultProto;
48 import com.google.android.icing.proto.ResetResultProto;
49 import com.google.android.icing.proto.ResultSpecProto;
50 import com.google.android.icing.proto.SchemaProto;
51 import com.google.android.icing.proto.SchemaTypeConfigProto;
52 import com.google.android.icing.proto.ScoringSpecProto;
53 import com.google.android.icing.proto.SearchResultProto;
54 import com.google.android.icing.proto.SearchSpecProto;
55 import com.google.android.icing.proto.SetSchemaRequestProto;
56 import com.google.android.icing.proto.SetSchemaResultProto;
57 import com.google.android.icing.proto.SnippetMatchProto;
58 import com.google.android.icing.proto.SnippetProto;
59 import com.google.android.icing.proto.StatusProto;
60 import com.google.android.icing.proto.StorageInfoResultProto;
61 import com.google.android.icing.proto.StringIndexingConfig;
62 import com.google.android.icing.proto.StringIndexingConfig.TokenizerType;
63 import com.google.android.icing.proto.SuggestionResponse;
64 import com.google.android.icing.proto.SuggestionScoringSpecProto;
65 import com.google.android.icing.proto.SuggestionSpecProto;
66 import com.google.android.icing.proto.TermMatchType;
67 import com.google.android.icing.proto.TermMatchType.Code;
68 import com.google.android.icing.proto.UsageReport;
69 import com.google.protobuf.ByteString;
70 import java.io.File;
71 import java.io.FileDescriptor;
72 import java.io.FileInputStream;
73 import java.io.FileOutputStream;
74 import java.lang.reflect.Field;
75 import java.security.MessageDigest;
76 import java.security.NoSuchAlgorithmException;
77 import java.util.HashMap;
78 import java.util.Map;
79 import java.util.Random;
80 import org.junit.After;
81 import org.junit.Before;
82 import org.junit.Ignore;
83 import org.junit.Rule;
84 import org.junit.Test;
85 import org.junit.rules.TemporaryFolder;
86 import org.junit.runner.RunWith;
87 import org.junit.runners.JUnit4;
88 
89 /**
90  * This test is not intended to fully test the functionality of each API. But rather to test the JNI
91  * wrapper and Java interfaces of Icing library {@link IcingSearchEngine}.
92  */
93 @RunWith(JUnit4.class)
94 public final class IcingSearchEngineTest {
95   @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
96 
97   private static final String SCHEMA_DATABASE_DELIMITER = "/";
98   private static final String EMAIL_TYPE = "Email";
99 
100   private File tempDir;
101 
102   private IcingSearchEngine icingSearchEngine;
103 
createEmailTypeConfig()104   private static SchemaTypeConfigProto createEmailTypeConfig() {
105     return SchemaTypeConfigProto.newBuilder()
106         .setSchemaType(EMAIL_TYPE)
107         .addProperties(
108             PropertyConfigProto.newBuilder()
109                 .setPropertyName("subject")
110                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
111                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
112                 .setStringIndexingConfig(
113                     StringIndexingConfig.newBuilder()
114                         .setTokenizerType(TokenizerType.Code.PLAIN)
115                         .setTermMatchType(TermMatchType.Code.PREFIX)))
116         .addProperties(
117             PropertyConfigProto.newBuilder()
118                 .setPropertyName("body")
119                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
120                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
121                 .setStringIndexingConfig(
122                     StringIndexingConfig.newBuilder()
123                         .setTokenizerType(TokenizerType.Code.PLAIN)
124                         .setTermMatchType(TermMatchType.Code.PREFIX)))
125         .build();
126   }
127 
createEmailTypeConfigWithDatabase(String database)128   private static SchemaTypeConfigProto createEmailTypeConfigWithDatabase(String database) {
129     return SchemaTypeConfigProto.newBuilder()
130         .setSchemaType(database + SCHEMA_DATABASE_DELIMITER + EMAIL_TYPE)
131         .setDatabase(database)
132         .addProperties(
133             PropertyConfigProto.newBuilder()
134                 .setPropertyName("subject")
135                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
136                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
137                 .setStringIndexingConfig(
138                     StringIndexingConfig.newBuilder()
139                         .setTokenizerType(TokenizerType.Code.PLAIN)
140                         .setTermMatchType(TermMatchType.Code.PREFIX)))
141         .addProperties(
142             PropertyConfigProto.newBuilder()
143                 .setPropertyName("body")
144                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
145                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
146                 .setStringIndexingConfig(
147                     StringIndexingConfig.newBuilder()
148                         .setTokenizerType(TokenizerType.Code.PLAIN)
149                         .setTermMatchType(TermMatchType.Code.PREFIX)))
150         .build();
151   }
152 
createEmailDocument(String namespace, String uri)153   private static DocumentProto createEmailDocument(String namespace, String uri) {
154     return DocumentProto.newBuilder()
155         .setNamespace(namespace)
156         .setUri(uri)
157         .setSchema(EMAIL_TYPE)
158         .setCreationTimestampMs(1) // Arbitrary non-zero number so Icing doesn't override it
159         .build();
160   }
161 
162   /** Generate an array contains random bytes for the given length. */
generateRandomBytes(int length)163   private static byte[] generateRandomBytes(int length) {
164     byte[] bytes = new byte[length];
165     Random rd = new Random(); // creating Random object
166     rd.nextBytes(bytes);
167     return bytes;
168   }
169 
170   /** Calculate the sha-256 digest for the given data. */
calculateDigest(byte[] data)171   private static byte[] calculateDigest(byte[] data) throws NoSuchAlgorithmException {
172     MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
173     messageDigest.update(data);
174     return messageDigest.digest();
175   }
176 
177   @Before
setUp()178   public void setUp() throws Exception {
179     tempDir = temporaryFolder.newFolder();
180     IcingSearchEngineOptions options =
181         IcingSearchEngineOptions.newBuilder().setBaseDir(tempDir.getCanonicalPath()).build();
182     icingSearchEngine = new IcingSearchEngine(options);
183   }
184 
185   @After
tearDown()186   public void tearDown() throws Exception {
187     icingSearchEngine.close();
188   }
189 
190   @Test
testInitialize()191   public void testInitialize() throws Exception {
192     InitializeResultProto initializeResultProto = icingSearchEngine.initialize();
193     assertStatusOk(initializeResultProto.getStatus());
194   }
195 
196   @Test
testSetAndGetSchema()197   public void testSetAndGetSchema() throws Exception {
198     assertStatusOk(icingSearchEngine.initialize().getStatus());
199 
200     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
201     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
202     SetSchemaResultProto setSchemaResultProto =
203         icingSearchEngine.setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false);
204     assertStatusOk(setSchemaResultProto.getStatus());
205 
206     GetSchemaResultProto getSchemaResultProto = icingSearchEngine.getSchema();
207     assertStatusOk(getSchemaResultProto.getStatus());
208     assertThat(getSchemaResultProto.getSchema()).isEqualTo(schema);
209 
210     GetSchemaTypeResultProto getSchemaTypeResultProto =
211         icingSearchEngine.getSchemaType(emailTypeConfig.getSchemaType());
212     assertStatusOk(getSchemaTypeResultProto.getStatus());
213     assertThat(getSchemaTypeResultProto.getSchemaTypeConfig()).isEqualTo(emailTypeConfig);
214   }
215 
216   // TODO: b/383379132 - Re-enable this test once the JNI API is pre-registered and dropped back
217   // into g3.
218   @Ignore
219   @Test
setAndGetSchemaWithDatabase_ok()220   public void setAndGetSchemaWithDatabase_ok() throws Exception {
221     IcingSearchEngineOptions options =
222         IcingSearchEngineOptions.newBuilder()
223             .setBaseDir(tempDir.getCanonicalPath())
224             .setEnableSchemaDatabase(true)
225             .build();
226     IcingSearchEngine icingSearchEngine = new IcingSearchEngine(options);
227     assertStatusOk(icingSearchEngine.initialize().getStatus());
228 
229     String db1 = "db1";
230     String db2 = "db2";
231     SchemaProto db1Schema =
232         SchemaProto.newBuilder().addTypes(createEmailTypeConfigWithDatabase(db1)).build();
233     SchemaProto db2Schema =
234         SchemaProto.newBuilder().addTypes(createEmailTypeConfigWithDatabase(db2)).build();
235 
236     SetSchemaRequestProto requestProto1 =
237         SetSchemaRequestProto.newBuilder()
238             .setSchema(db1Schema)
239             .setDatabase(db1)
240             .setIgnoreErrorsAndDeleteDocuments(false)
241             .build();
242     SetSchemaResultProto setSchemaResultProto =
243         icingSearchEngine.setSchemaWithRequestProto(requestProto1);
244     assertStatusOk(setSchemaResultProto.getStatus());
245 
246     SetSchemaRequestProto requestProto2 =
247         SetSchemaRequestProto.newBuilder()
248             .setSchema(db2Schema)
249             .setDatabase(db2)
250             .setIgnoreErrorsAndDeleteDocuments(false)
251             .build();
252     setSchemaResultProto = icingSearchEngine.setSchemaWithRequestProto(requestProto2);
253     assertStatusOk(setSchemaResultProto.getStatus());
254 
255     // Get schema for individual databases.
256     GetSchemaResultProto getSchemaResultProto = icingSearchEngine.getSchemaForDatabase(db1);
257     assertStatusOk(getSchemaResultProto.getStatus());
258     assertThat(getSchemaResultProto.getSchema()).isEqualTo(db1Schema);
259 
260     getSchemaResultProto = icingSearchEngine.getSchemaForDatabase(db2);
261     assertStatusOk(getSchemaResultProto.getStatus());
262     assertThat(getSchemaResultProto.getSchema()).isEqualTo(db2Schema);
263 
264     // The getSchema() API should still return the full schema.
265     SchemaProto fullSchema =
266         SchemaProto.newBuilder()
267             .addTypes(createEmailTypeConfigWithDatabase(db1))
268             .addTypes(createEmailTypeConfigWithDatabase(db2))
269             .build();
270     getSchemaResultProto = icingSearchEngine.getSchema();
271     assertStatusOk(getSchemaResultProto.getStatus());
272     assertThat(getSchemaResultProto.getSchema()).isEqualTo(fullSchema);
273   }
274 
275   @Test
testPutAndGetDocuments()276   public void testPutAndGetDocuments() throws Exception {
277     assertStatusOk(icingSearchEngine.initialize().getStatus());
278 
279     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
280     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
281     assertThat(
282             icingSearchEngine
283                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
284                 .getStatus()
285                 .getCode())
286         .isEqualTo(StatusProto.Code.OK);
287 
288     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
289     PutResultProto putResultProto = icingSearchEngine.put(emailDocument);
290     assertStatusOk(putResultProto.getStatus());
291 
292     GetResultProto getResultProto =
293         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
294     assertStatusOk(getResultProto.getStatus());
295     assertThat(getResultProto.getDocument()).isEqualTo(emailDocument);
296   }
297 
298   @Test
testBatchPutAndGetDocuments()299   public void testBatchPutAndGetDocuments() throws Exception {
300     assertStatusOk(icingSearchEngine.initialize().getStatus());
301 
302     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
303     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
304     assertThat(
305             icingSearchEngine
306                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
307                 .getStatus()
308                 .getCode())
309         .isEqualTo(StatusProto.Code.OK);
310 
311     DocumentProto emailDocument1 = createEmailDocument("namespace", "uri1");
312     DocumentProto emailDocument2 = createEmailDocument("namespace", "uri2");
313     PutDocumentRequest putDocumentRequest =
314         PutDocumentRequest.newBuilder()
315             .addDocuments(emailDocument1)
316             .addDocuments(emailDocument2)
317             .build();
318     BatchPutResultProto batchPutResultProto = icingSearchEngine.batchPut(putDocumentRequest);
319 
320     assertStatusOk(batchPutResultProto.getStatus());
321     assertThat(batchPutResultProto.getPutResultProtos(0).getUri()).isEqualTo("uri1");
322     assertStatusOk(batchPutResultProto.getPutResultProtos(0).getStatus());
323     assertThat(batchPutResultProto.getPutResultProtos(1).getUri()).isEqualTo("uri2");
324     assertStatusOk(batchPutResultProto.getPutResultProtos(1).getStatus());
325 
326     // PersistToDiskResultProto should not be set if persist_type is not set in the
327     // PutDocumentRequest.
328     assertThat(batchPutResultProto.getPersistToDiskResultProto().getStatus().getCode())
329         .isEqualTo(StatusProto.Code.UNKNOWN);
330 
331     GetResultSpecProto getResultSpecProto =
332         GetResultSpecProto.newBuilder()
333             .setNamespaceRequested("namespace")
334             .addIds("uri1")
335             .addIds("uri2")
336             .build();
337     BatchGetResultProto batchGetResultProto = icingSearchEngine.batchGet(getResultSpecProto);
338 
339     assertStatusOk(batchGetResultProto.getStatus());
340     // Check doc1
341     DocumentProto document = batchGetResultProto.getGetResultProtos(0).getDocument();
342     assertStatusOk(batchGetResultProto.getGetResultProtos(0).getStatus());
343     assertThat(document).isEqualTo(emailDocument1);
344     // Check doc2
345     document = batchGetResultProto.getGetResultProtos(1).getDocument();
346     assertStatusOk(batchGetResultProto.getGetResultProtos(1).getStatus());
347     assertThat(document).isEqualTo(emailDocument2);
348   }
349 
350   @Test
testBatchGetWithEmptyResult()351   public void testBatchGetWithEmptyResult() throws Exception {
352     assertStatusOk(icingSearchEngine.initialize().getStatus());
353 
354     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
355     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
356     assertThat(
357             icingSearchEngine
358                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
359                 .getStatus()
360                 .getCode())
361         .isEqualTo(StatusProto.Code.OK);
362 
363     DocumentProto emailDocument1 = createEmailDocument("namespace", "uri1");
364     DocumentProto emailDocument2 = createEmailDocument("namespace", "uri2");
365     PutDocumentRequest putDocumentRequest =
366         PutDocumentRequest.newBuilder()
367             .addDocuments(emailDocument1)
368             .addDocuments(emailDocument2)
369             .build();
370     BatchPutResultProto batchPutResultProto = icingSearchEngine.batchPut(putDocumentRequest);
371     assertStatusOk(batchPutResultProto.getStatus());
372 
373     // no ids.
374     GetResultSpecProto getResultSpecProto =
375         GetResultSpecProto.newBuilder().setNamespaceRequested("namespace").build();
376     BatchGetResultProto batchGetResultProto = icingSearchEngine.batchGet(getResultSpecProto);
377 
378     // Check no doc returned if no ids are specified.
379     assertStatusOk(batchGetResultProto.getStatus());
380     assertThat(batchGetResultProto.getGetResultProtosList()).isEmpty();
381 
382     // empty namespace.
383     getResultSpecProto = GetResultSpecProto.newBuilder().addIds("uri1").build();
384     batchGetResultProto = icingSearchEngine.batchGet(getResultSpecProto);
385     assertStatusOk(batchGetResultProto.getStatus());
386     assertThat(batchGetResultProto.getGetResultProtosList()).hasSize(1);
387     assertThat(batchGetResultProto.getGetResultProtos(0).getStatus().getCode())
388         .isEqualTo(StatusProto.Code.NOT_FOUND);
389 
390     // different namespace.
391     getResultSpecProto =
392         GetResultSpecProto.newBuilder()
393             .setNamespaceRequested("otherNameSpace")
394             .addIds("uri1")
395             .addIds("uri2")
396             .build();
397     batchGetResultProto = icingSearchEngine.batchGet(getResultSpecProto);
398 
399     // Check not found returned if namespace is different.
400     assertStatusOk(batchGetResultProto.getStatus());
401     assertThat(batchGetResultProto.getGetResultProtosList()).hasSize(2);
402     assertThat(batchGetResultProto.getGetResultProtos(0).getStatus().getCode())
403         .isEqualTo(StatusProto.Code.NOT_FOUND);
404     assertThat(batchGetResultProto.getGetResultProtos(1).getStatus().getCode())
405         .isEqualTo(StatusProto.Code.NOT_FOUND);
406   }
407 
408   @Test
testBatchPutWithDuplicatedDocuments()409   public void testBatchPutWithDuplicatedDocuments() throws Exception {
410     assertStatusOk(icingSearchEngine.initialize().getStatus());
411 
412     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
413     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
414     assertThat(
415             icingSearchEngine
416                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
417                 .getStatus()
418                 .getCode())
419         .isEqualTo(StatusProto.Code.OK);
420 
421     // Two docs with same uri.
422     DocumentProto emailDocument1 = createEmailDocument("namespace", "uri");
423     DocumentProto emailDocument2 = createEmailDocument("namespace", "uri");
424     PutDocumentRequest putDocumentRequest =
425         PutDocumentRequest.newBuilder()
426             .addDocuments(emailDocument1)
427             .addDocuments(emailDocument2)
428             .build();
429     BatchPutResultProto batchPutResultProto = icingSearchEngine.batchPut(putDocumentRequest);
430 
431     // We should still get two putResults back. That's intended behavior.
432     assertThat(batchPutResultProto.getPutResultProtosList()).hasSize(2);
433     assertThat(batchPutResultProto.getPutResultProtos(0).getUri()).isEqualTo("uri");
434     assertStatusOk(batchPutResultProto.getPutResultProtos(0).getStatus());
435     assertThat(batchPutResultProto.getPutResultProtos(0).getWasReplacement()).isFalse();
436     assertThat(batchPutResultProto.getPutResultProtos(1).getUri()).isEqualTo("uri");
437     assertStatusOk(batchPutResultProto.getPutResultProtos(1).getStatus());
438     assertThat(batchPutResultProto.getPutResultProtos(1).getWasReplacement()).isTrue();
439 
440     // PersistToDiskResultProto should not be set if persist_type is not set in the
441     // PutDocumentRequest.
442     assertThat(batchPutResultProto.getPersistToDiskResultProto().getStatus().getCode())
443         .isEqualTo(StatusProto.Code.UNKNOWN);
444   }
445 
446   @Test
testBatchPutWithEmptyRequest()447   public void testBatchPutWithEmptyRequest() throws Exception {
448     assertStatusOk(icingSearchEngine.initialize().getStatus());
449 
450     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
451     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
452     assertThat(
453             icingSearchEngine
454                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
455                 .getStatus()
456                 .getCode())
457         .isEqualTo(StatusProto.Code.OK);
458 
459     PutDocumentRequest putDocumentRequest = PutDocumentRequest.getDefaultInstance();
460     BatchPutResultProto batchPutResultProto = icingSearchEngine.batchPut(putDocumentRequest);
461 
462     BatchPutResultProto expected =
463         BatchPutResultProto.newBuilder()
464             .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK))
465             .build();
466     assertThat(batchPutResultProto).isEqualTo(expected);
467 
468     // PersistToDiskResultProto should not be set if persist_type is not set in the
469     // PutDocumentRequest.
470     assertThat(batchPutResultProto.getPersistToDiskResultProto().getStatus().getCode())
471         .isEqualTo(StatusProto.Code.UNKNOWN);
472   }
473 
474   @Test
testBatchPutAndGetDocumentsWithError()475   public void testBatchPutAndGetDocumentsWithError() throws Exception {
476     assertStatusOk(icingSearchEngine.initialize().getStatus());
477 
478     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
479     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
480     assertThat(
481             icingSearchEngine
482                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
483                 .getStatus()
484                 .getCode())
485         .isEqualTo(StatusProto.Code.OK);
486     // Document 1 has no namespace.
487     DocumentProto emailDocument1 = DocumentProto.newBuilder().setUri("uri1").build();
488     DocumentProto emailDocument2 = createEmailDocument("namespace", "uri2");
489     PutDocumentRequest putDocumentRequest =
490         PutDocumentRequest.newBuilder()
491             .addDocuments(emailDocument1)
492             .addDocuments(emailDocument2)
493             .build();
494     BatchPutResultProto batchPutResultProto = icingSearchEngine.batchPut(putDocumentRequest);
495 
496     PutResultProto putResult1 = batchPutResultProto.getPutResultProtos(0);
497     // result0 error as namespace is missing.
498     assertThat(putResult1.getUri()).isEqualTo("uri1");
499     assertWithMessage(putResult1.getStatus().getMessage())
500         .that(putResult1.getStatus().getCode())
501         .isEqualTo(StatusProto.Code.INVALID_ARGUMENT);
502     // result1 is ok.
503     assertThat(batchPutResultProto.getPutResultProtos(1).getUri()).isEqualTo("uri2");
504     assertStatusOk(batchPutResultProto.getPutResultProtos(1).getStatus());
505 
506     // PersistToDiskResultProto should not be set if persist_type is not set in the
507     // PutDocumentRequest.
508     assertThat(batchPutResultProto.getPersistToDiskResultProto().getStatus().getCode())
509         .isEqualTo(StatusProto.Code.UNKNOWN);
510 
511     // Check document 1
512     GetResultProto getResultProto =
513         icingSearchEngine.get("namespace", "uri1", GetResultSpecProto.getDefaultInstance());
514     assertWithMessage(getResultProto.getStatus().getMessage())
515         .that(getResultProto.getStatus().getCode())
516         .isEqualTo(StatusProto.Code.NOT_FOUND);
517     // check document 2
518     getResultProto =
519         icingSearchEngine.get("namespace", "uri2", GetResultSpecProto.getDefaultInstance());
520     assertStatusOk(getResultProto.getStatus());
521     assertThat(getResultProto.getDocument()).isEqualTo(emailDocument2);
522   }
523 
524   @Test
testBatchPutWithPersistToDisk()525   public void testBatchPutWithPersistToDisk() throws Exception {
526     assertStatusOk(icingSearchEngine.initialize().getStatus());
527 
528     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
529     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
530     assertThat(
531             icingSearchEngine
532                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
533                 .getStatus()
534                 .getCode())
535         .isEqualTo(StatusProto.Code.OK);
536 
537     DocumentProto emailDocument1 = createEmailDocument("namespace", "uri1");
538     DocumentProto emailDocument2 = createEmailDocument("namespace", "uri2");
539     PutDocumentRequest putDocumentRequest =
540         PutDocumentRequest.newBuilder()
541             .addDocuments(emailDocument1)
542             .addDocuments(emailDocument2)
543             .setPersistType(PersistType.Code.FULL)
544             .build();
545     BatchPutResultProto batchPutResultProto = icingSearchEngine.batchPut(putDocumentRequest);
546 
547     assertThat(batchPutResultProto.getPutResultProtos(0).getUri()).isEqualTo("uri1");
548     assertStatusOk(batchPutResultProto.getPutResultProtos(0).getStatus());
549     assertThat(batchPutResultProto.getPutResultProtos(1).getUri()).isEqualTo("uri2");
550     assertStatusOk(batchPutResultProto.getPutResultProtos(1).getStatus());
551 
552     // PersistToDisk should be called if persist_type is set in the PutDocumentRequest.
553     assertStatusOk(batchPutResultProto.getPersistToDiskResultProto().getStatus());
554   }
555 
556   @Test
testSearch()557   public void testSearch() throws Exception {
558     assertStatusOk(icingSearchEngine.initialize().getStatus());
559 
560     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
561     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
562     assertThat(
563             icingSearchEngine
564                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
565                 .getStatus()
566                 .getCode())
567         .isEqualTo(StatusProto.Code.OK);
568 
569     DocumentProto emailDocument =
570         createEmailDocument("namespace", "uri").toBuilder()
571             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
572             .build();
573     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
574 
575     SearchSpecProto searchSpec =
576         SearchSpecProto.newBuilder()
577             .setQuery("foo")
578             .setTermMatchType(TermMatchType.Code.PREFIX)
579             .build();
580 
581     SearchResultProto searchResultProto =
582         icingSearchEngine.search(
583             searchSpec,
584             ScoringSpecProto.getDefaultInstance(),
585             ResultSpecProto.getDefaultInstance());
586     assertStatusOk(searchResultProto.getStatus());
587     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
588     assertThat(searchResultProto.getResults(0).getDocument()).isEqualTo(emailDocument);
589 
590     assertThat(searchResultProto.getQueryStats().hasNativeToJavaStartTimestampMs()).isTrue();
591     assertThat(searchResultProto.getQueryStats().hasNativeToJavaJniLatencyMs()).isTrue();
592     assertThat(searchResultProto.getQueryStats().hasJavaToNativeJniLatencyMs()).isTrue();
593     assertThat(searchResultProto.getQueryStats().getNativeToJavaStartTimestampMs())
594         .isGreaterThan(0);
595     assertThat(searchResultProto.getQueryStats().getNativeToJavaJniLatencyMs()).isAtLeast(0);
596     assertThat(searchResultProto.getQueryStats().getJavaToNativeJniLatencyMs()).isAtLeast(0);
597   }
598 
599   @Test
testGetNextPage()600   public void testGetNextPage() throws Exception {
601     assertStatusOk(icingSearchEngine.initialize().getStatus());
602 
603     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
604     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
605     assertThat(
606             icingSearchEngine
607                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
608                 .getStatus()
609                 .getCode())
610         .isEqualTo(StatusProto.Code.OK);
611 
612     Map<String, DocumentProto> documents = new HashMap<>();
613     for (int i = 0; i < 10; i++) {
614       DocumentProto emailDocument =
615           createEmailDocument("namespace", "uri:" + i).toBuilder()
616               .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
617               .build();
618       documents.put("uri:" + i, emailDocument);
619       assertWithMessage(icingSearchEngine.put(emailDocument).getStatus().getMessage())
620           .that(icingSearchEngine.put(emailDocument).getStatus().getCode())
621           .isEqualTo(StatusProto.Code.OK);
622     }
623 
624     SearchSpecProto searchSpec =
625         SearchSpecProto.newBuilder()
626             .setQuery("foo")
627             .setTermMatchType(TermMatchType.Code.PREFIX)
628             .build();
629     ResultSpecProto resultSpecProto = ResultSpecProto.newBuilder().setNumPerPage(1).build();
630 
631     SearchResultProto searchResultProto =
632         icingSearchEngine.search(
633             searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto);
634     assertStatusOk(searchResultProto.getStatus());
635     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
636     DocumentProto resultDocument = searchResultProto.getResults(0).getDocument();
637     assertThat(resultDocument).isEqualTo(documents.remove(resultDocument.getUri()));
638 
639     assertThat(searchResultProto.getQueryStats().hasNativeToJavaStartTimestampMs()).isTrue();
640     assertThat(searchResultProto.getQueryStats().hasNativeToJavaJniLatencyMs()).isTrue();
641     assertThat(searchResultProto.getQueryStats().hasJavaToNativeJniLatencyMs()).isTrue();
642     assertThat(searchResultProto.getQueryStats().getNativeToJavaStartTimestampMs())
643         .isGreaterThan(0);
644     assertThat(searchResultProto.getQueryStats().getNativeToJavaJniLatencyMs()).isAtLeast(0);
645     assertThat(searchResultProto.getQueryStats().getJavaToNativeJniLatencyMs()).isAtLeast(0);
646 
647     // fetch rest pages
648     for (int i = 1; i < 5; i++) {
649       searchResultProto = icingSearchEngine.getNextPage(searchResultProto.getNextPageToken());
650       assertWithMessage(searchResultProto.getStatus().getMessage())
651           .that(searchResultProto.getStatus().getCode())
652           .isEqualTo(StatusProto.Code.OK);
653       assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
654       resultDocument = searchResultProto.getResults(0).getDocument();
655       assertThat(resultDocument).isEqualTo(documents.remove(resultDocument.getUri()));
656     }
657 
658     // invalidate rest result
659     icingSearchEngine.invalidateNextPageToken(searchResultProto.getNextPageToken());
660 
661     searchResultProto = icingSearchEngine.getNextPage(searchResultProto.getNextPageToken());
662     assertStatusOk(searchResultProto.getStatus());
663     assertThat(searchResultProto.getResultsCount()).isEqualTo(0);
664   }
665 
666   @Ignore // b/350530146
667   @Test
writeAndReadBlob_blobContentMatches()668   public void writeAndReadBlob_blobContentMatches() throws Exception {
669     // 1 Arrange: set up IcingSearchEngine with and blob data
670     File tempDir = temporaryFolder.newFolder();
671     IcingSearchEngineOptions options =
672         IcingSearchEngineOptions.newBuilder()
673             .setBaseDir(tempDir.getCanonicalPath())
674             .setEnableBlobStore(true)
675             .build();
676     IcingSearchEngine icing = new IcingSearchEngine(options);
677     assertStatusOk(icing.initialize().getStatus());
678 
679     byte[] data = generateRandomBytes(100); // 10 Bytes
680     byte[] digest = calculateDigest(data);
681     PropertyProto.BlobHandleProto blobHandle =
682         PropertyProto.BlobHandleProto.newBuilder()
683             .setDigest(ByteString.copyFrom(digest))
684             .setNamespace("namespace")
685             .build();
686 
687     // 2 Act: write the blob and read it back.
688     BlobProto openWriteBlobProto = icing.openWriteBlob(blobHandle);
689     assertStatusOk(openWriteBlobProto.getStatus());
690     Field field = FileDescriptor.class.getDeclaredField("fd");
691     field.setAccessible(true); // Make the field accessible
692 
693     // Create a new FileDescriptor object
694     FileDescriptor writeFd = new FileDescriptor();
695 
696     // Set the file descriptor value using reflection
697     field.setInt(writeFd, openWriteBlobProto.getFileDescriptor());
698 
699     try (FileOutputStream outputStream = new FileOutputStream(writeFd)) {
700       outputStream.write(data);
701     }
702 
703     // Commit and read the blob.
704     BlobProto commitBlobProto = icing.commitBlob(blobHandle);
705     assertStatusOk(commitBlobProto.getStatus());
706 
707     BlobProto openReadBlobProto = icing.openReadBlob(blobHandle);
708     assertStatusOk(openReadBlobProto.getStatus());
709 
710     FileDescriptor readFd = new FileDescriptor();
711     field.setInt(readFd, openReadBlobProto.getFileDescriptor());
712     byte[] output = new byte[data.length];
713     try (FileInputStream inputStream = new FileInputStream(readFd)) {
714       inputStream.read(output);
715     }
716 
717     // 3 Assert: the blob content matches.
718     assertThat(output).isEqualTo(data);
719   }
720 
721   @Ignore // b/350530146
722   @Test
removeBlob()723   public void removeBlob() throws Exception {
724     // 1 Arrange: set up IcingSearchEngine with and blob data
725     File tempDir = temporaryFolder.newFolder();
726     IcingSearchEngineOptions options =
727         IcingSearchEngineOptions.newBuilder()
728             .setBaseDir(tempDir.getCanonicalPath())
729             .setEnableBlobStore(true)
730             .build();
731     IcingSearchEngine icing = new IcingSearchEngine(options);
732     assertStatusOk(icing.initialize().getStatus());
733 
734     byte[] data = generateRandomBytes(100); // 10 Bytes
735     byte[] digest = calculateDigest(data);
736     PropertyProto.BlobHandleProto blobHandle =
737         PropertyProto.BlobHandleProto.newBuilder()
738             .setNamespace("ns")
739             .setDigest(ByteString.copyFrom(digest))
740             .build();
741 
742     // 2 Act: write the blob and read it back.
743     BlobProto openWriteBlobProto = icing.openWriteBlob(blobHandle);
744     assertStatusOk(openWriteBlobProto.getStatus());
745     Field field = FileDescriptor.class.getDeclaredField("fd");
746     field.setAccessible(true); // Make the field accessible
747 
748     // Create a new FileDescriptor object
749     FileDescriptor writeFd = new FileDescriptor();
750 
751     // Set the file descriptor value using reflection
752     field.setInt(writeFd, openWriteBlobProto.getFileDescriptor());
753 
754     try (FileOutputStream outputStream = new FileOutputStream(writeFd)) {
755       outputStream.write(data);
756     }
757 
758     // Remove the blob.
759     BlobProto removeBlobProto = icing.removeBlob(blobHandle);
760     assertStatusOk(removeBlobProto.getStatus());
761 
762     // Commit will not found.
763     BlobProto commitBlobProto = icing.commitBlob(blobHandle);
764     assertThat(commitBlobProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
765   }
766 
767   @Test
testDelete()768   public void testDelete() throws Exception {
769     assertStatusOk(icingSearchEngine.initialize().getStatus());
770 
771     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
772     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
773     assertThat(
774             icingSearchEngine
775                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
776                 .getStatus()
777                 .getCode())
778         .isEqualTo(StatusProto.Code.OK);
779 
780     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
781     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
782 
783     DeleteResultProto deleteResultProto = icingSearchEngine.delete("namespace", "uri");
784     assertStatusOk(deleteResultProto.getStatus());
785 
786     GetResultProto getResultProto =
787         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
788     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
789   }
790 
791   @Test
testDeleteByNamespace()792   public void testDeleteByNamespace() throws Exception {
793     assertStatusOk(icingSearchEngine.initialize().getStatus());
794 
795     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
796     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
797     assertThat(
798             icingSearchEngine
799                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
800                 .getStatus()
801                 .getCode())
802         .isEqualTo(StatusProto.Code.OK);
803 
804     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
805     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
806 
807     DeleteByNamespaceResultProto deleteByNamespaceResultProto =
808         icingSearchEngine.deleteByNamespace("namespace");
809     assertStatusOk(deleteByNamespaceResultProto.getStatus());
810 
811     GetResultProto getResultProto =
812         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
813     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
814   }
815 
816   @Test
testDeleteBySchemaType()817   public void testDeleteBySchemaType() throws Exception {
818     assertStatusOk(icingSearchEngine.initialize().getStatus());
819 
820     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
821     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
822     assertThat(
823             icingSearchEngine
824                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
825                 .getStatus()
826                 .getCode())
827         .isEqualTo(StatusProto.Code.OK);
828 
829     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
830     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
831 
832     DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto =
833         icingSearchEngine.deleteBySchemaType(EMAIL_TYPE);
834     assertStatusOk(deleteBySchemaTypeResultProto.getStatus());
835 
836     GetResultProto getResultProto =
837         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
838     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
839   }
840 
841   @Test
testDeleteByQuery()842   public void testDeleteByQuery() throws Exception {
843     assertStatusOk(icingSearchEngine.initialize().getStatus());
844 
845     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
846     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
847     assertThat(
848             icingSearchEngine
849                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
850                 .getStatus()
851                 .getCode())
852         .isEqualTo(StatusProto.Code.OK);
853 
854     DocumentProto emailDocument1 =
855         createEmailDocument("namespace", "uri1").toBuilder()
856             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
857             .build();
858 
859     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
860     DocumentProto emailDocument2 =
861         createEmailDocument("namespace", "uri2").toBuilder()
862             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("bar"))
863             .build();
864 
865     assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus());
866 
867     SearchSpecProto searchSpec =
868         SearchSpecProto.newBuilder()
869             .setQuery("foo")
870             .setTermMatchType(TermMatchType.Code.PREFIX)
871             .build();
872 
873     SearchResultProto searchResultProto =
874         icingSearchEngine.search(
875             searchSpec,
876             ScoringSpecProto.getDefaultInstance(),
877             ResultSpecProto.getDefaultInstance());
878     assertStatusOk(searchResultProto.getStatus());
879     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
880     assertThat(searchResultProto.getResults(0).getDocument()).isEqualTo(emailDocument1);
881 
882     DeleteByQueryResultProto deleteResultProto = icingSearchEngine.deleteByQuery(searchSpec);
883     assertStatusOk(deleteResultProto.getStatus());
884     // By default, the deleteByQuery API does not return the summary about deleted documents, unless
885     // the returnDeletedDocumentInfo parameter is set to true.
886     assertThat(deleteResultProto.getDeletedDocumentsList()).isEmpty();
887 
888     GetResultProto getResultProto =
889         icingSearchEngine.get("namespace", "uri1", GetResultSpecProto.getDefaultInstance());
890     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
891     getResultProto =
892         icingSearchEngine.get("namespace", "uri2", GetResultSpecProto.getDefaultInstance());
893     assertStatusOk(getResultProto.getStatus());
894   }
895 
896   @Test
testDeleteByQueryWithDeletedDocumentInfo()897   public void testDeleteByQueryWithDeletedDocumentInfo() throws Exception {
898     assertStatusOk(icingSearchEngine.initialize().getStatus());
899 
900     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
901     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
902     assertThat(
903             icingSearchEngine
904                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
905                 .getStatus()
906                 .getCode())
907         .isEqualTo(StatusProto.Code.OK);
908 
909     DocumentProto emailDocument1 =
910         createEmailDocument("namespace", "uri1").toBuilder()
911             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
912             .build();
913 
914     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
915     DocumentProto emailDocument2 =
916         createEmailDocument("namespace", "uri2").toBuilder()
917             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("bar"))
918             .build();
919 
920     assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus());
921 
922     SearchSpecProto searchSpec =
923         SearchSpecProto.newBuilder()
924             .setQuery("foo")
925             .setTermMatchType(TermMatchType.Code.PREFIX)
926             .build();
927 
928     DeleteByQueryResultProto deleteResultProto =
929         icingSearchEngine.deleteByQuery(searchSpec, /* returnDeletedDocumentInfo= */ true);
930     assertStatusOk(deleteResultProto.getStatus());
931     DeleteByQueryResultProto.DocumentGroupInfo info =
932         DeleteByQueryResultProto.DocumentGroupInfo.newBuilder()
933             .setNamespace("namespace")
934             .setSchema("Email")
935             .addUris("uri1")
936             .build();
937     assertThat(deleteResultProto.getDeletedDocumentsList()).containsExactly(info);
938 
939     GetResultProto getResultProto =
940         icingSearchEngine.get("namespace", "uri1", GetResultSpecProto.getDefaultInstance());
941     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
942     getResultProto =
943         icingSearchEngine.get("namespace", "uri2", GetResultSpecProto.getDefaultInstance());
944     assertStatusOk(getResultProto.getStatus());
945   }
946 
947   @Test
testPersistToDisk()948   public void testPersistToDisk() throws Exception {
949     assertStatusOk(icingSearchEngine.initialize().getStatus());
950 
951     PersistToDiskResultProto persistToDiskResultProto =
952         icingSearchEngine.persistToDisk(PersistType.Code.LITE);
953     assertStatusOk(persistToDiskResultProto.getStatus());
954   }
955 
956   @Test
testOptimize()957   public void testOptimize() throws Exception {
958     assertStatusOk(icingSearchEngine.initialize().getStatus());
959 
960     OptimizeResultProto optimizeResultProto = icingSearchEngine.optimize();
961     assertStatusOk(optimizeResultProto.getStatus());
962   }
963 
964   @Test
testGetOptimizeInfo()965   public void testGetOptimizeInfo() throws Exception {
966     assertStatusOk(icingSearchEngine.initialize().getStatus());
967 
968     GetOptimizeInfoResultProto getOptimizeInfoResultProto = icingSearchEngine.getOptimizeInfo();
969     assertStatusOk(getOptimizeInfoResultProto.getStatus());
970     assertThat(getOptimizeInfoResultProto.getOptimizableDocs()).isEqualTo(0);
971     assertThat(getOptimizeInfoResultProto.getEstimatedOptimizableBytes()).isEqualTo(0);
972   }
973 
974   @Test
testGetStorageInfo()975   public void testGetStorageInfo() throws Exception {
976     assertStatusOk(icingSearchEngine.initialize().getStatus());
977 
978     StorageInfoResultProto storageInfoResultProto = icingSearchEngine.getStorageInfo();
979     assertStatusOk(storageInfoResultProto.getStatus());
980   }
981 
982   @Test
testGetDebugInfo()983   public void testGetDebugInfo() throws Exception {
984     assertStatusOk(icingSearchEngine.initialize().getStatus());
985 
986     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
987     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
988     assertThat(
989             icingSearchEngine
990                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
991                 .getStatus()
992                 .getCode())
993         .isEqualTo(StatusProto.Code.OK);
994 
995     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
996     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
997 
998     DebugInfoResultProto debugInfoResultProtoBasic =
999         icingSearchEngine.getDebugInfo(DebugInfoVerbosity.Code.BASIC);
1000     assertStatusOk(debugInfoResultProtoBasic.getStatus());
1001     assertThat(debugInfoResultProtoBasic.getDebugInfo().getDocumentInfo().getCorpusInfoList())
1002         .isEmpty(); // because verbosity=BASIC
1003 
1004     DebugInfoResultProto debugInfoResultProtoDetailed =
1005         icingSearchEngine.getDebugInfo(DebugInfoVerbosity.Code.DETAILED);
1006     assertStatusOk(debugInfoResultProtoDetailed.getStatus());
1007     assertThat(debugInfoResultProtoDetailed.getDebugInfo().getDocumentInfo().getCorpusInfoList())
1008         .hasSize(1); // because verbosity=DETAILED
1009   }
1010 
1011   @Test
testGetAllNamespaces()1012   public void testGetAllNamespaces() throws Exception {
1013     assertStatusOk(icingSearchEngine.initialize().getStatus());
1014 
1015     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
1016     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
1017     assertThat(
1018             icingSearchEngine
1019                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
1020                 .getStatus()
1021                 .getCode())
1022         .isEqualTo(StatusProto.Code.OK);
1023 
1024     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
1025     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
1026 
1027     GetAllNamespacesResultProto getAllNamespacesResultProto = icingSearchEngine.getAllNamespaces();
1028     assertStatusOk(getAllNamespacesResultProto.getStatus());
1029     assertThat(getAllNamespacesResultProto.getNamespacesList()).containsExactly("namespace");
1030   }
1031 
1032   @Test
testReset()1033   public void testReset() throws Exception {
1034     assertStatusOk(icingSearchEngine.initialize().getStatus());
1035 
1036     ResetResultProto resetResultProto = icingSearchEngine.reset();
1037     assertStatusOk(resetResultProto.getStatus());
1038   }
1039 
1040   @Test
testReportUsage()1041   public void testReportUsage() throws Exception {
1042     assertStatusOk(icingSearchEngine.initialize().getStatus());
1043 
1044     // Set schema and put a document.
1045     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
1046     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
1047     assertThat(
1048             icingSearchEngine
1049                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
1050                 .getStatus()
1051                 .getCode())
1052         .isEqualTo(StatusProto.Code.OK);
1053 
1054     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
1055     PutResultProto putResultProto = icingSearchEngine.put(emailDocument);
1056     assertStatusOk(putResultProto.getStatus());
1057 
1058     // Report usage
1059     UsageReport usageReport =
1060         UsageReport.newBuilder()
1061             .setDocumentNamespace("namespace")
1062             .setDocumentUri("uri")
1063             .setUsageTimestampMs(1)
1064             .setUsageType(UsageReport.UsageType.USAGE_TYPE1)
1065             .build();
1066     ReportUsageResultProto reportUsageResultProto = icingSearchEngine.reportUsage(usageReport);
1067     assertStatusOk(reportUsageResultProto.getStatus());
1068   }
1069 
1070   @Test
testCJKTSnippets()1071   public void testCJKTSnippets() throws Exception {
1072     assertStatusOk(icingSearchEngine.initialize().getStatus());
1073 
1074     SchemaProto schema = SchemaProto.newBuilder().addTypes(createEmailTypeConfig()).build();
1075     assertStatusOk(
1076         icingSearchEngine
1077             .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
1078             .getStatus());
1079 
1080     // String:     "天是蓝的"
1081     //              ^ ^^ ^
1082     // UTF16 idx:   0 1 2 3
1083     // Breaks into segments: "天", "是", "蓝", "的"
1084     // "The sky is blue"
1085     String chinese = "天是蓝的";
1086     assertThat(chinese.length()).isEqualTo(4);
1087     DocumentProto emailDocument1 =
1088         createEmailDocument("namespace", "uri1").toBuilder()
1089             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues(chinese))
1090             .build();
1091     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
1092 
1093     // Search and request snippet matching but no windowing.
1094     SearchSpecProto searchSpec =
1095         SearchSpecProto.newBuilder()
1096             .setQuery("是")
1097             .setTermMatchType(TermMatchType.Code.PREFIX)
1098             .build();
1099     ResultSpecProto resultSpecProto =
1100         ResultSpecProto.newBuilder()
1101             .setSnippetSpec(
1102                 ResultSpecProto.SnippetSpecProto.newBuilder()
1103                     .setNumToSnippet(Integer.MAX_VALUE)
1104                     .setNumMatchesPerProperty(Integer.MAX_VALUE))
1105             .build();
1106 
1107     // Search and make sure that we got a single successful results
1108     SearchResultProto searchResultProto =
1109         icingSearchEngine.search(
1110             searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto);
1111     assertStatusOk(searchResultProto.getStatus());
1112     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
1113 
1114     // Ensure that one and only one property was matched and it was "subject"
1115     SnippetProto snippetProto = searchResultProto.getResults(0).getSnippet();
1116     assertThat(snippetProto.getEntriesList()).hasSize(1);
1117     SnippetProto.EntryProto entryProto = snippetProto.getEntries(0);
1118     assertThat(entryProto.getPropertyName()).isEqualTo("subject");
1119 
1120     // Get the content for "subject" and see what the match is.
1121     DocumentProto resultDocument = searchResultProto.getResults(0).getDocument();
1122     assertThat(resultDocument.getPropertiesList()).hasSize(1);
1123     PropertyProto subjectProperty = resultDocument.getProperties(0);
1124     assertThat(subjectProperty.getName()).isEqualTo("subject");
1125     assertThat(subjectProperty.getStringValuesList()).hasSize(1);
1126     String content = subjectProperty.getStringValues(0);
1127 
1128     // Ensure that there is one and only one match within "subject"
1129     assertThat(entryProto.getSnippetMatchesList()).hasSize(1);
1130     SnippetMatchProto matchProto = entryProto.getSnippetMatches(0);
1131 
1132     int matchStart = matchProto.getExactMatchUtf16Position();
1133     int matchEnd = matchStart + matchProto.getExactMatchUtf16Length();
1134     assertThat(matchStart).isEqualTo(1);
1135     assertThat(matchEnd).isEqualTo(2);
1136     String match = content.substring(matchStart, matchEnd);
1137     assertThat(match).isEqualTo("是");
1138   }
1139 
1140   @Test
testUtf16MultiByteSnippets()1141   public void testUtf16MultiByteSnippets() throws Exception {
1142     assertStatusOk(icingSearchEngine.initialize().getStatus());
1143 
1144     SchemaProto schema = SchemaProto.newBuilder().addTypes(createEmailTypeConfig()).build();
1145     assertStatusOk(
1146         icingSearchEngine
1147             .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
1148             .getStatus());
1149 
1150     // String:    "���� ���� ��"
1151     //             ^  ^  ^
1152     // UTF16 idx:  0  5  10
1153     // Breaks into segments: "����", "����", "��"
1154     String text = "���� ���� ��";
1155     assertThat(text.length()).isEqualTo(12);
1156     DocumentProto emailDocument1 =
1157         createEmailDocument("namespace", "uri1").toBuilder()
1158             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues(text))
1159             .build();
1160     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
1161 
1162     // Search and request snippet matching but no windowing.
1163     SearchSpecProto searchSpec =
1164         SearchSpecProto.newBuilder()
1165             .setQuery("��")
1166             .setTermMatchType(TermMatchType.Code.PREFIX)
1167             .build();
1168     ResultSpecProto resultSpecProto =
1169         ResultSpecProto.newBuilder()
1170             .setSnippetSpec(
1171                 ResultSpecProto.SnippetSpecProto.newBuilder()
1172                     .setNumToSnippet(Integer.MAX_VALUE)
1173                     .setNumMatchesPerProperty(Integer.MAX_VALUE))
1174             .build();
1175 
1176     // Search and make sure that we got a single successful results
1177     SearchResultProto searchResultProto =
1178         icingSearchEngine.search(
1179             searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto);
1180     assertStatusOk(searchResultProto.getStatus());
1181     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
1182 
1183     // Ensure that one and only one property was matched and it was "subject"
1184     SnippetProto snippetProto = searchResultProto.getResults(0).getSnippet();
1185     assertThat(snippetProto.getEntriesList()).hasSize(1);
1186     SnippetProto.EntryProto entryProto = snippetProto.getEntries(0);
1187     assertThat(entryProto.getPropertyName()).isEqualTo("subject");
1188 
1189     // Get the content for "subject" and see what the match is.
1190     DocumentProto resultDocument = searchResultProto.getResults(0).getDocument();
1191     assertThat(resultDocument.getPropertiesList()).hasSize(1);
1192     PropertyProto subjectProperty = resultDocument.getProperties(0);
1193     assertThat(subjectProperty.getName()).isEqualTo("subject");
1194     assertThat(subjectProperty.getStringValuesList()).hasSize(1);
1195     String content = subjectProperty.getStringValues(0);
1196 
1197     // Ensure that there is one and only one match within "subject"
1198     assertThat(entryProto.getSnippetMatchesList()).hasSize(1);
1199     SnippetMatchProto matchProto = entryProto.getSnippetMatches(0);
1200 
1201     int matchStart = matchProto.getExactMatchUtf16Position();
1202     int matchEnd = matchStart + matchProto.getExactMatchUtf16Length();
1203     assertThat(matchStart).isEqualTo(5);
1204     assertThat(matchEnd).isEqualTo(9);
1205     String match = content.substring(matchStart, matchEnd);
1206     assertThat(match).isEqualTo("����");
1207   }
1208 
1209   @Test
testSearchSuggestions()1210   public void testSearchSuggestions() {
1211     assertStatusOk(icingSearchEngine.initialize().getStatus());
1212 
1213     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
1214     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
1215     assertThat(
1216             icingSearchEngine
1217                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
1218                 .getStatus()
1219                 .getCode())
1220         .isEqualTo(StatusProto.Code.OK);
1221 
1222     DocumentProto emailDocument1 =
1223         createEmailDocument("namespace", "uri1").toBuilder()
1224             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("fo"))
1225             .build();
1226     DocumentProto emailDocument2 =
1227         createEmailDocument("namespace", "uri2").toBuilder()
1228             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
1229             .build();
1230     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
1231     assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus());
1232 
1233     SuggestionSpecProto suggestionSpec =
1234         SuggestionSpecProto.newBuilder()
1235             .setPrefix("f")
1236             .setNumToReturn(10)
1237             .setScoringSpec(
1238                 SuggestionScoringSpecProto.newBuilder()
1239                     .setScoringMatchType(Code.EXACT_ONLY)
1240                     .build())
1241             .build();
1242 
1243     SuggestionResponse response = icingSearchEngine.searchSuggestions(suggestionSpec);
1244     assertStatusOk(response.getStatus());
1245     assertThat(response.getSuggestionsList()).hasSize(2);
1246     assertThat(response.getSuggestions(0).getQuery()).isEqualTo("foo");
1247     assertThat(response.getSuggestions(1).getQuery()).isEqualTo("fo");
1248   }
1249 
1250   @Test
testLogging()1251   public void testLogging() throws Exception {
1252     // Set to INFO
1253     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.INFO)).isTrue();
1254     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.INFO)).isTrue();
1255     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.DBG)).isFalse();
1256 
1257     // Set to WARNING
1258     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.WARNING)).isTrue();
1259     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.WARNING)).isTrue();
1260     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.INFO)).isFalse();
1261 
1262     // Set to DEBUG
1263     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.DBG)).isTrue();
1264     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.DBG)).isTrue();
1265     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE)).isFalse();
1266 
1267     // Set to VERBOSE
1268     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.VERBOSE, (short) 1)).isTrue();
1269     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE, (short) 1)).isTrue();
1270     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE, (short) 2)).isFalse();
1271 
1272     assertThat(IcingSearchEngine.getLoggingTag()).isNotEmpty();
1273   }
1274 
assertStatusOk(StatusProto status)1275   private static void assertStatusOk(StatusProto status) {
1276     assertWithMessage(status.getMessage()).that(status.getCode()).isEqualTo(StatusProto.Code.OK);
1277   }
1278 }
1279