xref: /aosp_15_r20/external/icing/java/tests/instrumentation/src/com/google/android/icing/IcingSearchEngineTest.java (revision 8b6cd535a057e39b3b86660c4aa06c99747c2136)
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.BlobProto;
22 import com.google.android.icing.proto.DebugInfoResultProto;
23 import com.google.android.icing.proto.DebugInfoVerbosity;
24 import com.google.android.icing.proto.DeleteByNamespaceResultProto;
25 import com.google.android.icing.proto.DeleteByQueryResultProto;
26 import com.google.android.icing.proto.DeleteBySchemaTypeResultProto;
27 import com.google.android.icing.proto.DeleteResultProto;
28 import com.google.android.icing.proto.DocumentProto;
29 import com.google.android.icing.proto.GetAllNamespacesResultProto;
30 import com.google.android.icing.proto.GetOptimizeInfoResultProto;
31 import com.google.android.icing.proto.GetResultProto;
32 import com.google.android.icing.proto.GetResultSpecProto;
33 import com.google.android.icing.proto.GetSchemaResultProto;
34 import com.google.android.icing.proto.GetSchemaTypeResultProto;
35 import com.google.android.icing.proto.IcingSearchEngineOptions;
36 import com.google.android.icing.proto.InitializeResultProto;
37 import com.google.android.icing.proto.LogSeverity;
38 import com.google.android.icing.proto.OptimizeResultProto;
39 import com.google.android.icing.proto.PersistToDiskResultProto;
40 import com.google.android.icing.proto.PersistType;
41 import com.google.android.icing.proto.PropertyConfigProto;
42 import com.google.android.icing.proto.PropertyProto;
43 import com.google.android.icing.proto.PutResultProto;
44 import com.google.android.icing.proto.ReportUsageResultProto;
45 import com.google.android.icing.proto.ResetResultProto;
46 import com.google.android.icing.proto.ResultSpecProto;
47 import com.google.android.icing.proto.SchemaProto;
48 import com.google.android.icing.proto.SchemaTypeConfigProto;
49 import com.google.android.icing.proto.ScoringSpecProto;
50 import com.google.android.icing.proto.SearchResultProto;
51 import com.google.android.icing.proto.SearchSpecProto;
52 import com.google.android.icing.proto.SetSchemaResultProto;
53 import com.google.android.icing.proto.SnippetMatchProto;
54 import com.google.android.icing.proto.SnippetProto;
55 import com.google.android.icing.proto.StatusProto;
56 import com.google.android.icing.proto.StorageInfoResultProto;
57 import com.google.android.icing.proto.StringIndexingConfig;
58 import com.google.android.icing.proto.StringIndexingConfig.TokenizerType;
59 import com.google.android.icing.proto.SuggestionResponse;
60 import com.google.android.icing.proto.SuggestionScoringSpecProto;
61 import com.google.android.icing.proto.SuggestionSpecProto;
62 import com.google.android.icing.proto.TermMatchType;
63 import com.google.android.icing.proto.TermMatchType.Code;
64 import com.google.android.icing.proto.UsageReport;
65 import com.google.protobuf.ByteString;
66 import java.io.File;
67 import java.io.FileDescriptor;
68 import java.io.FileInputStream;
69 import java.io.FileOutputStream;
70 import java.lang.reflect.Field;
71 import java.security.MessageDigest;
72 import java.security.NoSuchAlgorithmException;
73 import java.util.HashMap;
74 import java.util.Map;
75 import java.util.Random;
76 import org.junit.After;
77 import org.junit.Before;
78 import org.junit.Ignore;
79 import org.junit.Rule;
80 import org.junit.Test;
81 import org.junit.rules.TemporaryFolder;
82 import org.junit.runner.RunWith;
83 import org.junit.runners.JUnit4;
84 
85 /**
86  * This test is not intended to fully test the functionality of each API. But rather to test the JNI
87  * wrapper and Java interfaces of Icing library {@link IcingSearchEngine}.
88  */
89 @RunWith(JUnit4.class)
90 public final class IcingSearchEngineTest {
91   @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
92 
93   private static final String SCHEMA_DATABASE_DELIMITER = "/";
94   private static final String EMAIL_TYPE = "Email";
95 
96   private File tempDir;
97 
98   private IcingSearchEngine icingSearchEngine;
99 
createEmailTypeConfig()100   private static SchemaTypeConfigProto createEmailTypeConfig() {
101     return SchemaTypeConfigProto.newBuilder()
102         .setSchemaType(EMAIL_TYPE)
103         .addProperties(
104             PropertyConfigProto.newBuilder()
105                 .setPropertyName("subject")
106                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
107                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
108                 .setStringIndexingConfig(
109                     StringIndexingConfig.newBuilder()
110                         .setTokenizerType(TokenizerType.Code.PLAIN)
111                         .setTermMatchType(TermMatchType.Code.PREFIX)))
112         .addProperties(
113             PropertyConfigProto.newBuilder()
114                 .setPropertyName("body")
115                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
116                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
117                 .setStringIndexingConfig(
118                     StringIndexingConfig.newBuilder()
119                         .setTokenizerType(TokenizerType.Code.PLAIN)
120                         .setTermMatchType(TermMatchType.Code.PREFIX)))
121         .build();
122   }
123 
createEmailTypeConfigWithDatabase(String database)124   private static SchemaTypeConfigProto createEmailTypeConfigWithDatabase(String database) {
125     return SchemaTypeConfigProto.newBuilder()
126         .setSchemaType(database + SCHEMA_DATABASE_DELIMITER + EMAIL_TYPE)
127         .setDatabase(database)
128         .addProperties(
129             PropertyConfigProto.newBuilder()
130                 .setPropertyName("subject")
131                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
132                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
133                 .setStringIndexingConfig(
134                     StringIndexingConfig.newBuilder()
135                         .setTokenizerType(TokenizerType.Code.PLAIN)
136                         .setTermMatchType(TermMatchType.Code.PREFIX)))
137         .addProperties(
138             PropertyConfigProto.newBuilder()
139                 .setPropertyName("body")
140                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
141                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
142                 .setStringIndexingConfig(
143                     StringIndexingConfig.newBuilder()
144                         .setTokenizerType(TokenizerType.Code.PLAIN)
145                         .setTermMatchType(TermMatchType.Code.PREFIX)))
146         .build();
147   }
148 
createEmailDocument(String namespace, String uri)149   private static DocumentProto createEmailDocument(String namespace, String uri) {
150     return DocumentProto.newBuilder()
151         .setNamespace(namespace)
152         .setUri(uri)
153         .setSchema(EMAIL_TYPE)
154         .setCreationTimestampMs(1) // Arbitrary non-zero number so Icing doesn't override it
155         .build();
156   }
157 
158   /** Generate an array contains random bytes for the given length. */
generateRandomBytes(int length)159   private static byte[] generateRandomBytes(int length) {
160     byte[] bytes = new byte[length];
161     Random rd = new Random(); // creating Random object
162     rd.nextBytes(bytes);
163     return bytes;
164   }
165 
166   /** Calculate the sha-256 digest for the given data. */
calculateDigest(byte[] data)167   private static byte[] calculateDigest(byte[] data) throws NoSuchAlgorithmException {
168     MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
169     messageDigest.update(data);
170     return messageDigest.digest();
171   }
172 
173   @Before
setUp()174   public void setUp() throws Exception {
175     tempDir = temporaryFolder.newFolder();
176     IcingSearchEngineOptions options =
177         IcingSearchEngineOptions.newBuilder().setBaseDir(tempDir.getCanonicalPath()).build();
178     icingSearchEngine = new IcingSearchEngine(options);
179   }
180 
181   @After
tearDown()182   public void tearDown() throws Exception {
183     icingSearchEngine.close();
184   }
185 
186   @Test
testInitialize()187   public void testInitialize() throws Exception {
188     InitializeResultProto initializeResultProto = icingSearchEngine.initialize();
189     assertStatusOk(initializeResultProto.getStatus());
190   }
191 
192   @Test
testSetAndGetSchema()193   public void testSetAndGetSchema() throws Exception {
194     assertStatusOk(icingSearchEngine.initialize().getStatus());
195 
196     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
197     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
198     SetSchemaResultProto setSchemaResultProto =
199         icingSearchEngine.setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false);
200     assertStatusOk(setSchemaResultProto.getStatus());
201 
202     GetSchemaResultProto getSchemaResultProto = icingSearchEngine.getSchema();
203     assertStatusOk(getSchemaResultProto.getStatus());
204     assertThat(getSchemaResultProto.getSchema()).isEqualTo(schema);
205 
206     GetSchemaTypeResultProto getSchemaTypeResultProto =
207         icingSearchEngine.getSchemaType(emailTypeConfig.getSchemaType());
208     assertStatusOk(getSchemaTypeResultProto.getStatus());
209     assertThat(getSchemaTypeResultProto.getSchemaTypeConfig()).isEqualTo(emailTypeConfig);
210   }
211 
212   // TODO(b/337913932) re-enable this test after we preregister this API in jni
213   @Ignore
214   @Test
setAndGetSchemaWithDatabase_ok()215   public void setAndGetSchemaWithDatabase_ok() throws Exception {
216     IcingSearchEngineOptions options =
217         IcingSearchEngineOptions.newBuilder()
218             .setBaseDir(tempDir.getCanonicalPath())
219             .setEnableSchemaDatabase(true)
220             .build();
221     IcingSearchEngine icingSearchEngine = new IcingSearchEngine(options);
222     assertStatusOk(icingSearchEngine.initialize().getStatus());
223 
224     String db1 = "db1";
225     String db2 = "db2";
226     SchemaProto db1Schema =
227         SchemaProto.newBuilder().addTypes(createEmailTypeConfigWithDatabase(db1)).build();
228     SchemaProto db2Schema =
229         SchemaProto.newBuilder().addTypes(createEmailTypeConfigWithDatabase(db2)).build();
230 
231     SetSchemaResultProto setSchemaResultProto =
232         icingSearchEngine.setSchema(db1Schema, /* ignoreErrorsAndDeleteDocuments= */ false);
233     assertStatusOk(setSchemaResultProto.getStatus());
234     setSchemaResultProto =
235         icingSearchEngine.setSchema(db2Schema, /* ignoreErrorsAndDeleteDocuments= */ false);
236     assertStatusOk(setSchemaResultProto.getStatus());
237 
238     // Get schema for individual databases.
239     GetSchemaResultProto getSchemaResultProto = icingSearchEngine.getSchemaForDatabase(db1);
240     assertStatusOk(getSchemaResultProto.getStatus());
241     assertThat(getSchemaResultProto.getSchema()).isEqualTo(db1Schema);
242 
243     getSchemaResultProto = icingSearchEngine.getSchemaForDatabase(db2);
244     assertStatusOk(getSchemaResultProto.getStatus());
245     assertThat(getSchemaResultProto.getSchema()).isEqualTo(db2Schema);
246 
247     // The getSchema() API should still return the full schema.
248     SchemaProto fullSchema =
249         SchemaProto.newBuilder()
250             .addTypes(createEmailTypeConfigWithDatabase(db1))
251             .addTypes(createEmailTypeConfigWithDatabase(db2))
252             .build();
253     getSchemaResultProto = icingSearchEngine.getSchema();
254     assertStatusOk(getSchemaResultProto.getStatus());
255     assertThat(getSchemaResultProto.getSchema()).isEqualTo(fullSchema);
256   }
257 
258   @Test
testPutAndGetDocuments()259   public void testPutAndGetDocuments() throws Exception {
260     assertStatusOk(icingSearchEngine.initialize().getStatus());
261 
262     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
263     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
264     assertThat(
265             icingSearchEngine
266                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
267                 .getStatus()
268                 .getCode())
269         .isEqualTo(StatusProto.Code.OK);
270 
271     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
272     PutResultProto putResultProto = icingSearchEngine.put(emailDocument);
273     assertStatusOk(putResultProto.getStatus());
274 
275     GetResultProto getResultProto =
276         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
277     assertStatusOk(getResultProto.getStatus());
278     assertThat(getResultProto.getDocument()).isEqualTo(emailDocument);
279   }
280 
281   @Test
testSearch()282   public void testSearch() throws Exception {
283     assertStatusOk(icingSearchEngine.initialize().getStatus());
284 
285     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
286     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
287     assertThat(
288             icingSearchEngine
289                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
290                 .getStatus()
291                 .getCode())
292         .isEqualTo(StatusProto.Code.OK);
293 
294     DocumentProto emailDocument =
295         createEmailDocument("namespace", "uri").toBuilder()
296             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
297             .build();
298     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
299 
300     SearchSpecProto searchSpec =
301         SearchSpecProto.newBuilder()
302             .setQuery("foo")
303             .setTermMatchType(TermMatchType.Code.PREFIX)
304             .build();
305 
306     SearchResultProto searchResultProto =
307         icingSearchEngine.search(
308             searchSpec,
309             ScoringSpecProto.getDefaultInstance(),
310             ResultSpecProto.getDefaultInstance());
311     assertStatusOk(searchResultProto.getStatus());
312     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
313     assertThat(searchResultProto.getResults(0).getDocument()).isEqualTo(emailDocument);
314 
315     assertThat(searchResultProto.getQueryStats().hasNativeToJavaStartTimestampMs()).isTrue();
316     assertThat(searchResultProto.getQueryStats().hasNativeToJavaJniLatencyMs()).isTrue();
317     assertThat(searchResultProto.getQueryStats().hasJavaToNativeJniLatencyMs()).isTrue();
318     assertThat(searchResultProto.getQueryStats().getNativeToJavaStartTimestampMs())
319         .isGreaterThan(0);
320     assertThat(searchResultProto.getQueryStats().getNativeToJavaJniLatencyMs()).isAtLeast(0);
321     assertThat(searchResultProto.getQueryStats().getJavaToNativeJniLatencyMs()).isAtLeast(0);
322   }
323 
324   @Test
testGetNextPage()325   public void testGetNextPage() throws Exception {
326     assertStatusOk(icingSearchEngine.initialize().getStatus());
327 
328     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
329     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
330     assertThat(
331             icingSearchEngine
332                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
333                 .getStatus()
334                 .getCode())
335         .isEqualTo(StatusProto.Code.OK);
336 
337     Map<String, DocumentProto> documents = new HashMap<>();
338     for (int i = 0; i < 10; i++) {
339       DocumentProto emailDocument =
340           createEmailDocument("namespace", "uri:" + i).toBuilder()
341               .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
342               .build();
343       documents.put("uri:" + i, emailDocument);
344       assertWithMessage(icingSearchEngine.put(emailDocument).getStatus().getMessage())
345           .that(icingSearchEngine.put(emailDocument).getStatus().getCode())
346           .isEqualTo(StatusProto.Code.OK);
347     }
348 
349     SearchSpecProto searchSpec =
350         SearchSpecProto.newBuilder()
351             .setQuery("foo")
352             .setTermMatchType(TermMatchType.Code.PREFIX)
353             .build();
354     ResultSpecProto resultSpecProto = ResultSpecProto.newBuilder().setNumPerPage(1).build();
355 
356     SearchResultProto searchResultProto =
357         icingSearchEngine.search(
358             searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto);
359     assertStatusOk(searchResultProto.getStatus());
360     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
361     DocumentProto resultDocument = searchResultProto.getResults(0).getDocument();
362     assertThat(resultDocument).isEqualTo(documents.remove(resultDocument.getUri()));
363 
364     assertThat(searchResultProto.getQueryStats().hasNativeToJavaStartTimestampMs()).isTrue();
365     assertThat(searchResultProto.getQueryStats().hasNativeToJavaJniLatencyMs()).isTrue();
366     assertThat(searchResultProto.getQueryStats().hasJavaToNativeJniLatencyMs()).isTrue();
367     assertThat(searchResultProto.getQueryStats().getNativeToJavaStartTimestampMs())
368         .isGreaterThan(0);
369     assertThat(searchResultProto.getQueryStats().getNativeToJavaJniLatencyMs()).isAtLeast(0);
370     assertThat(searchResultProto.getQueryStats().getJavaToNativeJniLatencyMs()).isAtLeast(0);
371 
372     // fetch rest pages
373     for (int i = 1; i < 5; i++) {
374       searchResultProto = icingSearchEngine.getNextPage(searchResultProto.getNextPageToken());
375       assertWithMessage(searchResultProto.getStatus().getMessage())
376           .that(searchResultProto.getStatus().getCode())
377           .isEqualTo(StatusProto.Code.OK);
378       assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
379       resultDocument = searchResultProto.getResults(0).getDocument();
380       assertThat(resultDocument).isEqualTo(documents.remove(resultDocument.getUri()));
381     }
382 
383     // invalidate rest result
384     icingSearchEngine.invalidateNextPageToken(searchResultProto.getNextPageToken());
385 
386     searchResultProto = icingSearchEngine.getNextPage(searchResultProto.getNextPageToken());
387     assertStatusOk(searchResultProto.getStatus());
388     assertThat(searchResultProto.getResultsCount()).isEqualTo(0);
389   }
390 
391   @Ignore // b/350530146
392   @Test
writeAndReadBlob_blobContentMatches()393   public void writeAndReadBlob_blobContentMatches() throws Exception {
394     // 1 Arrange: set up IcingSearchEngine with and blob data
395     File tempDir = temporaryFolder.newFolder();
396     IcingSearchEngineOptions options =
397         IcingSearchEngineOptions.newBuilder()
398             .setBaseDir(tempDir.getCanonicalPath())
399             .setEnableBlobStore(true)
400             .build();
401     IcingSearchEngine icing = new IcingSearchEngine(options);
402     assertStatusOk(icing.initialize().getStatus());
403 
404     byte[] data = generateRandomBytes(100); // 10 Bytes
405     byte[] digest = calculateDigest(data);
406     PropertyProto.BlobHandleProto blobHandle =
407         PropertyProto.BlobHandleProto.newBuilder()
408             .setDigest(ByteString.copyFrom(digest))
409             .setNamespace("namespace")
410             .build();
411 
412     // 2 Act: write the blob and read it back.
413     BlobProto openWriteBlobProto = icing.openWriteBlob(blobHandle);
414     assertStatusOk(openWriteBlobProto.getStatus());
415     Field field = FileDescriptor.class.getDeclaredField("fd");
416     field.setAccessible(true); // Make the field accessible
417 
418     // Create a new FileDescriptor object
419     FileDescriptor writeFd = new FileDescriptor();
420 
421     // Set the file descriptor value using reflection
422     field.setInt(writeFd, openWriteBlobProto.getFileDescriptor());
423 
424     try (FileOutputStream outputStream = new FileOutputStream(writeFd)) {
425       outputStream.write(data);
426     }
427 
428     // Commit and read the blob.
429     BlobProto commitBlobProto = icing.commitBlob(blobHandle);
430     assertStatusOk(commitBlobProto.getStatus());
431 
432     BlobProto openReadBlobProto = icing.openReadBlob(blobHandle);
433     assertStatusOk(openReadBlobProto.getStatus());
434 
435     FileDescriptor readFd = new FileDescriptor();
436     field.setInt(readFd, openReadBlobProto.getFileDescriptor());
437     byte[] output = new byte[data.length];
438     try (FileInputStream inputStream = new FileInputStream(readFd)) {
439       inputStream.read(output);
440     }
441 
442     // 3 Assert: the blob content matches.
443     assertThat(output).isEqualTo(data);
444   }
445 
446   @Ignore // b/350530146
447   @Test
removeBlob()448   public void removeBlob() throws Exception {
449     // 1 Arrange: set up IcingSearchEngine with and blob data
450     File tempDir = temporaryFolder.newFolder();
451     IcingSearchEngineOptions options =
452         IcingSearchEngineOptions.newBuilder()
453             .setBaseDir(tempDir.getCanonicalPath())
454             .setEnableBlobStore(true)
455             .build();
456     IcingSearchEngine icing = new IcingSearchEngine(options);
457     assertStatusOk(icing.initialize().getStatus());
458 
459     byte[] data = generateRandomBytes(100); // 10 Bytes
460     byte[] digest = calculateDigest(data);
461     PropertyProto.BlobHandleProto blobHandle =
462         PropertyProto.BlobHandleProto.newBuilder()
463             .setNamespace("ns")
464             .setDigest(ByteString.copyFrom(digest))
465             .build();
466 
467     // 2 Act: write the blob and read it back.
468     BlobProto openWriteBlobProto = icing.openWriteBlob(blobHandle);
469     assertStatusOk(openWriteBlobProto.getStatus());
470     Field field = FileDescriptor.class.getDeclaredField("fd");
471     field.setAccessible(true); // Make the field accessible
472 
473     // Create a new FileDescriptor object
474     FileDescriptor writeFd = new FileDescriptor();
475 
476     // Set the file descriptor value using reflection
477     field.setInt(writeFd, openWriteBlobProto.getFileDescriptor());
478 
479     try (FileOutputStream outputStream = new FileOutputStream(writeFd)) {
480       outputStream.write(data);
481     }
482 
483     // Remove the blob.
484     BlobProto removeBlobProto = icing.removeBlob(blobHandle);
485     assertStatusOk(removeBlobProto.getStatus());
486 
487     // Commit will not found.
488     BlobProto commitBlobProto = icing.commitBlob(blobHandle);
489     assertThat(commitBlobProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
490   }
491 
492   @Test
testDelete()493   public void testDelete() throws Exception {
494     assertStatusOk(icingSearchEngine.initialize().getStatus());
495 
496     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
497     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
498     assertThat(
499             icingSearchEngine
500                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
501                 .getStatus()
502                 .getCode())
503         .isEqualTo(StatusProto.Code.OK);
504 
505     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
506     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
507 
508     DeleteResultProto deleteResultProto = icingSearchEngine.delete("namespace", "uri");
509     assertStatusOk(deleteResultProto.getStatus());
510 
511     GetResultProto getResultProto =
512         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
513     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
514   }
515 
516   @Test
testDeleteByNamespace()517   public void testDeleteByNamespace() throws Exception {
518     assertStatusOk(icingSearchEngine.initialize().getStatus());
519 
520     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
521     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
522     assertThat(
523             icingSearchEngine
524                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
525                 .getStatus()
526                 .getCode())
527         .isEqualTo(StatusProto.Code.OK);
528 
529     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
530     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
531 
532     DeleteByNamespaceResultProto deleteByNamespaceResultProto =
533         icingSearchEngine.deleteByNamespace("namespace");
534     assertStatusOk(deleteByNamespaceResultProto.getStatus());
535 
536     GetResultProto getResultProto =
537         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
538     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
539   }
540 
541   @Test
testDeleteBySchemaType()542   public void testDeleteBySchemaType() throws Exception {
543     assertStatusOk(icingSearchEngine.initialize().getStatus());
544 
545     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
546     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
547     assertThat(
548             icingSearchEngine
549                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
550                 .getStatus()
551                 .getCode())
552         .isEqualTo(StatusProto.Code.OK);
553 
554     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
555     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
556 
557     DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto =
558         icingSearchEngine.deleteBySchemaType(EMAIL_TYPE);
559     assertStatusOk(deleteBySchemaTypeResultProto.getStatus());
560 
561     GetResultProto getResultProto =
562         icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance());
563     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
564   }
565 
566   @Test
testDeleteByQuery()567   public void testDeleteByQuery() throws Exception {
568     assertStatusOk(icingSearchEngine.initialize().getStatus());
569 
570     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
571     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
572     assertThat(
573             icingSearchEngine
574                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
575                 .getStatus()
576                 .getCode())
577         .isEqualTo(StatusProto.Code.OK);
578 
579     DocumentProto emailDocument1 =
580         createEmailDocument("namespace", "uri1").toBuilder()
581             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
582             .build();
583 
584     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
585     DocumentProto emailDocument2 =
586         createEmailDocument("namespace", "uri2").toBuilder()
587             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("bar"))
588             .build();
589 
590     assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus());
591 
592     SearchSpecProto searchSpec =
593         SearchSpecProto.newBuilder()
594             .setQuery("foo")
595             .setTermMatchType(TermMatchType.Code.PREFIX)
596             .build();
597 
598     SearchResultProto searchResultProto =
599         icingSearchEngine.search(
600             searchSpec,
601             ScoringSpecProto.getDefaultInstance(),
602             ResultSpecProto.getDefaultInstance());
603     assertStatusOk(searchResultProto.getStatus());
604     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
605     assertThat(searchResultProto.getResults(0).getDocument()).isEqualTo(emailDocument1);
606 
607     DeleteByQueryResultProto deleteResultProto = icingSearchEngine.deleteByQuery(searchSpec);
608     assertStatusOk(deleteResultProto.getStatus());
609     // By default, the deleteByQuery API does not return the summary about deleted documents, unless
610     // the returnDeletedDocumentInfo parameter is set to true.
611     assertThat(deleteResultProto.getDeletedDocumentsList()).isEmpty();
612 
613     GetResultProto getResultProto =
614         icingSearchEngine.get("namespace", "uri1", GetResultSpecProto.getDefaultInstance());
615     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
616     getResultProto =
617         icingSearchEngine.get("namespace", "uri2", GetResultSpecProto.getDefaultInstance());
618     assertStatusOk(getResultProto.getStatus());
619   }
620 
621   @Test
testDeleteByQueryWithDeletedDocumentInfo()622   public void testDeleteByQueryWithDeletedDocumentInfo() throws Exception {
623     assertStatusOk(icingSearchEngine.initialize().getStatus());
624 
625     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
626     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
627     assertThat(
628             icingSearchEngine
629                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
630                 .getStatus()
631                 .getCode())
632         .isEqualTo(StatusProto.Code.OK);
633 
634     DocumentProto emailDocument1 =
635         createEmailDocument("namespace", "uri1").toBuilder()
636             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
637             .build();
638 
639     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
640     DocumentProto emailDocument2 =
641         createEmailDocument("namespace", "uri2").toBuilder()
642             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("bar"))
643             .build();
644 
645     assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus());
646 
647     SearchSpecProto searchSpec =
648         SearchSpecProto.newBuilder()
649             .setQuery("foo")
650             .setTermMatchType(TermMatchType.Code.PREFIX)
651             .build();
652 
653     DeleteByQueryResultProto deleteResultProto =
654         icingSearchEngine.deleteByQuery(searchSpec, /* returnDeletedDocumentInfo= */ true);
655     assertStatusOk(deleteResultProto.getStatus());
656     DeleteByQueryResultProto.DocumentGroupInfo info =
657         DeleteByQueryResultProto.DocumentGroupInfo.newBuilder()
658             .setNamespace("namespace")
659             .setSchema("Email")
660             .addUris("uri1")
661             .build();
662     assertThat(deleteResultProto.getDeletedDocumentsList()).containsExactly(info);
663 
664     GetResultProto getResultProto =
665         icingSearchEngine.get("namespace", "uri1", GetResultSpecProto.getDefaultInstance());
666     assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND);
667     getResultProto =
668         icingSearchEngine.get("namespace", "uri2", GetResultSpecProto.getDefaultInstance());
669     assertStatusOk(getResultProto.getStatus());
670   }
671 
672   @Test
testPersistToDisk()673   public void testPersistToDisk() throws Exception {
674     assertStatusOk(icingSearchEngine.initialize().getStatus());
675 
676     PersistToDiskResultProto persistToDiskResultProto =
677         icingSearchEngine.persistToDisk(PersistType.Code.LITE);
678     assertStatusOk(persistToDiskResultProto.getStatus());
679   }
680 
681   @Test
testOptimize()682   public void testOptimize() throws Exception {
683     assertStatusOk(icingSearchEngine.initialize().getStatus());
684 
685     OptimizeResultProto optimizeResultProto = icingSearchEngine.optimize();
686     assertStatusOk(optimizeResultProto.getStatus());
687   }
688 
689   @Test
testGetOptimizeInfo()690   public void testGetOptimizeInfo() throws Exception {
691     assertStatusOk(icingSearchEngine.initialize().getStatus());
692 
693     GetOptimizeInfoResultProto getOptimizeInfoResultProto = icingSearchEngine.getOptimizeInfo();
694     assertStatusOk(getOptimizeInfoResultProto.getStatus());
695     assertThat(getOptimizeInfoResultProto.getOptimizableDocs()).isEqualTo(0);
696     assertThat(getOptimizeInfoResultProto.getEstimatedOptimizableBytes()).isEqualTo(0);
697   }
698 
699   @Test
testGetStorageInfo()700   public void testGetStorageInfo() throws Exception {
701     assertStatusOk(icingSearchEngine.initialize().getStatus());
702 
703     StorageInfoResultProto storageInfoResultProto = icingSearchEngine.getStorageInfo();
704     assertStatusOk(storageInfoResultProto.getStatus());
705   }
706 
707   @Test
testGetDebugInfo()708   public void testGetDebugInfo() throws Exception {
709     assertStatusOk(icingSearchEngine.initialize().getStatus());
710 
711     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
712     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
713     assertThat(
714             icingSearchEngine
715                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
716                 .getStatus()
717                 .getCode())
718         .isEqualTo(StatusProto.Code.OK);
719 
720     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
721     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
722 
723     DebugInfoResultProto debugInfoResultProtoBasic =
724         icingSearchEngine.getDebugInfo(DebugInfoVerbosity.Code.BASIC);
725     assertStatusOk(debugInfoResultProtoBasic.getStatus());
726     assertThat(debugInfoResultProtoBasic.getDebugInfo().getDocumentInfo().getCorpusInfoList())
727         .isEmpty(); // because verbosity=BASIC
728 
729     DebugInfoResultProto debugInfoResultProtoDetailed =
730         icingSearchEngine.getDebugInfo(DebugInfoVerbosity.Code.DETAILED);
731     assertStatusOk(debugInfoResultProtoDetailed.getStatus());
732     assertThat(debugInfoResultProtoDetailed.getDebugInfo().getDocumentInfo().getCorpusInfoList())
733         .hasSize(1); // because verbosity=DETAILED
734   }
735 
736   @Test
testGetAllNamespaces()737   public void testGetAllNamespaces() throws Exception {
738     assertStatusOk(icingSearchEngine.initialize().getStatus());
739 
740     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
741     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
742     assertThat(
743             icingSearchEngine
744                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
745                 .getStatus()
746                 .getCode())
747         .isEqualTo(StatusProto.Code.OK);
748 
749     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
750     assertStatusOk(icingSearchEngine.put(emailDocument).getStatus());
751 
752     GetAllNamespacesResultProto getAllNamespacesResultProto = icingSearchEngine.getAllNamespaces();
753     assertStatusOk(getAllNamespacesResultProto.getStatus());
754     assertThat(getAllNamespacesResultProto.getNamespacesList()).containsExactly("namespace");
755   }
756 
757   @Test
testReset()758   public void testReset() throws Exception {
759     assertStatusOk(icingSearchEngine.initialize().getStatus());
760 
761     ResetResultProto resetResultProto = icingSearchEngine.reset();
762     assertStatusOk(resetResultProto.getStatus());
763   }
764 
765   @Test
testReportUsage()766   public void testReportUsage() throws Exception {
767     assertStatusOk(icingSearchEngine.initialize().getStatus());
768 
769     // Set schema and put a document.
770     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
771     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
772     assertThat(
773             icingSearchEngine
774                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
775                 .getStatus()
776                 .getCode())
777         .isEqualTo(StatusProto.Code.OK);
778 
779     DocumentProto emailDocument = createEmailDocument("namespace", "uri");
780     PutResultProto putResultProto = icingSearchEngine.put(emailDocument);
781     assertStatusOk(putResultProto.getStatus());
782 
783     // Report usage
784     UsageReport usageReport =
785         UsageReport.newBuilder()
786             .setDocumentNamespace("namespace")
787             .setDocumentUri("uri")
788             .setUsageTimestampMs(1)
789             .setUsageType(UsageReport.UsageType.USAGE_TYPE1)
790             .build();
791     ReportUsageResultProto reportUsageResultProto = icingSearchEngine.reportUsage(usageReport);
792     assertStatusOk(reportUsageResultProto.getStatus());
793   }
794 
795   @Test
testCJKTSnippets()796   public void testCJKTSnippets() throws Exception {
797     assertStatusOk(icingSearchEngine.initialize().getStatus());
798 
799     SchemaProto schema = SchemaProto.newBuilder().addTypes(createEmailTypeConfig()).build();
800     assertStatusOk(
801         icingSearchEngine
802             .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
803             .getStatus());
804 
805     // String:     "天是蓝的"
806     //              ^ ^^ ^
807     // UTF16 idx:   0 1 2 3
808     // Breaks into segments: "天", "是", "蓝", "的"
809     // "The sky is blue"
810     String chinese = "天是蓝的";
811     assertThat(chinese.length()).isEqualTo(4);
812     DocumentProto emailDocument1 =
813         createEmailDocument("namespace", "uri1").toBuilder()
814             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues(chinese))
815             .build();
816     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
817 
818     // Search and request snippet matching but no windowing.
819     SearchSpecProto searchSpec =
820         SearchSpecProto.newBuilder()
821             .setQuery("是")
822             .setTermMatchType(TermMatchType.Code.PREFIX)
823             .build();
824     ResultSpecProto resultSpecProto =
825         ResultSpecProto.newBuilder()
826             .setSnippetSpec(
827                 ResultSpecProto.SnippetSpecProto.newBuilder()
828                     .setNumToSnippet(Integer.MAX_VALUE)
829                     .setNumMatchesPerProperty(Integer.MAX_VALUE))
830             .build();
831 
832     // Search and make sure that we got a single successful results
833     SearchResultProto searchResultProto =
834         icingSearchEngine.search(
835             searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto);
836     assertStatusOk(searchResultProto.getStatus());
837     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
838 
839     // Ensure that one and only one property was matched and it was "subject"
840     SnippetProto snippetProto = searchResultProto.getResults(0).getSnippet();
841     assertThat(snippetProto.getEntriesList()).hasSize(1);
842     SnippetProto.EntryProto entryProto = snippetProto.getEntries(0);
843     assertThat(entryProto.getPropertyName()).isEqualTo("subject");
844 
845     // Get the content for "subject" and see what the match is.
846     DocumentProto resultDocument = searchResultProto.getResults(0).getDocument();
847     assertThat(resultDocument.getPropertiesList()).hasSize(1);
848     PropertyProto subjectProperty = resultDocument.getProperties(0);
849     assertThat(subjectProperty.getName()).isEqualTo("subject");
850     assertThat(subjectProperty.getStringValuesList()).hasSize(1);
851     String content = subjectProperty.getStringValues(0);
852 
853     // Ensure that there is one and only one match within "subject"
854     assertThat(entryProto.getSnippetMatchesList()).hasSize(1);
855     SnippetMatchProto matchProto = entryProto.getSnippetMatches(0);
856 
857     int matchStart = matchProto.getExactMatchUtf16Position();
858     int matchEnd = matchStart + matchProto.getExactMatchUtf16Length();
859     assertThat(matchStart).isEqualTo(1);
860     assertThat(matchEnd).isEqualTo(2);
861     String match = content.substring(matchStart, matchEnd);
862     assertThat(match).isEqualTo("是");
863   }
864 
865   @Test
testUtf16MultiByteSnippets()866   public void testUtf16MultiByteSnippets() throws Exception {
867     assertStatusOk(icingSearchEngine.initialize().getStatus());
868 
869     SchemaProto schema = SchemaProto.newBuilder().addTypes(createEmailTypeConfig()).build();
870     assertStatusOk(
871         icingSearchEngine
872             .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
873             .getStatus());
874 
875     // String:    "���� ���� ��"
876     //             ^  ^  ^
877     // UTF16 idx:  0  5  10
878     // Breaks into segments: "����", "����", "��"
879     String text = "���� ���� ��";
880     assertThat(text.length()).isEqualTo(12);
881     DocumentProto emailDocument1 =
882         createEmailDocument("namespace", "uri1").toBuilder()
883             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues(text))
884             .build();
885     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
886 
887     // Search and request snippet matching but no windowing.
888     SearchSpecProto searchSpec =
889         SearchSpecProto.newBuilder()
890             .setQuery("��")
891             .setTermMatchType(TermMatchType.Code.PREFIX)
892             .build();
893     ResultSpecProto resultSpecProto =
894         ResultSpecProto.newBuilder()
895             .setSnippetSpec(
896                 ResultSpecProto.SnippetSpecProto.newBuilder()
897                     .setNumToSnippet(Integer.MAX_VALUE)
898                     .setNumMatchesPerProperty(Integer.MAX_VALUE))
899             .build();
900 
901     // Search and make sure that we got a single successful results
902     SearchResultProto searchResultProto =
903         icingSearchEngine.search(
904             searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto);
905     assertStatusOk(searchResultProto.getStatus());
906     assertThat(searchResultProto.getResultsCount()).isEqualTo(1);
907 
908     // Ensure that one and only one property was matched and it was "subject"
909     SnippetProto snippetProto = searchResultProto.getResults(0).getSnippet();
910     assertThat(snippetProto.getEntriesList()).hasSize(1);
911     SnippetProto.EntryProto entryProto = snippetProto.getEntries(0);
912     assertThat(entryProto.getPropertyName()).isEqualTo("subject");
913 
914     // Get the content for "subject" and see what the match is.
915     DocumentProto resultDocument = searchResultProto.getResults(0).getDocument();
916     assertThat(resultDocument.getPropertiesList()).hasSize(1);
917     PropertyProto subjectProperty = resultDocument.getProperties(0);
918     assertThat(subjectProperty.getName()).isEqualTo("subject");
919     assertThat(subjectProperty.getStringValuesList()).hasSize(1);
920     String content = subjectProperty.getStringValues(0);
921 
922     // Ensure that there is one and only one match within "subject"
923     assertThat(entryProto.getSnippetMatchesList()).hasSize(1);
924     SnippetMatchProto matchProto = entryProto.getSnippetMatches(0);
925 
926     int matchStart = matchProto.getExactMatchUtf16Position();
927     int matchEnd = matchStart + matchProto.getExactMatchUtf16Length();
928     assertThat(matchStart).isEqualTo(5);
929     assertThat(matchEnd).isEqualTo(9);
930     String match = content.substring(matchStart, matchEnd);
931     assertThat(match).isEqualTo("����");
932   }
933 
934   @Test
testSearchSuggestions()935   public void testSearchSuggestions() {
936     assertStatusOk(icingSearchEngine.initialize().getStatus());
937 
938     SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig();
939     SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build();
940     assertThat(
941             icingSearchEngine
942                 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false)
943                 .getStatus()
944                 .getCode())
945         .isEqualTo(StatusProto.Code.OK);
946 
947     DocumentProto emailDocument1 =
948         createEmailDocument("namespace", "uri1").toBuilder()
949             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("fo"))
950             .build();
951     DocumentProto emailDocument2 =
952         createEmailDocument("namespace", "uri2").toBuilder()
953             .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo"))
954             .build();
955     assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus());
956     assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus());
957 
958     SuggestionSpecProto suggestionSpec =
959         SuggestionSpecProto.newBuilder()
960             .setPrefix("f")
961             .setNumToReturn(10)
962             .setScoringSpec(
963                 SuggestionScoringSpecProto.newBuilder()
964                     .setScoringMatchType(Code.EXACT_ONLY)
965                     .build())
966             .build();
967 
968     SuggestionResponse response = icingSearchEngine.searchSuggestions(suggestionSpec);
969     assertStatusOk(response.getStatus());
970     assertThat(response.getSuggestionsList()).hasSize(2);
971     assertThat(response.getSuggestions(0).getQuery()).isEqualTo("foo");
972     assertThat(response.getSuggestions(1).getQuery()).isEqualTo("fo");
973   }
974 
975   @Test
testLogging()976   public void testLogging() throws Exception {
977     // Set to INFO
978     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.INFO)).isTrue();
979     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.INFO)).isTrue();
980     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.DBG)).isFalse();
981 
982     // Set to WARNING
983     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.WARNING)).isTrue();
984     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.WARNING)).isTrue();
985     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.INFO)).isFalse();
986 
987     // Set to DEBUG
988     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.DBG)).isTrue();
989     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.DBG)).isTrue();
990     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE)).isFalse();
991 
992     // Set to VERBOSE
993     assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.VERBOSE, (short) 1)).isTrue();
994     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE, (short) 1)).isTrue();
995     assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE, (short) 2)).isFalse();
996 
997     assertThat(IcingSearchEngine.getLoggingTag()).isNotEmpty();
998   }
999 
assertStatusOk(StatusProto status)1000   private static void assertStatusOk(StatusProto status) {
1001     assertWithMessage(status.getMessage()).that(status.getCode()).isEqualTo(StatusProto.Code.OK);
1002   }
1003 }
1004