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("<<b>hello</b>>", 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&<b>b&c</b>&d", highlighted_text); 526} 527 528} // namespace internal 529} // namespace drive 530