| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/format_macros.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/media_galleries/fileapi/itunes_data_provider.h" |
| #include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h" |
| #include "chrome/browser/media_galleries/imported_media_gallery_registry.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "url/gurl.h" |
| |
| namespace itunes { |
| |
| namespace { |
| |
| struct LibraryEntry { |
| LibraryEntry(const std::string& artist, const std::string& album, |
| const base::FilePath& location) |
| : artist(artist), |
| album(album), |
| location(location) { |
| } |
| std::string artist; |
| std::string album; |
| base::FilePath location; |
| }; |
| |
| // 'c' with combinding cedilla. |
| const char kDeNormalizedName[] = { |
| 'c', static_cast<unsigned char>(0xCC), static_cast<unsigned char>(0xA7), 0}; |
| // 'c' with cedilla. |
| const char kNormalizedName[] = { |
| static_cast<unsigned char>(0xC3), static_cast<unsigned char>(0xA7), 0}; |
| |
| } // namespace |
| |
| class TestITunesDataProvider : public ITunesDataProvider { |
| public: |
| TestITunesDataProvider(const base::FilePath& xml_library_path, |
| const base::Closure& callback) |
| : ITunesDataProvider(xml_library_path), |
| callback_(callback) { |
| } |
| virtual ~TestITunesDataProvider() {} |
| |
| private: |
| virtual void OnLibraryChanged(const base::FilePath& path, |
| bool error) OVERRIDE { |
| ITunesDataProvider::OnLibraryChanged(path, error); |
| callback_.Run(); |
| } |
| |
| base::Closure callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestITunesDataProvider); |
| }; |
| |
| class ITunesDataProviderTest : public InProcessBrowserTest { |
| public: |
| ITunesDataProviderTest() {} |
| virtual ~ITunesDataProviderTest() {} |
| |
| protected: |
| virtual void SetUp() OVERRIDE { |
| ASSERT_TRUE(library_dir_.CreateUniqueTempDir()); |
| WriteLibraryInternal(SetUpLibrary()); |
| // The ImportedMediaGalleryRegistry is created on which ever thread calls |
| // GetInstance() first. It shouldn't matter what thread creates, however |
| // in practice it is always created on the UI thread, so this calls |
| // GetInstance here to mirror those real conditions. |
| ImportedMediaGalleryRegistry::GetInstance(); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void RunTest() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| base::RunLoop loop; |
| quit_closure_ = loop.QuitClosure(); |
| MediaFileSystemBackend::MediaTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(&ITunesDataProviderTest::StartTestOnMediaTaskRunner, |
| base::Unretained(this))); |
| loop.Run(); |
| } |
| |
| void WriteLibrary(const std::vector<LibraryEntry>& entries, |
| const base::Closure& callback) { |
| SetLibraryChangeCallback(callback); |
| WriteLibraryInternal(entries); |
| } |
| |
| void SetLibraryChangeCallback(const base::Closure& callback) { |
| EXPECT_TRUE(library_changed_callback_.is_null()); |
| library_changed_callback_ = callback; |
| } |
| |
| ITunesDataProvider* data_provider() const { |
| return ImportedMediaGalleryRegistry::ITunesDataProvider(); |
| } |
| |
| const base::FilePath& library_dir() const { |
| return library_dir_.path(); |
| } |
| |
| base::FilePath XmlFile() const { |
| return library_dir_.path().AppendASCII("library.xml"); |
| } |
| |
| void ExpectTrackLocation(const std::string& artist, const std::string& album, |
| const std::string& track_name) { |
| base::FilePath track = |
| library_dir().AppendASCII(track_name).NormalizePathSeparators(); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| artist, album, track_name).NormalizePathSeparators().value()); |
| } |
| |
| void ExpectNoTrack(const std::string& artist, const std::string& album, |
| const std::string& track_name) { |
| EXPECT_TRUE(data_provider()->GetTrackLocation( |
| artist, album, track_name).empty()) << track_name; |
| } |
| |
| |
| // Get the initial set of library entries, called by SetUp. If no entries |
| // are returned the xml file is not created. |
| virtual std::vector<LibraryEntry> SetUpLibrary() { |
| return std::vector<LibraryEntry>(); |
| } |
| |
| // Start the test. The data provider is refreshed before calling StartTest |
| // and the result of the refresh is passed in. |
| virtual void StartTest(bool parse_success) = 0; |
| |
| void TestDone() { |
| DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); |
| ImportedMediaGalleryRegistry* imported_registry = |
| ImportedMediaGalleryRegistry::GetInstance(); |
| imported_registry->itunes_data_provider_.reset(); |
| content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| quit_closure_); |
| } |
| |
| private: |
| void StartTestOnMediaTaskRunner() { |
| DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); |
| ImportedMediaGalleryRegistry* imported_registry = |
| ImportedMediaGalleryRegistry::GetInstance(); |
| imported_registry->itunes_data_provider_.reset( |
| new TestITunesDataProvider( |
| XmlFile(), |
| base::Bind(&ITunesDataProviderTest::OnLibraryChanged, |
| base::Unretained(this)))); |
| data_provider()->RefreshData(base::Bind(&ITunesDataProviderTest::StartTest, |
| base::Unretained(this))); |
| }; |
| |
| void OnLibraryChanged() { |
| DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); |
| if (!library_changed_callback_.is_null()) { |
| library_changed_callback_.Run(); |
| library_changed_callback_.Reset(); |
| } |
| } |
| |
| void WriteLibraryInternal(const std::vector<LibraryEntry>& entries) { |
| if (!entries.size()) |
| return; |
| std::string xml = "<plist><dict><key>Tracks</key><dict>\n"; |
| for (size_t i = 0; i < entries.size(); ++i) { |
| std::string separator; |
| #if defined(OS_WIN) |
| separator = "/"; |
| #endif |
| GURL location("file://localhost" + separator + |
| entries[i].location.AsUTF8Unsafe()); |
| std::string entry_string = base::StringPrintf( |
| "<key>%" PRIuS "</key><dict>\n" |
| " <key>Track ID</key><integer>%" PRIuS "</integer>\n" |
| " <key>Location</key><string>%s</string>\n" |
| " <key>Artist</key><string>%s</string>\n" |
| " <key>Album</key><string>%s</string>\n" |
| "</dict>\n", |
| i + 1, i + 1, location.spec().c_str(), entries[i].artist.c_str(), |
| entries[i].album.c_str()); |
| xml += entry_string; |
| } |
| xml += "</dict></dict></plist>\n"; |
| ASSERT_EQ(static_cast<int>(xml.size()), |
| base::WriteFile(XmlFile(), xml.c_str(), xml.size())); |
| } |
| |
| base::ScopedTempDir library_dir_; |
| |
| base::Closure library_changed_callback_; |
| |
| base::Closure quit_closure_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderTest); |
| }; |
| |
| class ITunesDataProviderBasicTest : public ITunesDataProviderTest { |
| public: |
| ITunesDataProviderBasicTest() {} |
| virtual ~ITunesDataProviderBasicTest() {} |
| |
| virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { |
| base::FilePath track = library_dir().AppendASCII("Track.mp3"); |
| std::vector<LibraryEntry> entries; |
| entries.push_back(LibraryEntry("Artist", "Album", track)); |
| return entries; |
| } |
| |
| virtual void StartTest(bool parse_success) OVERRIDE { |
| EXPECT_TRUE(parse_success); |
| |
| // KnownArtist |
| EXPECT_TRUE(data_provider()->KnownArtist("Artist")); |
| EXPECT_FALSE(data_provider()->KnownArtist("Artist2")); |
| |
| // KnownAlbum |
| EXPECT_TRUE(data_provider()->KnownAlbum("Artist", "Album")); |
| EXPECT_FALSE(data_provider()->KnownAlbum("Artist", "Album2")); |
| EXPECT_FALSE(data_provider()->KnownAlbum("Artist2", "Album")); |
| |
| // GetTrackLocation |
| ExpectTrackLocation("Artist", "Album", "Track.mp3"); |
| ExpectNoTrack("Artist", "Album", "Track2.mp3"); |
| ExpectNoTrack("Artist", "Album2", "Track.mp3"); |
| ExpectNoTrack("Artist2", "Album", "Track.mp3"); |
| |
| // GetArtistNames |
| std::set<ITunesDataProvider::ArtistName> artists = |
| data_provider()->GetArtistNames(); |
| ASSERT_EQ(1U, artists.size()); |
| EXPECT_EQ("Artist", *artists.begin()); |
| |
| // GetAlbumNames |
| std::set<ITunesDataProvider::AlbumName> albums = |
| data_provider()->GetAlbumNames("Artist"); |
| ASSERT_EQ(1U, albums.size()); |
| EXPECT_EQ("Album", *albums.begin()); |
| |
| albums = data_provider()->GetAlbumNames("Artist2"); |
| EXPECT_EQ(0U, albums.size()); |
| |
| // GetAlbum |
| base::FilePath track = |
| library_dir().AppendASCII("Track.mp3").NormalizePathSeparators(); |
| ITunesDataProvider::Album album = |
| data_provider()->GetAlbum("Artist", "Album"); |
| ASSERT_EQ(1U, album.size()); |
| EXPECT_EQ(track.BaseName().AsUTF8Unsafe(), album.begin()->first); |
| EXPECT_EQ(track.value(), |
| album.begin()->second.NormalizePathSeparators().value()); |
| |
| album = data_provider()->GetAlbum("Artist", "Album2"); |
| EXPECT_EQ(0U, album.size()); |
| |
| album = data_provider()->GetAlbum("Artist2", "Album"); |
| EXPECT_EQ(0U, album.size()); |
| |
| TestDone(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderBasicTest); |
| }; |
| |
| class ITunesDataProviderRefreshTest : public ITunesDataProviderTest { |
| public: |
| ITunesDataProviderRefreshTest() {} |
| virtual ~ITunesDataProviderRefreshTest() {} |
| |
| virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { |
| base::FilePath track = library_dir().AppendASCII("Track.mp3"); |
| std::vector<LibraryEntry> entries; |
| entries.push_back(LibraryEntry("Artist", "Album", track)); |
| return entries; |
| } |
| |
| virtual void StartTest(bool parse_success) OVERRIDE { |
| EXPECT_TRUE(parse_success); |
| |
| // Initial contents. |
| ExpectTrackLocation("Artist", "Album", "Track.mp3"); |
| ExpectNoTrack("Artist2", "Album2", "Track2.mp3"); |
| |
| // New file. |
| base::FilePath track2 = library_dir().AppendASCII("Track2.mp3"); |
| std::vector<LibraryEntry> entries; |
| entries.push_back(LibraryEntry("Artist2", "Album2", track2)); |
| WriteLibrary(entries, |
| base::Bind(&ITunesDataProviderRefreshTest::CheckAfterWrite, |
| base::Unretained(this))); |
| } |
| |
| void CheckAfterWrite() { |
| // Content the same. |
| ExpectTrackLocation("Artist", "Album", "Track.mp3"); |
| ExpectNoTrack("Artist2", "Album2", "Track2.mp3"); |
| |
| data_provider()->RefreshData( |
| base::Bind(&ITunesDataProviderRefreshTest::CheckRefresh, |
| base::Unretained(this))); |
| } |
| |
| void CheckRefresh(bool is_valid) { |
| EXPECT_TRUE(is_valid); |
| |
| ExpectTrackLocation("Artist2", "Album2", "Track2.mp3"); |
| ExpectNoTrack("Artist", "Album", "Track.mp3"); |
| TestDone(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderRefreshTest); |
| }; |
| |
| class ITunesDataProviderInvalidTest : public ITunesDataProviderTest { |
| public: |
| ITunesDataProviderInvalidTest() {} |
| virtual ~ITunesDataProviderInvalidTest() {} |
| |
| virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { |
| base::FilePath track = library_dir().AppendASCII("Track.mp3"); |
| std::vector<LibraryEntry> entries; |
| entries.push_back(LibraryEntry("Artist", "Album", track)); |
| return entries; |
| } |
| |
| virtual void StartTest(bool parse_success) OVERRIDE { |
| EXPECT_TRUE(parse_success); |
| |
| SetLibraryChangeCallback( |
| base::Bind(&ITunesDataProvider::RefreshData, |
| base::Unretained(data_provider()), |
| base::Bind(&ITunesDataProviderInvalidTest::CheckInvalid, |
| base::Unretained(this)))); |
| ASSERT_EQ(1L, base::WriteFile(XmlFile(), " ", 1)); |
| } |
| |
| void CheckInvalid(bool is_valid) { |
| EXPECT_FALSE(is_valid); |
| TestDone(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderInvalidTest); |
| }; |
| |
| class ITunesDataProviderUniqueNameTest : public ITunesDataProviderTest { |
| public: |
| ITunesDataProviderUniqueNameTest() {} |
| virtual ~ITunesDataProviderUniqueNameTest() {} |
| |
| virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { |
| base::FilePath track = library_dir().AppendASCII("Track.mp3"); |
| std::vector<LibraryEntry> entries; |
| // Dupe album names should get uniquified with the track id, which in the |
| // test framework is the vector index. |
| entries.push_back(LibraryEntry("Artist", "Album", track)); |
| entries.push_back(LibraryEntry("Artist", "Album", track)); |
| entries.push_back(LibraryEntry("Artist", "Album2", track)); |
| return entries; |
| } |
| |
| virtual void StartTest(bool parse_success) OVERRIDE { |
| EXPECT_TRUE(parse_success); |
| |
| base::FilePath track = |
| library_dir().AppendASCII("Track.mp3").NormalizePathSeparators(); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| "Artist", "Album", |
| "Track (1).mp3").NormalizePathSeparators().value()); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| "Artist", "Album", |
| "Track (2).mp3").NormalizePathSeparators().value()); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| "Artist", "Album2", |
| "Track.mp3").NormalizePathSeparators().value()); |
| |
| TestDone(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderUniqueNameTest); |
| }; |
| |
| class ITunesDataProviderEscapeTest : public ITunesDataProviderTest { |
| // Albums and tracks that aren't the same, but become the same after |
| // replacing bad characters are not handled properly, but that case should |
| // never happen in practice. |
| public: |
| ITunesDataProviderEscapeTest() {} |
| virtual ~ITunesDataProviderEscapeTest() {} |
| |
| virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { |
| base::FilePath track = library_dir().AppendASCII("Track:1.mp3"); |
| std::vector<LibraryEntry> entries; |
| entries.push_back(LibraryEntry("Artist:/name", "Album:name/", track)); |
| entries.push_back(LibraryEntry("Artist/name", "Album:name", track)); |
| entries.push_back(LibraryEntry("Artist/name", "Album:name", track)); |
| entries.push_back(LibraryEntry(kDeNormalizedName, kNormalizedName, track)); |
| return entries; |
| } |
| |
| virtual void StartTest(bool parse_success) OVERRIDE { |
| EXPECT_TRUE(parse_success); |
| |
| base::FilePath track = |
| library_dir().AppendASCII("Track:1.mp3").NormalizePathSeparators(); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| "Artist__name", "Album_name_", |
| "Track_1.mp3").NormalizePathSeparators().value()); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| "Artist_name", "Album_name", |
| "Track_1 (2).mp3").NormalizePathSeparators().value()); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| "Artist_name", "Album_name", |
| "Track_1 (3).mp3").NormalizePathSeparators().value()); |
| EXPECT_EQ(track.value(), |
| data_provider()->GetTrackLocation( |
| kNormalizedName, kNormalizedName, |
| "Track_1.mp3").NormalizePathSeparators().value()); |
| |
| TestDone(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderEscapeTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ITunesDataProviderBasicTest, BasicTest) { |
| RunTest(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ITunesDataProviderRefreshTest, RefreshTest) { |
| RunTest(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ITunesDataProviderInvalidTest, InvalidTest) { |
| RunTest(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ITunesDataProviderUniqueNameTest, UniqueNameTest) { |
| RunTest(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ITunesDataProviderEscapeTest, EscapeTest) { |
| RunTest(); |
| } |
| |
| } // namespace itunes |