1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include <memory>
23 
24 #include <gtest/gtest.h>
25 
26 #include <QMap>
27 #include <QString>
28 #include <QUrl>
29 #include <QThread>
30 #include <QSignalSpy>
31 #include <QSortFilterProxyModel>
32 #include <QtDebug>
33 
34 #include "test_utils.h"
35 
36 #include "core/logging.h"
37 #include "core/database.h"
38 #include "collection/collectionmodel.h"
39 #include "collection/collectionbackend.h"
40 #include "collection/collection.h"
41 
42 // clazy:excludeall=non-pod-global-static,returning-void-expression
43 
44 namespace {
45 
46 class CollectionModelTest : public ::testing::Test {
47  public:
CollectionModelTest()48   CollectionModelTest() : added_dir_(false) {}
49 
50  protected:
SetUp()51   void SetUp() override {
52     database_ = std::make_shared<MemoryDatabase>(nullptr);
53     backend_ = std::make_unique<CollectionBackend>();
54     backend_->Init(database_.get(), nullptr, Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
55     model_ = std::make_unique<CollectionModel>(backend_.get(), nullptr);
56 
57     added_dir_ = false;
58 
59     model_sorted_ =  std::make_unique<QSortFilterProxyModel>();
60     model_sorted_->setSourceModel(model_.get());
61     model_sorted_->setSortRole(CollectionModel::Role_SortText);
62     model_sorted_->setDynamicSortFilter(true);
63     model_sorted_->sort(0);
64 
65   }
66 
AddSong(Song & song)67   Song AddSong(Song &song) {
68     song.set_directory_id(1);
69     if (song.mtime() == 0) song.set_mtime(1);
70     if (song.ctime() == 0) song.set_ctime(1);
71     if (song.url().isEmpty()) song.set_url(QUrl("file:///tmp/foo"));
72     if (song.filesize() == -1) song.set_filesize(1);
73 
74     if (!added_dir_) {
75       backend_->AddDirectory("/tmp");
76       added_dir_ = true;
77     }
78 
79     backend_->AddOrUpdateSongs(SongList() << song);
80     return song;
81   }
82 
AddSong(const QString & title,const QString & artist,const QString & album,const int length)83   Song AddSong(const QString &title, const QString &artist, const QString &album, const int length) {
84     Song song;
85     song.Init(title, artist, album, length);
86     song.set_mtime(0);
87     song.set_ctime(0);
88     return AddSong(song);
89   }
90 
91   std::shared_ptr<Database> database_;  // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
92   std::unique_ptr<CollectionBackend> backend_;  // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
93   std::unique_ptr<CollectionModel> model_;  // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
94   std::unique_ptr<QSortFilterProxyModel> model_sorted_;  // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
95 
96   bool added_dir_;  // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
97 };
98 
TEST_F(CollectionModelTest,Initialization)99 TEST_F(CollectionModelTest, Initialization) {
100   EXPECT_EQ(0, model_->rowCount(QModelIndex()));
101 }
102 
TEST_F(CollectionModelTest,WithInitialArtists)103 TEST_F(CollectionModelTest, WithInitialArtists) {
104 
105   AddSong("Title", "Artist 1", "Album", 123);
106   AddSong("Title", "Artist 2", "Album", 123);
107   AddSong("Title", "Foo", "Album", 123);
108   model_->Init(false);
109 
110   ASSERT_EQ(5, model_sorted_->rowCount(QModelIndex()));
111   EXPECT_EQ("A", model_sorted_->index(0, 0, QModelIndex()).data().toString());
112   EXPECT_EQ("Artist 1", model_sorted_->index(1, 0, QModelIndex()).data().toString());
113   EXPECT_EQ("Artist 2", model_sorted_->index(2, 0, QModelIndex()).data().toString());
114   EXPECT_EQ("F", model_sorted_->index(3, 0, QModelIndex()).data().toString());
115   EXPECT_EQ("Foo", model_sorted_->index(4, 0, QModelIndex()).data().toString());
116 
117 }
118 
TEST_F(CollectionModelTest,CompilationAlbums)119 TEST_F(CollectionModelTest, CompilationAlbums) {
120 
121   Song song;
122   song.Init("Title", "Artist", "Album", 123);
123   song.set_compilation(true);
124   song.set_mtime(0);
125   song.set_ctime(0);
126 
127   AddSong(song);
128   model_->Init(false);
129   model_->fetchMore(model_->index(0, 0));
130 
131   ASSERT_EQ(1, model_->rowCount(QModelIndex()));
132 
133   QModelIndex va_index = model_->index(0, 0, QModelIndex());
134   EXPECT_EQ("Various artists", va_index.data().toString());
135   EXPECT_TRUE(model_->hasChildren(va_index));
136   ASSERT_EQ(model_->rowCount(va_index), 1);
137 
138   QModelIndex album_index = model_->index(0, 0, va_index);
139   EXPECT_EQ(model_->data(album_index).toString(), "Album");
140   EXPECT_TRUE(model_->hasChildren(album_index));
141 
142 }
143 
TEST_F(CollectionModelTest,NumericHeaders)144 TEST_F(CollectionModelTest, NumericHeaders) {
145 
146   AddSong("Title", "1artist", "Album", 123);
147   AddSong("Title", "2artist", "Album", 123);
148   AddSong("Title", "0artist", "Album", 123);
149   AddSong("Title", "zartist", "Album", 123);
150   model_->Init(false);
151 
152   ASSERT_EQ(6, model_sorted_->rowCount(QModelIndex()));
153   EXPECT_EQ("0-9", model_sorted_->index(0, 0, QModelIndex()).data().toString());
154   EXPECT_EQ("0artist", model_sorted_->index(1, 0, QModelIndex()).data().toString());
155   EXPECT_EQ("1artist", model_sorted_->index(2, 0, QModelIndex()).data().toString());
156   EXPECT_EQ("2artist", model_sorted_->index(3, 0, QModelIndex()).data().toString());
157   EXPECT_EQ("Z", model_sorted_->index(4, 0, QModelIndex()).data().toString());
158   EXPECT_EQ("zartist", model_sorted_->index(5, 0, QModelIndex()).data().toString());
159 
160 }
161 
TEST_F(CollectionModelTest,MixedCaseHeaders)162 TEST_F(CollectionModelTest, MixedCaseHeaders) {
163 
164   AddSong("Title", "Artist", "Album", 123);
165   AddSong("Title", "artist", "Album", 123);
166   model_->Init(false);
167 
168   ASSERT_EQ(3, model_sorted_->rowCount(QModelIndex()));
169   EXPECT_EQ("A", model_sorted_->index(0, 0, QModelIndex()).data().toString());
170   EXPECT_EQ("Artist", model_sorted_->index(1, 0, QModelIndex()).data().toString());
171   EXPECT_EQ("artist", model_sorted_->index(2, 0, QModelIndex()).data().toString());
172 
173 }
174 
TEST_F(CollectionModelTest,UnknownArtists)175 TEST_F(CollectionModelTest, UnknownArtists) {
176 
177   AddSong("Title", "", "Album", 123);
178   model_->Init(false);
179   model_->fetchMore(model_->index(0, 0));
180 
181   ASSERT_EQ(1, model_->rowCount(QModelIndex()));
182   QModelIndex unknown_index = model_->index(0, 0, QModelIndex());
183   EXPECT_EQ("Unknown", unknown_index.data().toString());
184 
185   ASSERT_EQ(1, model_->rowCount(unknown_index));
186   EXPECT_EQ("Album", model_->index(0, 0, unknown_index).data().toString());
187 
188 }
189 
TEST_F(CollectionModelTest,UnknownAlbums)190 TEST_F(CollectionModelTest, UnknownAlbums) {
191 
192   AddSong("Title", "Artist", "", 123);
193   AddSong("Title", "Artist", "Album", 123);
194   model_->Init(false);
195   model_->fetchMore(model_->index(0, 0));
196 
197   QModelIndex artist_index = model_->index(0, 0, QModelIndex());
198   ASSERT_EQ(2, model_->rowCount(artist_index));
199 
200   QModelIndex unknown_album_index = model_->index(0, 0, artist_index);
201   QModelIndex real_album_index = model_->index(1, 0, artist_index);
202 
203   EXPECT_EQ("Unknown", unknown_album_index.data().toString());
204   EXPECT_EQ("Album", real_album_index.data().toString());
205 
206 }
207 
TEST_F(CollectionModelTest,VariousArtistSongs)208 TEST_F(CollectionModelTest, VariousArtistSongs) {
209 
210   SongList songs;
211   for (int i=0 ; i < 4 ; ++i) {
212     QString n = QString::number(i+1);
213     Song song;
214     song.Init("Title " + n, "Artist " + n, "Album", 0);
215     song.set_mtime(0);
216     song.set_ctime(0);
217     songs << song;  // clazy:exclude=reserve-candidates
218   }
219 
220   // Different ways of putting songs in "Various Artist".  Make sure they all work
221   songs[0].set_compilation_detected(true);
222   songs[1].set_compilation(true);
223   songs[2].set_compilation_on(true);
224   songs[3].set_compilation_detected(true); songs[3].set_artist("Various Artists");
225 
226   for (int i=0 ; i < 4 ; ++i)
227     AddSong(songs[i]);
228   model_->Init(false);
229 
230   QModelIndex artist_index = model_->index(0, 0, QModelIndex());
231   model_->fetchMore(artist_index);
232   ASSERT_EQ(1, model_->rowCount(artist_index));
233 
234   QModelIndex album_index = model_->index(0, 0, artist_index);
235   model_->fetchMore(album_index);
236   ASSERT_EQ(4, model_->rowCount(album_index));
237 
238   EXPECT_EQ("Artist 1 - Title 1", model_->index(0, 0, album_index).data().toString());
239   EXPECT_EQ("Artist 2 - Title 2", model_->index(1, 0, album_index).data().toString());
240   EXPECT_EQ("Artist 3 - Title 3", model_->index(2, 0, album_index).data().toString());
241   EXPECT_EQ("Title 4", model_->index(3, 0, album_index).data().toString());
242 
243 }
244 
TEST_F(CollectionModelTest,RemoveSongsLazyLoaded)245 TEST_F(CollectionModelTest, RemoveSongsLazyLoaded) {
246 
247   Song one = AddSong("Title 1", "Artist", "Album", 123); one.set_id(1);
248   Song two = AddSong("Title 2", "Artist", "Album", 123); two.set_id(2);
249   AddSong("Title 3", "Artist", "Album", 123);
250   model_->Init(false);
251 
252   // Lazy load the items
253   QModelIndex artist_index = model_->index(0, 0, QModelIndex());
254   model_->fetchMore(artist_index);
255   ASSERT_EQ(1, model_->rowCount(artist_index));
256   QModelIndex album_index = model_->index(0, 0, artist_index);
257   model_->fetchMore(album_index);
258   ASSERT_EQ(3, model_->rowCount(album_index));
259 
260   // Remove the first two songs
261   QSignalSpy spy_preremove(model_.get(), &CollectionModel::rowsAboutToBeRemoved);
262   QSignalSpy spy_remove(model_.get(), &CollectionModel::rowsRemoved);
263   QSignalSpy spy_reset(model_.get(), &CollectionModel::modelReset);
264 
265   backend_->DeleteSongs(SongList() << one << two);
266 
267   ASSERT_EQ(2, spy_preremove.count());
268   ASSERT_EQ(2, spy_remove.count());
269   ASSERT_EQ(0, spy_reset.count());
270 
271   artist_index = model_->index(0, 0, QModelIndex());
272   ASSERT_EQ(1, model_->rowCount(artist_index));
273   album_index = model_->index(0, 0, artist_index);
274   ASSERT_EQ(1, model_->rowCount(album_index));
275   EXPECT_EQ("Title 3", model_->index(0, 0, album_index).data().toString());
276 
277 }
278 
TEST_F(CollectionModelTest,RemoveSongsNotLazyLoaded)279 TEST_F(CollectionModelTest, RemoveSongsNotLazyLoaded) {
280 
281   Song one = AddSong("Title 1", "Artist", "Album", 123); one.set_id(1);
282   Song two = AddSong("Title 2", "Artist", "Album", 123); two.set_id(2);
283   model_->Init(false);
284 
285   // Remove the first two songs
286   QSignalSpy spy_preremove(model_.get(), &CollectionModel::rowsAboutToBeRemoved);
287   QSignalSpy spy_remove(model_.get(), &CollectionModel::rowsRemoved);
288   QSignalSpy spy_reset(model_.get(), &CollectionModel::modelReset);
289 
290   backend_->DeleteSongs(SongList() << one << two);
291 
292   ASSERT_EQ(0, spy_preremove.count());
293   ASSERT_EQ(0, spy_remove.count());
294   ASSERT_EQ(1, spy_reset.count());
295 
296 }
297 
TEST_F(CollectionModelTest,RemoveEmptyAlbums)298 TEST_F(CollectionModelTest, RemoveEmptyAlbums) {
299 
300   Song one = AddSong("Title 1", "Artist", "Album 1", 123); one.set_id(1);
301   Song two = AddSong("Title 2", "Artist", "Album 2", 123); two.set_id(2);
302   Song three = AddSong("Title 3", "Artist", "Album 2", 123); three.set_id(3);
303   model_->Init(false);
304 
305   QModelIndex artist_index = model_->index(0, 0, QModelIndex());
306   model_->fetchMore(artist_index);
307   ASSERT_EQ(2, model_->rowCount(artist_index));
308 
309   // Remove one song from each album
310   backend_->DeleteSongs(SongList() << one << two);
311 
312   // Check the model
313   artist_index = model_->index(0, 0, QModelIndex());
314   model_->fetchMore(artist_index);
315   ASSERT_EQ(1, model_->rowCount(artist_index));
316   QModelIndex album_index = model_->index(0, 0, artist_index);
317   model_->fetchMore(album_index);
318   EXPECT_EQ("Album 2", album_index.data().toString());
319 
320   ASSERT_EQ(1, model_->rowCount(album_index));
321   EXPECT_EQ("Title 3", model_->index(0, 0, album_index).data().toString());
322 
323 }
324 
TEST_F(CollectionModelTest,RemoveEmptyArtists)325 TEST_F(CollectionModelTest, RemoveEmptyArtists) {
326 
327   Song one = AddSong("Title", "Artist", "Album", 123); one.set_id(1);
328   model_->Init(false);
329 
330   // Lazy load the items
331   QModelIndex artist_index = model_->index(0, 0, QModelIndex());
332   model_->fetchMore(artist_index);
333   ASSERT_EQ(1, model_->rowCount(artist_index));
334   QModelIndex album_index = model_->index(0, 0, artist_index);
335   model_->fetchMore(album_index);
336   ASSERT_EQ(1, model_->rowCount(album_index));
337 
338   // The artist header is there too right?
339   ASSERT_EQ(2, model_->rowCount(QModelIndex()));
340 
341   // Remove the song
342   backend_->DeleteSongs(SongList() << one);
343 
344   // Everything should be gone - even the artist header
345   ASSERT_EQ(0, model_->rowCount(QModelIndex()));
346 
347 }
348 
349 // Test to check that the container nodes are created identical and unique all through the model with all possible collection groupings.
350 // model1 - Nodes are created from a complete reset done through lazy-loading.
351 // model2 - Initial container nodes are created in SongsDiscovered.
352 // model3 - All container nodes are created in SongsDiscovered.
353 
354 // WARNING: This test can take up to 30 minutes to complete.
355 #if 0
356 TEST_F(CollectionModelTest, TestContainerNodes) {
357 
358   SongList songs;
359   int year = 1960;
360   // Add some normal albums.
361   for (int artist_number = 1; artist_number <= 3 ; ++artist_number) {
362     Song song(Song::Source_Collection);
363     song.set_artist(QString("Artist %1").arg(artist_number));
364     song.set_composer(QString("Composer %1").arg(artist_number));
365     song.set_performer(QString("Performer %1").arg(artist_number));
366     song.set_mtime(1);
367     song.set_ctime(1);
368     song.set_directory_id(1);
369     song.set_filetype(Song::FileType_FLAC);
370     song.set_filesize(1);
371     for (int album_number = 1; album_number <= 3 ; ++album_number) {
372       if (year > 2020) year = 1960;
373       song.set_album(QString("Artist %1 - Album %2").arg(artist_number).arg(album_number));
374       song.set_album_id(QString::number(album_number));
375       song.set_year(year++);
376       song.set_genre("Rock");
377       for (int song_number = 1; song_number <= 5 ; ++song_number) {
378         song.set_url(QUrl(QString("file:///mnt/music/Artist %1/Album %2/%3 - artist song-n-%3").arg(artist_number).arg(album_number).arg(song_number)));
379         song.set_title(QString("Title %1").arg(song_number));
380         song.set_track(song_number);
381         songs << song;
382       }
383     }
384   }
385 
386   // Add some albums with 'album artist'.
387   for (int album_artist_number = 1; album_artist_number <= 3 ; ++album_artist_number) {
388     Song song(Song::Source_Collection);
389     song.set_albumartist(QString("Album Artist %1").arg(album_artist_number));
390     song.set_composer(QString("Composer %1").arg(album_artist_number));
391     song.set_performer(QString("Performer %1").arg(album_artist_number));
392     song.set_mtime(1);
393     song.set_ctime(1);
394     song.set_directory_id(1);
395     song.set_filetype(Song::FileType_FLAC);
396     song.set_filesize(1);
397     for (int album_number = 1; album_number <= 3 ; ++album_number) {
398       if (year > 2020) year = 1960;
399       song.set_album(QString("Album Artist %1 - Album %2").arg(album_artist_number).arg(album_number));
400       song.set_album_id(QString::number(album_number));
401       song.set_year(year++);
402       song.set_genre("Rock");
403       int artist_number = 1;
404       for (int song_number = 1; song_number <= 5 ; ++song_number) {
405         song.set_url(QUrl(QString("file:///mnt/music/Album Artist %1/Album %2/%3 - album artist song-n-%3").arg(album_artist_number).arg(album_number).arg(QString::number(song_number))));
406         song.set_title("Title " + QString::number(song_number));
407         song.set_track(song_number);
408         song.set_artist("Artist " + QString::number(artist_number));
409         songs << song;
410         ++artist_number;
411       }
412     }
413   }
414 
415   // Add some compilation albums.
416   for (int album_number = 1; album_number <= 3 ; ++album_number) {
417     if (year > 2020) year = 1960;
418     Song song(Song::Source_Collection);
419     song.set_mtime(1);
420     song.set_ctime(1);
421     song.set_directory_id(1);
422     song.set_filetype(Song::FileType_FLAC);
423     song.set_filesize(1);
424     song.set_album(QString("Compilation Album %1").arg(album_number));
425     song.set_album_id(QString::number(album_number));
426     song.set_year(year++);
427     song.set_genre("Pop");
428     song.set_compilation(true);
429     int artist_number = 1;
430     for (int song_number = 1; song_number <= 4 ; ++song_number) {
431       song.set_url(QUrl(QString("file:///mnt/music/Compilation Artist %1/Compilation Album %2/%3 - compilation song-n-%3").arg(artist_number).arg(album_number).arg(QString::number(song_number))));
432       song.set_artist(QString("Compilation Artist %1").arg(artist_number));
433       song.set_composer(QString("Composer %1").arg(artist_number));
434       song.set_performer(QString("Performer %1").arg(artist_number));
435       song.set_title(QString("Title %1").arg(song_number));
436       song.set_track(song_number);
437       songs << song;
438       ++artist_number;
439     }
440   }
441 
442   // Songs with only title
443   {
444     Song song(Song::Source_Collection);
445     song.set_mtime(1);
446     song.set_ctime(1);
447     song.set_directory_id(1);
448     song.set_filetype(Song::FileType_FLAC);
449     song.set_filesize(1);
450     song.set_url(QUrl(QString("file:///mnt/music/no album song 1/song-only-1")));
451     song.set_title("Only Title 1");
452     songs << song;
453     song.set_url(QUrl(QString("file:///mnt/music/no album song 2/song-only-2")));
454     song.set_title("Only Title 2");
455     songs << song;
456   }
457 
458   // Song with only artist, album and title.
459   {
460     Song song(Song::Source_Collection);
461     song.set_url(QUrl(QString("file:///tmp/artist-album-title-song")));
462     song.set_artist("Not Only Artist");
463     song.set_album("Not Only Album");
464     song.set_title("Not Only Title");
465     song.set_mtime(1);
466     song.set_ctime(1);
467     song.set_directory_id(1);
468     song.set_filetype(Song::FileType_FLAC);
469     song.set_filesize(1);
470     song.set_year(1970);
471     song.set_track(1);
472     songs << song;
473   }
474 
475   // Add possible Various artists conflicting songs.
476   {
477     Song song(Song::Source_Collection);
478     song.set_url(QUrl(QString("file:///tmp/song-va-conflicting-1")));
479     song.set_artist("Various artists");
480     song.set_album("VA Album");
481     song.set_title("VA Title");
482     song.set_mtime(1);
483     song.set_ctime(1);
484     song.set_directory_id(1);
485     song.set_filetype(Song::FileType_FLAC);
486     song.set_filesize(1);
487     song.set_year(1970);
488     song.set_track(1);
489     songs << song;
490   }
491 
492   {
493     Song song(Song::Source_Collection);
494     song.set_url(QUrl(QString("file:///tmp/song-va-conflicting-2")));
495     song.set_artist("Various artists");
496     song.set_albumartist("Various artists");
497     song.set_album("VA Album");
498     song.set_title("VA Title");
499     song.set_mtime(1);
500     song.set_ctime(1);
501     song.set_directory_id(1);
502     song.set_filetype(Song::FileType_FLAC);
503     song.set_filesize(1);
504     song.set_year(1970);
505     song.set_track(1);
506     songs << song;
507   }
508 
509   {
510     Song song(Song::Source_Collection);
511     song.set_url(QUrl(QString("file:///tmp/song-va-conflicting-3")));
512     song.set_albumartist("Various artists");
513     song.set_album("VA Album");
514     song.set_title("VA Title");
515     song.set_mtime(1);
516     song.set_ctime(1);
517     song.set_directory_id(1);
518     song.set_filetype(Song::FileType_FLAC);
519     song.set_filesize(1);
520     song.set_year(1970);
521     song.set_track(1);
522     songs << song;
523   }
524 
525   // Albums with Album ID.
526   for (int album_id = 0; album_id <= 2 ; ++album_id) {
527     Song song(Song::Source_Collection);
528     song.set_url(QUrl(QString("file:///tmp/song-with-album-id-1")));
529     song.set_artist("Artist with Album ID");
530     song.set_album(QString("Album %1 with Album ID").arg(album_id));
531     song.set_album_id(QString("Album ID %1").arg(album_id));
532     song.set_mtime(1);
533     song.set_ctime(1);
534     song.set_directory_id(1);
535     song.set_filetype(Song::FileType_FLAC);
536     song.set_filesize(1);
537     song.set_year(1970);
538     for (int i = 0; i <= 3 ; ++i) {
539       song.set_title(QString("Title %1 %2").arg(album_id).arg(i));
540       song.set_track(i);
541       songs << song;
542     }
543   }
544 
545   for (int f = CollectionModel::GroupBy_None + 1 ; f < CollectionModel::GroupByCount ; ++f) {
546     for (int s = CollectionModel::GroupBy_None ; s < CollectionModel::GroupByCount ; ++s) {
547       for (int t = CollectionModel::GroupBy_None ; t < CollectionModel::GroupByCount ; ++t) {
548 
549         qLog(Debug) << "Testing collection model grouping: " << f << s << t;
550 
551         std::unique_ptr<Database> database1;
552         std::unique_ptr<Database> database2;
553         std::unique_ptr<Database> database3;
554         std::unique_ptr<CollectionBackend> backend1;
555         std::unique_ptr<CollectionBackend> backend2;
556         std::unique_ptr<CollectionBackend> backend3;
557         std::unique_ptr<CollectionModel> model1;
558         std::unique_ptr<CollectionModel> model2;
559         std::unique_ptr<CollectionModel> model3;
560 
561         database1 = std::make_unique<MemoryDatabase>(nullptr);
562         database2 = std::make_unique<MemoryDatabase>(nullptr);
563         database3 = std::make_unique<MemoryDatabase>(nullptr);
564         backend1 = std::make_unique<CollectionBackend>();
565         backend2= std::make_unique<CollectionBackend>();
566         backend3 = std::make_unique<CollectionBackend>();
567         backend1->Init(database1.get(), Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
568         backend2->Init(database2.get(), Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
569         backend3->Init(database3.get(), Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
570         model1 = std::make_unique<CollectionModel>(backend1.get(), nullptr);
571         model2 = std::make_unique<CollectionModel>(backend2.get(), nullptr);
572         model3 = std::make_unique<CollectionModel>(backend3.get(), nullptr);
573 
574         backend1->AddDirectory("/mnt/music");
575         backend2->AddDirectory("/mnt/music");
576         backend3->AddDirectory("/mut/music");
577 
578         model1->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy(f), CollectionModel::GroupBy(s), CollectionModel::GroupBy(t)));
579         model2->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy(f), CollectionModel::GroupBy(s), CollectionModel::GroupBy(t)));
580         model3->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy(f), CollectionModel::GroupBy(s), CollectionModel::GroupBy(t)));
581 
582         model3->set_use_lazy_loading(false);
583 
584         QSignalSpy model1_update(model1.get(), &CollectionModel::rowsInserted);
585         QSignalSpy model2_update(model2.get(), &CollectionModel::rowsInserted);
586         QSignalSpy model3_update(model3.get(), &CollectionModel::rowsInserted);
587 
588         backend1->AddOrUpdateSongs(songs);
589         backend2->AddOrUpdateSongs(songs);
590         backend3->AddOrUpdateSongs(songs);
591 
592         ASSERT_EQ(model1->song_nodes().count(), 0);
593         ASSERT_EQ(model2->song_nodes().count(), 0);
594         ASSERT_EQ(model3->song_nodes().count(), songs.count());
595 
596         model1->Init(false);
597 
598         model1->ExpandAll();
599         model2->ExpandAll();
600         // All nodes in model3 should be created already.
601 
602         ASSERT_EQ(model1->song_nodes().count(), songs.count());
603         ASSERT_EQ(model2->song_nodes().count(), songs.count());
604         ASSERT_EQ(model3->song_nodes().count(), songs.count());
605 
606         // Container nodes for all models should now be identical.
607         for (int i = 0 ; i < 3 ; ++i) {
608           for (CollectionItem *node : model1->container_nodes(i).values()) {
609             ASSERT_TRUE(model2->container_nodes(i).keys().contains(node->key));
610             CollectionItem *node2 = model2->container_nodes(i)[node->key];
611             ASSERT_EQ(node->key, node2->key);
612             ASSERT_EQ(node->display_text, node2->display_text);
613             ASSERT_EQ(node->sort_text, node2->sort_text);
614           }
615           for (CollectionItem *node : model1->container_nodes(i).values()) {
616             ASSERT_TRUE(model3->container_nodes(i).keys().contains(node->key));
617             CollectionItem *node2 = model2->container_nodes(i)[node->key];
618             ASSERT_EQ(node->key, node2->key);
619             ASSERT_EQ(node->display_text, node2->display_text);
620             ASSERT_EQ(node->sort_text, node2->sort_text);
621           }
622 
623           for (CollectionItem *node : model2->container_nodes(i).values()) {
624             ASSERT_TRUE(model1->container_nodes(i).keys().contains(node->key));
625             CollectionItem *node2 = model2->container_nodes(i)[node->key];
626             ASSERT_EQ(node->key, node2->key);
627             ASSERT_EQ(node->display_text, node2->display_text);
628             ASSERT_EQ(node->sort_text, node2->sort_text);
629           }
630           for (CollectionItem *node : model2->container_nodes(i).values()) {
631             ASSERT_TRUE(model3->container_nodes(i).keys().contains(node->key));
632             CollectionItem *node2 = model2->container_nodes(i)[node->key];
633             ASSERT_EQ(node->key, node2->key);
634             ASSERT_EQ(node->display_text, node2->display_text);
635             ASSERT_EQ(node->sort_text, node2->sort_text);
636           }
637 
638           for (CollectionItem *node : model3->container_nodes(i).values()) {
639             ASSERT_TRUE(model1->container_nodes(i).keys().contains(node->key));
640             CollectionItem *node2 = model2->container_nodes(i)[node->key];
641             ASSERT_EQ(node->key, node2->key);
642             ASSERT_EQ(node->display_text, node2->display_text);
643             ASSERT_EQ(node->sort_text, node2->sort_text);
644           }
645           for (CollectionItem *node : model3->container_nodes(i).values()) {
646             ASSERT_TRUE(model2->container_nodes(i).keys().contains(node->key));
647             CollectionItem *node2 = model2->container_nodes(i)[node->key];
648             ASSERT_EQ(node->key, node2->key);
649             ASSERT_EQ(node->display_text, node2->display_text);
650             ASSERT_EQ(node->sort_text, node2->sort_text);
651           }
652         }
653 
654         QSignalSpy database_reset_1(backend1.get(), &CollectionBackend::DatabaseReset);
655         QSignalSpy database_reset_2(backend2.get(), &CollectionBackend::DatabaseReset);
656         QSignalSpy database_reset_3(backend3.get(), &CollectionBackend::DatabaseReset);
657 
658         backend1->DeleteAll();
659         backend2->DeleteAll();
660         backend3->DeleteAll();
661 
662         ASSERT_EQ(database_reset_1.count(), 1);
663         ASSERT_EQ(database_reset_2.count(), 1);
664         ASSERT_EQ(database_reset_3.count(), 1);
665 
666         // Make sure all nodes are deleted.
667 
668         ASSERT_EQ(model1->container_nodes(0).count(), 0);
669         ASSERT_EQ(model1->container_nodes(1).count(), 0);
670         ASSERT_EQ(model1->container_nodes(2).count(), 0);
671 
672         ASSERT_EQ(model2->container_nodes(0).count(), 0);
673         ASSERT_EQ(model2->container_nodes(1).count(), 0);
674         ASSERT_EQ(model2->container_nodes(2).count(), 0);
675 
676         ASSERT_EQ(model3->container_nodes(0).count(), 0);
677         ASSERT_EQ(model3->container_nodes(1).count(), 0);
678         ASSERT_EQ(model3->container_nodes(2).count(), 0);
679 
680         ASSERT_EQ(model1->song_nodes().count(), 0);
681         ASSERT_EQ(model2->song_nodes().count(), 0);
682         ASSERT_EQ(model3->song_nodes().count(), 0);
683 
684         ASSERT_EQ(model1->divider_nodes_count(), 0);
685         ASSERT_EQ(model2->divider_nodes_count(), 0);
686         ASSERT_EQ(model3->divider_nodes_count(), 0);
687 
688         backend1->Close();
689         backend2->Close();
690         backend3->Close();
691 
692       }
693     }
694   }
695 
696 }
697 #endif
698 
699 }  // namespace
700