• 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 #ifndef ICING_TESTING_COMMON_MATCHERS_H_
16 #define ICING_TESTING_COMMON_MATCHERS_H_
17 
18 #include <algorithm>
19 #include <array>
20 #include <cinttypes>
21 #include <cmath>
22 #include <string>
23 #include <unordered_map>
24 
25 #include "icing/text_classifier/lib3/utils/base/status.h"
26 #include "icing/text_classifier/lib3/utils/base/status_macros.h"
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "icing/absl_ports/str_cat.h"
30 #include "icing/absl_ports/str_join.h"
31 #include "icing/index/hit/doc-hit-info.h"
32 #include "icing/index/hit/hit.h"
33 #include "icing/index/iterator/doc-hit-info-iterator-test-util.h"
34 #include "icing/index/iterator/doc-hit-info-iterator.h"
35 #include "icing/legacy/core/icing-string-util.h"
36 #include "icing/portable/equals-proto.h"
37 #include "icing/proto/search.pb.h"
38 #include "icing/proto/status.pb.h"
39 #include "icing/result/snippet-context.h"
40 #include "icing/schema/joinable-property.h"
41 #include "icing/schema/schema-store.h"
42 #include "icing/schema/scorable_property_manager.h"
43 #include "icing/schema/section.h"
44 #include "icing/scoring/scored-document-hit.h"
45 #include "icing/util/character-iterator.h"
46 
47 namespace icing {
48 namespace lib {
49 
50 using ::testing::DoubleNear;
51 using ::testing::Matches;
52 
53 constexpr float kEps = 1e-6;
54 
55 // Used to match Token(Token::Type type, std::string_view text)
56 MATCHER_P2(EqualsToken, type, text, "") {
57   std::string arg_string(arg.text.data(), arg.text.length());
58   if (arg.type != type || arg.text != text) {
59     *result_listener << IcingStringUtil::StringPrintf(
60         "(Expected: type=%d, text=\"%s\". Actual: type=%d, text=\"%s\")", type,
61         text, arg.type, arg_string.c_str());
62     return false;
63   }
64   return true;
65 }
66 
67 MATCHER_P(EqualsNormalizedTerm, text, "") {
68   std::string arg_string(arg.text.data(), arg.text.length());
69   if (arg.text != text) {
70     *result_listener << IcingStringUtil::StringPrintf(
71         "(Expected: text=\"%s\". Actual: text=\"%s\")", text,
72         arg_string.c_str());
73     return false;
74   }
75   return true;
76 }
77 
78 // Used to match a DocHitInfo
79 MATCHER_P2(EqualsDocHitInfo, document_id, section_ids, "") {
80   const DocHitInfo& actual = arg;
81   SectionIdMask section_mask = kSectionIdMaskNone;
82   for (SectionId section_id : section_ids) {
83     section_mask |= UINT64_C(1) << section_id;
84   }
85   *result_listener << IcingStringUtil::StringPrintf(
86       "(actual is {document_id=%d, section_mask=%" PRIu64
87       "}, but expected was "
88       "{document_id=%d, section_mask=%" PRIu64 "}.)",
89       actual.document_id(), actual.hit_section_ids_mask(), document_id,
90       section_mask);
91   return actual.document_id() == document_id &&
92          actual.hit_section_ids_mask() == section_mask;
93 }
94 
95 // Used to match a DocHitInfoIterator::CallStats
96 MATCHER_P5(EqualsDocHitInfoIteratorCallStats, num_leaf_advance_calls_lite_index,
97            num_leaf_advance_calls_main_index,
98            num_leaf_advance_calls_integer_index,
99            num_leaf_advance_calls_no_index, num_blocks_inspected, "") {
100   const DocHitInfoIterator::CallStats& actual = arg;
101   *result_listener << IcingStringUtil::StringPrintf(
102       "(actual is {num_leaf_advance_calls_lite_index=%d, "
103       "num_leaf_advance_calls_main_index=%d, "
104       "num_leaf_advance_calls_integer_index=%d, "
105       "num_leaf_advance_calls_no_index=%d, num_blocks_inspected=%d}, but "
106       "expected was {num_leaf_advance_calls_lite_index=%d, "
107       "num_leaf_advance_calls_main_index=%d, "
108       "num_leaf_advance_calls_integer_index=%d, "
109       "num_leaf_advance_calls_no_index=%d, num_blocks_inspected=%d}.)",
110       actual.num_leaf_advance_calls_lite_index,
111       actual.num_leaf_advance_calls_main_index,
112       actual.num_leaf_advance_calls_integer_index,
113       actual.num_leaf_advance_calls_no_index, actual.num_blocks_inspected,
114       num_leaf_advance_calls_lite_index, num_leaf_advance_calls_main_index,
115       num_leaf_advance_calls_integer_index, num_leaf_advance_calls_no_index,
116       num_blocks_inspected);
117   return actual.num_leaf_advance_calls_lite_index ==
118              num_leaf_advance_calls_lite_index &&
119          actual.num_leaf_advance_calls_main_index ==
120              num_leaf_advance_calls_main_index &&
121          actual.num_leaf_advance_calls_integer_index ==
122              num_leaf_advance_calls_integer_index &&
123          actual.num_leaf_advance_calls_no_index ==
124              num_leaf_advance_calls_no_index &&
125          actual.num_blocks_inspected == num_blocks_inspected;
126 }
127 
128 // Used to match a DocumentAssociatedScoreData
129 MATCHER_P5(EqualsDocumentAssociatedScoreData, corpus_id, document_score,
130            creation_timestamp_ms, length_in_tokens,
131            has_valid_scorable_property_cache_index, "") {
132   bool expected_has_valid_scorable_property_cache_index =
133       arg.scorable_property_cache_index() != -1;
134   return arg.corpus_id() == corpus_id &&
135          arg.document_score() == document_score &&
136          arg.creation_timestamp_ms() == creation_timestamp_ms &&
137          arg.length_in_tokens() == length_in_tokens &&
138          expected_has_valid_scorable_property_cache_index ==
139              has_valid_scorable_property_cache_index;
140 }
141 
142 // Used to match a ScorablePropertyManager::ScorablePropertyInfo
143 MATCHER_P2(EqualsScorablePropertyInfo, property_path, data_type, "") {
144   const ScorablePropertyManager::ScorablePropertyInfo& actual = arg;
145   return actual.property_path == property_path && actual.data_type == data_type;
146 }
147 
148 struct ExtractTermFrequenciesResult {
149   std::array<Hit::TermFrequency, kTotalNumSections> term_frequencies = {0};
150   SectionIdMask section_mask = kSectionIdMaskNone;
151 };
152 // Extracts the term frequencies represented by the section_ids_tf_map.
153 // Returns:
154 //   - a SectionIdMask representing all sections that appears as entries in the
155 //     map, even if they have an entry with term_frequency==0
156 //   - an array representing the term frequencies for each section. Sections not
157 //     present in section_ids_tf_map have a term frequency of 0.
158 ExtractTermFrequenciesResult ExtractTermFrequencies(
159     const std::unordered_map<SectionId, Hit::TermFrequency>&
160         section_ids_tf_map);
161 
162 struct CheckTermFrequencyResult {
163   std::string expected_term_frequencies_str;
164   std::string actual_term_frequencies_str;
165   bool term_frequencies_match = true;
166 };
167 // Checks that the term frequencies in actual_term_frequencies match those
168 // specified in expected_section_ids_tf_map. If there is no entry in
169 // expected_section_ids_tf_map, then it is assumed that the term frequency for
170 // that section is 0.
171 // Returns:
172 //   - a bool indicating if the term frequencies match
173 //   - debug strings representing the contents of the actual and expected term
174 //     term frequency arrays.
175 CheckTermFrequencyResult CheckTermFrequency(
176     const std::array<Hit::TermFrequency, kTotalNumSections>&
177         expected_term_frequencies,
178     const std::array<Hit::TermFrequency, kTotalNumSections>&
179         actual_term_frequencies);
180 
181 // Used to match a DocHitInfo
182 MATCHER_P2(EqualsDocHitInfoWithTermFrequency, document_id,
183            section_ids_to_term_frequencies_map, "") {
184   const DocHitInfoTermFrequencyPair& actual = arg;
185   std::array<Hit::TermFrequency, kTotalNumSections> actual_tf_array;
186   for (SectionId section_id = 0; section_id < kTotalNumSections; ++section_id) {
187     actual_tf_array[section_id] = actual.hit_term_frequency(section_id);
188   }
189   ExtractTermFrequenciesResult expected =
190       ExtractTermFrequencies(section_ids_to_term_frequencies_map);
191   CheckTermFrequencyResult check_tf_result =
192       CheckTermFrequency(expected.term_frequencies, actual_tf_array);
193 
194   *result_listener << IcingStringUtil::StringPrintf(
195       "(actual is {document_id=%d, section_mask=%" PRIu64
196       ", term_frequencies=%s}, but expected was "
197       "{document_id=%d, section_mask=%" PRIu64 ", term_frequencies=%s}.)",
198       actual.doc_hit_info().document_id(),
199       actual.doc_hit_info().hit_section_ids_mask(),
200       check_tf_result.actual_term_frequencies_str.c_str(), document_id,
201       expected.section_mask,
202       check_tf_result.expected_term_frequencies_str.c_str());
203   return actual.doc_hit_info().document_id() == document_id &&
204          actual.doc_hit_info().hit_section_ids_mask() ==
205              expected.section_mask &&
206          check_tf_result.term_frequencies_match;
207 }
208 
209 MATCHER_P2(EqualsTermMatchInfo, term, section_ids_to_term_frequencies_map, "") {
210   const TermMatchInfo& actual = arg;
211   std::string term_str(term);
212   ExtractTermFrequenciesResult expected =
213       ExtractTermFrequencies(section_ids_to_term_frequencies_map);
214   CheckTermFrequencyResult check_tf_result =
215       CheckTermFrequency(expected.term_frequencies, actual.term_frequencies);
216   *result_listener << IcingStringUtil::StringPrintf(
217       "(actual is {term=%s, section_mask=%" PRIu64
218       ", term_frequencies=%s}, but expected was "
219       "{term=%s, section_mask=%" PRIu64 ", term_frequencies=%s}.)",
220       actual.term.data(), actual.section_ids_mask,
221       check_tf_result.actual_term_frequencies_str.c_str(), term_str.data(),
222       expected.section_mask,
223       check_tf_result.expected_term_frequencies_str.c_str());
224   return actual.term == term &&
225          actual.section_ids_mask == expected.section_mask &&
226          check_tf_result.term_frequencies_match;
227 }
228 
229 class ScoredDocumentHitFormatter {
230  public:
operator()231   std::string operator()(const ScoredDocumentHit& scored_document_hit) {
232     return IcingStringUtil::StringPrintf(
233         "(document_id=%d, hit_section_id_mask=%" PRId64 ", score=%.2f)",
234         scored_document_hit.document_id(),
235         scored_document_hit.hit_section_id_mask(), scored_document_hit.score());
236   }
237 };
238 
239 class ScoredDocumentHitEqualComparator {
240  public:
operator()241   bool operator()(const ScoredDocumentHit& lhs,
242                   const ScoredDocumentHit& rhs) const {
243     bool additional_scores_match = true;
244     if (lhs.additional_scores() != nullptr &&
245         rhs.additional_scores() != nullptr) {
246       additional_scores_match =
247           *lhs.additional_scores() == *rhs.additional_scores();
248     } else {
249       additional_scores_match =
250           lhs.additional_scores() == rhs.additional_scores();
251     }
252     return lhs.document_id() == rhs.document_id() &&
253            lhs.hit_section_id_mask() == rhs.hit_section_id_mask() &&
254            std::fabs(lhs.score() - rhs.score()) < 1e-6 &&
255            additional_scores_match;
256   }
257 };
258 
259 // Used to match a ScoredDocumentHit
260 MATCHER_P(EqualsScoredDocumentHit, expected_scored_document_hit, "") {
261   ScoredDocumentHitEqualComparator equal_comparator;
262   if (!equal_comparator(arg, expected_scored_document_hit)) {
263     ScoredDocumentHitFormatter formatter;
264     *result_listener << "Expected: " << formatter(expected_scored_document_hit)
265                      << ". Actual: " << formatter(arg);
266     return false;
267   }
268   return true;
269 }
270 
271 // Used to match a JoinedScoredDocumentHit
272 MATCHER_P(EqualsJoinedScoredDocumentHit, expected_joined_scored_document_hit,
273           "") {
274   ScoredDocumentHitEqualComparator equal_comparator;
275   if (std::fabs(arg.final_score() -
276                 expected_joined_scored_document_hit.final_score()) > 1e-6 ||
277       !equal_comparator(
278           arg.parent_scored_document_hit(),
279           expected_joined_scored_document_hit.parent_scored_document_hit()) ||
280       arg.child_scored_document_hits().size() !=
281           expected_joined_scored_document_hit.child_scored_document_hits()
282               .size() ||
283       !std::equal(
284           arg.child_scored_document_hits().cbegin(),
285           arg.child_scored_document_hits().cend(),
286           expected_joined_scored_document_hit.child_scored_document_hits()
287               .cbegin(),
288           equal_comparator)) {
289     ScoredDocumentHitFormatter formatter;
290 
291     *result_listener << IcingStringUtil::StringPrintf(
292         "Expected: final_score=%.2f, parent_scored_document_hit=%s, "
293         "child_scored_document_hits=[%s]. Actual: final_score=%.2f, "
294         "parent_scored_document_hit=%s, child_scored_document_hits=[%s]",
295         expected_joined_scored_document_hit.final_score(),
296         formatter(
297             expected_joined_scored_document_hit.parent_scored_document_hit())
298             .c_str(),
299         absl_ports::StrJoin(
300             expected_joined_scored_document_hit.child_scored_document_hits(),
301             ",", formatter)
302             .c_str(),
303         arg.final_score(), formatter(arg.parent_scored_document_hit()).c_str(),
304         absl_ports::StrJoin(arg.child_scored_document_hits(), ",", formatter)
305             .c_str());
306     return false;
307   }
308   return true;
309 }
310 
311 MATCHER_P(EqualsSetSchemaResult, expected, "") {
312   const SchemaStore::SetSchemaResult& actual = arg;
313 
314   if (actual.success == expected.success &&
315       actual.old_schema_type_ids_changed ==
316           expected.old_schema_type_ids_changed &&
317       actual.schema_types_deleted_by_name ==
318           expected.schema_types_deleted_by_name &&
319       actual.schema_types_deleted_by_id ==
320           expected.schema_types_deleted_by_id &&
321       actual.schema_types_incompatible_by_name ==
322           expected.schema_types_incompatible_by_name &&
323       actual.schema_types_incompatible_by_id ==
324           expected.schema_types_incompatible_by_id &&
325       actual.schema_types_new_by_name == expected.schema_types_new_by_name &&
326       actual.schema_types_changed_fully_compatible_by_name ==
327           expected.schema_types_changed_fully_compatible_by_name &&
328       actual.schema_types_index_incompatible_by_name ==
329           expected.schema_types_index_incompatible_by_name &&
330       actual.schema_types_join_incompatible_by_name ==
331           expected.schema_types_join_incompatible_by_name &&
332       actual.schema_types_scorable_property_inconsistent_by_id ==
333           expected.schema_types_scorable_property_inconsistent_by_id) {
334     return true;
335   }
336 
337   // Format schema_type_ids_changed
338   std::string actual_old_schema_type_ids_changed = absl_ports::StrCat(
339       "[",
340       absl_ports::StrJoin(actual.old_schema_type_ids_changed, ",",
341                           absl_ports::NumberFormatter()),
342       "]");
343 
344   std::string expected_old_schema_type_ids_changed = absl_ports::StrCat(
345       "[",
346       absl_ports::StrJoin(expected.old_schema_type_ids_changed, ",",
347                           absl_ports::NumberFormatter()),
348       "]");
349 
350   // Format schema_types_deleted_by_name
351   std::string actual_schema_types_deleted_by_name = absl_ports::StrCat(
352       "[", absl_ports::StrJoin(actual.schema_types_deleted_by_name, ","), "]");
353 
354   std::string expected_schema_types_deleted_by_name = absl_ports::StrCat(
355       "[", absl_ports::StrJoin(expected.schema_types_deleted_by_name, ","),
356       "]");
357 
358   // Format schema_types_deleted_by_id
359   std::string actual_schema_types_deleted_by_id = absl_ports::StrCat(
360       "[",
361       absl_ports::StrJoin(actual.schema_types_deleted_by_id, ",",
362                           absl_ports::NumberFormatter()),
363       "]");
364 
365   std::string expected_schema_types_deleted_by_id = absl_ports::StrCat(
366       "[",
367       absl_ports::StrJoin(expected.schema_types_deleted_by_id, ",",
368                           absl_ports::NumberFormatter()),
369       "]");
370 
371   // Format schema_types_incompatible_by_name
372   std::string actual_schema_types_incompatible_by_name = absl_ports::StrCat(
373       "[", absl_ports::StrJoin(actual.schema_types_incompatible_by_name, ","),
374       "]");
375 
376   std::string expected_schema_types_incompatible_by_name = absl_ports::StrCat(
377       "[", absl_ports::StrJoin(expected.schema_types_incompatible_by_name, ","),
378       "]");
379 
380   // Format schema_types_incompatible_by_id
381   std::string actual_schema_types_incompatible_by_id = absl_ports::StrCat(
382       "[",
383       absl_ports::StrJoin(actual.schema_types_incompatible_by_id, ",",
384                           absl_ports::NumberFormatter()),
385       "]");
386 
387   std::string expected_schema_types_incompatible_by_id = absl_ports::StrCat(
388       "[",
389       absl_ports::StrJoin(expected.schema_types_incompatible_by_id, ",",
390                           absl_ports::NumberFormatter()),
391       "]");
392 
393   // Format schema_types_new_by_name
394   std::string actual_schema_types_new_by_name = absl_ports::StrCat(
395       "[", absl_ports::StrJoin(actual.schema_types_new_by_name, ","), "]");
396 
397   std::string expected_schema_types_new_by_name = absl_ports::StrCat(
398       "[", absl_ports::StrJoin(expected.schema_types_new_by_name, ","), "]");
399 
400   // Format schema_types_changed_fully_compatible_by_name
401   std::string actual_schema_types_changed_fully_compatible_by_name =
402       absl_ports::StrCat(
403           "[",
404           absl_ports::StrJoin(
405               actual.schema_types_changed_fully_compatible_by_name, ","),
406           "]");
407 
408   std::string expected_schema_types_changed_fully_compatible_by_name =
409       absl_ports::StrCat(
410           "[",
411           absl_ports::StrJoin(
412               expected.schema_types_changed_fully_compatible_by_name, ","),
413           "]");
414 
415   // Format schema_types_deleted_by_id
416   std::string actual_schema_types_index_incompatible_by_name =
417       absl_ports::StrCat(
418           "[",
419           absl_ports::StrJoin(actual.schema_types_index_incompatible_by_name,
420                               ","),
421           "]");
422 
423   std::string expected_schema_types_index_incompatible_by_name =
424       absl_ports::StrCat(
425           "[",
426           absl_ports::StrJoin(expected.schema_types_index_incompatible_by_name,
427                               ","),
428           "]");
429 
430   // Format schema_types_join_incompatible_by_name
431   std::string actual_schema_types_join_incompatible_by_name =
432       absl_ports::StrCat(
433           "[",
434           absl_ports::StrJoin(actual.schema_types_join_incompatible_by_name,
435                               ","),
436           "]");
437 
438   std::string expected_schema_types_join_incompatible_by_name =
439       absl_ports::StrCat(
440           "[",
441           absl_ports::StrJoin(expected.schema_types_join_incompatible_by_name,
442                               ","),
443           "]");
444 
445   *result_listener << IcingStringUtil::StringPrintf(
446       "\nExpected {\n"
447       "\tsuccess=%d,\n"
448       "\told_schema_type_ids_changed=%s,\n"
449       "\tschema_types_deleted_by_name=%s,\n"
450       "\tschema_types_deleted_by_id=%s,\n"
451       "\tschema_types_incompatible_by_name=%s,\n"
452       "\tschema_types_incompatible_by_id=%s\n"
453       "\tschema_types_new_by_name=%s,\n"
454       "\tschema_types_changed_fully_compatible_by_name=%s\n"
455       "\tschema_types_index_incompatible_by_name=%s,\n"
456       "\tschema_types_join_incompatible_by_name=%s\n"
457       "}\n"
458       "Actual {\n"
459       "\tsuccess=%d,\n"
460       "\told_schema_type_ids_changed=%s,\n"
461       "\tschema_types_deleted_by_name=%s,\n"
462       "\tschema_types_deleted_by_id=%s,\n"
463       "\tschema_types_incompatible_by_name=%s,\n"
464       "\tschema_types_incompatible_by_id=%s\n"
465       "\tschema_types_new_by_name=%s,\n"
466       "\tschema_types_changed_fully_compatible_by_name=%s\n"
467       "\tschema_types_index_incompatible_by_name=%s,\n"
468       "\tschema_types_join_incompatible_by_name=%s\n"
469       "}\n",
470       expected.success, expected_old_schema_type_ids_changed.c_str(),
471       expected_schema_types_deleted_by_name.c_str(),
472       expected_schema_types_deleted_by_id.c_str(),
473       expected_schema_types_incompatible_by_name.c_str(),
474       expected_schema_types_incompatible_by_id.c_str(),
475       expected_schema_types_new_by_name.c_str(),
476       expected_schema_types_changed_fully_compatible_by_name.c_str(),
477       expected_schema_types_index_incompatible_by_name.c_str(),
478       expected_schema_types_join_incompatible_by_name.c_str(), actual.success,
479       actual_old_schema_type_ids_changed.c_str(),
480       actual_schema_types_deleted_by_name.c_str(),
481       actual_schema_types_deleted_by_id.c_str(),
482       actual_schema_types_incompatible_by_name.c_str(),
483       actual_schema_types_incompatible_by_id.c_str(),
484       actual_schema_types_new_by_name.c_str(),
485       actual_schema_types_changed_fully_compatible_by_name.c_str(),
486       actual_schema_types_index_incompatible_by_name.c_str(),
487       actual_schema_types_join_incompatible_by_name.c_str());
488   return false;
489 }
490 
491 MATCHER_P3(EqualsSectionMetadata, expected_id, expected_property_path,
492            expected_property_config_proto, "") {
493   const SectionMetadata& actual = arg;
494   return actual.id == expected_id && actual.path == expected_property_path &&
495          actual.data_type == expected_property_config_proto.data_type() &&
496          actual.tokenizer ==
497              expected_property_config_proto.string_indexing_config()
498                  .tokenizer_type() &&
499          actual.term_match_type ==
500              expected_property_config_proto.string_indexing_config()
501                  .term_match_type() &&
502          actual.numeric_match_type ==
503              expected_property_config_proto.integer_indexing_config()
504                  .numeric_match_type() &&
505          actual.embedding_indexing_type ==
506              expected_property_config_proto.embedding_indexing_config()
507                  .embedding_indexing_type() &&
508          actual.quantization_type ==
509              expected_property_config_proto.embedding_indexing_config()
510                  .quantization_type();
511 }
512 
513 MATCHER_P3(EqualsJoinablePropertyMetadata, expected_id, expected_property_path,
514            expected_property_config_proto, "") {
515   const JoinablePropertyMetadata& actual = arg;
516   return actual.id == expected_id && actual.path == expected_property_path &&
517          actual.data_type == expected_property_config_proto.data_type() &&
518          actual.value_type ==
519              expected_property_config_proto.joinable_config().value_type() &&
520          actual.delete_propagation_type ==
521              expected_property_config_proto.joinable_config()
522                  .delete_propagation_type();
523 }
524 
525 std::string StatusCodeToString(libtextclassifier3::StatusCode code);
526 
527 std::string ProtoStatusCodeToString(StatusProto::Code code);
528 
529 MATCHER(IsOk, "") {
530   libtextclassifier3::StatusAdapter adapter(arg);
531   if (adapter.status().ok()) {
532     return true;
533   }
534   *result_listener << IcingStringUtil::StringPrintf(
535       "Expected OK, actual was (%s:%s)",
536       StatusCodeToString(adapter.status().CanonicalCode()).c_str(),
537       adapter.status().error_message().c_str());
538   return false;
539 }
540 
541 MATCHER_P(IsOkAndHolds, matcher, "") {
542   if (!arg.ok()) {
543     *result_listener << IcingStringUtil::StringPrintf(
544         "Expected OK, actual was (%s:%s)",
545         StatusCodeToString(arg.status().CanonicalCode()).c_str(),
546         arg.status().error_message().c_str());
547     return false;
548   }
549   return ExplainMatchResult(matcher, arg.ValueOrDie(), result_listener);
550 }
551 
552 MATCHER_P(StatusIs, status_code, "") {
553   libtextclassifier3::StatusAdapter adapter(arg);
554   if (adapter.status().CanonicalCode() == status_code) {
555     return true;
556   }
557   *result_listener << IcingStringUtil::StringPrintf(
558       "Expected (%s:), actual was (%s:%s)",
559       StatusCodeToString(status_code).c_str(),
560       StatusCodeToString(adapter.status().CanonicalCode()).c_str(),
561       adapter.status().error_message().c_str());
562   return false;
563 }
564 
565 MATCHER_P2(StatusIs, status_code, error_matcher, "") {
566   libtextclassifier3::StatusAdapter adapter(arg);
567   if (adapter.status().CanonicalCode() != status_code) {
568     *result_listener << IcingStringUtil::StringPrintf(
569         "Expected (%s:), actual was (%s:%s)",
570         StatusCodeToString(status_code).c_str(),
571         StatusCodeToString(adapter.status().CanonicalCode()).c_str(),
572         adapter.status().error_message().c_str());
573     return false;
574   }
575   return ExplainMatchResult(error_matcher, adapter.status().error_message(),
576                             result_listener);
577 }
578 
579 MATCHER(ProtoIsOk, "") {
580   if (arg.code() == StatusProto::OK) {
581     return true;
582   }
583   *result_listener << IcingStringUtil::StringPrintf(
584       "Expected OK, actual was (%s:%s)",
585       ProtoStatusCodeToString(arg.code()).c_str(), arg.message().c_str());
586   return false;
587 }
588 
589 MATCHER_P(ProtoStatusIs, status_code, "") {
590   if (arg.code() == status_code) {
591     return true;
592   }
593   *result_listener << IcingStringUtil::StringPrintf(
594       "Expected (%s:), actual was (%s:%s)",
595       ProtoStatusCodeToString(status_code).c_str(),
596       ProtoStatusCodeToString(arg.code()).c_str(), arg.message().c_str());
597   return false;
598 }
599 
600 MATCHER_P2(ProtoStatusIs, status_code, error_matcher, "") {
601   if (arg.code() != status_code) {
602     *result_listener << IcingStringUtil::StringPrintf(
603         "Expected (%s:), actual was (%s:%s)",
604         ProtoStatusCodeToString(status_code).c_str(),
605         ProtoStatusCodeToString(arg.code()).c_str(), arg.message().c_str());
606     return false;
607   }
608   return ExplainMatchResult(error_matcher, arg.message(), result_listener);
609 }
610 
611 MATCHER_P(EqualsSearchResultIgnoreStatsAndScores, expected, "") {
612   SearchResultProto actual_copy = arg;
613   actual_copy.clear_query_stats();
614   actual_copy.clear_debug_info();
615   for (SearchResultProto::ResultProto& result :
616        *actual_copy.mutable_results()) {
617     // Joined results
618     for (SearchResultProto::ResultProto& joined_result :
619          *result.mutable_joined_results()) {
620       joined_result.clear_score();
621     }
622     result.clear_score();
623   }
624 
625   SearchResultProto expected_copy = expected;
626   expected_copy.clear_query_stats();
627   expected_copy.clear_debug_info();
628   for (SearchResultProto::ResultProto& result :
629        *expected_copy.mutable_results()) {
630     // Joined results
631     for (SearchResultProto::ResultProto& joined_result :
632          *result.mutable_joined_results()) {
633       joined_result.clear_score();
634     }
635     result.clear_score();
636   }
637   return ExplainMatchResult(portable_equals_proto::EqualsProto(expected_copy),
638                             actual_copy, result_listener);
639 }
640 
641 MATCHER_P4(EqualsCharacterIterator, expected_text, expected_utf8_index,
642            expected_utf16_index, expected_utf32_index, "") {
643   const CharacterIterator& actual = arg;
644   return actual.text() == expected_text &&
645          actual.utf8_index() == expected_utf8_index &&
646          actual.utf16_index() == expected_utf16_index &&
647          actual.utf32_index() == expected_utf32_index;
648 }
649 
650 MATCHER_P(EqualsHit, expected_hit, "") {
651   const Hit& actual = arg;
652   return actual.value() == expected_hit.value() &&
653          actual.flags() == expected_hit.flags() &&
654          actual.term_frequency() == expected_hit.term_frequency();
655 }
656 
657 MATCHER(EqualsHit, "") {
658   return ExplainMatchResult(EqualsHit(std::get<1>(arg)), std::get<0>(arg),
659                             result_listener);
660 }
661 
662 MATCHER_P(EqualsEmbeddingMatchInfoEntry, expected, "") {
663   const SnippetContext::EmbeddingMatchInfoEntry& actual = arg;
664 
665   *result_listener << IcingStringUtil::StringPrintf(
666       "Expected: {score=%f, metric_type=%d, position=%d, "
667       "query_vector_index=%d, section_id=%d}, but got: {score=%f, "
668       "metric_type=%d, "
669       "position=%d, query_vector_index=%d, section_id=%d}",
670       expected.score, expected.metric_type, expected.position,
671       expected.query_vector_index, expected.section_id, actual.score,
672       actual.metric_type, actual.position, actual.query_vector_index,
673       expected.section_id);
674 
675   return Matches(DoubleNear(expected.score, kEps))(actual.score) &&
676          actual.metric_type == expected.metric_type &&
677          actual.query_vector_index == expected.query_vector_index &&
678          actual.position == expected.position &&
679          actual.section_id == expected.section_id;
680 }
681 
682 MATCHER_P(EqualsEmbeddingMatchSnippetProto, expected, "") {
683   const EmbeddingMatchSnippetProto& actual = arg;
684 
685   *result_listener << IcingStringUtil::StringPrintf(
686       "Expected: {semantic_score=%f, embedding_query_vector_index=%d, "
687       "embedding_query_metric_type=%d}, but got: {semantic_score=%f, "
688       "embedding_query_vector_index=%d, embedding_query_metric_type=%d}",
689       expected.semantic_score(), expected.embedding_query_vector_index(),
690       expected.embedding_query_metric_type(), actual.semantic_score(),
691       actual.embedding_query_vector_index(),
692       actual.embedding_query_metric_type());
693 
694   return Matches(DoubleNear(expected.semantic_score(), kEps))(
695              actual.semantic_score()) &&
696          actual.embedding_query_vector_index() ==
697              expected.embedding_query_vector_index() &&
698          actual.embedding_query_metric_type() ==
699              expected.embedding_query_metric_type();
700 }
701 
702 // TODO(tjbarron) Remove this once icing has switched to depend on TC3 Status
703 #define ICING_STATUS_MACROS_CONCAT_NAME(x, y) \
704   ICING_STATUS_MACROS_CONCAT_IMPL(x, y)
705 #define ICING_STATUS_MACROS_CONCAT_IMPL(x, y) x##y
706 
707 #define ICING_EXPECT_OK(func) EXPECT_THAT(func, IsOk())
708 #define ICING_ASSERT_OK(func) ASSERT_THAT(func, IsOk())
709 #define ICING_ASSERT_OK_AND_ASSIGN(lhs, rexpr)                             \
710   ICING_ASSERT_OK_AND_ASSIGN_IMPL(                                         \
711       ICING_STATUS_MACROS_CONCAT_NAME(_status_or_value, __COUNTER__), lhs, \
712       rexpr)
713 #define ICING_ASSERT_OK_AND_ASSIGN_IMPL(statusor, lhs, rexpr) \
714   auto statusor = (rexpr);                                    \
715   ICING_ASSERT_OK(statusor.status());                         \
716   lhs = std::move(statusor).ValueOrDie()
717 
718 #define ICING_ASSERT_HAS_VALUE_AND_ASSIGN(lhs, rexpr) \
719   ASSERT_TRUE(rexpr);                                 \
720   lhs = rexpr.value()
721 
722 }  // namespace lib
723 }  // namespace icing
724 
725 #endif  // ICING_TESTING_COMMON_MATCHERS_H_
726