ocarina/tests/core/queue.c
Anna Schumaker c2178bc265 core: Cut back on hardcoded dbe_index uses
The dbe_index of a given database item might change in the future, so we
can't rely on it everywhere.  Let's just use it for saving and loading
files, with the expectation that changes will happen sometime after
startup.

Implements #69: Reduce use of dbe_index in Ocarina code
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2016-09-11 10:50:45 -04:00

530 lines
15 KiB
C

/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/queue.h>
#include <core/tags/tags.h>
#include <tests/test.h>
unsigned int count_init = 0;
unsigned int count_deinit = 0;
unsigned int count_added = 0;
unsigned int count_erase = 0;
unsigned int count_deleted = 0;
unsigned int count_cleared = 0;
unsigned int count_flags = 0;
unsigned int count_sort = 0;
unsigned int count_updated = 0;
bool can_erase = true;
static void *queue_op_init(struct queue *queue, void *data)
{
count_init++;
return GUINT_TO_POINTER(count_init);
}
static void queue_op_deinit(struct queue *queue)
{
count_deinit++;
}
static void queue_op_added(struct queue *queue, unsigned int pos)
{
count_added++;
}
static bool queue_op_erase(struct queue *queue, struct track *track)
{
count_erase++;
return can_erase;
}
static void queue_op_removed(struct queue *queue, unsigned int pos)
{
count_deleted++;
}
static void queue_op_cleared(struct queue *queue, unsigned int n)
{
count_cleared++;
}
static void queue_op_save(struct queue *queue, enum queue_flags flag)
{
if (flag == Q_SAVE_FLAGS)
count_flags++;
else if (flag == Q_SAVE_SORT)
count_sort++;
}
static void queue_op_updated(struct queue *queue, unsigned int pos)
{
count_updated++;
}
static const struct queue_ops test_ops = {
.qop_init = queue_op_init,
.qop_deinit = queue_op_deinit,
.qop_added = queue_op_added,
.qop_erase = queue_op_erase,
.qop_removed = queue_op_removed,
.qop_cleared = queue_op_cleared,
.qop_save = queue_op_save,
.qop_updated = queue_op_updated,
};
static void __test_init_core()
{
struct library *library;
tags_init();
library = library_find("tests/Music");
track_add(library, "tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
track_add(library, "tests/Music/Hyrule Symphony/02 - Kokiri Forest.ogg");
track_add(library, "tests/Music/Hyrule Symphony/03 - Hyrule Field.ogg");
track_add(library, "tests/Music/Hyrule Symphony/04 - Hyrule Castle.ogg");
track_add(library, "tests/Music/Hyrule Symphony/05 - Lon Lon Ranch.ogg");
track_add(library, "tests/Music/Hyrule Symphony/06 - Kakariko Village.ogg");
track_add(library, "tests/Music/Hyrule Symphony/07 - Death Mountain.ogg");
track_add(library, "tests/Music/Hyrule Symphony/08 - Zora's Domain.ogg");
track_add(library, "tests/Music/Hyrule Symphony/09 - Gerudo Valley.ogg");
track_add(library, "tests/Music/Hyrule Symphony/10 - Ganondorf.ogg");
track_add(library, "tests/Music/Hyrule Symphony/11 - Princess Zelda.ogg");
track_add(library, "tests/Music/Hyrule Symphony/12 - Ocarina Medley.ogg");
track_add(library,
"tests/Music/Hyrule Symphony/13 - The Legend of Zelda Medley.ogg");
}
static void __test_deinit_core()
{
tags_deinit();
}
static void test_init()
{
struct queue q;
struct queue_iter it;
__test_init_core();
queue_init(&q, 0, NULL, NULL);
test_equal(count_init, 0);
test_equal(GPOINTER_TO_UINT(q.q_private), 0);
test_equal(q.q_cur.it_pos, (unsigned int)-1);
test_equal(q.q_flags, 0);
test_equal(q.q_length, 0);
test_equal((void *)q.q_sort, NULL);
test_equal((void *)q.q_ops, NULL);
test_equal((void *)queue_next(&q), (void *)NULL);
queue_iter_init(&q, &it);
g_assert_null(it.it_iter);
g_assert_cmpuint(it.it_pos, ==, (unsigned int)-1);
g_assert_null(queue_iter_val(&it));
queue_deinit(&q);
test_equal(count_deinit, 0);
queue_init(&q, Q_ENABLED | Q_RANDOM, &test_ops, NULL);
test_equal(count_init, 1);
test_equal(GPOINTER_TO_UINT(q.q_private), 1);
test_equal(q.q_cur.it_pos, (unsigned int)-1);
test_equal(q.q_flags, Q_ENABLED | Q_RANDOM);
test_equal(q.q_length, 0);
test_equal((void *)q.q_sort, NULL);
test_equal((void *)q.q_ops, (void *)&test_ops);
test_equal((void *)queue_next(&q), (void *)NULL);
queue_deinit(&q);
test_equal(count_deinit, 1);
}
static void test_flags()
{
struct queue q;
queue_init(&q, 0, &test_ops, NULL);
test_equal(q.q_flags, 0);
test_equal(queue_has_flag(&q, Q_ENABLED), (bool)false);
test_equal(queue_has_flag(&q, Q_RANDOM), (bool)false);
test_equal(queue_has_flag(&q, Q_REPEAT), (bool)false);
test_equal(queue_has_flag(&q, Q_NO_SORT), (bool)false);
test_equal(queue_has_flag(&q, Q_SAVE_FLAGS), (bool)false);
test_equal(queue_has_flag(&q, Q_SAVE_SORT), (bool)false);
test_equal(queue_has_flag(&q, Q_ADD_FRONT), (bool)false);
queue_set_flag(&q, Q_ENABLED);
test_equal(q.q_flags, Q_ENABLED);
test_equal(count_flags, 0);
queue_unset_flag(&q, Q_ENABLED);
test_equal(q.q_flags, 0);
test_equal(count_flags, 0);
queue_set_flag(&q, Q_SAVE_FLAGS);
queue_set_flag(&q, Q_ENABLED);
queue_set_flag(&q, Q_RANDOM);
queue_set_flag(&q, Q_REPEAT);
queue_set_flag(&q, Q_NO_SORT);
queue_set_flag(&q, Q_ADD_FRONT);
test_equal(queue_has_flag(&q, Q_ENABLED), (bool)true);
test_equal(queue_has_flag(&q, Q_RANDOM), (bool)true);
test_equal(queue_has_flag(&q, Q_REPEAT), (bool)true);
test_equal(queue_has_flag(&q, Q_NO_SORT), (bool)true);
test_equal(queue_has_flag(&q, Q_SAVE_FLAGS), (bool)true);
test_equal(queue_has_flag(&q, Q_ADD_FRONT), (bool)true);
test_equal(count_flags, 6);
queue_unset_flag(&q, Q_ENABLED);
queue_unset_flag(&q, Q_RANDOM);
queue_unset_flag(&q, Q_REPEAT);
queue_unset_flag(&q, Q_NO_SORT);
queue_unset_flag(&q, Q_ADD_FRONT);
queue_unset_flag(&q, Q_SAVE_FLAGS);
test_equal(q.q_flags, 0);
test_equal(count_flags, 11);
}
static void test_stress(unsigned int N)
{
unsigned int ex_length = 0;
unsigned int ex_size = N;
struct queue_iter it;
struct track *track;
unsigned int i;
struct queue q;
count_added = 0;
count_deleted = 0;
count_cleared = 0;
count_updated = 0;
queue_init(&q, 0, &test_ops, NULL);
/* queue_add() */
for (i = 0; i < N; i++) {
track = track_get(i % 13);
ex_length += track->tr_length;
test_loop_equal(queue_add(&q, track), i, i);
test_loop_equal(count_added, i + 1, i);
} test_loop_passed();
test_equal(q.q_length, ex_length);
test_equal(queue_size(&q), ex_size);
/* queue_iter_init() */
if (N > 0) {
queue_iter_init(&q, &it);
g_assert_nonnull(it.it_iter);
g_assert_cmpuint(it.it_pos, ==, 0);
}
/* queue_for_each() */
i = 0;
queue_for_each(&q, &it) {
g_assert_cmpuint(it.it_pos, ==, i);
g_assert(queue_iter_val(&it) == track_get(i % 13));
i++;
}
g_assert_cmpuint(i, ==, N);
g_assert_cmpuint(queue_size(&q), ==, ex_size);
/* queue_remove_all() and queue_has() */
track = track_get(0);
ex_length -= track->tr_length * (N / 13);
ex_size -= (N / 13);
if (N > 0)
test_equal(queue_has(&q, track), (bool)true);
else
test_equal(queue_has(&q, track), (bool)false);
test_equal(queue_remove_all(&q, track), N / 13);
test_equal(q.q_length, ex_length);
test_equal(queue_size(&q), ex_size);
test_equal(queue_has(&q, track), (bool)false);
/* queue_erase() = false */
can_erase = false;
for (i = 0; i < ex_size; i += 11) {
queue_erase(&q, i);
test_loop_equal(q.q_length, ex_length, i);
test_loop_equal(queue_size(&q), ex_size, i);
} test_loop_passed();
/* queue_remove() and queue_erase() == true */
can_erase = true;
track = track_get(1);
ex_length -= track->tr_length * (N / 13);
ex_size -= (N / 13);
for (i = 0; i < ex_size; i += 11) {
test_loop_equal((void *)queue_at(&q, i), (void *)track, i);
if (i % 2 == 0)
queue_remove(&q, i);
else
queue_erase(&q, i);
} test_loop_passed();
test_equal(q.q_length, ex_length);
test_equal(queue_size(&q), ex_size);
/* queue_updated() */
track = track_get(2);
queue_updated(&q, track);
test_equal(count_updated, N / 13);
test_equal((void *)queue_next(&q), NULL);
test_equal(queue_size(&q), ex_size);
/* Tracks should not be removed. */
queue_set_flag(&q, Q_ENABLED);
queue_set_flag(&q, Q_REPEAT);
for (i = 0; i < ex_size; i++) {
test_loop_equal((void *)queue_next(&q),
(void *)track_get((i % 11) + 2), i);
test_loop_equal(count_updated, (N / 13) + (2 * i) + 1, i);
queue_selected(&q, i);
test_loop_equal(count_updated, (N / 13) + (2 * i) + 2, i);
test_loop_equal(queue_size(&q), ex_size, i);
} test_loop_passed();
/* Tracks should be removed. */
queue_unset_flag(&q, Q_REPEAT);
for (i = 0; i < ex_size / 2; i++) {
track = queue_next(&q);
ex_length -= track->tr_length;
ex_size--;
test_loop_equal((void *)track,
(void *)track_get((i % 11) + 2), i);
test_loop_equal(queue_size(&q), ex_size, i);
test_loop_equal(q.q_length, ex_length, i);
} test_loop_passed();
queue_clear(&q);
test_equal(count_cleared, 1);
test_equal(queue_size(&q), 0);
test_equal(q.q_length, 0);
queue_deinit(&q);
test_equal(count_cleared, 2);
test_equal((void *)q.q_sort, NULL);
}
static void test_basics() { test_stress(13); }
static void test_stress_0() { test_stress(0); }
static void test_stress_100K() { test_stress(100009); }
static void test_rand_select()
{
unsigned int i;
struct queue q;
g_random_set_seed(0);
queue_init(&q, Q_ENABLED | Q_RANDOM, &test_ops, NULL);
/* Call next() on an empty queue. */
for (i = 0; i < 13; i++) {
test_loop_equal((void *)queue_next(&q), NULL, i);
test_loop_equal(queue_size(&q), 0, i);
} test_loop_passed();
for (i = 0; i < 13; i++)
queue_add(&q, track_get(i));
queue_sort(&q, COMPARE_TRACK, true);
/*
* The comments below use the following notation:
* <val>: The value pointed to by q._cur.
* (val): The value selected by q.track_selected().
* [val]: The value picked by q.next().
*/
/* rand() = 9, q = { <>, 1, 2, 3, 4, 5, 6, 7, 8, [9], 10, 11, 12, 13 } */
test_equal(queue_next(&q)->tr_track, 9);
test_equal(queue_size(&q), 12);
/* select = 6, q = { 1, 2, 3, 4, 5, 6, (7), <8>, 10, 11, 12, 13 } */
test_equal(queue_selected(&q, 6)->tr_track, 7);
test_equal(queue_size(&q), 11);
/* rand() = 10, q = { 1, 2, 3, 4, [5], <6>, 8, 10, 11, 12, 13 } */
test_equal(queue_next(&q)->tr_track, 5);
test_equal(queue_size(&q), 10);
/* select = 7, q = { 1, 2, 3, <4>, 6, 8, 10, (11), 12, 13 } */
test_equal(queue_selected(&q, 7)->tr_track, 11);
test_equal(queue_size(&q), 9);
/* rand() = 6, q = { 1, 2, 3, [4], 6, 8, <10>, 12, 13 } */
test_equal(queue_next(&q)->tr_track, 4);
test_equal(queue_size(&q), 8);
/* select = 2, q = { 1, 2, (<3>), 6, 8, 10, 12, 13 } */
test_equal(queue_selected(&q, 2)->tr_track, 3);
test_equal(queue_size(&q), 7);
/* rand() = 1, q = { 1, <2>, [6], 8, 10, 12, 13 } */
test_equal(queue_next(&q)->tr_track, 6);
test_equal(queue_size(&q), 6);
/* select = 1, q = { 1, (<2>), 8, 10, 12, 13 } */
test_equal(queue_selected(&q, 1)->tr_track, 2);
test_equal(queue_size(&q), 5);
/* rand() = 4, q = { <1>, 8, 10, 12, [13] } */
test_equal(queue_next(&q)->tr_track, 13);
test_equal(queue_size(&q), 4);
/* rand() = 1, q = { [1], 8, 10, <12> } */
test_equal(queue_next(&q)->tr_track, 1);
test_equal(queue_size(&q), 3);
/* select = 1, q = { <>, 8, (10), 12 } */
test_equal(queue_selected(&q, 1)->tr_track, 10);
test_equal(queue_size(&q), 2);
/* rand() = 1, q = { <8>, [12] } */
test_equal(queue_next(&q)->tr_track, 12);
test_equal(queue_size(&q), 1);
/* select = 0, q = { (<8>) } */
test_equal(queue_selected(&q, 0)->tr_track, 8);
test_equal(queue_size(&q), 0);
/* q = { } */
test_equal((void *)queue_next(&q), NULL);
test_equal((void *)queue_selected(&q, 3), NULL);
queue_deinit(&q);
}
static void test_sorting()
{
unsigned int ex_count[] = { 7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6 };
unsigned int ex_title[] = { 7, 10, 9, 4, 3, 6, 2, 5, 12, 11, 13, 1, 8 };
unsigned int ex_co_ti[] = { 4, 3, 6, 2, 5, 1, 7, 10, 9, 12, 11, 13, 8 };
struct track *track;
unsigned int i;
struct queue q;
queue_init(&q, Q_SAVE_SORT | Q_ADD_FRONT, &test_ops, NULL);
for (i = 0; i < 13; i++) {
track = track_get(i);
track->tr_count = (track->tr_track <= 6) ? 4 : 2;
queue_add(&q, track);
}
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
test_loop_not_equal((void *)track, NULL, i);
test_loop_equal(track->tr_dbe.dbe_index, 12 - i, i);
} test_loop_passed();
test_equal(count_sort, 0);
queue_sort(&q, COMPARE_TRACK, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
test_loop_not_equal((void *)track, NULL, i);
test_loop_equal(track->tr_track, i + 1, i);
} test_loop_passed();
test_equal(count_sort, 1);
queue_sort(&q, COMPARE_COUNT, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
test_loop_not_equal((void *)track, NULL, i);
test_loop_equal(track->tr_track, ex_count[i], i);
} test_loop_passed();
test_equal(count_sort, 2);
queue_set_flag(&q, Q_NO_SORT);
queue_sort(&q, COMPARE_TITLE, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
test_loop_not_equal((void *)track, NULL, i);
test_loop_equal(track->tr_track, ex_count[i], i);
} test_loop_passed();
test_equal(count_sort, 2);
queue_unset_flag(&q, Q_NO_SORT);
queue_sort(&q, COMPARE_TITLE, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
test_loop_not_equal((void *)track, NULL, i);
test_loop_equal(track->tr_track, ex_title[i], i);
} test_loop_passed();
test_equal(count_sort, 3);
queue_sort(&q, COMPARE_COUNT, true);
queue_sort(&q, COMPARE_TITLE, false);
queue_sort(&q, COMPARE_COUNT, false);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
test_loop_not_equal((void *)track, NULL, i);
test_loop_equal(track->tr_track, ex_co_ti[i], i);
} test_loop_passed();
test_equal(count_sort, 6);
queue_unset_flag(&q, Q_SAVE_SORT);
queue_sort(&q, COMPARE_ARTIST, true);
queue_sort(&q, COMPARE_ALBUM, false);
queue_sort(&q, COMPARE_TRACK, false);
queue_sort(&q, COMPARE_TRACK, false);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
test_loop_not_equal((void *)track, NULL, i);
test_loop_equal(track->tr_track, 13 - i, i);
} test_loop_passed();
test_equal(count_sort, 6);
queue_deinit(&q);
test_equal(q.q_length, 0);
test_equal(queue_size(&q), 0);
test_equal((void *)q.q_sort, NULL);
}
static void test_save_load()
{
struct file f = FILE_INIT("queue.q", 0, 0);
struct queue q, r;
unsigned int i;
queue_init(&q, 0, &test_ops, NULL);
queue_init(&r, 0, &test_ops, NULL);
for (i = 0; i < 13; i++)
queue_add(&q, track_get(i));
test_equal(file_exists(&f), (bool)false);
test_equal(file_open(&f, OPEN_WRITE), (bool)true);
queue_save_tracks(&q, &f);
file_close(&f);
test_equal(file_exists(&f), (bool)true);
test_equal(file_open(&f, OPEN_READ), (bool)true);
queue_load_tracks(&r, &f);
file_close(&f);
test_equal(queue_size(&r), 13);
for (i = 0; i < 13; i++) {
test_loop_equal(queue_has(&r, track_get(i)), (bool)true, i);
} test_loop_passed();
__test_deinit_core();
}
DECLARE_UNIT_TESTS(
UNIT_TEST("Queue Initialization", test_init),
UNIT_TEST("Queue Flags", test_flags),
UNIT_TEST("Queue Basics", test_basics),
UNIT_TEST("Queue Stress (n = 0)", test_stress_0),
UNIT_TEST("Queue Stress (n = 100,000)", test_stress_100K),
UNIT_TEST("Queue Random Next and Selection", test_rand_select),
UNIT_TEST("Queue Sorting", test_sorting),
UNIT_TEST("Queue Save and Load", test_save_load),
);