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 20 namespace drive { 21 namespace internal { 22 23 namespace { 24 25 const int kDefaultAtMostNumMatches = 10; 26 27 // A simple wrapper for testing FindAndHighlightWrapper(). It just converts the 28 // query text parameter to FixedPatternStringSearchIgnoringCaseAndAccents. 29 bool 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 40 class 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(base::CreateTemporaryFileInDir(temp_dir_.path(), &temp_file)); 67 const std::string temp_file_md5 = "md5"; 68 69 ResourceEntry entry; 70 std::string local_id; 71 72 // drive/root 73 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry( 74 util::kDriveMyDriveRootDirName, "root", 100, 75 util::kDriveGrandRootLocalId), &local_id)); 76 const std::string root_local_id = local_id; 77 78 // drive/root/Directory 1 79 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry( 80 "Directory 1", "dir1", 1, root_local_id), &local_id)); 81 const std::string dir1_local_id = local_id; 82 83 // drive/root/Directory 1/SubDirectory File 1.txt 84 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry( 85 "SubDirectory File 1.txt", "file1a", 2, dir1_local_id), &local_id)); 86 EXPECT_EQ(FILE_ERROR_OK, cache_->Store( 87 local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY)); 88 89 // drive/root/Directory 1/Shared To The Account Owner.txt 90 entry = GetFileEntry( 91 "Shared To The Account Owner.txt", "file1b", 3, dir1_local_id); 92 entry.set_shared_with_me(true); 93 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id)); 94 95 // drive/root/Directory 2 excludeDir-test 96 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry( 97 "Directory 2 excludeDir-test", "dir2", 4, root_local_id), &local_id)); 98 99 // drive/root/Slash \xE2\x88\x95 in directory 100 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry( 101 GetDirectoryEntry("Slash \xE2\x88\x95 in directory", "dir3", 5, 102 root_local_id), &local_id)); 103 const std::string dir3_local_id = local_id; 104 105 // drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt 106 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry( 107 "Slash SubDir File.txt", "file3a", 6, dir3_local_id), &local_id)); 108 109 // drive/root/File 2.txt 110 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry( 111 "File 2.txt", "file2", 7, root_local_id), &local_id)); 112 EXPECT_EQ(FILE_ERROR_OK, cache_->Store( 113 local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY)); 114 115 // drive/root/Document 1 excludeDir-test 116 entry = GetFileEntry( 117 "Document 1 excludeDir-test", "doc1", 8, root_local_id); 118 entry.mutable_file_specific_info()->set_is_hosted_document(true); 119 entry.mutable_file_specific_info()->set_document_extension(".gdoc"); 120 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id)); 121 122 } 123 124 ResourceEntry GetFileEntry(const std::string& name, 125 const std::string& resource_id, 126 int64 last_accessed, 127 const std::string& parent_local_id) { 128 ResourceEntry entry; 129 entry.set_title(name); 130 entry.set_resource_id(resource_id); 131 entry.set_parent_local_id(parent_local_id); 132 entry.mutable_file_info()->set_last_accessed(last_accessed); 133 return entry; 134 } 135 136 ResourceEntry GetDirectoryEntry(const std::string& name, 137 const std::string& resource_id, 138 int64 last_accessed, 139 const std::string& parent_local_id) { 140 ResourceEntry entry; 141 entry.set_title(name); 142 entry.set_resource_id(resource_id); 143 entry.set_parent_local_id(parent_local_id); 144 entry.mutable_file_info()->set_last_accessed(last_accessed); 145 entry.mutable_file_info()->set_is_directory(true); 146 return entry; 147 } 148 149 content::TestBrowserThreadBundle thread_bundle_; 150 base::ScopedTempDir temp_dir_; 151 scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_; 152 scoped_ptr<ResourceMetadataStorage, 153 test_util::DestroyHelperForTests> metadata_storage_; 154 scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests> 155 resource_metadata_; 156 scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_; 157 }; 158 159 TEST_F(SearchMetadataTest, SearchMetadata_ZeroMatches) { 160 FileError error = FILE_ERROR_FAILED; 161 scoped_ptr<MetadataSearchResultVector> result; 162 163 SearchMetadata(base::MessageLoopProxy::current(), 164 resource_metadata_.get(), 165 "NonExistent", 166 SEARCH_METADATA_ALL, 167 kDefaultAtMostNumMatches, 168 google_apis::test_util::CreateCopyResultCallback( 169 &error, &result)); 170 base::RunLoop().RunUntilIdle(); 171 EXPECT_EQ(FILE_ERROR_OK, error); 172 ASSERT_TRUE(result); 173 ASSERT_EQ(0U, result->size()); 174 } 175 176 TEST_F(SearchMetadataTest, SearchMetadata_RegularFile) { 177 FileError error = FILE_ERROR_FAILED; 178 scoped_ptr<MetadataSearchResultVector> result; 179 180 SearchMetadata(base::MessageLoopProxy::current(), 181 resource_metadata_.get(), 182 "SubDirectory File 1.txt", 183 SEARCH_METADATA_ALL, 184 kDefaultAtMostNumMatches, 185 google_apis::test_util::CreateCopyResultCallback( 186 &error, &result)); 187 base::RunLoop().RunUntilIdle(); 188 EXPECT_EQ(FILE_ERROR_OK, error); 189 ASSERT_TRUE(result); 190 ASSERT_EQ(1U, result->size()); 191 EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt", 192 result->at(0).path.AsUTF8Unsafe()); 193 } 194 195 // This test checks if |FindAndHighlightWrapper| does case-insensitive search. 196 // Tricker test cases for |FindAndHighlightWrapper| can be found below. 197 TEST_F(SearchMetadataTest, SearchMetadata_CaseInsensitiveSearch) { 198 FileError error = FILE_ERROR_FAILED; 199 scoped_ptr<MetadataSearchResultVector> result; 200 201 // The query is all in lower case. 202 SearchMetadata(base::MessageLoopProxy::current(), 203 resource_metadata_.get(), 204 "subdirectory file 1.txt", 205 SEARCH_METADATA_ALL, 206 kDefaultAtMostNumMatches, 207 google_apis::test_util::CreateCopyResultCallback( 208 &error, &result)); 209 base::RunLoop().RunUntilIdle(); 210 EXPECT_EQ(FILE_ERROR_OK, error); 211 ASSERT_TRUE(result); 212 ASSERT_EQ(1U, result->size()); 213 EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt", 214 result->at(0).path.AsUTF8Unsafe()); 215 } 216 217 TEST_F(SearchMetadataTest, SearchMetadata_RegularFiles) { 218 FileError error = FILE_ERROR_FAILED; 219 scoped_ptr<MetadataSearchResultVector> result; 220 221 SearchMetadata(base::MessageLoopProxy::current(), 222 resource_metadata_.get(), 223 "SubDir", 224 SEARCH_METADATA_ALL, 225 kDefaultAtMostNumMatches, 226 google_apis::test_util::CreateCopyResultCallback( 227 &error, &result)); 228 base::RunLoop().RunUntilIdle(); 229 EXPECT_EQ(FILE_ERROR_OK, error); 230 ASSERT_TRUE(result); 231 ASSERT_EQ(2U, result->size()); 232 233 // The results should be sorted by the last accessed time in descending order. 234 EXPECT_EQ(6, result->at(0).entry.file_info().last_accessed()); 235 EXPECT_EQ(2, result->at(1).entry.file_info().last_accessed()); 236 237 // All base names should contain "File". 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