1// Copyright 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 <string> 6#include <vector> 7 8#include "base/bind.h" 9#include "base/files/file_path.h" 10#include "base/files/file_util.h" 11#include "base/files/scoped_temp_dir.h" 12#include "base/format_macros.h" 13#include "base/logging.h" 14#include "base/memory/scoped_ptr.h" 15#include "base/message_loop/message_loop.h" 16#include "base/run_loop.h" 17#include "base/strings/stringprintf.h" 18#include "chrome/browser/media_galleries/fileapi/itunes_data_provider.h" 19#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h" 20#include "chrome/browser/media_galleries/imported_media_gallery_registry.h" 21#include "chrome/test/base/in_process_browser_test.h" 22#include "content/public/browser/browser_thread.h" 23#include "url/gurl.h" 24 25namespace itunes { 26 27namespace { 28 29struct LibraryEntry { 30 LibraryEntry(const std::string& artist, const std::string& album, 31 const base::FilePath& location) 32 : artist(artist), 33 album(album), 34 location(location) { 35 } 36 std::string artist; 37 std::string album; 38 base::FilePath location; 39}; 40 41// 'c' with combinding cedilla. 42const char kDeNormalizedName[] = { 43 'c', static_cast<unsigned char>(0xCC), static_cast<unsigned char>(0xA7), 0}; 44// 'c' with cedilla. 45const char kNormalizedName[] = { 46 static_cast<unsigned char>(0xC3), static_cast<unsigned char>(0xA7), 0}; 47 48} // namespace 49 50class TestITunesDataProvider : public ITunesDataProvider { 51 public: 52 TestITunesDataProvider(const base::FilePath& xml_library_path, 53 const base::Closure& callback) 54 : ITunesDataProvider(xml_library_path), 55 callback_(callback) { 56 } 57 virtual ~TestITunesDataProvider() {} 58 59 private: 60 virtual void OnLibraryChanged(const base::FilePath& path, 61 bool error) OVERRIDE { 62 ITunesDataProvider::OnLibraryChanged(path, error); 63 callback_.Run(); 64 } 65 66 base::Closure callback_; 67 68 DISALLOW_COPY_AND_ASSIGN(TestITunesDataProvider); 69}; 70 71class ITunesDataProviderTest : public InProcessBrowserTest { 72 public: 73 ITunesDataProviderTest() {} 74 virtual ~ITunesDataProviderTest() {} 75 76 protected: 77 virtual void SetUp() OVERRIDE { 78 ASSERT_TRUE(library_dir_.CreateUniqueTempDir()); 79 WriteLibraryInternal(SetUpLibrary()); 80 // The ImportedMediaGalleryRegistry is created on which ever thread calls 81 // GetInstance() first. It shouldn't matter what thread creates, however 82 // in practice it is always created on the UI thread, so this calls 83 // GetInstance here to mirror those real conditions. 84 ImportedMediaGalleryRegistry::GetInstance(); 85 InProcessBrowserTest::SetUp(); 86 } 87 88 void RunTest() { 89 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 90 base::RunLoop loop; 91 quit_closure_ = loop.QuitClosure(); 92 MediaFileSystemBackend::MediaTaskRunner()->PostTask( 93 FROM_HERE, 94 base::Bind(&ITunesDataProviderTest::StartTestOnMediaTaskRunner, 95 base::Unretained(this))); 96 loop.Run(); 97 } 98 99 void WriteLibrary(const std::vector<LibraryEntry>& entries, 100 const base::Closure& callback) { 101 SetLibraryChangeCallback(callback); 102 WriteLibraryInternal(entries); 103 } 104 105 void SetLibraryChangeCallback(const base::Closure& callback) { 106 EXPECT_TRUE(library_changed_callback_.is_null()); 107 library_changed_callback_ = callback; 108 } 109 110 ITunesDataProvider* data_provider() const { 111 return ImportedMediaGalleryRegistry::ITunesDataProvider(); 112 } 113 114 const base::FilePath& library_dir() const { 115 return library_dir_.path(); 116 } 117 118 base::FilePath XmlFile() const { 119 return library_dir_.path().AppendASCII("library.xml"); 120 } 121 122 void ExpectTrackLocation(const std::string& artist, const std::string& album, 123 const std::string& track_name) { 124 base::FilePath track = 125 library_dir().AppendASCII(track_name).NormalizePathSeparators(); 126 EXPECT_EQ(track.value(), 127 data_provider()->GetTrackLocation( 128 artist, album, track_name).NormalizePathSeparators().value()); 129 } 130 131 void ExpectNoTrack(const std::string& artist, const std::string& album, 132 const std::string& track_name) { 133 EXPECT_TRUE(data_provider()->GetTrackLocation( 134 artist, album, track_name).empty()) << track_name; 135 } 136 137 138 // Get the initial set of library entries, called by SetUp. If no entries 139 // are returned the xml file is not created. 140 virtual std::vector<LibraryEntry> SetUpLibrary() { 141 return std::vector<LibraryEntry>(); 142 } 143 144 // Start the test. The data provider is refreshed before calling StartTest 145 // and the result of the refresh is passed in. 146 virtual void StartTest(bool parse_success) = 0; 147 148 void TestDone() { 149 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 150 ImportedMediaGalleryRegistry* imported_registry = 151 ImportedMediaGalleryRegistry::GetInstance(); 152 imported_registry->itunes_data_provider_.reset(); 153 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 154 quit_closure_); 155 } 156 157 private: 158 void StartTestOnMediaTaskRunner() { 159 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 160 ImportedMediaGalleryRegistry* imported_registry = 161 ImportedMediaGalleryRegistry::GetInstance(); 162 imported_registry->itunes_data_provider_.reset( 163 new TestITunesDataProvider( 164 XmlFile(), 165 base::Bind(&ITunesDataProviderTest::OnLibraryChanged, 166 base::Unretained(this)))); 167 data_provider()->RefreshData(base::Bind(&ITunesDataProviderTest::StartTest, 168 base::Unretained(this))); 169 }; 170 171 void OnLibraryChanged() { 172 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 173 if (!library_changed_callback_.is_null()) { 174 library_changed_callback_.Run(); 175 library_changed_callback_.Reset(); 176 } 177 } 178 179 void WriteLibraryInternal(const std::vector<LibraryEntry>& entries) { 180 if (!entries.size()) 181 return; 182 std::string xml = "<plist><dict><key>Tracks</key><dict>\n"; 183 for (size_t i = 0; i < entries.size(); ++i) { 184 std::string separator; 185#if defined(OS_WIN) 186 separator = "/"; 187#endif 188 GURL location("file://localhost" + separator + 189 entries[i].location.AsUTF8Unsafe()); 190 std::string entry_string = base::StringPrintf( 191 "<key>%" PRIuS "</key><dict>\n" 192 " <key>Track ID</key><integer>%" PRIuS "</integer>\n" 193 " <key>Location</key><string>%s</string>\n" 194 " <key>Artist</key><string>%s</string>\n" 195 " <key>Album</key><string>%s</string>\n" 196 "</dict>\n", 197 i + 1, i + 1, location.spec().c_str(), entries[i].artist.c_str(), 198 entries[i].album.c_str()); 199 xml += entry_string; 200 } 201 xml += "</dict></dict></plist>\n"; 202 ASSERT_EQ(static_cast<int>(xml.size()), 203 base::WriteFile(XmlFile(), xml.c_str(), xml.size())); 204 } 205 206 base::ScopedTempDir library_dir_; 207 208 base::Closure library_changed_callback_; 209 210 base::Closure quit_closure_; 211 212 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderTest); 213}; 214 215class ITunesDataProviderBasicTest : public ITunesDataProviderTest { 216 public: 217 ITunesDataProviderBasicTest() {} 218 virtual ~ITunesDataProviderBasicTest() {} 219 220 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 221 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 222 std::vector<LibraryEntry> entries; 223 entries.push_back(LibraryEntry("Artist", "Album", track)); 224 return entries; 225 } 226 227 virtual void StartTest(bool parse_success) OVERRIDE { 228 EXPECT_TRUE(parse_success); 229 230 // KnownArtist 231 EXPECT_TRUE(data_provider()->KnownArtist("Artist")); 232 EXPECT_FALSE(data_provider()->KnownArtist("Artist2")); 233 234 // KnownAlbum 235 EXPECT_TRUE(data_provider()->KnownAlbum("Artist", "Album")); 236 EXPECT_FALSE(data_provider()->KnownAlbum("Artist", "Album2")); 237 EXPECT_FALSE(data_provider()->KnownAlbum("Artist2", "Album")); 238 239 // GetTrackLocation 240 ExpectTrackLocation("Artist", "Album", "Track.mp3"); 241 ExpectNoTrack("Artist", "Album", "Track2.mp3"); 242 ExpectNoTrack("Artist", "Album2", "Track.mp3"); 243 ExpectNoTrack("Artist2", "Album", "Track.mp3"); 244 245 // GetArtistNames 246 std::set<ITunesDataProvider::ArtistName> artists = 247 data_provider()->GetArtistNames(); 248 ASSERT_EQ(1U, artists.size()); 249 EXPECT_EQ("Artist", *artists.begin()); 250 251 // GetAlbumNames 252 std::set<ITunesDataProvider::AlbumName> albums = 253 data_provider()->GetAlbumNames("Artist"); 254 ASSERT_EQ(1U, albums.size()); 255 EXPECT_EQ("Album", *albums.begin()); 256 257 albums = data_provider()->GetAlbumNames("Artist2"); 258 EXPECT_EQ(0U, albums.size()); 259 260 // GetAlbum 261 base::FilePath track = 262 library_dir().AppendASCII("Track.mp3").NormalizePathSeparators(); 263 ITunesDataProvider::Album album = 264 data_provider()->GetAlbum("Artist", "Album"); 265 ASSERT_EQ(1U, album.size()); 266 EXPECT_EQ(track.BaseName().AsUTF8Unsafe(), album.begin()->first); 267 EXPECT_EQ(track.value(), 268 album.begin()->second.NormalizePathSeparators().value()); 269 270 album = data_provider()->GetAlbum("Artist", "Album2"); 271 EXPECT_EQ(0U, album.size()); 272 273 album = data_provider()->GetAlbum("Artist2", "Album"); 274 EXPECT_EQ(0U, album.size()); 275 276 TestDone(); 277 } 278 279 private: 280 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderBasicTest); 281}; 282 283class ITunesDataProviderRefreshTest : public ITunesDataProviderTest { 284 public: 285 ITunesDataProviderRefreshTest() {} 286 virtual ~ITunesDataProviderRefreshTest() {} 287 288 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 289 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 290 std::vector<LibraryEntry> entries; 291 entries.push_back(LibraryEntry("Artist", "Album", track)); 292 return entries; 293 } 294 295 virtual void StartTest(bool parse_success) OVERRIDE { 296 EXPECT_TRUE(parse_success); 297 298 // Initial contents. 299 ExpectTrackLocation("Artist", "Album", "Track.mp3"); 300 ExpectNoTrack("Artist2", "Album2", "Track2.mp3"); 301 302 // New file. 303 base::FilePath track2 = library_dir().AppendASCII("Track2.mp3"); 304 std::vector<LibraryEntry> entries; 305 entries.push_back(LibraryEntry("Artist2", "Album2", track2)); 306 WriteLibrary(entries, 307 base::Bind(&ITunesDataProviderRefreshTest::CheckAfterWrite, 308 base::Unretained(this))); 309 } 310 311 void CheckAfterWrite() { 312 // Content the same. 313 ExpectTrackLocation("Artist", "Album", "Track.mp3"); 314 ExpectNoTrack("Artist2", "Album2", "Track2.mp3"); 315 316 data_provider()->RefreshData( 317 base::Bind(&ITunesDataProviderRefreshTest::CheckRefresh, 318 base::Unretained(this))); 319 } 320 321 void CheckRefresh(bool is_valid) { 322 EXPECT_TRUE(is_valid); 323 324 ExpectTrackLocation("Artist2", "Album2", "Track2.mp3"); 325 ExpectNoTrack("Artist", "Album", "Track.mp3"); 326 TestDone(); 327 } 328 329 private: 330 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderRefreshTest); 331}; 332 333class ITunesDataProviderInvalidTest : public ITunesDataProviderTest { 334 public: 335 ITunesDataProviderInvalidTest() {} 336 virtual ~ITunesDataProviderInvalidTest() {} 337 338 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 339 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 340 std::vector<LibraryEntry> entries; 341 entries.push_back(LibraryEntry("Artist", "Album", track)); 342 return entries; 343 } 344 345 virtual void StartTest(bool parse_success) OVERRIDE { 346 EXPECT_TRUE(parse_success); 347 348 SetLibraryChangeCallback( 349 base::Bind(&ITunesDataProvider::RefreshData, 350 base::Unretained(data_provider()), 351 base::Bind(&ITunesDataProviderInvalidTest::CheckInvalid, 352 base::Unretained(this)))); 353 ASSERT_EQ(1L, base::WriteFile(XmlFile(), " ", 1)); 354 } 355 356 void CheckInvalid(bool is_valid) { 357 EXPECT_FALSE(is_valid); 358 TestDone(); 359 } 360 361 private: 362 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderInvalidTest); 363}; 364 365class ITunesDataProviderUniqueNameTest : public ITunesDataProviderTest { 366 public: 367 ITunesDataProviderUniqueNameTest() {} 368 virtual ~ITunesDataProviderUniqueNameTest() {} 369 370 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 371 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 372 std::vector<LibraryEntry> entries; 373 // Dupe album names should get uniquified with the track id, which in the 374 // test framework is the vector index. 375 entries.push_back(LibraryEntry("Artist", "Album", track)); 376 entries.push_back(LibraryEntry("Artist", "Album", track)); 377 entries.push_back(LibraryEntry("Artist", "Album2", track)); 378 return entries; 379 } 380 381 virtual void StartTest(bool parse_success) OVERRIDE { 382 EXPECT_TRUE(parse_success); 383 384 base::FilePath track = 385 library_dir().AppendASCII("Track.mp3").NormalizePathSeparators(); 386 EXPECT_EQ(track.value(), 387 data_provider()->GetTrackLocation( 388 "Artist", "Album", 389 "Track (1).mp3").NormalizePathSeparators().value()); 390 EXPECT_EQ(track.value(), 391 data_provider()->GetTrackLocation( 392 "Artist", "Album", 393 "Track (2).mp3").NormalizePathSeparators().value()); 394 EXPECT_EQ(track.value(), 395 data_provider()->GetTrackLocation( 396 "Artist", "Album2", 397 "Track.mp3").NormalizePathSeparators().value()); 398 399 TestDone(); 400 } 401 402 private: 403 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderUniqueNameTest); 404}; 405 406class ITunesDataProviderEscapeTest : public ITunesDataProviderTest { 407 // Albums and tracks that aren't the same, but become the same after 408 // replacing bad characters are not handled properly, but that case should 409 // never happen in practice. 410 public: 411 ITunesDataProviderEscapeTest() {} 412 virtual ~ITunesDataProviderEscapeTest() {} 413 414 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 415 base::FilePath track = library_dir().AppendASCII("Track:1.mp3"); 416 std::vector<LibraryEntry> entries; 417 entries.push_back(LibraryEntry("Artist:/name", "Album:name/", track)); 418 entries.push_back(LibraryEntry("Artist/name", "Album:name", track)); 419 entries.push_back(LibraryEntry("Artist/name", "Album:name", track)); 420 entries.push_back(LibraryEntry(kDeNormalizedName, kNormalizedName, track)); 421 return entries; 422 } 423 424 virtual void StartTest(bool parse_success) OVERRIDE { 425 EXPECT_TRUE(parse_success); 426 427 base::FilePath track = 428 library_dir().AppendASCII("Track:1.mp3").NormalizePathSeparators(); 429 EXPECT_EQ(track.value(), 430 data_provider()->GetTrackLocation( 431 "Artist__name", "Album_name_", 432 "Track_1.mp3").NormalizePathSeparators().value()); 433 EXPECT_EQ(track.value(), 434 data_provider()->GetTrackLocation( 435 "Artist_name", "Album_name", 436 "Track_1 (2).mp3").NormalizePathSeparators().value()); 437 EXPECT_EQ(track.value(), 438 data_provider()->GetTrackLocation( 439 "Artist_name", "Album_name", 440 "Track_1 (3).mp3").NormalizePathSeparators().value()); 441 EXPECT_EQ(track.value(), 442 data_provider()->GetTrackLocation( 443 kNormalizedName, kNormalizedName, 444 "Track_1.mp3").NormalizePathSeparators().value()); 445 446 TestDone(); 447 } 448 449 private: 450 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderEscapeTest); 451}; 452 453IN_PROC_BROWSER_TEST_F(ITunesDataProviderBasicTest, BasicTest) { 454 RunTest(); 455} 456 457IN_PROC_BROWSER_TEST_F(ITunesDataProviderRefreshTest, RefreshTest) { 458 RunTest(); 459} 460 461IN_PROC_BROWSER_TEST_F(ITunesDataProviderInvalidTest, InvalidTest) { 462 RunTest(); 463} 464 465IN_PROC_BROWSER_TEST_F(ITunesDataProviderUniqueNameTest, UniqueNameTest) { 466 RunTest(); 467} 468 469IN_PROC_BROWSER_TEST_F(ITunesDataProviderEscapeTest, EscapeTest) { 470 RunTest(); 471} 472 473} // namespace itunes 474