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 "base/logging.h" 6#include "chrome/common/media_galleries/itunes_library.h" 7#include "chrome/utility/media_galleries/itunes_library_parser.h" 8#include "testing/gtest/include/gtest/gtest.h" 9 10#define SIMPLE_HEADER() \ 11 "<plist>" \ 12 " <dict>" \ 13 " <key>Tracks</key>" \ 14 " <dict>" 15 16#define SIMPLE_TRACK(key, id, path, artist, album) \ 17 "<key>" #key "</key>" \ 18 "<dict>" \ 19 " <key>Track ID</key><integer>" #id "</integer>" \ 20 " <key>Location</key><string>file://localhost/" path "</string>" \ 21 " <key>Artist</key><string>" artist "</string>" \ 22 " <key>Album</key><string>" album "</string>" \ 23 "</dict>" 24 25#define SIMPLE_FOOTER() \ 26 " </dict>" \ 27 " </dict>" \ 28 "</plist>" 29 30namespace itunes { 31 32namespace { 33 34void CompareTrack(const parser::Track& a, const parser::Track& b) { 35 EXPECT_EQ(a.id, b.id); 36 EXPECT_EQ(a.location.value(), b.location.value()); 37} 38 39void CompareAlbum(const parser::Album& a, const parser::Album& b) { 40 EXPECT_EQ(a.size(), b.size()); 41 42 parser::Album::const_iterator a_it; 43 parser::Album::const_iterator b_it; 44 for (a_it = a.begin(), b_it = b.begin(); 45 a_it != a.end() && b_it != b.end(); 46 ++a_it, ++b_it) { 47 CompareTrack(*a_it, *b_it); 48 } 49} 50 51void CompareAlbums(const parser::Albums& a, const parser::Albums& b) { 52 EXPECT_EQ(a.size(), b.size()); 53 54 parser::Albums::const_iterator a_it; 55 parser::Albums::const_iterator b_it; 56 for (a_it = a.begin(), b_it = b.begin(); 57 a_it != a.end() && b_it != b.end(); 58 ++a_it, ++b_it) { 59 EXPECT_EQ(a_it->first, b_it->first); 60 CompareAlbum(a_it->second, b_it->second); 61 } 62} 63 64void CompareLibrary(const parser::Library& a, const parser::Library& b) { 65 EXPECT_EQ(a.size(), b.size()); 66 67 parser::Library::const_iterator a_it; 68 parser::Library::const_iterator b_it; 69 for (a_it = a.begin(), b_it = b.begin(); 70 a_it != a.end() && b_it != b.end(); 71 ++a_it, ++b_it) { 72 EXPECT_EQ(a_it->first, b_it->first); 73 CompareAlbums(a_it->second, b_it->second); 74 } 75} 76 77class ITunesLibraryParserTest : public testing::Test { 78 public: 79 ITunesLibraryParserTest() {} 80 81 void TestParser(bool expected_result, const std::string& xml) { 82 ITunesLibraryParser parser; 83 84 EXPECT_EQ(expected_result, parser.Parse(xml)); 85 if (!expected_result) 86 return; 87 88 CompareLibrary(expected_library_, parser.library()); 89 } 90 91 void AddExpectedTrack(uint32 id, const std::string& location, 92 const std::string& artist, const std::string& album) { 93 // On Mac this pretends that C: is a directory. 94#if defined(OS_MACOSX) 95 std::string os_location = "/" + location; 96#else 97 const std::string& os_location = location; 98#endif 99 parser::Track track(id, base::FilePath::FromUTF8Unsafe(os_location)); 100 expected_library_[artist][album].insert(track); 101 } 102 103 private: 104 parser::Library expected_library_; 105 106 DISALLOW_COPY_AND_ASSIGN(ITunesLibraryParserTest); 107}; 108 109TEST_F(ITunesLibraryParserTest, EmptyLibrary) { 110 TestParser(false, ""); 111} 112 113TEST_F(ITunesLibraryParserTest, MinimalXML) { 114 AddExpectedTrack(1, "C:/dir/Song With Space.mp3", "Artist A", "Album A"); 115 TestParser( 116 true, 117 SIMPLE_HEADER() 118 SIMPLE_TRACK(1, 1, "C:/dir/Song%20With%20Space.mp3", "Artist A", 119 "Album A") 120 SIMPLE_FOOTER()); 121} 122 123TEST_F(ITunesLibraryParserTest, MultipleSongs) { 124 AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A"); 125 AddExpectedTrack(2, "C:/dir/SongA2.mp3", "Artist A", "Album A"); 126 AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Artist A", "Album A"); 127 AddExpectedTrack(4, "C:/dir/SongB1.mp3", "Artist A", "Album B"); 128 AddExpectedTrack(5, "C:/dir/SongB2.mp3", "Artist A", "Album B"); 129 AddExpectedTrack(6, "C:/dir2/SongB1.mp3", "Artist B", "Album B"); 130 AddExpectedTrack(7, "C:/dir2/SongB2.mp3", "Artist B", "Album B"); 131 TestParser( 132 true, 133 SIMPLE_HEADER() 134 SIMPLE_TRACK(1, 1, "C:/dir/SongA1.mp3", "Artist A", "Album A") 135 SIMPLE_TRACK(2, 2, "C:/dir/SongA2.mp3", "Artist A", "Album A") 136 SIMPLE_TRACK(3, 3, "C:/dir/SongA3.mp3", "Artist A", "Album A") 137 SIMPLE_TRACK(4, 4, "C:/dir/SongB1.mp3", "Artist A", "Album B") 138 SIMPLE_TRACK(5, 5, "C:/dir/SongB2.mp3", "Artist A", "Album B") 139 SIMPLE_TRACK(6, 6, "C:/dir2/SongB1.mp3", "Artist B", "Album B") 140 SIMPLE_TRACK(7, 7, "C:/dir2/SongB2.mp3", "Artist B", "Album B") 141 SIMPLE_FOOTER()); 142} 143 144TEST_F(ITunesLibraryParserTest, MismatchedId) { 145 TestParser( 146 false, 147 SIMPLE_HEADER() 148 SIMPLE_TRACK(1, 2, "C:/dir/SongA1.mp3", "Artist A", "Album A") 149 SIMPLE_FOOTER()); 150 151 AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A"); 152 TestParser( 153 true, 154 SIMPLE_HEADER() 155 SIMPLE_TRACK(1, 1, "C:/dir/SongA1.mp3", "Artist A", "Album A") 156 SIMPLE_TRACK(2, 3, "C:/dir/SongA2.mp3", "Artist A", "Album A") 157 SIMPLE_FOOTER()); 158} 159 160TEST_F(ITunesLibraryParserTest, OtherDictionaryEntries) { 161 AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A"); 162 TestParser( 163 true, 164 "<plist>" 165 " <dict>" 166 " <key>Other section</key>" 167 " <dict>" 168 // In Other section, not Tracks. 169 SIMPLE_TRACK(10, 10, "C:/dir/SongB2.mp3", "Artist B", "Album B") 170 " </dict>" 171 " <key>Tracks</key>" 172 " <dict>" 173 " <key>1</key>" 174 " <dict>" 175 // In the body of a track dictionary before the interesting entries. 176 SIMPLE_TRACK(20, 20, "C:/dir/SongB2.mp3", "Artist B", "Album B") 177 // Entries in a different order. 178 " <key>Artist</key><string>Artist A</string>" 179 " <key>Location</key>" 180 " <string>file://localhost/C:/dir/SongA1.mp3</string>" 181 " <key>Album</key><string>Album A</string>" 182 " <key>Track ID</key><integer>1</integer>" 183 // In the body of a track dictionary after the interesting entries. 184 SIMPLE_TRACK(30, 30, "C:/dir/SongB3.mp3", "Artist B", "Album B") 185 " </dict>" 186 " <key>40</key>" 187 " <dict>" 188 // Missing album name. 189 " <key>Artist</key><string>Artist B</string>" 190 " <key>Location</key>" 191 " <string>file://localhost/C:/dir/SongB4.mp3</string>" 192 " <key>Track ID</key><integer>1</integer>" 193 " </dict>" 194 SIMPLE_FOOTER()); 195} 196 197TEST_F(ITunesLibraryParserTest, MissingEntry) { 198 AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A"); 199 AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Artist A", "Album A"); 200 TestParser( 201 true, 202 SIMPLE_HEADER() 203 SIMPLE_TRACK(1, 1, "C:/dir/SongA1.mp3", "Artist A", "Album A") 204 "<key>2</key><dict>" 205 " <key>Track ID</key><integer>2</integer>" 206 " <key>Album</key><string>Album A</string>" 207 " <key>Foo</key><string>Bar</string>" 208 " " // A whitespace node is important for the test. 209 "</dict>" 210 SIMPLE_TRACK(3, 3, "C:/dir/SongA3.mp3", "Artist A", "Album A") 211 SIMPLE_FOOTER()); 212} 213 214TEST_F(ITunesLibraryParserTest, UnknownAlbumOrArtist) { 215 AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Unknown Album"); 216 AddExpectedTrack(2, "C:/dir/SongA2.mp3", "Unknown Artist", "Album A"); 217 AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Unknown Artist", "Unknown Album"); 218 TestParser( 219 true, 220 SIMPLE_HEADER() 221 "<key>1</key><dict>" 222 " <key>Track ID</key><integer>1</integer>" 223 " <key>Location</key><string>file://localhost/C:/dir/SongA1.mp3</string>" 224 " <key>Artist</key><string>Artist A</string>" 225 "</dict>" 226 "<key>2</key><dict>" 227 " <key>Track ID</key><integer>2</integer>" 228 " <key>Location</key><string>file://localhost/C:/dir/SongA2.mp3</string>" 229 " <key>Album</key><string>Album A</string>" 230 "</dict>" 231 "<key>3</key><dict>" 232 " <key>Track ID</key><integer>3</integer>" 233 " <key>Location</key><string>file://localhost/C:/dir/SongA3.mp3</string>" 234 "</dict>" 235 SIMPLE_FOOTER()); 236} 237 238TEST_F(ITunesLibraryParserTest, AlbumArtist) { 239 AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Unknown Album"); 240 AddExpectedTrack(2, "C:/dir/SongA2.mp3", "Artist A", "Unknown Album"); 241 AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Artist A", "Unknown Album"); 242 AddExpectedTrack(4, "C:/dir/SongA4.mp3", "Artist A", "Album"); 243 TestParser( 244 true, 245 SIMPLE_HEADER() 246 "<key>1</key><dict>" 247 " <key>Track ID</key><integer>1</integer>" 248 " <key>Location</key><string>file://localhost/C:/dir/SongA1.mp3</string>" 249 " <key>Album Artist</key><string>Artist A</string>" 250 "</dict>" 251 "<key>2</key><dict>" 252 " <key>Track ID</key><integer>2</integer>" 253 " <key>Location</key><string>file://localhost/C:/dir/SongA2.mp3</string>" 254 " <key>Artist</key><string>Artist B</string>" 255 " <key>Album Artist</key><string>Artist A</string>" 256 "</dict>" 257 "<key>3</key><dict>" 258 " <key>Track ID</key><integer>3</integer>" 259 " <key>Location</key><string>file://localhost/C:/dir/SongA3.mp3</string>" 260 " <key>Album Artist</key><string>Artist A</string>" 261 " <key>Artist</key><string>Artist B</string>" 262 "</dict>" 263 "<key>4</key><dict>" 264 " <key>Track ID</key><integer>4</integer>" 265 " <key>Location</key><string>file://localhost/C:/dir/SongA4.mp3</string>" 266 " <key>Album</key><string>Album</string>" 267 " <key>Artist</key><string>Artist B</string>" 268 " <key>Album Artist</key><string>Artist A</string>" 269 "</dict>" 270 SIMPLE_FOOTER()); 271} 272 273TEST_F(ITunesLibraryParserTest, MacPath) { 274 AddExpectedTrack(1, "dir/Song With Space.mp3", "Artist A", "Album A"); 275 TestParser( 276 true, 277 SIMPLE_HEADER() 278 // This path is concatenated with "http://localhost/", so no leading 279 // slash should be used. 280 SIMPLE_TRACK(1, 1, "dir/Song%20With%20Space.mp3", "Artist A", "Album A") 281 SIMPLE_FOOTER()); 282} 283 284} // namespace 285 286} // namespace itunes 287