RSS

(root)/calliope : /tests/musicsourceview-vertical-test.c (revision 454)

Line Revision Contents
1 185 /*  Calliope Music Player
2  *  Copyright 2005-09 Sam Thursfield <ssssam gmail.com>
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15 261  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 185  */
17 261
18 /* Test the vertical performance of a musicsourceview - that it
19 185  * reads pages in the correct order etc. Prototyped in musicsourceview-tests.h.
20  */
21
22 #include <stdlib.h>
23 #include <glib.h>
24 #include <gtk/gtk.h>
25 #include "misc.h"
26 #include "conftool.h"
27 #include "entry.h"
28 #include "musicsource.h"
29 #include "musicsourceimporting.h"
30 #include "musicsourceview.h"
31 #include "test-utils.h"
32 #include "musicsourceview-tests.h"
33
34 // For testing its internals
35 #include "musicsourceview-private.h"
36 361 #include "genericview-private.h"
37 185
38 MusicSource *(*source_constructor)();
39
40
41 406 /***************************************************************************************************
42  * Utility functions
43  */
44
45 /* Dynamic tests can run with a GtkTreeView attached to test whether it stays accurate or has some
46  * internal brain hemmhorage from receiving the wrong signals. */
47
48 static GtkTreeView *dynamic_view_test_setup (MusicSource *source, const char *config_string) {
49         if (source==NULL)
50                 source = source_constructor();
51
52         ViewConfig *config = view_config_string_parse(config_string);
53         MusicSourceView *music_source_view = music_source_create_view(source, config);
54
55 419         GtkWidget *tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(music_source_view));
56 412         g_object_ref_sink (tree_view);
57 406
58 442         _music_source_view_check_tree (music_source_view, NULL);
59
60 420         return GTK_TREE_VIEW(tree_view);
61 406 };
62
63 static void dynamic_view_test_teardown (GtkTreeView *tree_view) {
64         MusicSourceView *music_source_view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
65         g_object_unref_many (tree_view, music_source_view,
66                              music_source_view_get_source(music_source_view), NULL);
67         _entry_cleanup ();
68 };
69
70 185
71 //////////////////////////////////////////////
72 // Basic vertical tests.
73 //
74
75 static void test_flat_with_files(int count) {
76 215         MusicSource *source = source_constructor();
77 261
78 185         // Add count files.
79         //
80         music_source_begin_transaction(source);
81         if (count>MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*2)
82                 printf("\nAdding entries: 0/%i", count);
83 261
84 185         for (int i=0;i<count;i++) {
85 286                 test_add_song (source, i, i, i, 0, 0);
86 185                 if (count>MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*2 && i&15)
87                         printf("\rAdding entries: %i/%i", i, count);
88         };
89         music_source_end_transaction(source);
90         if (count>MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*2)
91                 printf("\rRunning %i entries test.. ", count);
92 261
93 185         // Create view.
94 215         ViewConfig *config = view_config_string_parse("file[path]");
95 185         MusicSourceView *view=music_source_create_view(source, config);
96         _music_source_view_check_tree(view, NULL);
97
98         GtkTreeIter iter;
99         gboolean result;
100 261
101 185         //print_view(view, 5);
102 261
103 185         // Test we have correct number of children (the estimate should be correct here because
104         // this is a simple view.
105         int n_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
106         //_music_source_view_dump_node(view, NULL); fflush(stdout);
107         //printf("n %i c %i\n", n_children, count); fflush(stdout);
108         g_assert_cmpint(n_children, ==, count);
109 261
110 185         result=gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(view), &iter, NULL, count);
111         g_assert_cmpint(result, ==, FALSE);
112 261
113 185         result=gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter);
114         if (count==0) {
115 261                 g_assert_cmpint(result, ==, FALSE);
116 185         } else {
117                 g_assert_cmpint(result, ==, TRUE);
118 261
119 185                 // Iterate through the fellow.
120                 //
121                 // FIXME: how can we test the iter goes into freefall in the right place? Should we,
122                 // or should we just check the function executes with reasonable speed ? Once we have
123                 // a testing framework which can track execution speed regressions such a test would
124                 // be unneccessary I guess.
125 261                 for (int i=0; i<count; i++) {
126 221                         result = gtk_tree_model_iter_has_child(GTK_TREE_MODEL(view), &iter);
127 185                         g_assert_cmpint(result, ==, FALSE);
128 261
129                         result = gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
130 185                         if (i==count-1)
131                                 g_assert_cmpint(result, ==, FALSE);
132                         else
133 261                                 g_assert_cmpint(result, ==, TRUE);
134 185                 };
135 261
136 185                 result=gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
137                 g_assert_cmpint(result, ==, FALSE);
138 261
139
140 185                 // Pick some entries at random and check it's all good.
141                 //
142                 int random_rows=count;
143 261                 if (random_rows>10)
144 185                         random_rows=MAX(5, count/10);
145                 for (int i=0;i<random_rows;i++) {
146                         int n=g_test_rand_int_range(0, count);
147                         result=gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(view), &iter, NULL, n);
148                         g_assert_cmpint(result, ==, TRUE);
149 261
150 185                         GValue value={0};
151 286                         gtk_tree_model_get_value(GTK_TREE_MODEL(view), &iter, COLUMN_FILE_PATH, &value);
152                         char *str=g_strdup_printf(TEST_FILE_FORMAT, n, n);
153 185                         g_assert_cmpstr(g_value_get_string(&value), ==, str);
154                         g_free(str);
155                 };
156 261         };
157
158 185         _music_source_view_check_tree(view, NULL);
159         g_object_unref(view);
160         g_object_unref(source);
161 261
162 185         if (count>MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*2)
163                 printf("done\n");
164 273         _entry_cleanup ();
165 185 };
166
167
168 261 static void test_flat() {
169 185         test_flat_with_files(0);
170         test_flat_with_files(1);
171         test_flat_with_files(2);
172         test_flat_with_files(3);
173         test_flat_with_files(MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/2);
174         test_flat_with_files(MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/2+MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE);
175 261
176 185         if (g_test_slow())
177                 test_flat_with_files(MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*100);
178 };
179
180 //////////////////////////////////////////////////////
181 //
182
183 259 #define ADD_RECORDINGS(_n_rec, _n_file) {                                   \
184     for (int i=0; i<(_n_rec); i++)                                          \
185                 test_add_track_and_recording (source, i*2 + offs, (_n_file));       \
186         offs += (_n_rec)*2; total_files += (_n_rec)*(_n_file);                  }
187
188 206 int populate_for_chaining_overestimate(MusicSource *source) {
189 259         music_source_begin_transaction (source);
190         int offs = 0, total_files = 0;
191         ADD_RECORDINGS ((MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/10)+1, (MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/10)+1);
192         ADD_RECORDINGS ((MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE+1),    1);
193         ADD_RECORDINGS ((MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/10)+1, (MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/10)+1);
194 261         music_source_end_transaction (source);
195 259         return total_files;
196 185 };
197
198 209 int populate_for_chaining_underestimate(MusicSource *source) {
199 185         music_source_begin_transaction(source);
200 261         int offs = 0, total_files = 0;
201 259         ADD_RECORDINGS ((MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE+1),    1);
202         ADD_RECORDINGS ((MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/5)+1, (MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/10)+1);
203         ADD_RECORDINGS ((MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE+1),    1);
204 261         music_source_end_transaction (source);
205 259         return total_files;
206 185 };
207
208 int populate_for_chaining_4(MusicSource *source) {
209         // Create just one recording, and then a whole bunch of tracks which point to it.
210         // This gives a situation where two nodes have the same leaf entry, but differ by their
211         // intermediate ids!
212         //
213         music_source_begin_transaction(source);
214 286         Entry *artist = entry_new(ENTRY_TYPE_ARTIST, 0, NULL, "test");
215         entry_set_property (artist, ARTIST_NAME, "Test Artist");
216 261
217 185         Entry *composition = entry_new (ENTRY_TYPE_COMPOSITION, 0, NULL, "test");
218 286         entry_set_property (composition, COMPOSITION_ARTIST, artist);
219 185         entry_set_property (composition, COMPOSITION_NAME, "test composition");
220 261
221 276         Entry *recording = entry_new(ENTRY_TYPE_RECORDING, 0, NULL, "test");
222 185         entry_take_property (recording, RECORDING_COMPOSITION, composition);
223 276         entry_set_property (recording, RECORDING_NAME, "test recording");
224         int file_id = test_add_file(source, recording, 0, 0);
225         entry_unref (recording, "test");
226 261
227 276         Entry *file = music_source_query_entry(source, ENTRY_TYPE_FILE, file_id, "test");
228 185         recording=entry_get_property(file, FILE_RECORDING);
229 261
230 286         Entry *album = entry_new(ENTRY_TYPE_ALBUM, 0, NULL, "test");
231         entry_set_property (album, ALBUM_NAME, "Test Album");
232         entry_set_property (album, ALBUM_ARTIST, artist);
233
234         Entry *release = entry_new(ENTRY_TYPE_RELEASE, 0, NULL, "test");
235         entry_take_property (release, RELEASE_ALBUM, album);
236
237 259         for (int i=0; i<(MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE+1)*3; i++) {
238 185                 Entry *track=entry_new(ENTRY_TYPE_TRACK, 0, NULL, "test");
239 286                 entry_set_property(track, TRACK_NUMBER, GINT_TO_POINTER(i+1));
240 185                 entry_set_property(track, TRACK_RECORDING, recording);
241 286                 entry_set_property(track, TRACK_RELEASE, release);
242 185                 music_source_add_entry(source, track);
243         };
244 261
245 286         entry_unref (file, "test");
246         entry_unref (artist, "test");
247         entry_unref (release, "test");
248 261         music_source_end_transaction(source);
249 185         return (MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE+1)*3;
250 };
251
252 static GtkTreeRowReference **chaining_create_refs(MusicSourceView *view, const int e_children) {
253         // Set up some row references - they are tested later.
254 259         GtkTreeRowReference **row_references = g_new(GtkTreeRowReference *, 3);
255 261
256 185         GtkTreePath *path = gtk_tree_path_new_first();
257         row_references[0] = gtk_tree_row_reference_new(GTK_TREE_MODEL(view), path);
258         gtk_tree_path_free (path);
259 261
260 259         path = gtk_tree_path_new_from_indices(e_children-1, -1);
261         row_references[1] = gtk_tree_row_reference_new(GTK_TREE_MODEL(view), path);
262 261         gtk_tree_path_free (path);
263 259
264         path = gtk_tree_path_new_from_indices(MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE+1, -1);
265         row_references[2] = gtk_tree_row_reference_new(GTK_TREE_MODEL(view), path);
266         gtk_tree_path_free (path);
267 261
268 185         return row_references;
269 };
270
271 static void chaining_check_refs(MusicSourceView *view, GtkTreeRowReference **row_references, const int n_children) {
272         // Check the row references.
273         GtkTreePath *path = gtk_tree_row_reference_get_path(row_references[0]);
274         int *indices = gtk_tree_path_get_indices(path);
275         g_assert_cmpint (indices[0], ==, 0);
276 261
277 185         path=gtk_tree_row_reference_get_path(row_references[1]);
278         indices=gtk_tree_path_get_indices(path);
279         g_assert_cmpint(indices[0], ==, n_children-1);
280 261
281 185         path=gtk_tree_row_reference_get_path(row_references[2]);
282         indices=gtk_tree_path_get_indices(path);
283         g_assert_cmpint(indices[0], ==, MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE+1);
284
285         gtk_tree_row_reference_free(row_references[0]);
286         gtk_tree_row_reference_free(row_references[1]);
287         gtk_tree_row_reference_free(row_references[2]);
288         g_free(row_references);
289 };
290
291 203 // Chaining one - a static source which appears bigger than it is.
292 206 static void test_chaining_overestimate_static () {
293 185         // Ignore "more than one connection" view config message.
294 261         g_log_set_handler (NULL, G_LOG_LEVEL_MESSAGE, log_ignore, NULL);
295 185
296         MusicSource *source = source_constructor();
297 206         int count = populate_for_chaining_overestimate(source);
298 185
299         // Create view.
300 215         ViewConfig *config = view_config_string_parse("track[number]:recording[name]:file[path]");
301 185         MusicSourceView *view = music_source_create_view(source, config);
302         _music_source_view_check_tree (view, NULL);
303 261
304 185         // Estimate should be exact or way over
305         int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
306 261         g_assert_cmpint ((int)e_children, >=, count);
307 185
308 259         //_music_source_view_dump_node (view, NULL);
309
310         //printf ("Creating refs...\n"); fflush (stdout);
311 185         GtkTreeRowReference **row_references = chaining_create_refs(view, e_children);
312
313         // Let's query the row in the middle! If this doesn't break things, nothing will. This
314         // causes the surplus between middle and last pages to be discovered and removed.
315         GtkTreeIter iter;
316 259         //printf ("Querying child %i.\n", e_children/2); fflush (stdout);
317 261         gboolean result = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(view), &iter, NULL,
318 259                                                         e_children / 2);
319 185         g_assert (result==TRUE);
320
321 261         // Now iterate over the surplus between the first and middle pages.
322 185         result = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(view), &iter, NULL, MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/2);
323         for (int i=0; i<MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE-1; i++) {
324                 result = gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
325                 g_assert (result==TRUE);
326                 g_assert (iter.user_data2!=NULL);       // Should not go into freefall within one page's worth of rows.
327 261
328 259                 //ViewPage *page = (ViewPage *)iter.user_data2;
329                 //const int page_i = GPOINTER_TO_INT(iter.user_data3);
330                 //ViewNode *node = g_array_index(page->nodes, ViewNode *, page_i);
331                 //printf ("%i: [%i] %i\n", i, page_i+page->start_e, node->id); fflush (stdout);
332 185         };
333 261
334 185         // Go through the whole view, checking it's okay.
335 261         _music_source_view_check_tree (view, NULL);
336 185         //_music_source_view_dump_node (view, NULL);
337         int n_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
338         //printf("n %i, c %i\n", n_children, count);fflush(stdout);
339 259         g_assert_cmpint (n_children, ==, count);
340 185
341         result = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter);
342         int i=0;
343         while (result!=FALSE) {
344                 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(view), &iter);
345                 int id = music_source_view_get_id_at_path(view, ENTRY_TYPE_FILE, path);
346                 //printf("i %i, id %i\n", i, id);fflush(stdout);
347                 i++; g_assert_cmpint (i, ==, id);
348                 gtk_tree_path_free (path);
349                 result = gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
350         };
351
352 261         chaining_check_refs (view, row_references, n_children);
353 185         _music_source_view_check_tree (view, NULL);
354 261
355 185         g_object_unref (view);
356 261         g_log_set_handler (NULL, G_LOG_LEVEL_MESSAGE, g_log_default_handler, NULL);
357         g_object_unref (source);
358 273         _entry_cleanup ();
359 185 };
360
361 203
362 206 // Chaining two - adding to a source which appears bigger than it is.
363 350 /*static void test_chaining_overestimate_adding () {
364 206         // Ignore "more than one connection" view config message.
365 261         g_log_set_handler (NULL, G_LOG_LEVEL_MESSAGE, log_ignore, NULL);
366 206
367         MusicSource *source = source_constructor();
368         int file_count = populate_for_chaining_overestimate(source);
369
370         // Create view.
371 215         ViewConfig *config = view_config_string_parse("track[number]:recording[name]:file[path]");
372 206         MusicSourceView *view = music_source_create_view(source, config);
373 261         _music_source_view_check_tree (view, NULL);
374 206
375         // Estimate should be exact or way over
376 208         int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL),
377             e_children_original = e_children;
378 261         g_assert_cmpint (e_children, >=, file_count);
379
380 208         GtkTreeRowReference **row_references = chaining_create_refs(view, e_children);
381 206
382         // Add an entry half way down the last page.
383         int recording_count = music_source_get_n_entries(source, ENTRY_TYPE_RECORDING, NULL, NULL);
384 261
385 206         int recording_n = recording_count - ((MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE/10) / 2);
386         if (!(recording_n & 1)) recording_n++;
387         test_add_track_and_recording (source, recording_n, 10);
388 261         _music_source_view_check_tree (view, NULL);
389 208
390         // The estimate will probably have shrunk, because while looking for where to insert the
391         // previous entries more pages will have been queried.
392         //
393         e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
394 261         g_assert_cmpint (e_children, >=, file_count+10);
395         g_assert_cmpint (e_children, <=, e_children_original+10);
396 208
397         // Add an entry half way down the imaginary page.
398         recording_n = recording_count / 2;
399 261         if (!(recording_n & 1)) recording_n++;
400 208         test_add_track_and_recording (source, recording_n, 1);
401 261         _music_source_view_check_tree (view, NULL);
402 208
403         // We still probably don't have the right number of children.
404         e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
405 261         g_assert_cmpint (e_children, >=, file_count+11);
406         g_assert_cmpint (e_children, <=, e_children_original+11);
407
408 208         _music_source_view_dump_node (view, NULL);
409 261
410         // Now iterate over the surplus between the first and middle pages.
411 208         GtkTreeIter iter;
412 261         gboolean result = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(view), &iter, NULL,
413 208                                                         MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE / 2);
414 206         for (int i=0; i<MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE-1; i++) {
415                 result = gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
416                 g_assert (result==TRUE);
417 208                 g_assert (iter.user_data2!=NULL);       // Shouldn't freefall within one page's worth of rows.
418 261         };
419 206         _music_source_view_check_tree (view, NULL);
420 261
421         // Due to the way locate_entry works (binary search) all of the surplus should now have been
422 208         // queried fully. Yay!
423 261
424         int n_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
425         g_assert_cmpint(n_children, ==, (file_count + 11));
426
427         chaining_check_refs (view, row_references, n_children);
428
429 206         g_object_unref (view);
430 261         g_log_set_handler (NULL, G_LOG_LEVEL_MESSAGE, g_log_default_handler, NULL);
431         g_object_unref (source);
432 350 };*/
433 206
434
435 185 // Chaining three - a source which appears smaller than it is, and testing the border between the bottom of
436 // one page and the next.
437 209 // This is three instead of two because I still feel there should be more overestimation tests.
438 185 // I just can't work out what they should be at the moment. ..
439 209 static void test_chaining_underestimate_static () {
440 185         // Ignore "more than one connection" view config message.
441 261         g_log_set_handler(NULL, G_LOG_LEVEL_MESSAGE, log_ignore, NULL);
442 185
443         MusicSource *source = source_constructor();
444 209         int count = populate_for_chaining_underestimate(source);
445 261
446 185         // Create view.
447 215         ViewConfig *config=view_config_string_parse("track[number]:recording[name]:file[path]");
448 185         MusicSourceView *view=music_source_create_view(source, config);
449         _music_source_view_check_tree(view, NULL);
450
451         // Estimate should be exact or way under
452         int e_children=gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
453 261         g_assert_cmpint((int)e_children, <=, count);
454 185
455         GtkTreeRowReference **row_references=chaining_create_refs(view, e_children);
456 261
457 185         // Iterate through it all, to try and break it.
458         GtkTreeIter iter;
459         gboolean result=gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter);
460         int i=0;
461         while(result!=FALSE) {
462                 GtkTreePath *path=gtk_tree_model_get_path(GTK_TREE_MODEL(view), &iter);
463                 int id=music_source_view_get_id_at_path(view, ENTRY_TYPE_FILE, path);
464                 i++; g_assert_cmpint(i, ==, id);
465                 gtk_tree_path_free(path);
466                 result=gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
467         };
468 261
469 185         int n_children=gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
470 261         g_assert_cmpint((int)n_children, ==, count);
471 185
472         chaining_check_refs(view, row_references, n_children);
473 261
474 185         _music_source_view_check_tree(view, NULL);
475         g_object_unref(view);
476         g_log_set_handler(NULL, G_LOG_LEVEL_MESSAGE, g_log_default_handler, NULL);
477 261         g_object_unref(source);
478 185 };
479
480 217 /* This tests intermediate id handling, but only the specific case of intermediate ids on
481  * chained sources. */
482 185 static void test_chaining_4() {
483         // Ignore "more than one connection" view config message.
484 261         g_log_set_handler(NULL, G_LOG_LEVEL_MESSAGE, log_ignore, NULL);
485 185
486         MusicSource *source = source_constructor();
487         int count=populate_for_chaining_4(source);
488
489 261         //music_source_dump(source);
490 185         // Create view.
491 215         ViewConfig *config=view_config_string_parse("track[number]:recording[name]:file[path]");
492 185         MusicSourceView *view=music_source_create_view(source, config);
493         _music_source_view_check_tree(view, NULL);
494 261
495 185         // Estimate should be exact or way under
496         int e_children=gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
497 261         g_assert_cmpint((int)e_children, <=, count);
498 185         //printf("e %i\n", e_children); fflush(stdout);
499
500         // Iterate through it all, to try and break it.
501         GtkTreeIter iter;
502         gboolean result=gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter);
503         int i=0;
504         while(result!=FALSE) {
505                 //printf("iter: page %x, n %i\n", iter.user_data2, iter.user_data3);fflush(stdout);
506                 //_music_source_view_dump_node(view, iter.user_data);
507                 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(view), &iter);
508                 //printf("path %s\n", gtk_tree_path_to_string(path));
509                 //int id = music_source_view_get_id_at_path(view, ENTRY_TYPE_TRACK, path);
510                 //printf("i: %i, id: %i\n", i, id);fflush(stdout);
511                 i++; //g_assert_cmpint(i, ==, id);
512                 gtk_tree_path_free (path);
513                 result = gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
514         };
515 261
516 185         _music_source_view_check_tree(view, NULL);
517         g_object_unref(view);
518         g_log_set_handler(NULL, G_LOG_LEVEL_MESSAGE, g_log_default_handler, NULL);
519 261         g_object_unref(source);
520 185 };
521
522 259
523 261 /* This tests for a couple of weird bugs that appeared in Libraryview. */
524 259 static void test_chaining_5 () {
525         MusicSource *source = source_constructor ();
526 261
527 259         /* Every odd file id is orphaned - remember that when i==0, file 1 etc. is added. */
528         music_source_begin_transaction (source);
529         for (int i=0; i<256; i++) {
530 261                 test_add_song (source, i/16, i, i, i&1? i/16: 0, i&1? i&15: 0);
531 259         };
532         music_source_end_transaction (source);
533 261
534 259         ViewConfig *config = view_config_string_parse(
535                                "artist[name]:(album[name]:release[date]>track[number]:"
536                                "recording[name]:file[path])/(recording[name]>file[path])");
537         MusicSourceView *view = music_source_create_view (source, config);
538 261
539         // This can break when e_children is calculated wrongly.
540 259         GtkTreeIter iter;
541         gboolean result = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(view), &iter, NULL, 16*9);
542         g_assert (result==TRUE);
543 261
544         //_music_source_view_dump_node (view, NULL);
545
546         // Sometimes the node is just populated wrong.
547         result = gtk_tree_model_iter_children (GTK_TREE_MODEL(view), &iter, NULL);
548         int i = 0;
549         while (result) {
550                 const int correct_artist_id = i<16? 3: 4+(i-16)/9,
551                           real_artist_id = music_source_view_get_id_at_iter(view, ENTRY_TYPE_ARTIST, &iter);
552
553                 if (correct_artist_id!=real_artist_id) {
554                         print_view (view, NULL); g_message ("At row %i: ", i);
555                         g_assert_cmpint (correct_artist_id, ==, real_artist_id);
556                 };
557
558                 result = gtk_tree_model_iter_next (GTK_TREE_MODEL(view), &iter);
559                 i++;
560         };
561
562         _music_source_view_check_tree (view, NULL);
563
564 259         g_object_unref_many (view, source, NULL);
565 };
566
567 185 // Test that recording:file[bitrate] doesn't group the files by recording.
568 222 static void test_sorting_1() {
569 185         MusicSource *source = source_constructor();
570         music_source_begin_transaction(source);
571 261
572         Entry *recording = NULL,
573 286               *artist = entry_new(ENTRY_TYPE_ARTIST, 0, NULL, "test"),
574               *composition = entry_new(ENTRY_TYPE_COMPOSITION, 0, NULL, "test");
575         entry_set_property (artist, ARTIST_NAME, "test artist");
576 185         entry_set_property (composition, COMPOSITION_NAME, "test composition");
577 286         entry_take_property (composition, COMPOSITION_ARTIST, artist);
578 185         for (int i=0;i<4;i++) {
579                 if ((i%2)==0) {
580                         recording = entry_new(ENTRY_TYPE_RECORDING, 0, NULL, "test");
581                         entry_set_property (recording, RECORDING_COMPOSITION, composition);
582 273                         entry_take_property (recording, RECORDING_NAME, g_strdup_printf("Recording %i", i%2));
583 261                 };
584 276                 Entry *file = test_make_file (source, NULL, 0, i);
585                 entry_set_property (file, FILE_RECORDING, recording);
586 185                 switch (i) {
587 276                         case 0: entry_set_property (file, FILE_BITRATE, GINT_TO_POINTER(64)); break;
588                         case 1: entry_set_property (file, FILE_BITRATE, GINT_TO_POINTER(320)); break;
589                         case 2: entry_set_property (file, FILE_BITRATE, GINT_TO_POINTER(192)); break;
590                         case 3: entry_set_property (file, FILE_BITRATE, GINT_TO_POINTER(256)); break;
591 185                 };
592 276                 music_source_add_entry (source, file);
593 185                 if (i%2==1)
594 276                         entry_unref (recording, "test");
595 185         };
596         entry_unref (composition, "test");
597         music_source_end_transaction(source);
598
599 276         ViewConfig *config = view_config_string_parse("recording:file[path]");
600         MusicSourceView *view = music_source_create_view(source, config);
601         _music_source_view_check_tree (view, NULL);
602 261
603 185         // Should be four files
604 276         int n_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
605 185         //printf("n %i\n", n_children);fflush(stdout);
606 276         g_assert_cmpint (n_children, ==, 4);
607 261
608 185         GtkTreeIter iter;
609         gboolean result = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter);
610         // FIXME: why not have enums
611         int path_column = -1;
612 276         for (int i=0; i<MUSIC_SOURCE_VIEW_COLUMN_COUNT; i++)
613 185                 if (column[i].apid == _FILE_PATH) {
614 276                         path_column = i;
615 185                         break;
616                 };
617 276         g_assert (path_column>=0 && path_column<MUSIC_SOURCE_VIEW_COLUMN_COUNT);
618 261
619 185         GtkTreePath *path=gtk_tree_path_new_first();
620         for (int i=0;i<4;i++) {
621                 int id = music_source_view_get_id_at_path(view, ENTRY_TYPE_FILE, path);
622                 g_assert_cmpint(id, ==, i+1);
623                 gtk_tree_path_next(path);
624         };
625         gtk_tree_path_free(path);
626 261         _music_source_view_check_tree(view, NULL);
627         g_object_unref(view);
628 185
629         config=view_config_string_parse("recording:file[bitrate]");
630 261         view=music_source_create_view(source, config);
631 185         _music_source_view_check_tree(view, NULL);
632 261
633 185         result = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter);
634         // FIXME: why not have enums
635         int bitrate_column = -1;
636         for (int i=0;i<MUSIC_SOURCE_VIEW_COLUMN_COUNT;i++)
637                 if (column[i].apid == _FILE_BITRATE) {
638                         bitrate_column=i;
639                         break;
640                 };
641         g_assert(bitrate_column>=0 && bitrate_column<MUSIC_SOURCE_VIEW_COLUMN_COUNT);
642 261
643 185         for (int i=0;i<4;i++) {
644                 g_assert(result == TRUE);
645                 char *bitrate_string;
646                 gtk_tree_model_get(GTK_TREE_MODEL(view), &iter, bitrate_column, &bitrate_string, -1);
647                 int bitrate = atoi(bitrate_string);
648                 switch (i) {
649                         case 0: g_assert_cmpint(bitrate, ==, 64); break;
650                         case 1: g_assert_cmpint(bitrate, ==, 192); break;
651                         case 2: g_assert_cmpint(bitrate, ==, 256); break;
652                         case 3: g_assert_cmpint(bitrate, ==, 320); break;
653                 };
654                 g_free(bitrate_string);
655                 result = gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter);
656         };
657 261         _music_source_view_check_tree(view, NULL);
658         g_object_unref(view);
659
660 185         g_object_unref(source);
661 273         _entry_cleanup ();
662 185 }
663
664 221 /* Test sort grouping on various configs. */
665 222 static void test_sorting_2() {
666 185         MusicSource *source = source_constructor();
667         music_source_begin_transaction(source);
668         // artist, composition, file, album, track
669 286         test_add_song(source, 0, 0, 1, 1, 1);
670         test_add_song(source, 0, 0, 2, 1, 1);
671         test_add_song(source, 0, 1, 0, 1, 2);
672         test_add_song(source, 0, 1, 3, 1, 2);
673         test_add_song(source, 1, 2, 5, 2, 3);
674         test_add_song(source, 1, 2, 6, 2, 3);
675         test_add_song(source, 1, 3, 4, 2, 4);
676         test_add_song(source, 1, 3, 7, 2, 4);
677 185         music_source_end_transaction(source);
678 261
679         MusicSourceView *view;
680 453 /*      const char *reference_1[8][5] = {
681 185                 {"Test Artist 000000", "Test Composition 000001", "Test File 000000 (000001)", NULL, NULL},
682                 {"Test Artist 000000", "Test Composition 000000", "Test File 000001 (000000)", NULL, NULL},
683                 {"Test Artist 000000", "Test Composition 000000", "Test File 000002 (000000)", NULL, NULL},
684                 {"Test Artist 000000", "Test Composition 000001", "Test File 000003 (000001)", NULL, NULL},
685                 {"Test Artist 000001", "Test Composition 000003", "Test File 000004 (000003)", NULL, NULL},
686                 {"Test Artist 000001", "Test Composition 000002", "Test File 000005 (000002)", NULL, NULL},
687                 {"Test Artist 000001", "Test Composition 000002", "Test File 000006 (000002)", NULL, NULL},
688                 {"Test Artist 000001", "Test Composition 000003", "Test File 000007 (000003)", NULL, NULL}};
689 261
690 185         const char *config_1 = "artist[name]:recording:file[path]";
691 261         view = music_source_create_view(source, view_config_string_parse(config_1));
692         //printf ("Config: %s.\n", view_config_to_string(view_config_string_parse(config_1), TRUE)); fflush (stdout);
693         assert_view (view, NULL, 8, reference_1);
694 185         g_object_unref (view);
695 261
696 185
697         const char *reference_2[8][5] = {
698 286                 {"Test Artist 000000", "Test Composition 000001", "Test File 000000 (000001)", "Test Album 000001", "2"},
699                 {"Test Artist 000000", "Test Composition 000000", "Test File 000001 (000000)", "Test Album 000001", "1"},
700                 {"Test Artist 000000", "Test Composition 000000", "Test File 000002 (000000)", "Test Album 000001", "1"},
701                 {"Test Artist 000000", "Test Composition 000001", "Test File 000003 (000001)", "Test Album 000001", "2"},
702                 {"Test Artist 000001", "Test Composition 000003", "Test File 000004 (000003)", "Test Album 000002", "4"},
703                 {"Test Artist 000001", "Test Composition 000002", "Test File 000005 (000002)", "Test Album 000002", "3"},
704                 {"Test Artist 000001", "Test Composition 000002", "Test File 000006 (000002)", "Test Album 000002", "3"},
705                 {"Test Artist 000001", "Test Composition 000003", "Test File 000007 (000003)", "Test Album 000002", "4"}};
706 261
707 185         const char *config_2 = "artist[name]:album:release:track:recording:file[path]";
708 261         view = music_source_create_view(source, view_config_string_parse(config_2));
709         assert_view (view, NULL, 8, reference_2);
710 185         g_object_unref (view);
711 453 */
712
713         // 3 - Test for sort grouping being broken. Each pair of files is in a sort group defined by
714         //     its track, in this case.
715 185         const char *reference_3[8][5] = {
716 286                 {"Test Artist 000000", "Test Composition 000000", "Test File 000001 (000000)", "Test Album 000001", "1"},
717                 {"Test Artist 000000", "Test Composition 000000", "Test File 000002 (000000)", "Test Album 000001", "1"},
718                 {"Test Artist 000000", "Test Composition 000001", "Test File 000000 (000001)", "Test Album 000001", "2"},
719                 {"Test Artist 000000", "Test Composition 000001", "Test File 000003 (000001)", "Test Album 000001", "2"},
720                 {"Test Artist 000001", "Test Composition 000002", "Test File 000005 (000002)", "Test Album 000002", "3"},
721                 {"Test Artist 000001", "Test Composition 000002", "Test File 000006 (000002)", "Test Album 000002", "3"},
722                 {"Test Artist 000001", "Test Composition 000003", "Test File 000004 (000003)", "Test Album 000002", "4"},
723                 {"Test Artist 000001", "Test Composition 000003", "Test File 000007 (000003)", "Test Album 000002", "4"}};
724 261
725 453         const char *config_3 = "artist[name]:album:release[date]:track[number]:recording:file[path]";
726 261         view = music_source_create_view(source, view_config_string_parse(config_3));
727         assert_view (view, NULL, 8, reference_3);
728 185         g_object_unref (view);
729
730         g_object_unref(source);
731 273         _entry_cleanup ();
732 185 }
733
734 235
735 185 ////////////////////////////////////////////////////
736 // Location
737 //
738 225 //   More tests for location happen in preset tests - there's no point duplicating the tests here.
739 //   Bear in mind these tests don't cover a big portion of the location functionality.
740 //
741 185
742 // Source should have 'recording_count' consecutive recordings.
743 //
744 226 static void test_location_with_source (MusicSource *source, const char *config_string,
745 261                                        int file_count, int file_depth) {
746 226         ViewConfig *config = view_config_string_parse (config_string);
747 185         MusicSourceView *view = music_source_create_view (source, config);
748 261         //print_view (view, NULL);
749
750 185         // On first iteration, find location of last file. This tests estimated sources.
751 261         GtkTreePath *path = music_source_view_get_path_for_id(view, ENTRY_TYPE_FILE, file_count);
752 226         //printf ("Path: %s.\n", gtk_tree_path_to_string (path)); fflush (stdout);
753 185         int n_children = gtk_tree_model_iter_n_children (GTK_TREE_MODEL(view), NULL);
754 225         //_music_source_view_dump_node (view, NULL);
755 185         g_assert (path!=NULL);
756 226         g_assert_cmpint (gtk_tree_path_get_depth(path), ==, file_depth);
757         if (file_depth==1)
758                 g_assert_cmpint (gtk_tree_path_get_indices(path)[0], ==, n_children-1);
759 185         gtk_tree_path_free (path);
760 261
761 276         // Test the iterative version (this is for execution in idles etc., because
762 261         // get_path_for_id can be pretty slow.)
763 185         //
764 276         const int test_count = MAX(file_count/10, 20);
765         for (int i=0; i<test_count*2; i++) {
766 185                 // On first iteration, find location of last file. This tests estimated sources.
767                 int file_id = g_test_rand_int_range (1, file_count+1);
768                 //if (!music_source_is_valid_id (source, ENTRY_TYPE_RECORDING, recording_id))
769                 //      continue;
770 261
771 185                 MusicSourceViewSearch *state = music_source_view_get_path_for_id_begin
772 261                                                  (view, ENTRY_TYPE_FILE, file_id);
773 185                 while (!music_source_view_get_path_for_id_step(view, state)) {
774                         if (g_test_rand_int()&4) {
775                                 // Sometimes, let's just give up halfway through!
776                                 music_source_view_get_path_for_id_end (view, state);
777                                 state = NULL;
778                                 break;
779                         };
780                 };
781 261
782 185                 if (state!=NULL) {
783                         GtkTreePath *path = music_source_view_get_path_for_id_end (view, state);
784 261
785 225                         //_music_source_view_dump_node (view, NULL);
786 185                         g_assert (path!=NULL);
787 226                         g_assert_cmpint (gtk_tree_path_get_depth(path), ==, file_depth);
788 261
789 185                         g_assert_cmpint (music_source_view_get_id_at_path(view, ENTRY_TYPE_FILE, path), ==,
790                                                          file_id);
791 226                         //g_assert_cmpint (gtk_tree_path_get_indices(path)[file_depth-1], ==, file_id-1);
792 261
793 185                         gtk_tree_path_free (path);
794                 };
795 261         };
796 237
797         for (int i=0; i<test_count; i++) {
798                 // On first iteration, find location of last file. This tests estimated sources.
799                 int file_id = g_test_rand_int_range (1, file_count+1);
800                 //if (!music_source_is_valid_id (source, ENTRY_TYPE_RECORDING, recording_id))
801                 //      continue;
802 261
803                 path = music_source_view_get_path_for_id (view, ENTRY_TYPE_FILE, file_id);
804 237                 //printf ("Got path: %s.\n", gtk_tree_path_to_string(path)); fflush (stdout);
805 261
806 237                 //_music_source_view_dump_node (view, NULL);
807                 g_assert (path!=NULL);
808                 g_assert_cmpint (gtk_tree_path_get_depth(path), ==, file_depth);
809 261
810 237                 g_assert_cmpint (music_source_view_get_id_at_path(view, ENTRY_TYPE_FILE, path), ==,
811                                  file_id);
812                 //g_assert_cmpint (gtk_tree_path_get_indices(path)[file_depth-1], ==, file_id-1);
813 261
814 237                 gtk_tree_path_free (path);
815         };
816 261
817 185         g_object_unref (view);
818 };
819
820
821 261 static void test_location_1() {
822         int file_count;
823         MusicSource *source = source_constructor();
824 185         test_add_n_songs (source, 10, TRUE);
825 226         test_location_with_source (source, "track[number]:recording[name]:file[path]", 10, 1);
826 185         g_object_unref (source);
827 276         _entry_cleanup ();
828 261
829 185         source = source_constructor();
830 261         test_add_n_songs (source, MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*4, TRUE);
831         test_location_with_source (source, "track[number]:recording[name]:file[path]",
832 226                                    MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*4, 1);
833 185         g_object_unref (source);
834 276         _entry_cleanup ();
835 261
836 185         //printf ("Chaining 1\n");
837         source = source_constructor();
838 261         file_count = populate_for_chaining_overestimate(source);
839 226         test_location_with_source (source, "track[number]:recording[name]:file[path]", file_count, 1);
840 185         g_object_unref (source);
841 276         _entry_cleanup ();
842 261
843 185         //printf ("\nChaining 3\n");
844         source = source_constructor();
845 209         file_count = populate_for_chaining_underestimate(source);
846 226         test_location_with_source (source, "track[number]:recording[name]:file[path]", file_count, 1);
847 185         g_object_unref (source);
848 273         _entry_cleanup ();
849 185         // FIXME: chaining 4 doesn't make much sense at the moment, because we can't choose which
850         // intermediate entries to use when picking a node. However when this code does exist it
851 261         // will be an excellent test!
852 185         //printf ("\nChaining 4\n");
853         /*source = source_constructor();
854         file_count = populate_for_chaining_4 (source);
855         test_location_with_source (source, file_count);
856         g_object_unref (source);*/
857 261
858 185         // More tests on location are performed in the preset tests:
859         //  - testing artist:album:blah blah:files, with orphans.
860         //  - testing a branching view, with orphans not in the root.
861 };
862
863 static void test_location_2() {
864         // Slightly weird corner case - this requires brute force searching, because the property
865         // 'file' is sorted by is the same for each file.
866         MusicSource *source = source_constructor ();
867 261
868 185         music_source_begin_transaction (source);
869         test_add_song (source, 1, 1, 1, 0, 0);
870         test_add_song (source, 1, 1, 2, 0, 0);
871         test_add_song (source, 1, 1, 3, 0, 0);
872         music_source_end_transaction (source);
873
874 215         ViewConfig *config = view_config_string_parse ("composition[name]:recording:file[bitrate]");
875 185         MusicSourceView *view = music_source_create_view(source, config);
876 261
877         GtkTreePath *path;
878 185         path = music_source_view_get_path_for_id (view, ENTRY_TYPE_FILE, 2);
879         g_assert_cmpint (gtk_tree_path_get_depth(path), ==, 1);
880         g_assert_cmpint (gtk_tree_path_get_indices(path)[0], ==, 1);
881         gtk_tree_path_free (path);
882 261
883 227         MusicSourceViewSearch *search = music_source_view_get_path_for_id_begin(view, ENTRY_TYPE_FILE, 2);
884 185         while (!music_source_view_get_path_for_id_step(view, search));
885         path = music_source_view_get_path_for_id_end(view, search);
886         g_assert_cmpint (gtk_tree_path_get_depth(path), ==, 1);
887         g_assert_cmpint (gtk_tree_path_get_indices(path)[0], ==, 1);
888 261         gtk_tree_path_free (path);
889
890 185         g_object_unref (view);
891         g_object_unref (source);
892 281         _entry_cleanup ();
893 185 };
894
895 226 /* This test has 3 levels! */
896 static void test_location_3() {
897         MusicSource *source = source_constructor ();
898 261         test_add_n_songs (source, MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*4, TRUE);
899 226         test_location_with_source (source, "artist[name]>composition:recording[name]>"
900                                            "file[bitrate]", MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*4, 3);
901         g_object_unref (source);
902 281         _entry_cleanup ();
903 226 };
904
905 227 /* Test orphans somehow! */
906 static void test_location_4() {
907         MusicSource *source = source_constructor ();
908 261
909 228         /* Every odd file id is orphaned - remember that when i==0, file 1 etc. is added. */
910 259         music_source_begin_transaction (source);
911         for (int i=0; i<MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*4; i++) {
912 261                 test_add_song (source, i/16, i, i, i&1? i/16: 0, i&1? i&15: 0);
913 259         };
914         music_source_end_transaction (source);
915 261
916 227         test_location_with_source (source, "artist[name]:(album[name]:release[date]>track[number]:"
917                                            "recording[name]:file[path])/(recording[name]>file[path])",
918 228                                            MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE*4, 2);
919 261
920 227         g_object_unref (source);
921 281         _entry_cleanup ();
922 227 };
923 185
924
925 259
926 185 //////////////////////////////////////////////////
927 // Removal test cases
928 //
929
930 441 /* removal 1: Test removal of a specific entry.
931  * 
932  *   This is more of a challenge than it appears, because the files are sorted by bitrate and they
933  *   have identical bitrates, so the removal function has to use brute force to find which to
934  *   remove. */
935 static void test_removal_1 (void) {
936 185         MusicSource *source = source_constructor ();
937 261
938 185         music_source_begin_transaction (source);
939         test_add_song (source, 1, 1, 1, 0, 0);
940         test_add_song (source, 1, 1, 2, 0, 0);
941         test_add_song (source, 1, 1, 3, 0, 0);
942         music_source_end_transaction (source);
943
944 454         const char *config_string = "composition[name]:recording:file[path]";
945 412         GtkTreeView *tree_view = dynamic_view_test_setup(source, config_string);
946         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
947 261
948         const char *reference_1[3][5] = {
949 185                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL},
950                 {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", NULL, NULL},
951                 {"Test Artist 000001", "Test Composition 000001", "Test File 000003 (000001)", NULL, NULL}};
952         assert_view (view, NULL, 3, reference_1);
953         //_music_source_view_dump_node (view, NULL);
954 261
955 185         music_source_remove_entry (source, ENTRY_TYPE_FILE, 2);
956 261
957 224         /* This will give a "file 2 was NULL :(" error if the entry gets removed but the view doesn't
958          * remove the row. */
959 261         const char *reference_2[2][5] = {
960 185                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL},
961                 {"Test Artist 000001", "Test Composition 000001", "Test File 000003 (000001)", NULL, NULL}};
962         assert_view (view, NULL, 2, reference_2);
963
964         music_source_remove_entry (source, ENTRY_TYPE_FILE, 3);
965 261         music_source_remove_entry (source, ENTRY_TYPE_FILE, 1);
966
967 185         assert_view (view, NULL, 0, NULL);
968 261
969 412         dynamic_view_test_teardown (tree_view);
970 185 };
971 261
972 454 /* removal 2: Remove an entry that appears in several rows under the same node.
973  *
974  *   Note that part of this test relies on arbitrary result order; it's not easy to rewrite the
975  *   test to not depending on this. If you're using genericview with a source other than
976  *   genericsource, maybe comment it out or rewrite it. */
977 441 static void test_removal_2 (void) {
978 185         MusicSource *source = source_constructor ();
979 261
980 435         // This is a slightly complex case. We add:
981         //   One artist, who has two albums. Album 1 has two tracks and album 2 has one track.
982         //   One recording, which has two files. Every album track is this recording, and ...
983         //   it is sorted by file path.
984         //
985         // So the view is of six files, two for each track. 
986 361         music_source_begin_transaction (source);
987         test_add_song (source, 1, 1, 1, 1, 1);
988         test_add_song (source, 1, 1, 1, 2, 1);
989         test_add_song (source, 1, 1, 2, 1, 2);
990         music_source_end_transaction (source);
991
992         // Config with a m:1 join to make things awkward.
993 412         const char *config_string = "artist[name]:album:release:track:recording:file[path]";
994         GtkTreeView *tree_view = dynamic_view_test_setup(source, config_string);
995         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
996 361
997         const char *reference_1[6][5] = {
998                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "1"},
999                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "2"},
1000                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000002", "1"},
1001                 {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", "Test Album 000001", "1"},
1002                 {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", "Test Album 000001", "2"},
1003                 {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", "Test Album 000002", "1"}};
1004         assert_view (view, NULL, 6, reference_1);
1005
1006         music_source_remove_entry (source, ENTRY_TYPE_FILE, 1);
1007
1008 390         // These are actually valid in any order, because it's only sorted by file path and the file
1009         // paths are identical.
1010 361         const char *reference_2[3][5] = {
1011 402                 {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", "Test Album 000001", "1"},
1012 397                 {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", "Test Album 000002", "1"},
1013                 {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", "Test Album 000001", "2"}};
1014 361         assert_view (view, NULL, 3, reference_2);
1015 412
1016         dynamic_view_test_teardown (tree_view);
1017 };
1018
1019 441 /* removal 3: Two entries of one row */
1020 static void test_removal_3 (void) {
1021 412         MusicSource *source = source_constructor ();
1022         test_add_song (source, 1, 1, 1, 1, 1);
1023
1024         const char *config_string = "artist[name]:album:release[date]:track:recording[name]:file[path]";
1025         GtkTreeView *tree_view = dynamic_view_test_setup(source, config_string);
1026         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
1027
1028         const char *reference_1[1][5] = {
1029           {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "1"}};
1030
1031         assert_view (view, NULL, 1, reference_1);
1032
1033         music_source_remove_entry (source, ENTRY_TYPE_RECORDING, 1);
1034
1035         g_assert_cmpint (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL), ==, 0);
1036
1037         dynamic_view_test_teardown (tree_view);
1038 };
1039
1040 441 /* removal 4: A multi-level config. */
1041 static void test_removal_4 (void) {
1042         MusicSource *source = source_constructor ();
1043         test_add_song (source, TEST_UNKNOWN_ARTIST, 1, 1, 0, 0);
1044
1045         const char *config_string = "artist[name] > recording[name]:file[path]";
1046         GtkTreeView *tree_view = dynamic_view_test_setup(source, config_string);
1047         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
1048
1049         const char *reference_root[1][5] = {
1050           {"Unknown Artist", NULL, NULL, NULL, NULL}};
1051                 const char *reference_0[1][5] = {
1052                   {"Unknown Artist", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL}};
1053
1054         GtkTreeIter iter; gtk_tree_model_iter_children (GTK_TREE_MODEL(view), &iter, NULL);
1055         assert_view (view, NULL, 1, reference_root);
1056         assert_view (view, &iter, 1, reference_0);
1057
1058         music_source_remove_entry (source, ENTRY_TYPE_FILE, 1);
1059
1060         // Now, the 'Unknown Artist' entry should have gone too.
1061         g_assert_cmpint (gtk_tree_model_iter_n_children (GTK_TREE_MODEL(view), NULL), ==, 0);
1062
1063         dynamic_view_test_teardown (tree_view);
1064 }
1065 361
1066 354 /***************************************************************************************************
1067  * Changing test cases
1068  *   (needs to be before insertion test cases because adding entries causes -changed to be emitted
1069  *    a lot anyway).
1070  */
1071
1072 364 // Modify an entry and check that row-changed is emitted, and that it gets resorted when needed
1073 354 static void test_changing_1() {
1074 378         // FIXME: minimise the number of times its emitted ..
1075 402         gboolean row_1_row_changed_was_emitted;
1076 396         void row_changed (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
1077                           GtkTreePath *correct_path) {
1078                 if (gtk_tree_path_compare(path, correct_path)==0)
1079                         row_1_row_changed_was_emitted = TRUE;
1080         };
1081 402
1082 396         gboolean rows_reordered_was_emitted; const int rows_reordered_template[3] = { 1, 0, 2 };
1083         void rows_reordered (GtkTreeModel *model, GtkTreePath *parent_path, GtkTreeIter *parent_iter,
1084                              gint *new_order) {
1085                 g_assert_cmpintarray(new_order, rows_reordered_template, 3);
1086                 rows_reordered_was_emitted = TRUE;
1087         };
1088 402
1089 396         MusicSource *source = source_constructor();
1090         test_add_song (source, 1, 1, 1, 0, 0);
1091         test_add_song (source, 2, 2, 2, 0, 0);
1092         test_add_song (source, 1, 3, 3, 0, 0);
1093
1094 406         GtkTreeView *tree_view = dynamic_view_test_setup(source, "artist[name]:recording[name]");
1095         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
1096
1097 396         const char *reference_1[3][5] = {
1098           {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL},
1099           {"Test Artist 000001", "Test Composition 000003", "Test File 000003 (000003)", NULL, NULL},
1100           {"Test Artist 000002", "Test Composition 000002", "Test File 000002 (000002)", NULL, NULL},
1101         };
1102
1103         assert_view (view, NULL, 3, reference_1);
1104
1105         row_1_row_changed_was_emitted = FALSE; rows_reordered_was_emitted = FALSE;
1106 402         g_signal_connect (view, "row-changed", G_CALLBACK(row_changed),
1107 396                           gtk_tree_path_new_from_string("1"));
1108 402         g_signal_connect (view, "rows-reordered", G_CALLBACK(rows_reordered), NULL);
1109 396
1110         Entry *file_3 = music_source_checkout_entry(source, ENTRY_TYPE_FILE, 3, "test");
1111         entry_set_property (file_3, FILE_PATH, "Test File Y2");
1112         music_source_checkin_entry (source, file_3, "test");
1113
1114         g_assert (row_1_row_changed_was_emitted);
1115 397         //g_assert (!rows_reordered_was_emitted);
1116 396         row_1_row_changed_was_emitted = FALSE;
1117
1118         Entry *composition_1 = music_source_checkout_entry(source, ENTRY_TYPE_COMPOSITION, 1, "test");
1119         entry_set_property (composition_1, COMPOSITION_NAME, "Test Composition 4");
1120         music_source_checkin_entry (source, composition_1, "test");
1121
1122         const char *reference_2[3][5] = {
1123           {"Test Artist 000001", "Test Composition 000003", "Test File Y2", NULL, NULL},
1124           {"Test Artist 000001", "Test Composition 4",      "Test File 000001 (000001)", NULL, NULL},
1125           {"Test Artist 000002", "Test Composition 000002", "Test File 000002 (000002)", NULL, NULL},
1126         };
1127
1128         assert_view (view, NULL, 3, reference_2);
1129         g_assert (row_1_row_changed_was_emitted);
1130         g_assert (rows_reordered_was_emitted);
1131
1132 406         dynamic_view_test_teardown (tree_view);
1133 396 };
1134
1135 431 /* Changing 2: resorting 
1136  * 
1137  *   Tests that rows are reordered property when an entry changes. (If the sort order is messed up
1138  *   at any point, excluding while a view notify handler is processing, then everything breaks) */
1139 396 static void test_changing_2 () {
1140 431         MusicSource *source = source_constructor();
1141         ViewConfig *config = view_config_string_parse ("artist[name]:recording[name]:file[bitrate]");
1142         MusicSourceView *view = music_source_create_view (source, config);
1143
1144         // Read something from the node - otherwise, the view will assume it's invisible.
1145         gtk_tree_model_iter_n_children (GTK_TREE_MODEL(view), NULL);
1146
1147         for (int i=0; i<6; i++)
1148                 test_add_song (source, TEST_UNKNOWN_ARTIST, TEST_UNKNOWN_TITLE, i, 0, 0);
1149
1150         // Update the artist and composition entries. This is a part of what happens during tag reading.
1151         // The node now needs resorting.
1152         //
1153         Entry *file        = music_source_checkout_entry (source, ENTRY_TYPE_FILE, 4, "test"),
1154               *recording   = entry_get_property          (file, FILE_RECORDING),
1155 438               *composition = entry_new                   (ENTRY_TYPE_COMPOSITION, 0, NULL, "test"),
1156               *artist      = entry_new                   (ENTRY_TYPE_ARTIST, 0, NULL, "test");
1157 431         entry_set_property  (artist, ARTIST_NAME, "Inspecter 7");
1158         entry_take_property (composition, COMPOSITION_ARTIST, artist);
1159         entry_set_property  (composition, COMPOSITION_NAME, "Sharky 17");
1160         entry_take_property (recording, RECORDING_COMPOSITION, composition);
1161         music_source_checkin_entry (source, file, "test");
1162
1163         const char *reference[][5] = {
1164           {"Inspecter 7",    "Sharky 17",     "Test File 000003 (-00001)", NULL, NULL},
1165           {"Unknown Artist", "Unknown Title", "Test File 000001 (-00001)", NULL, NULL},
1166           {"Unknown Artist", "Unknown Title", "Test File 000002 (-00001)", NULL, NULL},
1167           {"Unknown Artist", "Unknown Title", "Test File 000004 (-00001)", NULL, NULL},
1168           {"Unknown Artist", "Unknown Title", "Test File 000005 (-00001)", NULL, NULL},
1169           {"Unknown Artist", "Unknown Title", "Test File 000000 (-00001)", NULL, NULL}};
1170         assert_view (view, NULL, 6, reference);
1171
1172         g_object_unref (view);
1173         g_object_unref (source);
1174 }
1175
1176 438 /* Changing 3: resorting 2
1177  * 
1178  *   Tests a common case of change propagation, where a recording and a file both change. There is a
1179  *   danger of the view getting confused and not finding either during change propagation. */
1180 431 static void test_changing_3 () {
1181 438         MusicSource *source = source_constructor();
1182         ViewConfig *config = view_config_string_parse ("artist[name]:recording[name]:file[bitrate]");
1183         MusicSourceView *view = music_source_create_view (source, config);
1184
1185         // Read something from the node - otherwise, the view will assume it's invisible.
1186         gtk_tree_model_iter_n_children (GTK_TREE_MODEL(view), NULL);
1187
1188         for (int i=0; i<6; i++)
1189                 test_add_song (source, TEST_UNKNOWN_ARTIST, TEST_UNKNOWN_TITLE, i, 0, 0);
1190
1191         // Update the artist and composition entries, as before.
1192         {
1193                 Entry *file        = music_source_checkout_entry (source, ENTRY_TYPE_FILE, 1, "test"),
1194                           *recording   = entry_get_property          (file, FILE_RECORDING),
1195                           *composition = entry_new                   (ENTRY_TYPE_COMPOSITION, 0, NULL, "test"),
1196                           *artist      = entry_new                   (ENTRY_TYPE_ARTIST, 0, NULL, "test");
1197                 entry_set_property  (artist, ARTIST_NAME, "Fat Freddy's Drop");
1198                 entry_take_property (composition, COMPOSITION_ARTIST, artist);
1199                 entry_set_property  (composition, COMPOSITION_NAME, "Big BW");
1200                 entry_take_property (recording, RECORDING_COMPOSITION, composition);
1201                 entry_set_property  (file, FILE_BITRATE, GINT_TO_POINTER(512000));
1202                 music_source_checkin_entry (source, file, "test");
1203         }
1204
1205         // Now, let's do this a second time, out of sequence to try to break the binary search.
1206         {
1207                 Entry *file        = music_source_checkout_entry (source, ENTRY_TYPE_FILE, 3, "test"),
1208                           *recording   = entry_get_property          (file, FILE_RECORDING),
1209                           *composition = entry_new                   (ENTRY_TYPE_COMPOSITION, 0, NULL, "test"),
1210                           *artist      = entry_new                   (ENTRY_TYPE_ARTIST, 0, NULL, "test");
1211                 entry_set_property  (artist, ARTIST_NAME, "Fat Freddy's Drop");
1212                 entry_take_property (composition, COMPOSITION_ARTIST, artist);
1213                 entry_set_property  (composition, COMPOSITION_NAME, "Shiverman");
1214                 entry_take_property (recording, RECORDING_COMPOSITION, composition);
1215                 entry_set_property  (file, FILE_BITRATE, GINT_TO_POINTER(512000));
1216                 music_source_checkin_entry (source, file, "test");
1217         }
1218
1219         const char *reference[][5] = {
1220           {"Fat Freddy's Drop", "Big BW",        "Test File 000000 (-00001)", NULL, NULL},
1221           {"Fat Freddy's Drop", "Shiverman",     "Test File 000002 (-00001)", NULL, NULL},
1222           {"Unknown Artist",    "Unknown Title", "Test File 000001 (-00001)", NULL, NULL},
1223           {"Unknown Artist",    "Unknown Title", "Test File 000003 (-00001)", NULL, NULL},
1224           {"Unknown Artist",    "Unknown Title", "Test File 000004 (-00001)", NULL, NULL},
1225           {"Unknown Artist",    "Unknown Title", "Test File 000005 (-00001)", NULL, NULL}};
1226         assert_view (view, NULL, 6, reference);
1227
1228         g_object_unref (view);
1229         g_object_unref (source);
1230 }
1231
1232 /* Changing 4: Tests rows-reordered emission more thoroughly. */
1233 static void test_changing_4 () {
1234 419         gboolean rows_reordered_was_emitted; const int *rows_reordered_template;
1235 396         void rows_reordered (GtkTreeModel *model, GtkTreePath *parent_path, GtkTreeIter *parent_iter,
1236                              gint *new_order) {
1237                 g_assert_cmpintarray(new_order, rows_reordered_template, 10);
1238                 rows_reordered_was_emitted = TRUE;
1239         };
1240 402
1241 396         MusicSource *source = source_constructor();
1242         ViewConfig *config = view_config_string_parse("artist[name]:recording[name]");
1243
1244         for (int i=1; i<11; i++)
1245                 test_add_song (source, i%5, i, i, 0, 0);
1246
1247         MusicSourceView *view = music_source_create_view(source, config);
1248         const char *reference_1[10][5] = {
1249           {"Test Artist 000000", "Test Composition 000005", "Test File 000005 (000005)", NULL, NULL},
1250           {"Test Artist 000000", "Test Composition 000010", "Test File 000010 (000010)", NULL, NULL},
1251           {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL},
1252           {"Test Artist 000001", "Test Composition 000006", "Test File 000006 (000006)", NULL, NULL},
1253           {"Test Artist 000002", "Test Composition 000002", "Test File 000002 (000002)", NULL, NULL},
1254           {"Test Artist 000002", "Test Composition 000007", "Test File 000007 (000007)", NULL, NULL},
1255           {"Test Artist 000003", "Test Composition 000003", "Test File 000003 (000003)", NULL, NULL},
1256           {"Test Artist 000003", "Test Composition 000008", "Test File 000008 (000008)", NULL, NULL},
1257           {"Test Artist 000004", "Test Composition 000004", "Test File 000004 (000004)", NULL, NULL},
1258           {"Test Artist 000004", "Test Composition 000009", "Test File 000009 (000009)", NULL, NULL}
1259         };
1260
1261         assert_view (view, NULL, 10, reference_1);
1262
1263 402         const int rows_reordered_template_1[10] = { 2, 3, 4, 5, 6, 7, 8, 9, 0, 1 };
1264 396         rows_reordered_was_emitted = FALSE;
1265 402         rows_reordered_template = rows_reordered_template_1;
1266
1267 396         g_signal_connect (view, "rows-reordered", G_CALLBACK(rows_reordered), NULL);
1268 402
1269 396         Entry *artist_0 = music_source_checkout_entry(source, ENTRY_TYPE_ARTIST, 7, "test");
1270         entry_set_property (artist_0, ARTIST_NAME, "Test Artist 000005");
1271         music_source_checkin_entry (source, artist_0, "test");
1272
1273         g_assert (rows_reordered_was_emitted);
1274
1275         g_object_unref_many (view, source, NULL); _entry_cleanup ();
1276 };
1277 378
1278 442 /* Changing 5 (Folding): checks Unknown Artist is hidden when its last child is gone. */
1279 438 static void test_changing_5_folding (void) {
1280 442         const char *config_string = "artist[name]>recording:file[path]";
1281         GtkTreeView *tree_view = dynamic_view_test_setup(NULL, config_string);
1282         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
1283         MusicSource *source = music_source_view_get_source(view);
1284         
1285         test_add_song (source, TEST_UNKNOWN_ARTIST, 1, 1, 0, 0);
1286
1287         const char *reference_1_root[1][5] = {
1288           {"Unknown Artist", NULL, NULL, NULL, NULL} };
1289         assert_view (view, NULL, 1, reference_1_root);
1290
1291         Entry *recording   = music_source_checkout_entry (source, ENTRY_TYPE_RECORDING, 1, "test"),
1292               *composition = entry_get_property (recording, RECORDING_COMPOSITION),
1293               *artist      = entry_new (ENTRY_TYPE_ARTIST, 0, NULL, "test");
1294         entry_set_property  (artist, ARTIST_NAME, "Groove Armada");
1295         entry_take_property (composition, COMPOSITION_ARTIST, artist);
1296         music_source_checkin_entry (source, recording, "test");
1297
1298         // Unknown Artist should now have been hidden.
1299         const char *reference_2_root[1][5] = {
1300           {"Groove Armada", NULL, NULL, NULL, NULL} };
1301         assert_view (view, NULL, 1, reference_2_root);
1302
1303         dynamic_view_test_teardown (tree_view);
1304 };
1305
1306 /* Changing 6 (Folding): checks emission of has-child-toggled. */
1307 static void test_changing_6_folding (void) {
1308 419         gboolean has_child_toggled_emitted = FALSE;
1309         void has_child_toggled (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, 
1310                                 gpointer user_data) {
1311 420                 g_assert_cmpstr (gtk_tree_path_to_string(path), ==, "0");
1312 419                 has_child_toggled_emitted = TRUE;
1313         };
1314
1315         const char *config_string = "artist[name]:album:release[date] > track:recording[name]";
1316         GtkTreeView *tree_view = dynamic_view_test_setup(NULL, config_string);
1317         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
1318         MusicSource *source = music_source_view_get_source(view);
1319
1320         g_signal_connect (view, "row-has-child-toggled", G_CALLBACK(has_child_toggled), NULL);
1321
1322 422         // Add first song - we should get has-child-toggled for its track. This is needed for
1323         // GtkTreeView, so that it knows to give the new artist:album:release row a > expander.
1324 419         test_add_song (source, 1, 1, 1, 1, 2);
1325         g_assert (has_child_toggled_emitted); has_child_toggled_emitted = FALSE;
1326
1327 422         // Add a second song on the same album, so the same root row. Now we should be signalled!
1328 419         test_add_song (source, 1, 2, 2, 1, 1);
1329         g_assert (!has_child_toggled_emitted);
1330
1331         const char *reference_1_root[1][5] = {
1332           {"Test Artist 000001", NULL, NULL, "Test Album 000001", NULL} };
1333
1334                 const char *reference_1_0[2][5] = {
1335                   {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "2"},
1336                   {"Test Artist 000001", "Test Composition 000002", "Test File 000002 (000002)", "Test Album 000001", "1"} };
1337
1338         assert_view (view, NULL, 1, reference_1_root);
1339
1340         GtkTreeIter iter; gtk_tree_model_iter_children(GTK_TREE_MODEL(view), &iter, NULL);
1341         assert_view (view, &iter, 2, reference_1_0);
1342
1343         music_source_remove_entry (source, ENTRY_TYPE_RECORDING, 1);
1344         g_assert (!has_child_toggled_emitted);
1345
1346 422         // We don't want has-child-toggled emitting - the whole row is about to get deleted, because
1347         // Calliope doesn't display empty rows ... so there is no point.
1348 419         music_source_remove_entry (source, ENTRY_TYPE_RECORDING, 2);
1349 422         g_assert (!has_child_toggled_emitted);
1350 419
1351         g_assert_cmpint (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL), ==, 0);
1352
1353         dynamic_view_test_teardown (tree_view);
1354 };
1355
1356 442 /* Changing 7 (Folding): checks emission of has-child-toggled on row changes. */
1357 static void test_changing_7_folding (void) {
1358 420         gboolean has_child_toggled_emitted = FALSE;
1359         void has_child_toggled (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, 
1360                                 gpointer user_data) {
1361                 g_assert_cmpstr (gtk_tree_path_to_string(path), ==, "0");
1362                 has_child_toggled_emitted = TRUE;
1363         };
1364
1365         GtkTreeIter iter;
1366         const char *config_string = "artist[name]:(album:release[date]>track[number]:recording:file[bitrate])"
1367                                     "            /(recording[name]:file[bitrate])";
1368         GtkTreeView *tree_view = dynamic_view_test_setup(NULL, config_string);
1369         MusicSourceView *view = MUSIC_SOURCE_VIEW(gtk_tree_view_get_model(tree_view));
1370         MusicSource *source = music_source_view_get_source(view);
1371         
1372         g_signal_connect (view, "row-has-child-toggled", G_CALLBACK(has_child_toggled), NULL);
1373
1374         test_add_song (source, 1, 1, 1, 0, 0);
1375
1376         const char *reference_1_root[1][5] = {
1377           {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL} };
1378
1379         assert_view (view, NULL, 1, reference_1_root);
1380         g_assert (!has_child_toggled_emitted);
1381
1382         Entry *album = entry_new(ENTRY_TYPE_ALBUM, 0, NULL, "test");
1383         entry_set_property (album, ALBUM_NAME, "Test Album 000001");
1384         entry_take_property (album, ALBUM_ARTIST, 
1385                              music_source_query_entry(source, ENTRY_TYPE_ARTIST, 3, "test"));
1386
1387         Entry *release = entry_new(ENTRY_TYPE_RELEASE, 0, NULL, "test");
1388         entry_take_property (release, RELEASE_ALBUM, album);
1389
1390         Entry *track = entry_new(ENTRY_TYPE_TRACK, 0, NULL, "test");
1391         entry_set_property (track, TRACK_NUMBER, GINT_TO_POINTER(1));
1392         entry_take_property (track, TRACK_RELEASE, release);
1393         entry_take_property (track, TRACK_RECORDING,
1394                              music_source_query_entry(source, ENTRY_TYPE_RECORDING, 1, "test"));
1395
1396         music_source_add_entry (source, track);
1397
1398         const char *reference_2_root[1][5] = {
1399           {"Test Artist 000001", NULL, NULL, NULL, NULL} };
1400
1401                 const char *reference_2_0[2][5] = {
1402                   {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "1"} };
1403
1404         assert_view (view, NULL, 1, reference_2_root);
1405         gtk_tree_model_iter_children(GTK_TREE_MODEL(view), &iter, NULL);
1406         assert_view (view, &iter, 1, reference_2_0);
1407
1408         g_assert (has_child_toggled_emitted);
1409
1410         /*music_source_remove_entry (source, ENTRY_TYPE_RECORDING, 1);
1411         g_assert (!has_child_toggled_emitted);
1412
1413         music_source_remove_entry (source, ENTRY_TYPE_RECORDING, 2);
1414         g_assert (has_child_toggled_emitted); has_child_toggled_emitted = FALSE;*/
1415
1416         //g_assert_cmpint (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL), ==, 0);
1417
1418         dynamic_view_test_teardown (tree_view);
1419 };
1420
1421 419
1422 361 /* FIXME: more test cases:
1423 374  *   1. changed signal on a m:1 join entry
1424 364  *   2. tougher tests that rows-reordered is always emitted correctly
1425  *   3. two tracks on different albums of same recording
1426  *   4. a file one recording moving to another
1427 365  *   5. changing an entry which is sort grouped.
1428 364  *   6. an entry not being an orphan any more !!
1429 361  */
1430 354
1431
1432 185 //////////////////////////////////////////////////
1433 // Insertion test cases
1434 //
1435 261 //   Note that these differ from what happens in real musicsources in one way - a normal musicsource will
1436 185 //   wrap an add in begin_transaction and end_transaction. For example, when a recording is added and
1437 261 //   then a track, the tests here add the recording as an orphan and then remove it again once the track is
1438 //   added. In practice, because entry-added::recording will almost never be triggered before the track is
1439 185 //   also added to the source, this doesn't happen. The tests don't reproduce this because they are making the
1440 //   sourceview work harder - if it passes these tests it will almost certainly work with the real sources.
1441 //
1442
1443 // Simplest insertion test case
1444 //
1445 static void test_inserting_1(void) {
1446         MusicSource *source = source_constructor();
1447 215         ViewConfig *config =  view_config_string_parse("file[path]");
1448 185         MusicSourceView *view = music_source_create_view(source, config);
1449 261
1450 185         int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
1451         g_assert_cmpint(e_children, ==, 0);
1452 261
1453 286         test_add_song(source, 3, 3, 3, 0, 0);
1454 185         GtkTreePath *path = gtk_tree_path_new_first();
1455         GtkTreeRowReference *row_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(view), path);
1456         gtk_tree_path_free(path);
1457 261
1458 286         test_add_song(source, 1, 1, 1, 0, 0);
1459         test_add_song(source, 4, 4, 4, 0, 0);
1460         test_add_song(source, 2, 2, 2, 0, 0);
1461         test_add_song(source, 5, 5, 5, 0, 0);
1462 185         _music_source_view_check_tree(view, NULL);
1463 261
1464 185         path = gtk_tree_row_reference_get_path(row_ref);
1465         int id = music_source_view_get_id_at_path(view, ENTRY_TYPE_FILE, path);
1466         g_assert_cmpint(id, ==, 1);
1467         gtk_tree_row_reference_free(row_ref);
1468 261
1469 185         const char *reference[5][5] = {
1470 286                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL},
1471                 {"Test Artist 000002", "Test Composition 000002", "Test File 000002 (000002)", NULL, NULL},
1472                 {"Test Artist 000003", "Test Composition 000003", "Test File 000003 (000003)", NULL, NULL},
1473                 {"Test Artist 000004", "Test Composition 000004", "Test File 000004 (000004)", NULL, NULL},
1474                 {"Test Artist 000005", "Test Composition 000005", "Test File 000005 (000005)", NULL, NULL}};
1475 261         assert_view(view, NULL, 5, reference);
1476
1477 185         g_object_unref_many(view, source, NULL);
1478 281         _entry_cleanup ();
1479 185 };
1480
1481 226 // Multiple levels
1482 //
1483 static void test_inserting_2(void) {
1484         MusicSource *source = source_constructor();
1485 261         ViewConfig *config = view_config_string_parse
1486 226               ("recording[name]>file[path]");
1487         MusicSourceView *view = music_source_create_view(source, config);
1488 261
1489 226         int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
1490         g_assert_cmpint (e_children, ==, 0);
1491
1492         test_add_song (source, 1, 1, 1, 0, 0);
1493         test_add_song (source, 1, 1, 2, 0, 0);
1494 261
1495 226         const char *reference_root_a[1][5] = {
1496                 {"Test Artist 000001", "Test Composition 000001", NULL, NULL, NULL}};
1497
1498                 const char *reference_recording_1_a[2][5] = {
1499                         {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL},
1500                         {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", NULL, NULL}};
1501
1502         GtkTreeIter recording_iter; gboolean result;
1503         assert_view (view, NULL, 1, reference_root_a);
1504 261
1505 226         result = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(view), &recording_iter);
1506         g_assert(result);
1507         assert_view (view, &recording_iter, 2, reference_recording_1_a);
1508 261
1509 226         // Now add some more. This is important because entrysadded takes the easy way out and
1510 261         // does nothing if the node isn't yet queried - so now it is queried the code takes a
1511 226         // different path.
1512         test_add_song (source, 1, 2, 3, 0, 0);
1513         test_add_song (source, 1, 1, 4, 0, 0);
1514 261
1515 226         const char *reference_root_b[2][5] = {
1516                 {"Test Artist 000001", "Test Composition 000001", NULL, NULL, NULL},
1517                 {"Test Artist 000001", "Test Composition 000002", NULL, NULL, NULL}};
1518
1519                 const char *reference_recording_1_b[3][5] = {
1520                         {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", NULL, NULL},
1521                         {"Test Artist 000001", "Test Composition 000001", "Test File 000002 (000001)", NULL, NULL},
1522                         {"Test Artist 000001", "Test Composition 000001", "Test File 000004 (000001)", NULL, NULL}};
1523 261
1524 226         assert_view (view, NULL, 2, reference_root_b);
1525 261
1526 226         result = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(view), &recording_iter);
1527         g_assert(result);
1528         assert_view (view, &recording_iter, 3, reference_recording_1_b);
1529
1530         g_object_unref_many (view, source, NULL);
1531 281         _entry_cleanup ();
1532 226 };
1533
1534 185 // Insertion with sort groups & many:1 joins.
1535 //
1536 226 static void test_inserting_3(void) {
1537         // This setting makes the test easier to debug, but potentially less thorough.
1538 185         //
1539 261         //#define SIMPLE
1540
1541 185         MusicSource *source = source_constructor();
1542 233         ViewConfig *config = view_config_string_parse("(album[name]:release:track:recording:file[path])"
1543                                                       "/ (artist[name]:recording:file[path])");
1544 185         MusicSourceView *view = music_source_create_view(source, config);
1545 261
1546 185         int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
1547         g_assert_cmpint(e_children, ==, 0);
1548 261
1549 185         music_source_begin_transaction(source); // Being in a transaction changes things subtly,
1550         test_add_song(source, 1, 1, 1, 1, 1);   // because now the file-added signal will come
1551         music_source_end_transaction(source);   // after the track entry is added.
1552
1553         GtkTreePath *path = gtk_tree_path_new_first();
1554         GtkTreeRowReference *row_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(view), path);
1555         gtk_tree_path_free(path);
1556
1557         // Add two tracks of same recording
1558 261         test_add_song (source, 1, 2, 2, 1, 2);
1559         test_add_song (source, 1, 2, 2, 2, 1);
1560 185
1561         // Add files to existing recording
1562         Entry *recording = music_source_query_entry(source, ENTRY_TYPE_RECORDING, 2, "test");
1563 281         g_assert (recording!=NULL);
1564 185
1565         test_add_file(source, recording, 2, 3);
1566 261
1567 185         #ifndef SIMPLE
1568         test_add_file(source, recording, 2, 4);
1569
1570         // How about another track of the same recording now!
1571 261         test_add_song(source, 1, 2, 2, 3, 1);
1572
1573 185         // Some more songs
1574 261         test_add_song(source, 1, 3, 5, 2, 2);
1575         test_add_song(source, 1, 4, 6, 2, 3);
1576 185         #endif
1577 281         entry_unref (recording, "test");
1578 185
1579         _music_source_view_check_tree(view, NULL);
1580         #ifdef SIMPLE
1581         const int n_children = 5;
1582         const char *reference[5][5] = {
1583                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "1"},
1584                 {"Test Artist 000001", "Test Composition 000002", "Test File 000002 (000002)", "Test Album 000001", "2"},
1585                 {"Test Artist 000001", "Test Composition 000002", "Test File 000003 (000002)", "Test Album 000001", "2"},
1586                 {"Test Artist 000001", "Test Composition 000002", "Test File 000002 (000002)", "Test Album 000002", "1"},
1587                 {"Test Artist 000001", "Test Composition 000002", "Test File 000003 (000002)", "Test Album 000002", "1"}};
1588         #else
1589         const int n_children = 12;
1590         const char *reference[12][5] = {
1591                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "1"},
1592                 {"Test Artist 000001", "Test Composition 000002", "Test File 000002 (000002)", "Test Album 000001", "2"},
1593                 {"Test Artist 000001", "Test Composition 000002", "Test File 000003 (000002)", "Test Album 000001", "2"},
1594                 {"Test Artist 000001", "Test Composition 000002", "Test File 000004 (000002)", "Test Album 000001", "2"},
1595 261
1596 185                 {"Test Artist 000001", "Test Composition 000002", "Test File 000002 (000002)", "Test Album 000002", "1"},
1597                 {"Test Artist 000001", "Test Composition 000002", "Test File 000003 (000002)", "Test Album 000002", "1"},
1598                 {"Test Artist 000001", "Test Composition 000002", "Test File 000004 (000002)", "Test Album 000002", "1"},
1599                 {"Test Artist 000001", "Test Composition 000003", "Test File 000005 (000003)", "Test Album 000002", "2"},
1600                 {"Test Artist 000001", "Test Composition 000004", "Test File 000006 (000004)", "Test Album 000002", "3"},
1601 261
1602 185                 {"Test Artist 000001", "Test Composition 000002", "Test File 000002 (000002)", "Test Album 000003", "1"},
1603                 {"Test Artist 000001", "Test Composition 000002", "Test File 000003 (000002)", "Test Album 000003", "1"},
1604                 {"Test Artist 000001", "Test Composition 000002", "Test File 000004 (000002)", "Test Album 000003", "1"}};
1605 261         #endif
1606
1607         assert_view(view, NULL, n_children, reference);
1608
1609 185         path = gtk_tree_row_reference_get_path(row_ref);
1610         int id = music_source_view_get_id_at_path(view, ENTRY_TYPE_FILE, path);
1611         g_assert_cmpint(id, ==, 1);
1612         gtk_tree_row_reference_free(row_ref);
1613 261
1614 185         g_object_unref_many(view, source, NULL);
1615 281         _entry_cleanup ();
1616 185 };
1617
1618 // Orphans!
1619 //
1620 static void test_inserting_4(void) {
1621         MusicSource *source = source_constructor();
1622 234         ViewConfig *config = view_config_string_parse
1623                                ("  artist[name] > (album:release[date]>track:recording:file[path])"
1624                                 "              /  (recording:file[path])");
1625 185         MusicSourceView *view = music_source_create_view(source, config);
1626 261
1627 185         int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
1628 269         g_assert_cmpint (e_children, ==, 0);
1629 185
1630 261         test_add_song (source, 1, 1, 1, 1, 1);
1631         test_add_song (source, 2, 2, 2, 2, 1);
1632
1633 185         // An orphan :(
1634 261         //printf ("\n\nAdding orphan ..\n"); fflush (stdout);
1635 185         test_add_song (source, 2, 3, 3, -1, -1);
1636 261
1637 269         const char *reference_root[2][5] = {
1638 185                 {"Test Artist 000001", NULL, NULL, NULL, NULL},
1639 269                 {"Test Artist 000002", NULL, NULL, NULL, NULL}};
1640 185
1641                 const char *reference_artist_1[1][5] = {
1642                         {"Test Artist 000001", NULL, NULL, "Test Album 000001", NULL}};
1643 261
1644 185                         const char *reference_album_1[1][5] = {
1645                                 {"Test Artist 000001", "Test Composition 000001", "Test File 000001 (000001)", "Test Album 000001", "1"}};
1646
1647                 const char *reference_artist_2[2][5] = {
1648                         {"Test Artist 000002", NULL, NULL, "Test Album 000002", NULL},
1649                         {"Test Artist 000002", "Test Composition 000003", "Test File 000003 (000003)", NULL, NULL}};
1650
1651                         const char *reference_album_2[1][5] = {
1652                                 {"Test Artist 000002", "Test Composition 000002", "Test File 000002 (000002)", "Test Album 000002", "1"}};
1653 261
1654
1655 185         GtkTreeIter artist_iter, album_iter; gboolean result;
1656 269         assert_view (view, NULL, 2, reference_root);
1657 261
1658 185         result = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(view), &artist_iter); g_assert(result);
1659         assert_view (view, &artist_iter, 1, reference_artist_1);
1660 261
1661 185                 result = gtk_tree_model_iter_children (GTK_TREE_MODEL(view), &album_iter, &artist_iter); g_assert(result);
1662                 assert_view (view, &album_iter, 1, reference_album_1);
1663 261
1664 185         result = gtk_tree_model_iter_next (GTK_TREE_MODEL(view), &artist_iter); g_assert(result);
1665         assert_view (view, &artist_iter, 2, reference_artist_2);
1666 261
1667 185                 result = gtk_tree_model_iter_children (GTK_TREE_MODEL(view), &album_iter, &artist_iter); g_assert(result);
1668                 assert_view (view, &album_iter, 1, reference_album_2);
1669 261
1670 185         // FIXME: add some more after nodes exist !!!
1671
1672         g_object_unref_many (view, source, NULL);
1673 281         _entry_cleanup ();
1674 185 };
1675
1676
1677 261 // Orphans 2.
1678 209 //
1679 261 // This config gives an orphan config of artist[name]:recording:file[bitrate] .. so files can't be
1680 390 // found using the binary search, because they are all the same bitrate.
1681 // FIXME: test that slightly more ...
1682 209 //
1683 static void test_inserting_5 (void) {
1684         MusicSource *source = source_constructor();
1685 261         ViewConfig *config = view_config_string_parse
1686 235                           ("artist[name]:(album:release[date] > "
1687 266                                            "track[number]:recording[many-to-one-join]:file[bitrate])"
1688 235                            "/ (recording:file[bitrate])");
1689 209         MusicSourceView *view = music_source_create_view(source, config);
1690
1691 261         // Read the node so the files are actually added - otherwise entry-added will see the nodes
1692 235         // haven't been read and leave them as such.
1693         gtk_tree_model_iter_n_children (GTK_TREE_MODEL(view), NULL);
1694
1695 261         // Because we add in a strange order, remember entry id's don't match up to the numbers given
1696 235         // here.
1697 213         test_add_song (source, 2, 2, 2, 2, 1);
1698         test_add_song (source, 1, 3, 3, -1, -1);
1699         test_add_song (source, 2, 4, 4, -1, -1);
1700 261         test_add_song (source, 1, 1, 1, 1, 1);
1701 235         test_add_song (source, 3, 5, 5, 3, 1);
1702 261
1703 235         const char *reference[5][5] = {
1704           {"Test Artist 000001", NULL, NULL, "Test Album 000001", NULL},
1705           {"Test Artist 000001", "Test Composition 000003", "Test File 000003 (000003)", NULL, NULL},
1706           {"Test Artist 000002", NULL, NULL, "Test Album 000002", NULL},
1707           {"Test Artist 000002", "Test Composition 000004", "Test File 000004 (000004)", NULL, NULL},
1708 412           {"Test Artist 000003", NULL, NULL, "Test Album 000003", NULL} };
1709 261
1710         assert_view (view, NULL, 5, reference);
1711
1712 235         // Test one of the track nodes.
1713 261         GtkTreeIter release_iter, track_iter;
1714 235         gboolean result = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(view), &release_iter, NULL, 2);
1715         g_assert_cmpint (result, ==, TRUE);
1716 261         g_assert_cmpint (((ViewNode *)release_iter.user_data)->depth, ==, 0);
1717 235         g_assert_cmpint (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), &release_iter), ==, 1);
1718 261
1719 235         result = gtk_tree_model_iter_children(GTK_TREE_MODEL(view), &track_iter, &release_iter);
1720         g_assert_cmpint (result, ==, TRUE);
1721 261
1722 390         // Test for get_path_for_id and get_id_at_path inconsistencies.
1723 235         for (int i=1; i<=5; i++) {
1724 209                 int recording_id = i;
1725 261                 GtkTreePath *path = music_source_view_get_path_for_id(view, ENTRY_TYPE_RECORDING,
1726 209                                                                                                                           recording_id);
1727 261                 //printf ("Got path %s.\n", gtk_tree_path_to_string(path)); fflush (stdout);
1728 209                 int id_at_path = music_source_view_get_id_at_path(view, ENTRY_TYPE_RECORDING,
1729                                                                                                                   path);
1730                 g_assert_cmpint (id_at_path, ==, recording_id);
1731 261                 gtk_tree_path_free (path);
1732 209         };
1733
1734 336         g_object_unref_many (view, source, NULL); _entry_cleanup ();
1735 };
1736
1737 412
1738 209
1739 185 typedef struct {
1740         MusicSource *source;
1741         int recording_count;
1742 } PresetFixture;
1743
1744 static void preset_static_setup(PresetFixture *fixture, const void *test_data) {
1745         fixture->source=source_constructor();
1746         music_source_begin_transaction(fixture->source);
1747         // say 250 recs and 100 orphans
1748         int artist_n=1;
1749         int album_n=1;
1750         int track_n=1, offs=0, i;
1751         for (i=1;i<=250;i++) {
1752                 test_add_song(fixture->source, artist_n, i, i, album_n, track_n);
1753                 if (i&16) artist_n++;
1754                 if (i&16) {
1755                         album_n++;
1756                         track_n=1;
1757 261                 } else track_n++;
1758 185         };
1759         offs=i;
1760         for (int i=1;i<=100;i++) {
1761                 test_add_song(fixture->source, artist_n, i+offs, i+offs, -1, -1);
1762                 if (i&16) artist_n++;
1763         };
1764         fixture->recording_count = 350;
1765         music_source_end_transaction(fixture->source);
1766 };
1767
1768 static void preset_static_teardown(PresetFixture *fixture, const void *test_data) {
1769         g_object_unref(fixture->source);
1770 273         _entry_cleanup ();
1771 185 };
1772
1773
1774 261 static void test_preset_static(PresetFixture *fixture, const void *test_data) {
1775 259         const int preset_n = GPOINTER_TO_INT(test_data);
1776 185         int exec_count=20;
1777         if (!g_test_slow() && !g_test_perf())
1778                 exec_count=1;
1779 261
1780 185         double secs=0.0;
1781         //music_source_dump (fixture->source);
1782         for (int i=0;i<exec_count;i++) {
1783 261                 ViewConfig *config = view_config_string_parse(view_config_preset[preset_n][1]);
1784                 MusicSourceView *view = music_source_create_view(fixture->source, config);
1785 185
1786                 g_test_timer_start();
1787 261
1788                 //print_view (view, 4);
1789
1790 185                 // Find some of these fellows.
1791 262                 for (int i=0; i<70; i++) {
1792 185                         int recording_id = g_test_rand_int_range (1, fixture->recording_count+1);
1793 262                         GtkTreePath *path = NULL;
1794
1795                         if (i & 1) {
1796                                 MusicSourceViewSearch *search = music_source_view_get_path_for_id_begin
1797                                                                                                   (view, ENTRY_TYPE_RECORDING, recording_id);
1798                                 while (!music_source_view_get_path_for_id_step(view, search));
1799                                 path = music_source_view_get_path_for_id_end (view, search);
1800                                 g_warn_if_fail (path!=NULL);
1801                         } else {
1802                                 path = music_source_view_get_path_for_id (view, ENTRY_TYPE_RECORDING,
1803                                                                                                                                            recording_id);
1804                                 g_warn_if_fail (path!=NULL);
1805                         };
1806
1807 185                         g_assert_cmpint (music_source_view_get_id_at_path (view, ENTRY_TYPE_RECORDING,
1808                                                                            path), ==, recording_id);
1809 261                         gtk_tree_path_free (path);
1810 185                 };
1811 261
1812 185                 GtkTreeIter iter;
1813                 int i=0;
1814                 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter)) {
1815                         i++;
1816                         while (gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter))
1817 261                                 i++;
1818 185                 }
1819 261
1820 185                 int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
1821                 //printf("e_children: %i, counted %i\n", e_children, i);                 fflush(stdout);
1822                 g_assert_cmpint (e_children, ==, i);
1823 261
1824 185                 secs+=g_test_timer_elapsed();
1825 261
1826 185                 g_object_unref(view);
1827         };
1828
1829         //secs/=(double)exec_count;
1830         if (g_test_perf() || g_test_slow())
1831                 g_test_minimized_result(secs, "%f", secs);
1832 };
1833
1834
1835 261 static void test_preset_dynamic(const void *data) {
1836 185         int preset_n = GPOINTER_TO_INT(data);
1837 209         int exec_count = 20;
1838 185         if (!g_test_slow() && !g_test_perf())
1839 209                 exec_count = 1;
1840 261
1841 209         double secs = 0.0;
1842         for (int i=0; i<exec_count; i++) {
1843 185                 MusicSource *source = source_constructor();
1844 261                 ViewConfig *config = view_config_string_parse(view_config_preset[preset_n][1]);
1845                 MusicSourceView *view = music_source_create_view(source, config);
1846 185
1847 209                 g_test_timer_start ();
1848 261
1849 185                 // FIXME: should this or should it not be done inside a transaction? Which is a better
1850                 // test?
1851                 music_source_begin_transaction (source);
1852                 int flag[351] = {0};
1853                 for (int i=0; i<350; i++) {
1854 421                         int n; do n = g_test_rand_int_range (1, 350+1); while (flag[n]);
1855 261
1856 185                         int artist_n = n / 16,
1857 209                             album_n = n > 250? -1: (n / 16) + 1,
1858                                 track_n = n > 250? -1: (n % 16) + 1;
1859 261
1860 185                         test_add_song (source, artist_n, n, n, album_n, track_n);
1861                         flag[n] = TRUE;
1862                 };
1863                 music_source_end_transaction (source);
1864 261
1865 209                 // Find some of these fellows. static does this randomly so that the chaining is tested
1866 185                 // a bit more thoroughly, but here it makes more sense to do a linear test.
1867                 for (int i=1; i<=350; i++) {
1868                         int recording_id = i;
1869 261                         GtkTreePath *path = music_source_view_get_path_for_id(view, ENTRY_TYPE_RECORDING,
1870 209                                                                               recording_id);
1871                         int id_at_path = music_source_view_get_id_at_path(view, ENTRY_TYPE_RECORDING,
1872                                                                           path);
1873 261                         //printf ("rec %i: Got path: %s, id at path %i\n", recording_id,
1874 209                         //        gtk_tree_path_to_string(path), id_at_path); fflush(stdout);
1875 185                         g_assert_cmpint (id_at_path, ==, recording_id);
1876 261                         gtk_tree_path_free (path);
1877 185                 };
1878 261
1879 185                 GtkTreeIter iter;
1880                 int i=0;
1881                 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(view), &iter)) {
1882                         i++;
1883                         while (gtk_tree_model_iter_next(GTK_TREE_MODEL(view), &iter))
1884 261                                 i++;
1885 185                 }
1886 261
1887 185                 // FIXME: also test removing!
1888 261
1889 185                 int e_children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view), NULL);
1890                 g_assert_cmpint (e_children, ==, i);
1891 261
1892 209                 secs += g_test_timer_elapsed();
1893 261
1894 185                 g_object_unref (view);
1895                 g_object_unref (source);
1896         };
1897
1898         //secs/=(double)exec_count;
1899         if (g_test_perf() || g_test_slow())
1900                 g_test_minimized_result(secs, "%f", secs);
1901 };
1902
1903 void vertical_tests_register(const char *root, MusicSource *(*create_func)()) {
1904         source_constructor=create_func;
1905 261
1906         char path[256];
1907 438         g_test_add_func (PATH_PRINTF("/%s/vertical/Flat", root), test_flat);
1908 261
1909 412         g_test_add_func (PATH_PRINTF("/%s/vertical/Chaining/overestimate/Static", root),
1910 206                          G_CALLBACK(test_chaining_overestimate_static));
1911 261         //g_test_add_func (PATH_PRINTF("/%s/vertical/Chaining/overestimate/Adding", root),
1912 221         //                 G_CALLBACK(test_chaining_overestimate_adding));
1913 261         g_test_add_func (PATH_PRINTF("/%s/vertical/Chaining/underestimate/Static", root),
1914 209                          G_CALLBACK(test_chaining_underestimate_static));
1915 237         g_test_add_func (PATH_PRINTF("/%s/vertical/Chaining 4", root), G_CALLBACK(test_chaining_4));
1916 259         g_test_add_func (PATH_PRINTF("/%s/vertical/Chaining 5", root), G_CALLBACK(test_chaining_5));
1917 261
1918 237         g_test_add_func (PATH_PRINTF("/%s/vertical/Sorting 1", root), test_sorting_1);
1919 390         g_test_add_func (PATH_PRINTF("/%s/vertical/Sorting 2", root), test_sorting_2);
1920         g_test_add_func (PATH_PRINTF("/%s/vertical/Location 1", root), test_location_1);
1921 231         g_test_add_func (PATH_PRINTF("/%s/vertical/Location 2", root), test_location_2);
1922         g_test_add_func (PATH_PRINTF("/%s/vertical/Location 3", root), test_location_3);
1923 418         g_test_add_func (PATH_PRINTF("/%s/vertical/Location 4", root), test_location_4);
1924 261
1925 441         g_test_add_func (PATH_PRINTF("/%s/vertical/Removal 1", root), test_removal_1);
1926         g_test_add_func (PATH_PRINTF("/%s/vertical/Removal 2", root), test_removal_2);
1927         g_test_add_func (PATH_PRINTF("/%s/vertical/Removal 3", root), test_removal_3);
1928         g_test_add_func (PATH_PRINTF("/%s/vertical/Removal 4", root), test_removal_4);
1929 354         g_test_add_func (PATH_PRINTF("/%s/vertical/Changing 1", root), test_changing_1);
1930 396         g_test_add_func (PATH_PRINTF("/%s/vertical/Changing 2", root), test_changing_2);
1931 431         g_test_add_func (PATH_PRINTF("/%s/vertical/Changing 3", root), test_changing_3);
1932 438         g_test_add_func (PATH_PRINTF("/%s/vertical/Changing 4", root), test_changing_4);
1933 431         g_test_add_func (PATH_PRINTF("/%s/vertical/Changing 5 (Folding)", root), 
1934                          test_changing_5_folding);
1935 438         g_test_add_func (PATH_PRINTF("/%s/vertical/Changing 6 (Folding)", root), 
1936                          test_changing_6_folding);
1937 442         g_test_add_func (PATH_PRINTF("/%s/vertical/Changing 7 (Folding)", root), 
1938                          test_changing_7_folding);
1939 226         g_test_add_func (PATH_PRINTF("/%s/vertical/Inserting 1", root), test_inserting_1);
1940 185         g_test_add_func (PATH_PRINTF("/%s/vertical/Inserting 2", root), test_inserting_2);
1941         g_test_add_func (PATH_PRINTF("/%s/vertical/Inserting 3", root), test_inserting_3);
1942 226         g_test_add_func (PATH_PRINTF("/%s/vertical/Inserting 4", root), test_inserting_4);
1943 418         g_test_add_func (PATH_PRINTF("/%s/vertical/Inserting 5", root), test_inserting_5);
1944 261
1945 412         for (int i=0; i<VIEW_CONFIG_PRESETS; i++) {
1946 261                 g_test_add (PATH_PRINTF("/%s/vertical/Preset %i Static: %s", root, i,
1947                                         view_config_preset[i][0]),
1948                             PresetFixture, GINT_TO_POINTER(i), preset_static_setup,
1949 185                                         test_preset_static, preset_static_teardown);
1950 261                 g_test_add_data_func (PATH_PRINTF("/%s/vertical/Preset %i Dynamic: %s", root, i,
1951                                                   view_config_preset[i][0]),
1952 209                                       GINT_TO_POINTER(i), test_preset_dynamic);
1953 412         };
1954 185 };
1955
1956

Loggerhead is a web-based interface for Bazaar branches