search_metadata_unittest.cc revision f2477e01787aa58f445919b809d89e252beef54f
1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/drive/search_metadata.h"
6
7#include "base/file_util.h"
8#include "base/files/scoped_temp_dir.h"
9#include "base/i18n/string_search.h"
10#include "base/message_loop/message_loop_proxy.h"
11#include "base/run_loop.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/chromeos/drive/fake_free_disk_space_getter.h"
14#include "chrome/browser/chromeos/drive/file_cache.h"
15#include "chrome/browser/chromeos/drive/file_system_util.h"
16#include "chrome/browser/chromeos/drive/test_util.h"
17#include "content/public/test/test_browser_thread_bundle.h"
18#include "testing/gtest/include/gtest/gtest.h"
19
20namespace drive {
21namespace internal {
22
23namespace {
24
25const int kDefaultAtMostNumMatches = 10;
26
27// A simple wrapper for testing FindAndHighlightWrapper(). It just converts the
28// query text parameter to FixedPatternStringSearchIgnoringCaseAndAccents.
29bool FindAndHighlightWrapper(
30    const std::string& text,
31    const std::string& query_text,
32    std::string* highlighted_text) {
33  base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
34      base::UTF8ToUTF16(query_text));
35  return FindAndHighlight(text, &query, highlighted_text);
36}
37
38}  // namespace
39
40class SearchMetadataTest : public testing::Test {
41 protected:
42  virtual void SetUp() OVERRIDE {
43    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
44    fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
45
46    metadata_storage_.reset(new ResourceMetadataStorage(
47        temp_dir_.path(), base::MessageLoopProxy::current().get()));
48    ASSERT_TRUE(metadata_storage_->Initialize());
49
50    cache_.reset(new FileCache(metadata_storage_.get(),
51                               temp_dir_.path(),
52                               base::MessageLoopProxy::current().get(),
53                               fake_free_disk_space_getter_.get()));
54    ASSERT_TRUE(cache_->Initialize());
55
56    resource_metadata_.reset(
57        new ResourceMetadata(metadata_storage_.get(),
58                             base::MessageLoopProxy::current()));
59    ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
60
61    AddEntriesToMetadata();
62  }
63
64  void AddEntriesToMetadata() {
65    base::FilePath temp_file;
66    EXPECT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
67                                                    &temp_file));
68    const std::string temp_file_md5 = "md5";
69
70    ResourceEntry entry;
71    std::string local_id;
72
73    // drive/root
74    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
75        util::kDriveMyDriveRootDirName, "root", 100,
76        util::kDriveGrandRootLocalId), &local_id));
77    const std::string root_local_id = local_id;
78
79    // drive/root/Directory 1
80    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
81        "Directory 1", "dir1", 1, root_local_id), &local_id));
82    const std::string dir1_local_id = local_id;
83
84    // drive/root/Directory 1/SubDirectory File 1.txt
85    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
86        "SubDirectory File 1.txt", "file1a", 2, dir1_local_id), &local_id));
87    EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
88        local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY));
89
90    // drive/root/Directory 1/Shared To The Account Owner.txt
91    entry = GetFileEntry(
92        "Shared To The Account Owner.txt", "file1b", 3, dir1_local_id);
93    entry.set_shared_with_me(true);
94    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
95
96    // drive/root/Directory 2 excludeDir-test
97    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
98        "Directory 2 excludeDir-test", "dir2", 4, root_local_id), &local_id));
99
100    // drive/root/Slash \xE2\x88\x95 in directory
101    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
102        GetDirectoryEntry("Slash \xE2\x88\x95 in directory", "dir3", 5,
103                          root_local_id), &local_id));
104    const std::string dir3_local_id = local_id;
105
106    // drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt
107    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
108        "Slash SubDir File.txt", "file3a", 6, dir3_local_id), &local_id));
109
110    // drive/root/File 2.txt
111    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
112        "File 2.txt", "file2", 7, root_local_id), &local_id));
113    EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
114        local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY));
115
116    // drive/root/Document 1 excludeDir-test
117    entry = GetFileEntry(
118        "Document 1 excludeDir-test", "doc1", 8, root_local_id);
119    entry.mutable_file_specific_info()->set_is_hosted_document(true);
120    entry.mutable_file_specific_info()->set_document_extension(".gdoc");
121    EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
122
123  }
124
125  ResourceEntry GetFileEntry(const std::string& name,
126                             const std::string& resource_id,
127                             int64 last_accessed,
128                             const std::string& parent_local_id) {
129    ResourceEntry entry;
130    entry.set_title(name);
131    entry.set_resource_id(resource_id);
132    entry.set_parent_local_id(parent_local_id);
133    entry.mutable_file_info()->set_last_accessed(last_accessed);
134    return entry;
135  }
136
137  ResourceEntry GetDirectoryEntry(const std::string& name,
138                                  const std::string& resource_id,
139                                  int64 last_accessed,
140                                  const std::string& parent_local_id) {
141    ResourceEntry entry;
142    entry.set_title(name);
143    entry.set_resource_id(resource_id);
144    entry.set_parent_local_id(parent_local_id);
145    entry.mutable_file_info()->set_last_accessed(last_accessed);
146    entry.mutable_file_info()->set_is_directory(true);
147    return entry;
148  }
149
150  content::TestBrowserThreadBundle thread_bundle_;
151  base::ScopedTempDir temp_dir_;
152  scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
153  scoped_ptr<ResourceMetadataStorage,
154             test_util::DestroyHelperForTests> metadata_storage_;
155  scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests>
156      resource_metadata_;
157  scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
158};
159
160TEST_F(SearchMetadataTest, SearchMetadata_ZeroMatches) {
161  FileError error = FILE_ERROR_FAILED;
162  scoped_ptr<MetadataSearchResultVector> result;
163
164  SearchMetadata(base::MessageLoopProxy::current(),
165                 resource_metadata_.get(),
166                 "NonExistent",
167                 SEARCH_METADATA_ALL,
168                 kDefaultAtMostNumMatches,
169                 google_apis::test_util::CreateCopyResultCallback(
170                     &error, &result));
171  base::RunLoop().RunUntilIdle();
172  EXPECT_EQ(FILE_ERROR_OK, error);
173  ASSERT_TRUE(result);
174  ASSERT_EQ(0U, result->size());
175}
176
177TEST_F(SearchMetadataTest, SearchMetadata_RegularFile) {
178  FileError error = FILE_ERROR_FAILED;
179  scoped_ptr<MetadataSearchResultVector> result;
180
181  SearchMetadata(base::MessageLoopProxy::current(),
182                 resource_metadata_.get(),
183                 "SubDirectory File 1.txt",
184                 SEARCH_METADATA_ALL,
185                 kDefaultAtMostNumMatches,
186                 google_apis::test_util::CreateCopyResultCallback(
187                     &error, &result));
188  base::RunLoop().RunUntilIdle();
189  EXPECT_EQ(FILE_ERROR_OK, error);
190  ASSERT_TRUE(result);
191  ASSERT_EQ(1U, result->size());
192  EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
193            result->at(0).path.AsUTF8Unsafe());
194}
195
196// This test checks if |FindAndHighlightWrapper| does case-insensitive search.
197// Tricker test cases for |FindAndHighlightWrapper| can be found below.
198TEST_F(SearchMetadataTest, SearchMetadata_CaseInsensitiveSearch) {
199  FileError error = FILE_ERROR_FAILED;
200  scoped_ptr<MetadataSearchResultVector> result;
201
202  // The query is all in lower case.
203  SearchMetadata(base::MessageLoopProxy::current(),
204                 resource_metadata_.get(),
205                 "subdirectory file 1.txt",
206                 SEARCH_METADATA_ALL,
207                 kDefaultAtMostNumMatches,
208                 google_apis::test_util::CreateCopyResultCallback(
209                     &error, &result));
210  base::RunLoop().RunUntilIdle();
211  EXPECT_EQ(FILE_ERROR_OK, error);
212  ASSERT_TRUE(result);
213  ASSERT_EQ(1U, result->size());
214  EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
215            result->at(0).path.AsUTF8Unsafe());
216}
217
218TEST_F(SearchMetadataTest, SearchMetadata_RegularFiles) {
219  FileError error = FILE_ERROR_FAILED;
220  scoped_ptr<MetadataSearchResultVector> result;
221
222  SearchMetadata(base::MessageLoopProxy::current(),
223                 resource_metadata_.get(),
224                 "SubDir",
225                 SEARCH_METADATA_ALL,
226                 kDefaultAtMostNumMatches,
227                 google_apis::test_util::CreateCopyResultCallback(
228                     &error, &result));
229  base::RunLoop().RunUntilIdle();
230  EXPECT_EQ(FILE_ERROR_OK, error);
231  ASSERT_TRUE(result);
232  ASSERT_EQ(2U, result->size());
233
234  // The results should be sorted by the last accessed time in descending order.
235  EXPECT_EQ(6, result->at(0).entry.file_info().last_accessed());
236  EXPECT_EQ(2, result->at(1).entry.file_info().last_accessed());
237
238  // All base names should contain "File".
239  EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
240            result->at(0).path.AsUTF8Unsafe());
241  EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
242            result->at(1).path.AsUTF8Unsafe());
243}
244
245TEST_F(SearchMetadataTest, SearchMetadata_AtMostOneFile) {
246  FileError error = FILE_ERROR_FAILED;
247  scoped_ptr<MetadataSearchResultVector> result;
248
249  // There are two files matching "SubDir" but only one file should be
250  // returned.
251  SearchMetadata(base::MessageLoopProxy::current(),
252                 resource_metadata_.get(),
253                 "SubDir",
254                 SEARCH_METADATA_ALL,
255                 1,  // at_most_num_matches
256                 google_apis::test_util::CreateCopyResultCallback(
257                     &error, &result));
258  base::RunLoop().RunUntilIdle();
259  EXPECT_EQ(FILE_ERROR_OK, error);
260  ASSERT_TRUE(result);
261  ASSERT_EQ(1U, result->size());
262  EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
263            result->at(0).path.AsUTF8Unsafe());
264}
265
266TEST_F(SearchMetadataTest, SearchMetadata_Directory) {
267  FileError error = FILE_ERROR_FAILED;
268  scoped_ptr<MetadataSearchResultVector> result;
269
270  SearchMetadata(base::MessageLoopProxy::current(),
271                 resource_metadata_.get(),
272                 "Directory 1",
273                 SEARCH_METADATA_ALL,
274                 kDefaultAtMostNumMatches,
275                 google_apis::test_util::CreateCopyResultCallback(
276                     &error, &result));
277  base::RunLoop().RunUntilIdle();
278  EXPECT_EQ(FILE_ERROR_OK, error);
279  ASSERT_TRUE(result);
280  ASSERT_EQ(1U, result->size());
281  EXPECT_EQ("drive/root/Directory 1", result->at(0).path.AsUTF8Unsafe());
282}
283
284TEST_F(SearchMetadataTest, SearchMetadata_HostedDocument) {
285  FileError error = FILE_ERROR_FAILED;
286  scoped_ptr<MetadataSearchResultVector> result;
287
288  SearchMetadata(base::MessageLoopProxy::current(),
289                 resource_metadata_.get(),
290                 "Document",
291                 SEARCH_METADATA_ALL,
292                 kDefaultAtMostNumMatches,
293                 google_apis::test_util::CreateCopyResultCallback(
294                     &error, &result));
295  base::RunLoop().RunUntilIdle();
296  EXPECT_EQ(FILE_ERROR_OK, error);
297  ASSERT_TRUE(result);
298  ASSERT_EQ(1U, result->size());
299
300  EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
301            result->at(0).path.AsUTF8Unsafe());
302}
303
304TEST_F(SearchMetadataTest, SearchMetadata_ExcludeHostedDocument) {
305  FileError error = FILE_ERROR_FAILED;
306  scoped_ptr<MetadataSearchResultVector> result;
307
308  SearchMetadata(base::MessageLoopProxy::current(),
309                 resource_metadata_.get(),
310                 "Document",
311                 SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS,
312                 kDefaultAtMostNumMatches,
313                 google_apis::test_util::CreateCopyResultCallback(
314                     &error, &result));
315  base::RunLoop().RunUntilIdle();
316  EXPECT_EQ(FILE_ERROR_OK, error);
317  ASSERT_TRUE(result);
318  ASSERT_EQ(0U, result->size());
319}
320
321TEST_F(SearchMetadataTest, SearchMetadata_SharedWithMe) {
322  FileError error = FILE_ERROR_FAILED;
323  scoped_ptr<MetadataSearchResultVector> result;
324
325  SearchMetadata(base::MessageLoopProxy::current(),
326                 resource_metadata_.get(),
327                 "",
328                 SEARCH_METADATA_SHARED_WITH_ME,
329                 kDefaultAtMostNumMatches,
330                 google_apis::test_util::CreateCopyResultCallback(
331                     &error, &result));
332  base::RunLoop().RunUntilIdle();
333  EXPECT_EQ(FILE_ERROR_OK, error);
334  ASSERT_TRUE(result);
335  ASSERT_EQ(1U, result->size());
336  EXPECT_EQ("drive/root/Directory 1/Shared To The Account Owner.txt",
337            result->at(0).path.AsUTF8Unsafe());
338}
339
340TEST_F(SearchMetadataTest, SearchMetadata_FileAndDirectory) {
341  FileError error = FILE_ERROR_FAILED;
342  scoped_ptr<MetadataSearchResultVector> result;
343
344  SearchMetadata(base::MessageLoopProxy::current(),
345                 resource_metadata_.get(),
346                 "excludeDir-test",
347                 SEARCH_METADATA_ALL,
348                 kDefaultAtMostNumMatches,
349                 google_apis::test_util::CreateCopyResultCallback(
350                     &error, &result));
351
352  base::RunLoop().RunUntilIdle();
353  EXPECT_EQ(FILE_ERROR_OK, error);
354  ASSERT_TRUE(result);
355  ASSERT_EQ(2U, result->size());
356
357  EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
358            result->at(0).path.AsUTF8Unsafe());
359  EXPECT_EQ("drive/root/Directory 2 excludeDir-test",
360            result->at(1).path.AsUTF8Unsafe());
361}
362
363TEST_F(SearchMetadataTest, SearchMetadata_ExcludeDirectory) {
364  FileError error = FILE_ERROR_FAILED;
365  scoped_ptr<MetadataSearchResultVector> result;
366
367  SearchMetadata(base::MessageLoopProxy::current(),
368                 resource_metadata_.get(),
369                 "excludeDir-test",
370                 SEARCH_METADATA_EXCLUDE_DIRECTORIES,
371                 kDefaultAtMostNumMatches,
372                 google_apis::test_util::CreateCopyResultCallback(
373                     &error, &result));
374
375  base::RunLoop().RunUntilIdle();
376  EXPECT_EQ(FILE_ERROR_OK, error);
377  ASSERT_TRUE(result);
378  ASSERT_EQ(1U, result->size());
379
380  EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
381            result->at(0).path.AsUTF8Unsafe());
382}
383
384// "drive", "drive/root", "drive/other" should be excluded.
385TEST_F(SearchMetadataTest, SearchMetadata_ExcludeSpecialDirectories) {
386  const char* kQueries[] = { "drive", "root", "other" };
387  for (size_t i = 0; i < arraysize(kQueries); ++i) {
388    FileError error = FILE_ERROR_FAILED;
389    scoped_ptr<MetadataSearchResultVector> result;
390
391    const std::string query = kQueries[i];
392    SearchMetadata(base::MessageLoopProxy::current(),
393                   resource_metadata_.get(),
394                   query,
395                   SEARCH_METADATA_ALL,
396                   kDefaultAtMostNumMatches,
397                   google_apis::test_util::CreateCopyResultCallback(
398                       &error, &result));
399
400    base::RunLoop().RunUntilIdle();
401    EXPECT_EQ(FILE_ERROR_OK, error);
402    ASSERT_TRUE(result);
403    ASSERT_TRUE(result->empty()) << ": " << query << " should not match";
404  }
405}
406
407TEST_F(SearchMetadataTest, SearchMetadata_Offline) {
408  FileError error = FILE_ERROR_FAILED;
409  scoped_ptr<MetadataSearchResultVector> result;
410
411  SearchMetadata(base::MessageLoopProxy::current(),
412                 resource_metadata_.get(),
413                 "",
414                 SEARCH_METADATA_OFFLINE,
415                 kDefaultAtMostNumMatches,
416                 google_apis::test_util::CreateCopyResultCallback(
417                     &error, &result));
418  base::RunLoop().RunUntilIdle();
419  EXPECT_EQ(FILE_ERROR_OK, error);
420  ASSERT_EQ(3U, result->size());
421
422  // This is not included in the cache but is a hosted document.
423  EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
424            result->at(0).path.AsUTF8Unsafe());
425
426  EXPECT_EQ("drive/root/File 2.txt",
427            result->at(1).path.AsUTF8Unsafe());
428  EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
429            result->at(2).path.AsUTF8Unsafe());
430}
431
432TEST(SearchMetadataSimpleTest, FindAndHighlight_ZeroMatches) {
433  std::string highlighted_text;
434  EXPECT_FALSE(FindAndHighlightWrapper("text", "query", &highlighted_text));
435}
436
437TEST(SearchMetadataSimpleTest, FindAndHighlight_EmptyText) {
438  std::string highlighted_text;
439  EXPECT_FALSE(FindAndHighlightWrapper("", "query", &highlighted_text));
440}
441
442TEST(SearchMetadataSimpleTest, FindAndHighlight_FullMatch) {
443  std::string highlighted_text;
444  EXPECT_TRUE(FindAndHighlightWrapper("hello", "hello", &highlighted_text));
445  EXPECT_EQ("<b>hello</b>", highlighted_text);
446}
447
448TEST(SearchMetadataSimpleTest, FindAndHighlight_StartWith) {
449  std::string highlighted_text;
450  EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "hello",
451                                     &highlighted_text));
452  EXPECT_EQ("<b>hello</b>, world", highlighted_text);
453}
454
455TEST(SearchMetadataSimpleTest, FindAndHighlight_EndWith) {
456  std::string highlighted_text;
457  EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "world",
458                                     &highlighted_text));
459  EXPECT_EQ("hello, <b>world</b>", highlighted_text);
460}
461
462TEST(SearchMetadataSimpleTest, FindAndHighlight_InTheMiddle) {
463  std::string highlighted_text;
464  EXPECT_TRUE(FindAndHighlightWrapper("yo hello, world", "hello",
465                                     &highlighted_text));
466  EXPECT_EQ("yo <b>hello</b>, world", highlighted_text);
467}
468
469TEST(SearchMetadataSimpleTest, FindAndHighlight_MultipeMatches) {
470  std::string highlighted_text;
471  EXPECT_TRUE(FindAndHighlightWrapper("yoyoyoyoy", "yoy", &highlighted_text));
472  // Only the first match is highlighted.
473  EXPECT_EQ("<b>yoy</b>oyoyoy", highlighted_text);
474}
475
476TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCase) {
477  std::string highlighted_text;
478  EXPECT_TRUE(FindAndHighlightWrapper("HeLLo", "hello", &highlighted_text));
479  EXPECT_EQ("<b>HeLLo</b>", highlighted_text);
480}
481
482TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCaseNonASCII) {
483  std::string highlighted_text;
484
485  // Case and accent ignorance in Greek. Find "socra" in "Socra'tes".
486  EXPECT_TRUE(FindAndHighlightWrapper(
487      "\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC\xCF\x84\xCE\xB7\xCF\x82",
488      "\xCF\x83\xCF\x89\xCE\xBA\xCF\x81\xCE\xB1", &highlighted_text));
489  EXPECT_EQ(
490      "<b>\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC</b>\xCF\x84\xCE\xB7\xCF\x82",
491      highlighted_text);
492
493  // In Japanese characters.
494  // Find Hiragana "pi" + "(small)ya" in Katakana "hi" + semi-voiced-mark + "ya"
495  EXPECT_TRUE(FindAndHighlightWrapper(
496      "\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83\xE3\x83\xBC",
497      "\xE3\x83\x94\xE3\x83\xA4",
498      &highlighted_text));
499  EXPECT_EQ(
500      "<b>\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83</b>\xE3\x83\xBC",
501      highlighted_text);
502}
503
504TEST(SearchMetadataSimpleTest, MultiTextBySingleQuery) {
505  base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
506      base::UTF8ToUTF16("hello"));
507
508  std::string highlighted_text;
509  EXPECT_TRUE(FindAndHighlight("hello", &query, &highlighted_text));
510  EXPECT_EQ("<b>hello</b>", highlighted_text);
511  EXPECT_FALSE(FindAndHighlight("goodbye", &query, &highlighted_text));
512  EXPECT_TRUE(FindAndHighlight("1hello2", &query, &highlighted_text));
513  EXPECT_EQ("1<b>hello</b>2", highlighted_text);
514}
515
516TEST(SearchMetadataSimpleTest, FindAndHighlight_MetaChars) {
517  std::string highlighted_text;
518  EXPECT_TRUE(FindAndHighlightWrapper("<hello>", "hello", &highlighted_text));
519  EXPECT_EQ("&lt;<b>hello</b>&gt;", highlighted_text);
520}
521
522TEST(SearchMetadataSimpleTest, FindAndHighlight_MoreMetaChars) {
523  std::string highlighted_text;
524  EXPECT_TRUE(FindAndHighlightWrapper("a&b&c&d", "b&c", &highlighted_text));
525  EXPECT_EQ("a&amp;<b>b&amp;c</b>&amp;d", highlighted_text);
526}
527
528}  // namespace internal
529}  // namespace drive
530