1 // Copyright (C) 2022 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 #include "icing/monkey_test/icing-monkey-test-runner.h"
16
17 #include <algorithm>
18 #include <array>
19 #include <cstdint>
20 #include <functional>
21 #include <memory>
22 #include <random>
23 #include <string>
24 #include <utility>
25 #include <vector>
26
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "icing/absl_ports/str_cat.h"
30 #include "icing/file/destructible-directory.h"
31 #include "icing/icing-search-engine.h"
32 #include "icing/monkey_test/in-memory-icing-search-engine.h"
33 #include "icing/monkey_test/monkey-test-generators.h"
34 #include "icing/monkey_test/monkey-test-util.h"
35 #include "icing/monkey_test/monkey-tokenized-document.h"
36 #include "icing/portable/equals-proto.h"
37 #include "icing/proto/document.pb.h"
38 #include "icing/proto/initialize.pb.h"
39 #include "icing/proto/schema.pb.h"
40 #include "icing/proto/scoring.pb.h"
41 #include "icing/proto/search.pb.h"
42 #include "icing/proto/status.pb.h"
43 #include "icing/proto/term.pb.h"
44 #include "icing/result/result-state-manager.h"
45 #include "icing/testing/common-matchers.h"
46 #include "icing/testing/tmp-directory.h"
47 #include "icing/util/logging.h"
48
49 namespace icing {
50 namespace lib {
51
52 namespace {
53
54 using ::icing::lib::portable_equals_proto::EqualsProto;
55 using ::testing::Eq;
56 using ::testing::Le;
57 using ::testing::Not;
58 using ::testing::SizeIs;
59 using ::testing::UnorderedElementsAreArray;
60
GenerateRandomSearchSpecProto(MonkeyTestRandomEngine * random,MonkeyDocumentGenerator * document_generator)61 SearchSpecProto GenerateRandomSearchSpecProto(
62 MonkeyTestRandomEngine* random,
63 MonkeyDocumentGenerator* document_generator) {
64 // Get a random token from the language set as a single term query.
65 std::string query(document_generator->GetToken());
66 std::uniform_int_distribution<> dist(0, 1);
67 TermMatchType::Code term_match_type = TermMatchType::EXACT_ONLY;
68 if (dist(*random) == 1) {
69 term_match_type = TermMatchType::PREFIX;
70 // Randomly drop a suffix of query to test prefix query.
71 std::uniform_int_distribution<> size_dist(1, query.size());
72 query.resize(size_dist(*random));
73 }
74 // 50% chance of getting a section restriction.
75 if (dist(*random) == 1) {
76 const SchemaTypeConfigProto& type_config = document_generator->GetType();
77 if (type_config.properties_size() > 0) {
78 std::uniform_int_distribution<> prop_dist(
79 0, type_config.properties_size() - 1);
80 query = absl_ports::StrCat(
81 type_config.properties(prop_dist(*random)).property_name(), ":",
82 query);
83 }
84 }
85 SearchSpecProto search_spec;
86 search_spec.set_term_match_type(term_match_type);
87 search_spec.set_query(query);
88 return search_spec;
89 }
90
GenerateRandomScoringSpec(MonkeyTestRandomEngine * random)91 ScoringSpecProto GenerateRandomScoringSpec(MonkeyTestRandomEngine* random) {
92 ScoringSpecProto scoring_spec;
93
94 constexpr std::array<ScoringSpecProto::RankingStrategy::Code, 3>
95 ranking_strategies = {
96 ScoringSpecProto::RankingStrategy::DOCUMENT_SCORE,
97 ScoringSpecProto::RankingStrategy::CREATION_TIMESTAMP,
98 ScoringSpecProto::RankingStrategy::RELEVANCE_SCORE};
99
100 std::uniform_int_distribution<> dist(0, ranking_strategies.size() - 1);
101 scoring_spec.set_rank_by(ranking_strategies[dist(*random)]);
102 return scoring_spec;
103 }
104
GenerateRandomSnippetSpecProto(MonkeyTestRandomEngine * random,const ResultSpecProto & result_spec)105 ResultSpecProto::SnippetSpecProto GenerateRandomSnippetSpecProto(
106 MonkeyTestRandomEngine* random, const ResultSpecProto& result_spec) {
107 ResultSpecProto::SnippetSpecProto snippet_spec;
108
109 std::uniform_int_distribution<> num_to_snippet_dist(
110 0, result_spec.num_per_page() * 2);
111 snippet_spec.set_num_to_snippet(num_to_snippet_dist(*random));
112
113 std::uniform_int_distribution<> num_matches_per_property_dist(0, 10);
114 snippet_spec.set_num_matches_per_property(
115 num_matches_per_property_dist(*random));
116
117 std::uniform_int_distribution<> dist(0, 4);
118 int random_num = dist(*random);
119 // 1/5 chance of getting one of 0 (disabled), 8, 32, 128, 512
120 int max_window_utf32_length =
121 random_num == 0 ? 0 : (1 << (2 * random_num + 1));
122 snippet_spec.set_max_window_utf32_length(max_window_utf32_length);
123 return snippet_spec;
124 }
125
GenerateTypePropertyMask(MonkeyTestRandomEngine * random,const SchemaTypeConfigProto & type_config)126 TypePropertyMask GenerateTypePropertyMask(
127 MonkeyTestRandomEngine* random, const SchemaTypeConfigProto& type_config) {
128 TypePropertyMask type_property_mask;
129 type_property_mask.set_schema_type(type_config.schema_type());
130 for (const auto& properties : type_config.properties()) {
131 // 25% chance of adding the current property to the mask.
132 std::uniform_int_distribution<> dist(0, 3);
133 if (dist(*random) == 0) {
134 type_property_mask.add_paths(properties.property_name());
135 }
136 }
137 return type_property_mask;
138 }
139
GenerateRandomResultSpecProto(MonkeyTestRandomEngine * random,const SchemaProto * schema)140 ResultSpecProto GenerateRandomResultSpecProto(MonkeyTestRandomEngine* random,
141 const SchemaProto* schema) {
142 std::uniform_int_distribution<> dist(0, 4);
143 ResultSpecProto result_spec;
144 // 1/5 chance of getting one of 1, 4, 16, 64, 256
145 int num_per_page = 1 << (2 * dist(*random));
146 result_spec.set_num_per_page(num_per_page);
147 *result_spec.mutable_snippet_spec() =
148 GenerateRandomSnippetSpecProto(random, result_spec);
149
150 // 1/5 chance of enabling projection.
151 if (dist(*random) == 0) {
152 for (const SchemaTypeConfigProto& type_config : schema->types()) {
153 // 25% chance of adding the current type to the projection.
154 std::uniform_int_distribution<> dist(0, 3);
155 if (dist(*random) == 0) {
156 *result_spec.add_type_property_masks() =
157 GenerateTypePropertyMask(random, type_config);
158 }
159 }
160 }
161 return result_spec;
162 }
163
SortDocuments(std::vector<DocumentProto> & documents)164 void SortDocuments(std::vector<DocumentProto>& documents) {
165 std::sort(documents.begin(), documents.end(),
166 [](const DocumentProto& doc1, const DocumentProto& doc2) {
167 if (doc1.namespace_() != doc2.namespace_()) {
168 return doc1.namespace_() < doc2.namespace_();
169 }
170 return doc1.uri() < doc2.uri();
171 });
172 }
173
174 } // namespace
175
IcingMonkeyTestRunner(IcingMonkeyTestRunnerConfiguration config)176 IcingMonkeyTestRunner::IcingMonkeyTestRunner(
177 IcingMonkeyTestRunnerConfiguration config)
178 : config_(std::move(config)),
179 random_(config_.seed),
180 in_memory_icing_(std::make_unique<InMemoryIcingSearchEngine>(&random_)),
181 schema_generator_(
182 std::make_unique<MonkeySchemaGenerator>(&random_, &config_)) {
183 ICING_LOG(INFO) << "Monkey test runner started with seed: " << config_.seed;
184 std::string dir = GetTestTempDir() + "/icing/monkey";
185 filesystem_.DeleteDirectoryRecursively(dir.c_str());
186 icing_dir_ = std::make_unique<DestructibleDirectory>(&filesystem_, dir);
187 }
188
Run(uint32_t num)189 void IcingMonkeyTestRunner::Run(uint32_t num) {
190 ASSERT_TRUE(icing_ != nullptr)
191 << "Icing search engine has not yet been created. Please call "
192 "Initialize() first";
193
194 uint32_t frequency_sum = 0;
195 for (const auto& schedule : config_.monkey_api_schedules) {
196 frequency_sum += schedule.second;
197 }
198 std::uniform_int_distribution<> dist(0, frequency_sum - 1);
199 for (; num; --num) {
200 int p = dist(random_);
201 for (const auto& schedule : config_.monkey_api_schedules) {
202 if (p < schedule.second) {
203 ASSERT_NO_FATAL_FAILURE(schedule.first(this));
204 break;
205 }
206 p -= schedule.second;
207 }
208 ICING_LOG(INFO) << "Completed Run #" << num
209 << ". Documents in the in-memory icing: "
210 << in_memory_icing_->GetNumAliveDocuments();
211 }
212 }
213
SetSchema(SchemaProto && schema)214 SetSchemaResultProto IcingMonkeyTestRunner::SetSchema(SchemaProto&& schema) {
215 in_memory_icing_->SetSchema(std::move(schema));
216 document_generator_ = std::make_unique<MonkeyDocumentGenerator>(
217 &random_, in_memory_icing_->GetSchema(), &config_);
218 return icing_->SetSchema(*in_memory_icing_->GetSchema(),
219 /*ignore_errors_and_delete_documents=*/true);
220 }
221
Initialize()222 void IcingMonkeyTestRunner::Initialize() {
223 ASSERT_NO_FATAL_FAILURE(CreateIcingSearchEngine());
224
225 SchemaProto schema = schema_generator_->GenerateSchema();
226 ICING_LOG(DBG) << "Schema Generated: " << schema.DebugString();
227
228 ASSERT_THAT(SetSchema(std::move(schema)).status(), ProtoIsOk());
229 }
230
DoUpdateSchema()231 void IcingMonkeyTestRunner::DoUpdateSchema() {
232 ICING_LOG(INFO) << "Monkey updating schema";
233
234 MonkeySchemaGenerator::UpdateSchemaResult result =
235 schema_generator_->UpdateSchema(*in_memory_icing_->GetSchema());
236 if (result.is_invalid_schema) {
237 SetSchemaResultProto set_schema_result =
238 icing_->SetSchema(result.schema,
239 /*ignore_errors_and_delete_documents=*/true);
240 ASSERT_THAT(set_schema_result.status(), Not(ProtoIsOk()));
241 return;
242 }
243 ICING_LOG(DBG) << "Updating schema to: " << result.schema.DebugString();
244 SetSchemaResultProto icing_set_schema_result =
245 SetSchema(std::move(result.schema));
246 ASSERT_THAT(icing_set_schema_result.status(), ProtoIsOk());
247 ASSERT_THAT(icing_set_schema_result.deleted_schema_types(),
248 UnorderedElementsAreArray(result.schema_types_deleted));
249 ASSERT_THAT(icing_set_schema_result.incompatible_schema_types(),
250 UnorderedElementsAreArray(result.schema_types_incompatible));
251 ASSERT_THAT(
252 icing_set_schema_result.index_incompatible_changed_schema_types(),
253 UnorderedElementsAreArray(result.schema_types_index_incompatible));
254
255 // Update in-memory icing
256 for (const std::string& deleted_type : result.schema_types_deleted) {
257 ICING_ASSERT_OK(in_memory_icing_->DeleteBySchemaType(deleted_type));
258 }
259 for (const std::string& incompatible_type :
260 result.schema_types_incompatible) {
261 ICING_ASSERT_OK(in_memory_icing_->DeleteBySchemaType(incompatible_type));
262 }
263 }
264
DoGet()265 void IcingMonkeyTestRunner::DoGet() {
266 InMemoryIcingSearchEngine::PickDocumentResult document =
267 in_memory_icing_->RandomPickDocument(/*p_alive=*/0.70, /*p_all=*/0.28,
268 /*p_other=*/0.02);
269 ICING_LOG(INFO) << "Monkey getting namespace: " << document.name_space
270 << ", uri: " << document.uri;
271 GetResultProto get_result =
272 icing_->Get(document.name_space, document.uri,
273 GetResultSpecProto::default_instance());
274 if (document.document.has_value()) {
275 ASSERT_THAT(get_result.status(), ProtoIsOk())
276 << "Cannot find the document that is supposed to exist.";
277 ASSERT_THAT(get_result.document(), EqualsProto(document.document.value()))
278 << "The document found does not match with the value in the in-memory "
279 "icing.";
280 } else {
281 // Should expect that no document has been found.
282 if (get_result.status().code() != StatusProto::NOT_FOUND) {
283 if (get_result.status().code() == StatusProto::OK) {
284 FAIL() << "Found a document that is not supposed to be found.";
285 }
286 FAIL() << "Icing search engine failure (code "
287 << get_result.status().code()
288 << "): " << get_result.status().message();
289 }
290 }
291 }
292
DoGetAllNamespaces()293 void IcingMonkeyTestRunner::DoGetAllNamespaces() {
294 ICING_LOG(INFO) << "Monkey getting all namespaces";
295 GetAllNamespacesResultProto get_result = icing_->GetAllNamespaces();
296 ASSERT_THAT(get_result.status(), ProtoIsOk());
297 ASSERT_THAT(get_result.namespaces(),
298 UnorderedElementsAreArray(in_memory_icing_->GetAllNamespaces()));
299 }
300
DoPut()301 void IcingMonkeyTestRunner::DoPut() {
302 MonkeyTokenizedDocument doc = document_generator_->GenerateDocument();
303 ICING_LOG(INFO) << "Monkey document generated, namespace: "
304 << doc.document.namespace_()
305 << ", uri: " << doc.document.uri();
306 ICING_LOG(DBG) << doc.document.DebugString();
307 in_memory_icing_->Put(doc);
308 ASSERT_THAT(icing_->Put(doc.document).status(), ProtoIsOk());
309 }
310
DoDelete()311 void IcingMonkeyTestRunner::DoDelete() {
312 InMemoryIcingSearchEngine::PickDocumentResult document =
313 in_memory_icing_->RandomPickDocument(/*p_alive=*/0.70, /*p_all=*/0.2,
314 /*p_other=*/0.1);
315 ICING_LOG(INFO) << "Monkey deleting namespace: " << document.name_space
316 << ", uri: " << document.uri;
317 DeleteResultProto delete_result =
318 icing_->Delete(document.name_space, document.uri);
319 if (document.document.has_value()) {
320 ICING_ASSERT_OK(
321 in_memory_icing_->Delete(document.name_space, document.uri));
322 ASSERT_THAT(delete_result.status(), ProtoIsOk())
323 << "Cannot delete an existing document.";
324 } else {
325 // Should expect that no document has been deleted.
326 if (delete_result.status().code() != StatusProto::NOT_FOUND) {
327 if (delete_result.status().code() == StatusProto::OK) {
328 FAIL() << "Deleted a non-existing document without an error.";
329 }
330 FAIL() << "Icing search engine failure (code "
331 << delete_result.status().code()
332 << "): " << delete_result.status().message();
333 }
334 }
335 }
336
DoDeleteByNamespace()337 void IcingMonkeyTestRunner::DoDeleteByNamespace() {
338 std::string name_space = document_generator_->GetNamespace();
339 ICING_LOG(INFO) << "Monkey deleting namespace: " << name_space;
340 DeleteByNamespaceResultProto delete_result =
341 icing_->DeleteByNamespace(name_space);
342 ICING_ASSERT_OK_AND_ASSIGN(uint32_t num_docs_deleted,
343 in_memory_icing_->DeleteByNamespace(name_space));
344 if (num_docs_deleted != 0) {
345 ASSERT_THAT(delete_result.status(), ProtoIsOk())
346 << "Cannot delete an existing namespace.";
347 ASSERT_THAT(delete_result.delete_stats().num_documents_deleted(),
348 Eq(num_docs_deleted));
349 } else {
350 // Should expect that no document has been deleted.
351 if (delete_result.status().code() != StatusProto::NOT_FOUND) {
352 if (delete_result.status().code() == StatusProto::OK) {
353 FAIL() << "Deleted a non-existing namespace without an error.";
354 }
355 FAIL() << "Icing search engine failure (code "
356 << delete_result.status().code()
357 << "): " << delete_result.status().message();
358 }
359 }
360 }
361
DoDeleteBySchemaType()362 void IcingMonkeyTestRunner::DoDeleteBySchemaType() {
363 std::string schema_type = document_generator_->GetType().schema_type();
364 ICING_LOG(INFO) << "Monkey deleting type: " << schema_type;
365 DeleteBySchemaTypeResultProto delete_result =
366 icing_->DeleteBySchemaType(schema_type);
367 ICING_ASSERT_OK_AND_ASSIGN(uint32_t num_docs_deleted,
368 in_memory_icing_->DeleteBySchemaType(schema_type));
369 if (num_docs_deleted != 0) {
370 ASSERT_THAT(delete_result.status(), ProtoIsOk())
371 << "Cannot delete an existing schema type.";
372 ASSERT_THAT(delete_result.delete_stats().num_documents_deleted(),
373 Eq(num_docs_deleted));
374 } else {
375 // Should expect that no document has been deleted.
376 if (delete_result.status().code() != StatusProto::NOT_FOUND) {
377 if (delete_result.status().code() == StatusProto::OK) {
378 FAIL() << "Deleted a non-existing schema type without an error.";
379 }
380 FAIL() << "Icing search engine failure (code "
381 << delete_result.status().code()
382 << "): " << delete_result.status().message();
383 }
384 }
385 }
386
DoDeleteByQuery()387 void IcingMonkeyTestRunner::DoDeleteByQuery() {
388 SearchSpecProto search_spec =
389 GenerateRandomSearchSpecProto(&random_, document_generator_.get());
390 ICING_LOG(INFO) << "Monkey deleting by query: " << search_spec.query();
391 DeleteByQueryResultProto delete_result = icing_->DeleteByQuery(search_spec);
392 ICING_ASSERT_OK_AND_ASSIGN(uint32_t num_docs_deleted,
393 in_memory_icing_->DeleteByQuery(search_spec));
394 if (num_docs_deleted != 0) {
395 ASSERT_THAT(delete_result.status(), ProtoIsOk())
396 << "Cannot delete documents that matches with the query.";
397 ASSERT_THAT(delete_result.delete_by_query_stats().num_documents_deleted(),
398 Eq(num_docs_deleted));
399 } else {
400 // Should expect that no document has been deleted.
401 if (delete_result.status().code() != StatusProto::NOT_FOUND) {
402 if (delete_result.status().code() == StatusProto::OK) {
403 FAIL() << "Deleted documents that should not match with the query "
404 "without an error.";
405 }
406 FAIL() << "Icing search engine failure (code "
407 << delete_result.status().code()
408 << "): " << delete_result.status().message();
409 }
410 }
411 ICING_LOG(INFO)
412 << delete_result.delete_by_query_stats().num_documents_deleted()
413 << " documents deleted by query.";
414 }
415
DoSearch()416 void IcingMonkeyTestRunner::DoSearch() {
417 std::unique_ptr<SearchSpecProto> search_spec =
418 std::make_unique<SearchSpecProto>(
419 GenerateRandomSearchSpecProto(&random_, document_generator_.get()));
420 std::unique_ptr<ScoringSpecProto> scoring_spec =
421 std::make_unique<ScoringSpecProto>(GenerateRandomScoringSpec(&random_));
422 std::unique_ptr<ResultSpecProto> result_spec =
423 std::make_unique<ResultSpecProto>(GenerateRandomResultSpecProto(
424 &random_, in_memory_icing_->GetSchema()));
425 const ResultSpecProto::SnippetSpecProto snippet_spec =
426 result_spec->snippet_spec();
427 bool is_projection_enabled = !result_spec->type_property_masks().empty();
428
429 ICING_LOG(INFO) << "Monkey searching by query: " << search_spec->query()
430 << ", term_match_type: " << search_spec->term_match_type();
431 ICING_VLOG(1) << "search_spec:\n" << search_spec->DebugString();
432 ICING_VLOG(1) << "scoring_spec:\n" << scoring_spec->DebugString();
433 ICING_VLOG(1) << "result_spec:\n" << result_spec->DebugString();
434
435 ICING_ASSERT_OK_AND_ASSIGN(std::vector<DocumentProto> exp_documents,
436 in_memory_icing_->Search(*search_spec));
437
438 SearchResultProto search_result =
439 icing_->Search(*search_spec, *scoring_spec, *result_spec);
440 ASSERT_THAT(search_result.status(), ProtoIsOk());
441
442 // Delete all of the specs used in the search. GetNextPage should have no
443 // problem because it shouldn't be keeping any references to them.
444 search_spec.reset();
445 scoring_spec.reset();
446 result_spec.reset();
447
448 std::vector<DocumentProto> actual_documents;
449 int num_snippeted = 0;
450 while (true) {
451 for (const SearchResultProto::ResultProto& doc : search_result.results()) {
452 actual_documents.push_back(doc.document());
453 if (!doc.snippet().entries().empty()) {
454 ++num_snippeted;
455 for (const SnippetProto::EntryProto& entry : doc.snippet().entries()) {
456 ASSERT_THAT(entry.snippet_matches(),
457 SizeIs(Le(snippet_spec.num_matches_per_property())));
458 }
459 }
460 }
461 if (search_result.next_page_token() == kInvalidNextPageToken) {
462 break;
463 }
464 search_result = icing_->GetNextPage(search_result.next_page_token());
465 ASSERT_THAT(search_result.status(), ProtoIsOk());
466 }
467 // The maximum number of scored documents allowed in Icing is 30000, in which
468 // case we are not able to compare the results with the in-memory Icing.
469 if (exp_documents.size() >= 30000) {
470 return;
471 }
472 if (snippet_spec.num_matches_per_property() > 0 && !is_projection_enabled) {
473 ASSERT_THAT(num_snippeted,
474 Eq(std::min<uint32_t>(exp_documents.size(),
475 snippet_spec.num_to_snippet())));
476 }
477 SortDocuments(exp_documents);
478 SortDocuments(actual_documents);
479 ASSERT_THAT(actual_documents, SizeIs(exp_documents.size()));
480 for (int i = 0; i < exp_documents.size(); ++i) {
481 if (is_projection_enabled) {
482 ASSERT_THAT(actual_documents[i].namespace_(),
483 Eq(exp_documents[i].namespace_()));
484 ASSERT_THAT(actual_documents[i].uri(), Eq(exp_documents[i].uri()));
485 continue;
486 }
487 ASSERT_THAT(actual_documents[i], EqualsProto(exp_documents[i]));
488 }
489 ICING_LOG(INFO) << exp_documents.size() << " documents found by query.";
490 }
491
ReloadFromDisk()492 void IcingMonkeyTestRunner::ReloadFromDisk() {
493 ICING_LOG(INFO) << "Monkey reloading from disk";
494 // Destruct the icing search engine by resetting the unique pointer.
495 icing_.reset();
496 ASSERT_NO_FATAL_FAILURE(CreateIcingSearchEngine());
497 }
498
DoOptimize()499 void IcingMonkeyTestRunner::DoOptimize() {
500 ICING_LOG(INFO) << "Monkey doing optimization";
501 ASSERT_THAT(icing_->Optimize().status(), ProtoIsOk());
502 }
503
CreateIcingSearchEngine()504 void IcingMonkeyTestRunner::CreateIcingSearchEngine() {
505 std::uniform_int_distribution<> dist(0, 1);
506
507 bool always_rebuild_index_optimize = dist(random_);
508 float optimize_rebuild_index_threshold =
509 always_rebuild_index_optimize ? 0.0 : 0.9;
510
511 IcingSearchEngineOptions icing_options;
512 icing_options.set_index_merge_size(config_.index_merge_size);
513 icing_options.set_base_dir(icing_dir_->dir());
514 icing_options.set_optimize_rebuild_index_threshold(
515 optimize_rebuild_index_threshold);
516 // The method will be called every time when we ReloadFromDisk(), so randomly
517 // flip this flag to test document store's compatibility.
518 icing_options.set_document_store_namespace_id_fingerprint(
519 (bool)dist(random_));
520 icing_ = std::make_unique<IcingSearchEngine>(icing_options);
521 ASSERT_THAT(icing_->Initialize().status(), ProtoIsOk());
522 }
523
524 } // namespace lib
525 } // namespace icing
526