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