diff options
Diffstat (limited to 'src/tests/memblockq-test.c')
-rw-r--r-- | src/tests/memblockq-test.c | 661 |
1 files changed, 661 insertions, 0 deletions
diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c new file mode 100644 index 0000000..2a9b88a --- /dev/null +++ b/src/tests/memblockq-test.c @@ -0,0 +1,661 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#include <check.h> + +#include <pulsecore/memblockq.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/core-util.h> + +#include <pulse/xmalloc.h> + +static const char *fixed[] = { + "1122444411441144__22__11______3333______________________________", + "__________________3333__________________________________________" +}; +static const char *manual[] = { + "1122444411441144__22__11______3333______________________________", + "__________________3333______________________________" +}; + +/* + * utility function to create a memchunk + */ +static pa_memchunk memchunk_from_str(pa_mempool *p, const char* data) +{ + pa_memchunk res; + size_t size = strlen(data); + + res.memblock = pa_memblock_new_fixed(p, (void*)data, size, true); + ck_assert_ptr_ne(res.memblock, NULL); + + res.index = 0; + res.length = pa_memblock_get_length(res.memblock); + + return res; +} + +static void dump_chunk(const pa_memchunk *chunk, pa_strbuf *buf) { + size_t n; + void *q; + char *e; + + fail_unless(chunk != NULL); + + q = pa_memblock_acquire(chunk->memblock); + for (e = (char*) q + chunk->index, n = 0; n < chunk->length; n++, e++) { + fprintf(stderr, "%c", *e); + pa_strbuf_putc(buf, *e); + } + pa_memblock_release(chunk->memblock); +} + +static void dump(pa_memblockq *bq, int n) { + pa_memchunk out; + pa_strbuf *buf; + char *str; + + pa_assert(bq); + + /* First let's dump this as fixed block */ + fprintf(stderr, "FIXED >"); + pa_memblockq_peek_fixed_size(bq, 64, &out); + buf = pa_strbuf_new(); + dump_chunk(&out, buf); + pa_memblock_unref(out.memblock); + str = pa_strbuf_to_string_free(buf); + fail_unless(pa_streq(str, fixed[n])); + pa_xfree(str); + fprintf(stderr, "<\n"); + + /* Then let's dump the queue manually */ + fprintf(stderr, "MANUAL>"); + + buf = pa_strbuf_new(); + for (;;) { + if (pa_memblockq_peek(bq, &out) < 0) + break; + + dump_chunk(&out, buf); + pa_memblock_unref(out.memblock); + pa_memblockq_drop(bq, out.length); + } + str = pa_strbuf_to_string_free(buf); + fail_unless(pa_streq(str, manual[n])); + pa_xfree(str); + fprintf(stderr, "<\n"); +} + +/* + * utility function to validate invariants + * + * The different values like base, maxlength etc follow certain rules. + * This convenience function makes sure that changes don't violate + * these rules. + */ +static void check_queue_invariants(pa_memblockq *bq) { + size_t base = pa_memblockq_get_base(bq); + size_t maxlength = pa_memblockq_get_maxlength(bq); + size_t tlength = pa_memblockq_get_tlength(bq); + size_t minreq = pa_memblockq_get_minreq(bq); + size_t prebuf = pa_memblockq_get_prebuf(bq); + size_t length = pa_memblockq_get_length(bq); + + /* base > zero */ + ck_assert_int_gt(base, 0); + + /* maxlength multiple of base + * maxlength >= base */ + ck_assert_int_eq(maxlength % base, 0); + ck_assert_int_ge(maxlength, base); + + /* tlength multiple of base + * tlength >= base + * tlength <= maxlength */ + ck_assert_int_eq(tlength % base, 0); + ck_assert_int_ge(tlength, base); + ck_assert_int_le(tlength, maxlength); + + /* minreq multiple of base + * minreq >= base + * minreq <= tlength */ + ck_assert_int_eq(minreq % base, 0); + ck_assert_int_ge(minreq, base); + ck_assert_int_le(minreq, tlength); + + /* prebuf multiple of base + * prebuf >= 0 + * prebuf <= tlength + base - minreq + * prebuf <= tlength (because minreq >= base) */ + ck_assert_int_eq(prebuf % base, 0); + ck_assert_int_ge(prebuf, 0); + ck_assert_int_le(prebuf, tlength + base - minreq); + ck_assert_int_le(prebuf, tlength); + + /* length >= 0 + * length <= maxlength */ + ck_assert_int_ge(length, 0); + ck_assert_int_le(length, maxlength); +} + +START_TEST (memchunk_from_str_test) { + pa_mempool *p; + pa_memchunk chunk; + + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); + ck_assert_ptr_ne(p, NULL); + + /* allocate memchunk and check default settings */ + chunk = memchunk_from_str(p, "abcd"); + ck_assert_ptr_ne(chunk.memblock, NULL); + ck_assert_int_eq(chunk.index, 0); + ck_assert_int_eq(chunk.length, 4); + + /* cleanup */ + pa_memblock_unref(chunk.memblock); + pa_mempool_unref(p); +} +END_TEST + +START_TEST (memblockq_test_initial_properties) { + pa_mempool *p; + pa_memblockq *bq; + pa_memchunk silence; + pa_sample_spec ss = { + .format = PA_SAMPLE_S32BE, + .rate = 48000, + .channels = 1 + }; + int64_t idx = 0; + size_t maxlength = 100; + size_t tlength = 20; + size_t prebuf = 16; + size_t minreq = 8; + size_t maxrewind = 40; + + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); + ck_assert_ptr_ne(p, NULL); + + silence = memchunk_from_str(p, "__"); + + bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence); + fail_unless(bq != NULL); + + /* check initial properties */ + ck_assert_int_eq(pa_memblockq_is_readable(bq), false); + ck_assert_int_eq(pa_memblockq_get_length(bq), 0); + ck_assert_int_eq(pa_memblockq_get_maxlength(bq), maxlength); + ck_assert_int_eq(pa_memblockq_get_tlength(bq), tlength); + ck_assert_int_eq(pa_memblockq_get_prebuf(bq), prebuf); + ck_assert_int_eq(pa_memblockq_get_minreq(bq), minreq); + ck_assert_int_eq(pa_memblockq_get_maxrewind(bq), maxrewind); + ck_assert_int_eq(pa_memblockq_get_base(bq), pa_frame_size(&ss)); + ck_assert_int_eq(pa_memblockq_get_read_index(bq), 0); + ck_assert_int_eq(pa_memblockq_get_write_index(bq), 0); + + check_queue_invariants(bq); + + /* Check reporting of missing bytes: + * Initially, tlength bytes are missing. The second call doesn't + * report additional missing data since the first call. */ + ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength); + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); + + /* cleanup */ + pa_memblockq_free(bq); + pa_memblock_unref(silence.memblock); + pa_mempool_unref(p); +} +END_TEST + +START_TEST (memblockq_test) { + int ret; + + pa_mempool *p; + pa_memblockq *bq; + pa_memchunk chunk1, chunk2, chunk3, chunk4; + pa_memchunk silence; + pa_sample_spec ss = { + .format = PA_SAMPLE_S16LE, + .rate = 48000, + .channels = 1 + }; + + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); + ck_assert_ptr_ne(p, NULL); + + silence = memchunk_from_str(p, "__"); + + bq = pa_memblockq_new("test memblockq", 0, 200, 10, &ss, 4, 4, 40, &silence); + fail_unless(bq != NULL); + check_queue_invariants(bq); + + chunk1 = memchunk_from_str(p, "11"); + chunk2 = memchunk_from_str(p, "XX22"); + chunk2.index += 2; + chunk2.length -= 2; + chunk3 = memchunk_from_str(p, "3333"); + chunk4 = memchunk_from_str(p, "44444444"); + + ret = pa_memblockq_push(bq, &chunk1); + fail_unless(ret == 0); + + ret = pa_memblockq_push(bq, &chunk2); + fail_unless(ret == 0); + + ret = pa_memblockq_push(bq, &chunk3); + fail_unless(ret == 0); + + ret = pa_memblockq_push(bq, &chunk4); + fail_unless(ret == 0); + + check_queue_invariants(bq); + + pa_memblockq_seek(bq, -6, 0, true); + ret = pa_memblockq_push(bq, &chunk3); + fail_unless(ret == 0); + + pa_memblockq_seek(bq, -2, 0, true); + ret = pa_memblockq_push(bq, &chunk1); + fail_unless(ret == 0); + + pa_memblockq_seek(bq, -10, 0, true); + ret = pa_memblockq_push(bq, &chunk4); + fail_unless(ret == 0); + + pa_memblockq_seek(bq, 10, 0, true); + + ret = pa_memblockq_push(bq, &chunk1); + fail_unless(ret == 0); + + pa_memblockq_seek(bq, -6, 0, true); + ret = pa_memblockq_push(bq, &chunk2); + fail_unless(ret == 0); + + /* Test splitting */ + pa_memblockq_seek(bq, -12, 0, true); + ret = pa_memblockq_push(bq, &chunk1); + fail_unless(ret == 0); + + pa_memblockq_seek(bq, 20, 0, true); + + /* Test merging */ + ret = pa_memblockq_push(bq, &chunk3); + fail_unless(ret == 0); + pa_memblockq_seek(bq, -2, 0, true); + + chunk3.index += 2; + chunk3.length -= 2; + ret = pa_memblockq_push(bq, &chunk3); + fail_unless(ret == 0); + + pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE, true); + + dump(bq, 0); + + pa_memblockq_rewind(bq, 52); + + dump(bq, 1); + + check_queue_invariants(bq); + + pa_memblockq_free(bq); + pa_memblock_unref(silence.memblock); + pa_memblock_unref(chunk1.memblock); + pa_memblock_unref(chunk2.memblock); + pa_memblock_unref(chunk3.memblock); + pa_memblock_unref(chunk4.memblock); + + pa_mempool_unref(p); +} +END_TEST + +START_TEST (memblockq_test_length_changes) { + pa_mempool *p; + pa_memblockq *bq; + pa_memchunk silence, data; + pa_sample_spec ss = { + .format = PA_SAMPLE_S32BE, + .rate = 48000, + .channels = 1 + }; + int64_t idx = 0; + size_t maxlength = 60; + size_t tlength = 40; + size_t prebuf = 16; + size_t minreq = 20; + size_t maxrewind = 40; + + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); + ck_assert_ptr_ne(p, NULL); + + silence = memchunk_from_str(p, "____"); + + bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence); + fail_unless(bq != NULL); + + data = memchunk_from_str(p, "12345678"); + + /* insert some data */ + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + + /* check state */ + ck_assert_int_eq(pa_memblockq_get_length(bq), 32); + + /* adjust maximum length + * This might modify tlength, prebuf, minreq, too. */ + pa_memblockq_set_maxlength(bq, maxlength/2); + check_queue_invariants(bq); + + /* adjust target length + * This might modify minreq, too. */ + pa_memblockq_set_tlength(bq, tlength/2); + check_queue_invariants(bq); + + /* adjust minimum requested length + * This might modify prebuf, too. */ + pa_memblockq_set_minreq(bq, minreq/2); + check_queue_invariants(bq); + + /* adjust prebuffer length */ + pa_memblockq_set_prebuf(bq, prebuf/2); + check_queue_invariants(bq); + + /* cleanup */ + pa_memblockq_free(bq); + pa_memblock_unref(silence.memblock); + pa_memblock_unref(data.memblock); + pa_mempool_unref(p); +} +END_TEST + +START_TEST (memblockq_test_pop_missing) { + pa_mempool *p; + pa_memblockq *bq; + pa_memchunk silence, data, chunk; + pa_sample_spec ss = { + .format = PA_SAMPLE_S16BE, + .rate = 48000, + .channels = 1 + }; + int64_t idx = 0; + size_t maxlength = 200; + size_t tlength = 100; + size_t prebuf = 0; + size_t minreq = 80; + size_t maxrewind = 0; + + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); + ck_assert_ptr_ne(p, NULL); + + silence = memchunk_from_str(p, "____"); + data = memchunk_from_str(p, "1234567890"); + + bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence); + fail_unless(bq != NULL); + + /* The following equation regarding the internal variables of a memblockq + * is always true: + * + * length + missing + requested = tlength + * + * "length" is the current memblockq length (write index minus read index) + * and "tlength" is the target length. The intuitive meaning of "missing" + * would be the difference between tlength and length, but actually + * "missing" and "requested" together constitute the amount that is missing + * from the queue. Writing to the queue decrements "requested" and reading + * from the queue increments "missing". pa_memblockq_pop_missing() resets + * "missing" to zero, returns the old "missing" value and adds the + * equivalent amount to "requested". + * + * This test has comments between each step documenting the assumed state + * of those internal variables. */ + + /* length + missing + requested = tlength + * 0 + 100 + 0 = 100 */ + + ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength); + + /* length + missing + requested = tlength + * 0 + 0 + 100 = 100 */ + + for (int i = 0; i != 2; ++i) + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + check_queue_invariants(bq); + + /* length + missing + requested = tlength + * 20 + 0 + 80 = 100 */ + + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); + + /* length + missing + requested = tlength + * 20 + 0 + 80 = 100 */ + + for (int i = 0; i != 8; ++i) + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + check_queue_invariants(bq); + + /* length + missing + requested = tlength + * 100 + 0 + 0 = 100 */ + + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); + + /* length + missing + requested = tlength + * 100 + 0 + 0 = 100 */ + + ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 40, &chunk), 0); + pa_memblockq_drop(bq, 40); + ck_assert_int_eq(chunk.length - chunk.index, 40); + pa_memblock_unref(chunk.memblock); + check_queue_invariants(bq); + + /* length + missing + requested = tlength + * 60 + 40 + 0 = 100 */ + + /* 40 bytes are missing, but that's less than minreq, so 0 is reported */ + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); + + /* length + missing + requested = tlength + * 60 + 40 + 0 = 100 */ + + /* Now we push 30 bytes even though it was not requested, so the requested + * counter goes negative! */ + for (int i = 0; i != 3; ++i) + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + check_queue_invariants(bq); + + /* length + missing + requested = tlength + * 90 + 40 + -30 = 100 */ + + /* missing < minreq, so nothing is reported missing. */ + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); + + /* length + missing + requested = tlength + * 90 + 40 + -30 = 100 */ + + ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0); + pa_memblockq_drop(bq, 20); + ck_assert_int_eq(chunk.length - chunk.index, 20); + pa_memblock_unref(chunk.memblock); + check_queue_invariants(bq); + + /* length + missing + requested = tlength + * 70 + 60 + -30 = 100 */ + + /* missing < minreq, so nothing is reported missing. */ + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); + + /* length + missing + requested = tlength + * 70 + 60 + -30 = 100 */ + + /* We push more data again even though it was not requested, so the + * requested counter goes further into the negative range. */ + for (int i = 0; i != 5; ++i) + ck_assert_int_eq(pa_memblockq_push(bq, &data), 0); + check_queue_invariants(bq); + + /* length + missing + requested = tlength + * 120 + 60 + -80 = 100 */ + + /* missing < minreq, so nothing is reported missing. */ + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0); + + /* length + missing + requested = tlength + * 120 + 60 + -80 = 100 */ + + ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0); + pa_memblockq_drop(bq, 20); + ck_assert_int_eq(chunk.length - chunk.index, 20); + pa_memblock_unref(chunk.memblock); + check_queue_invariants(bq); + + /* length + missing + requested = tlength + * 100 + 80 + -80 = 100 */ + + /* missing has now reached the minreq threshold */ + ck_assert_int_eq(pa_memblockq_pop_missing(bq), 80); + + /* length + missing + requested = tlength + * 100 + 0 + 0 = 100 */ + + /* cleanup */ + pa_memblockq_free(bq); + pa_memblock_unref(silence.memblock); + pa_memblock_unref(data.memblock); + pa_mempool_unref(p); +} +END_TEST + +START_TEST (memblockq_test_tlength_change) { + int ret; + size_t missing; + + pa_mempool *p; + pa_memblockq *bq; + pa_memchunk chunk; + char buffer[2048]; + pa_sample_spec ss = { + .format = PA_SAMPLE_S16LE, + .rate = 48000, + .channels = 1 + }; + + pa_log_set_level(PA_LOG_DEBUG); + + bq = pa_memblockq_new("test memblockq", 0, 4096, 2048, &ss, 0, 512, 512, NULL); + fail_unless(bq != NULL); + + /* Empty buffer, so expect tlength */ + missing = pa_memblockq_pop_missing(bq); + fail_unless(missing == 2048); + + /* Everything requested, so should be satisfied */ + missing = pa_memblockq_pop_missing(bq); + fail_unless(missing == 0); + + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); + + chunk.memblock = pa_memblock_new_fixed(p, buffer, sizeof(buffer), 1); + fail_unless(chunk.memblock != NULL); + + chunk.index = 0; + chunk.length = sizeof(buffer); + + /* Fill buffer (i.e. satisfy earlier request) */ + ret = pa_memblockq_push(bq, &chunk); + fail_unless(ret == 0); + + /* Should still be happy */ + missing = pa_memblockq_pop_missing(bq); + fail_unless(missing == 0); + + /* Check that we don't request less than minreq */ + pa_memblockq_drop(bq, 400); + missing = pa_memblockq_pop_missing(bq); + ck_assert_int_eq(missing, 0); + + missing = pa_memblockq_pop_missing(bq); + fail_unless(missing == 0); + + /* Reduce tlength under what's dropped and under previous minreq */ + pa_memblockq_set_tlength(bq, 256); + pa_memblockq_set_minreq(bq, 64); + + /* We are now overbuffered and should not request more */ + missing = pa_memblockq_pop_missing(bq); + fail_unless(missing == 0); + + /* Drop more data so we are below tlength again, but just barely */ + pa_memblockq_drop(bq, 1400); + + /* Should still honour minreq */ + missing = pa_memblockq_pop_missing(bq); + fail_unless(missing == 0); + + /* Finally drop enough to fall below minreq */ + pa_memblockq_drop(bq, 80); + + /* And expect a request */ + missing = pa_memblockq_pop_missing(bq); + fail_unless(missing == 88); + + pa_memblockq_free(bq); + pa_memblock_unref(chunk.memblock); + pa_mempool_unref(p); +} +END_TEST + + +int main(int argc, char *argv[]) { + int failed = 0; + Suite *s; + TCase *tc; + SRunner *sr; + + if (!getenv("MAKE_CHECK")) + pa_log_set_level(PA_LOG_DEBUG); + + s = suite_create("Memblock Queue"); + tc = tcase_create("memblockq"); + tcase_add_test(tc, memchunk_from_str_test); + tcase_add_test(tc, memblockq_test_initial_properties); + tcase_add_test(tc, memblockq_test); + tcase_add_test(tc, memblockq_test_length_changes); + tcase_add_test(tc, memblockq_test_pop_missing); + tcase_add_test(tc, memblockq_test_tlength_change); + suite_add_tcase(s, tc); + + sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} |