From 52a9d870536118b528024b04e2f1585a48097ecf Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 24 Sep 2018 17:28:45 +0530 Subject: [PATCH] tests: Refactor common client code from passthrough test This code can be reused for other tests that need to connect to the PA server and load a NULL sink. The idea is to grow this out with basic primitives that various tests need to make it easier to add more unit tests for various situations. --- src/tests/meson.build | 2 +- src/tests/passthrough-test.c | 259 ++++------------------------ src/tests/test-util.c | 325 +++++++++++++++++++++++++++++++++++ src/tests/test-util.h | 74 ++++++++ 4 files changed, 436 insertions(+), 224 deletions(-) create mode 100644 src/tests/test-util.c create mode 100644 src/tests/test-util.h diff --git a/src/tests/meson.build b/src/tests/meson.build index dceca55c7..02d614680 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -238,7 +238,7 @@ if get_option('daemon') daemon_tests = [ [ 'extended-test', 'extended-test.c', [ check_dep, libm_dep, libpulse_dep ] ], - [ 'passthrough-test', 'passthrough-test.c', + [ 'passthrough-test', [ 'passthrough-test.c', 'test-util.c' ], [ check_dep, libpulse_dep, libpulsecommon_dep ] ], [ 'sync-playback', 'sync-playback.c', [ check_dep, libm_dep, libpulse_dep ] ], diff --git a/src/tests/passthrough-test.c b/src/tests/passthrough-test.c index 4a1ef783e..4917edbde 100644 --- a/src/tests/passthrough-test.c +++ b/src/tests/passthrough-test.c @@ -19,234 +19,62 @@ #include #endif -#include -#include - #include #include -#include +#include "test-util.h" #define SINK_NAME "passthrough-test" #define RATE 48000 #define CHANNELS 6 -#define WAIT_FOR_OPERATION(o) \ - do { \ - while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { \ - pa_threaded_mainloop_wait(mainloop); \ - } \ - \ - fail_unless(pa_operation_get_state(o) == PA_OPERATION_DONE); \ - pa_operation_unref(o); \ - } while (false) - -static pa_threaded_mainloop *mainloop = NULL; -static pa_context *context = NULL; -static pa_mainloop_api *mainloop_api = NULL; -static uint32_t module_idx = PA_INVALID_INDEX; -static int sink_num = 0; -static char sink_name[256] = { 0, }; +static pa_test_context *ctx = NULL; +static uint32_t sink_idx = PA_INVALID_INDEX; static const char *bname = NULL; - -/* This is called whenever the context status changes */ -static void context_state_callback(pa_context *c, void *userdata) { - fail_unless(c != NULL); - - switch (pa_context_get_state(c)) { - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - - case PA_CONTEXT_READY: - fprintf(stderr, "Connection established.\n"); - pa_threaded_mainloop_signal(mainloop, false); - break; - - case PA_CONTEXT_TERMINATED: - mainloop_api->quit(mainloop_api, 0); - pa_threaded_mainloop_signal(mainloop, false); - break; - - case PA_CONTEXT_FAILED: - mainloop_api->quit(mainloop_api, 0); - pa_threaded_mainloop_signal(mainloop, false); - fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); - fail(); - break; - - default: - fail(); - } -} - -static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) { - fail_unless(idx != PA_INVALID_INDEX); - - module_idx = idx; - - pa_threaded_mainloop_signal(mainloop, false); -} +/* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */ +int16_t data[RATE * 2] = { 0, }; /* one second space */ static void success_cb(pa_context *c, int success, void *userdata) { fail_unless(success != 0); - pa_threaded_mainloop_signal(mainloop, false); + pa_threaded_mainloop_signal(ctx->mainloop, false); } static void passthrough_teardown() { - pa_operation *o; - - pa_threaded_mainloop_lock(mainloop); - - if (module_idx != PA_INVALID_INDEX) { - o = pa_context_unload_module(context, module_idx, success_cb, NULL); - WAIT_FOR_OPERATION(o); - } - - pa_context_disconnect(context); - pa_context_unref(context); - - pa_threaded_mainloop_unlock(mainloop); - - pa_threaded_mainloop_stop(mainloop); - pa_threaded_mainloop_free(mainloop); + pa_test_context_free(ctx); } static void passthrough_setup() { - char modargs[128]; - pa_operation *o; - int r; + ctx = pa_test_context_new(bname); - /* Set up a new main loop */ - mainloop = pa_threaded_mainloop_new(); - fail_unless(mainloop != NULL); - - mainloop_api = pa_threaded_mainloop_get_api(mainloop); - - pa_threaded_mainloop_lock(mainloop); - - pa_threaded_mainloop_start(mainloop); - - context = pa_context_new(mainloop_api, bname); - fail_unless(context != NULL); - - pa_context_set_state_callback(context, context_state_callback, NULL); - - /* Connect the context */ - r = pa_context_connect(context, NULL, 0, NULL); - fail_unless(r == 0); - - pa_threaded_mainloop_wait(mainloop); - - fail_unless(pa_context_get_state(context) == PA_CONTEXT_READY); - - pa_snprintf(sink_name, sizeof(sink_name), "%s-%d", SINK_NAME, sink_num); - pa_snprintf(modargs, sizeof(modargs), "sink_name='%s' formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'", sink_name); - - o = pa_context_load_module(context, "module-null-sink", modargs, module_index_cb, NULL); - WAIT_FOR_OPERATION(o); - - pa_threaded_mainloop_unlock(mainloop); - - return; -} - -static void nop_free_cb(void *p) {} - -static void underflow_cb(struct pa_stream *s, void *userdata) { - fprintf(stderr, "Stream finished\n"); - pa_threaded_mainloop_signal(mainloop, false); -} - -/* This routine is called whenever the stream state changes */ -static void stream_state_callback(pa_stream *s, void *userdata) { - /* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */ - int16_t data[RATE * 2] = { 0, }; /* one second space */ - - fail_unless(s != NULL); - - switch (pa_stream_get_state(s)) { - case PA_STREAM_UNCONNECTED: - case PA_STREAM_CREATING: - break; - - case PA_STREAM_TERMINATED: - pa_threaded_mainloop_signal(mainloop, false); - break; - - case PA_STREAM_READY: { - int r; - - r = pa_stream_write(s, data, sizeof(data), nop_free_cb, 0, PA_SEEK_ABSOLUTE); - fail_unless(r == 0); - - /* Be notified when this stream is drained */ - pa_stream_set_underflow_callback(s, underflow_cb, userdata); - - pa_threaded_mainloop_signal(mainloop, false); - break; - } - - case PA_STREAM_FAILED: - fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - pa_threaded_mainloop_signal(mainloop, false); - break; - - default: - fail(); - } + sink_idx = pa_test_context_load_null_sink(ctx, + "formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'"); } static pa_stream* connect_stream() { - int r; pa_stream *s; - pa_format_info *formats[1]; + pa_format_info *format; - pa_threaded_mainloop_lock(mainloop); - - formats[0] = pa_format_info_new(); - formats[0]->encoding = PA_ENCODING_AC3_IEC61937; + format = pa_format_info_new(); + format->encoding = PA_ENCODING_AC3_IEC61937; /* We set rate and channels to test that negotiation actually works. This * must correspond to the rate and channels we configure module-null-sink * for above. */ - pa_format_info_set_rate(formats[0], RATE); - pa_format_info_set_channels(formats[0], CHANNELS); + pa_format_info_set_rate(format, RATE); + pa_format_info_set_channels(format, CHANNELS); - s = pa_stream_new_extended(context, "passthrough test", formats, 1, NULL); + s = pa_test_context_create_stream(ctx, "passthrough test", sink_idx, format, PA_STREAM_NOFLAGS, data, sizeof(data)); fail_unless(s != NULL); - pa_stream_set_state_callback(s, stream_state_callback, NULL); - r = pa_stream_connect_playback(s, sink_name, NULL, PA_STREAM_NOFLAGS, NULL, NULL); - - fail_unless(r == 0); - - pa_threaded_mainloop_wait(mainloop); - - fail_unless(pa_stream_get_state(s) == PA_STREAM_READY); - - pa_threaded_mainloop_unlock(mainloop); + pa_format_info_free(format); return s; } static void disconnect_stream(pa_stream *s) { - int r; - - pa_threaded_mainloop_lock(mainloop); - - r = pa_stream_disconnect(s); - fail_unless(r == 0); - - pa_threaded_mainloop_wait(mainloop); - fail_unless(pa_stream_get_state(s) == PA_STREAM_TERMINATED); - - pa_stream_unref(s); - - pa_threaded_mainloop_unlock(mainloop); + pa_test_context_destroy_stream(ctx, s); } START_TEST (passthrough_playback_test) { @@ -256,36 +84,22 @@ START_TEST (passthrough_playback_test) { stream = connect_stream(); - /* Wait for underflow_cb() */ - pa_threaded_mainloop_lock(mainloop); - pa_threaded_mainloop_wait(mainloop); + /* Wait for the stream to be drained */ + pa_threaded_mainloop_lock(ctx->mainloop); + pa_threaded_mainloop_wait(ctx->mainloop); fail_unless(pa_stream_get_state(stream) == PA_STREAM_READY); - pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_unlock(ctx->mainloop); disconnect_stream(stream); } END_TEST -static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { +static bool check_sink_volume(const pa_sink_info *sink_info, void *userdata) { pa_cvolume *v = (pa_cvolume *) userdata; - if (eol) - return; + pa_assert(v); - *v = i->volume; - - pa_threaded_mainloop_signal(mainloop, false); -} - -static void get_sink_volume(pa_cvolume *v) { - pa_operation *o; - - pa_threaded_mainloop_lock(mainloop); - - o = pa_context_get_sink_info_by_name(context, sink_name, sink_info_cb, v); - WAIT_FOR_OPERATION(o); - - pa_threaded_mainloop_unlock(mainloop); + return pa_cvolume_equal(&sink_info->volume, v); } START_TEST (passthrough_volume_test) { @@ -296,28 +110,28 @@ START_TEST (passthrough_volume_test) { pa_operation *o; pa_cvolume volume, tmp; - pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_lock(ctx->mainloop); pa_cvolume_set(&volume, 2, PA_VOLUME_NORM / 2); - o = pa_context_set_sink_volume_by_name(context, sink_name, &volume, success_cb, NULL); - WAIT_FOR_OPERATION(o); + o = pa_context_set_sink_volume_by_index(ctx->context, sink_idx, &volume, success_cb, NULL); + WAIT_FOR_OPERATION(ctx, o); - pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_unlock(ctx->mainloop); stream = connect_stream(); - pa_threaded_mainloop_lock(mainloop); - pa_threaded_mainloop_wait(mainloop); + /* Wait for the stream to be drained */ + pa_threaded_mainloop_lock(ctx->mainloop); + pa_threaded_mainloop_wait(ctx->mainloop); fail_unless(PA_STREAM_IS_GOOD(pa_stream_get_state(stream))); - pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_unlock(ctx->mainloop); - get_sink_volume(&tmp); - fail_unless(pa_cvolume_is_norm(&tmp)); + pa_cvolume_set(&tmp, 2, PA_VOLUME_NORM); + fail_unless(pa_test_context_check_sink(ctx, sink_idx, check_sink_volume, &tmp)); disconnect_stream(stream); - get_sink_volume(&tmp); - fail_unless(pa_cvolume_equal(&volume, &tmp)); + fail_unless(pa_test_context_check_sink(ctx, sink_idx, check_sink_volume, &volume)); } END_TEST @@ -333,7 +147,6 @@ int main(int argc, char *argv[]) { tc = tcase_create("passthrough"); tcase_add_checked_fixture(tc, passthrough_setup, passthrough_teardown); tcase_add_test(tc, passthrough_playback_test); - sink_num++; tcase_add_test(tc, passthrough_volume_test); tcase_set_timeout(tc, 5); suite_add_tcase(s, tc); diff --git a/src/tests/test-util.c b/src/tests/test-util.c new file mode 100644 index 000000000..8bb4663e9 --- /dev/null +++ b/src/tests/test-util.c @@ -0,0 +1,325 @@ +/*** + 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 . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "test-util.h" + +#include +#include + +/* This is called whenever the context status changes */ +static void context_state_callback(pa_context *c, void *userdata) { + pa_test_context *ctx = (pa_test_context *) userdata; + + pa_assert(c); + pa_assert(ctx); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + pa_log_info("Connection established.\n"); + pa_threaded_mainloop_signal(ctx->mainloop, false); + break; + + case PA_CONTEXT_TERMINATED: + ctx->mainloop_api->quit(ctx->mainloop_api, 0); + pa_threaded_mainloop_signal(ctx->mainloop, false); + break; + + case PA_CONTEXT_FAILED: + ctx->mainloop_api->quit(ctx->mainloop_api, 0); + pa_threaded_mainloop_signal(ctx->mainloop, false); + pa_log_error("Context error: %s\n", pa_strerror(pa_context_errno(c))); + pa_assert_not_reached(); + break; + + default: + pa_assert_not_reached(); + } +} + +static void success_cb(pa_context *c, int success, void *userdata) { + pa_test_context *ctx = (pa_test_context *) userdata; + + pa_assert(c); + pa_assert(ctx); + pa_assert(success != 0); + + pa_threaded_mainloop_signal(ctx->mainloop, false); +} + +pa_test_context* pa_test_context_new(const char *name) { + pa_test_context *ctx; + int r; + + pa_assert(name); + + ctx = pa_xnew0(pa_test_context, 1); + + ctx->modules = pa_idxset_new(NULL, NULL); + + /* Set up a new main loop */ + ctx->mainloop = pa_threaded_mainloop_new(); + pa_assert(ctx->mainloop); + + ctx->mainloop_api = pa_threaded_mainloop_get_api(ctx->mainloop); + + pa_threaded_mainloop_lock(ctx->mainloop); + + pa_threaded_mainloop_start(ctx->mainloop); + + ctx->context = pa_context_new(ctx->mainloop_api, name); + pa_assert(ctx->context); + + pa_context_set_state_callback(ctx->context, context_state_callback, ctx); + + /* Connect the context */ + r = pa_context_connect(ctx->context, NULL, 0, NULL); + pa_assert(r == 0); + + pa_threaded_mainloop_wait(ctx->mainloop); + + pa_assert(pa_context_get_state(ctx->context) == PA_CONTEXT_READY); + + pa_threaded_mainloop_unlock(ctx->mainloop); + + return ctx; +} + +void pa_test_context_free(pa_test_context *ctx) { + void *module; + uint32_t idx; + + pa_threaded_mainloop_lock(ctx->mainloop); + + PA_IDXSET_FOREACH(module, ctx->modules, idx) { + pa_operation *o; + + o = pa_context_unload_module(ctx->context, PA_PTR_TO_UINT32(module), success_cb, ctx); + + WAIT_FOR_OPERATION(ctx, o); + } + + pa_idxset_free(ctx->modules, NULL); + + pa_context_disconnect(ctx->context); + pa_context_unref(ctx->context); + + pa_threaded_mainloop_unlock(ctx->mainloop); + + pa_threaded_mainloop_stop(ctx->mainloop); + pa_threaded_mainloop_free(ctx->mainloop); + + pa_xfree(ctx); +} + +static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) { + pa_test_context *ctx = (pa_test_context *) userdata; + + pa_assert(c); + pa_assert(ctx); + pa_assert(idx != PA_INVALID_INDEX); + + ctx->module_idx = idx; + + pa_threaded_mainloop_signal(ctx->mainloop, false); +} + +static void lookup_module_sink_idx(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + pa_test_context *ctx = (pa_test_context *) userdata; + + pa_assert(ctx); + + if (!i || eol) { + pa_threaded_mainloop_signal(ctx->mainloop, false); + return; + } + + if (i->owner_module == ctx->module_idx) + ctx->sink_idx = i->index; +} + +uint32_t pa_test_context_load_null_sink(pa_test_context *ctx, const char *modargs) { + pa_operation *o; + + pa_assert(ctx); + + pa_threaded_mainloop_lock(ctx->mainloop); + + /* Load the module */ + ctx->module_idx = PA_INVALID_INDEX; + o = pa_context_load_module(ctx->context, "module-null-sink", modargs, module_index_cb, ctx); + WAIT_FOR_OPERATION(ctx, o); + + pa_assert(ctx->module_idx != PA_INVALID_INDEX); + pa_idxset_put(ctx->modules, PA_UINT32_TO_PTR(ctx->module_idx), NULL); + + /* Look up the sink index corresponding to the module */ + ctx->sink_idx = PA_INVALID_INDEX; + o = pa_context_get_sink_info_list(ctx->context, lookup_module_sink_idx, ctx); + WAIT_FOR_OPERATION(ctx, o); + + pa_threaded_mainloop_unlock(ctx->mainloop); + + pa_assert(ctx->sink_idx != PA_INVALID_INDEX); + + return ctx->sink_idx; +} + +static void nop_free_cb(void *p) {} + +static void underflow_cb(struct pa_stream *s, void *userdata) { + pa_test_context *ctx = (pa_test_context *) userdata; + + pa_assert(ctx); + + pa_log_info("Stream finished\n"); + + pa_threaded_mainloop_signal(ctx->mainloop, false); +} + +/* This routine is called whenever the stream state changes */ +static void stream_state_callback(pa_stream *s, void *userdata) { + pa_test_context *ctx = (pa_test_context *) userdata; + + pa_assert(s); + pa_assert(ctx); + + switch (pa_stream_get_state(s)) { + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(ctx->mainloop, false); + break; + + case PA_STREAM_READY: { + int r; + + r = pa_stream_write(s, ctx->data, ctx->length, nop_free_cb, 0, PA_SEEK_ABSOLUTE); + pa_assert(r == 0); + + /* Be notified when this stream is drained */ + pa_stream_set_underflow_callback(s, underflow_cb, userdata); + + pa_threaded_mainloop_signal(ctx->mainloop, false); + break; + } + + case PA_STREAM_FAILED: + pa_log_error("Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_threaded_mainloop_signal(ctx->mainloop, false); + break; + + default: + pa_assert_not_reached(); + } +} + +pa_stream* pa_test_context_create_stream(pa_test_context *ctx, const char *name, uint32_t sink_idx, pa_format_info *format, + pa_stream_flags_t flags, void *data, size_t length) { + int r; + pa_stream *s; + pa_format_info *formats[1]; + char sink_name[5]; + + pa_threaded_mainloop_lock(ctx->mainloop); + + formats[0] = format; + + ctx->data = data; + ctx->length = length; + + s = pa_stream_new_extended(ctx->context, name, formats, 1, NULL); + pa_assert(s); + + pa_snprintf(sink_name, sizeof(sink_name), "%u", sink_idx); + + pa_stream_set_state_callback(s, stream_state_callback, ctx); + r = pa_stream_connect_playback(s, sink_name, NULL, flags, NULL, NULL); + + pa_assert(r == 0); + + pa_threaded_mainloop_wait(ctx->mainloop); + + pa_assert(pa_stream_get_state(s) == PA_STREAM_READY); + + pa_threaded_mainloop_unlock(ctx->mainloop); + + return s; +} + +void pa_test_context_destroy_stream(pa_test_context *ctx, pa_stream *s) { + int r; + + pa_threaded_mainloop_lock(ctx->mainloop); + + r = pa_stream_disconnect(s); + pa_assert(r == 0); + + pa_threaded_mainloop_wait(ctx->mainloop); + pa_assert(pa_stream_get_state(s) == PA_STREAM_TERMINATED); + + pa_stream_unref(s); + + pa_threaded_mainloop_unlock(ctx->mainloop); +} + +struct sink_info_pred { + pa_test_context *ctx; + pa_test_sink_info_pred_t func; + void *userdata; + + bool ret; +}; + +static void check_sink_info(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + struct sink_info_pred *pred = (struct sink_info_pred *) userdata; + + pa_assert(c); + pa_assert(pred); + + if (i) + pred->ret = pred->func(i, pred->userdata); + + pa_threaded_mainloop_signal(pred->ctx->mainloop, false); +} + +bool pa_test_context_check_sink(pa_test_context *ctx, uint32_t idx, pa_test_sink_info_pred_t predicate, void *userdata) { + pa_operation *o; + struct sink_info_pred pred = { ctx, predicate, userdata, false }; + + pa_assert(ctx); + pa_assert(predicate); + + pa_threaded_mainloop_lock(ctx->mainloop); + + o = pa_context_get_sink_info_by_index(ctx->context, idx, check_sink_info, &pred); + WAIT_FOR_OPERATION(ctx, o); + + pa_threaded_mainloop_unlock(ctx->mainloop); + + return pred.ret; +} diff --git a/src/tests/test-util.h b/src/tests/test-util.h new file mode 100644 index 000000000..3a20b962e --- /dev/null +++ b/src/tests/test-util.h @@ -0,0 +1,74 @@ +#ifndef footestutilhfoo +#define footestutilhfoo + +/*** + 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 . +***/ + +#include + +#include + +#include +#include + +#define WAIT_FOR_OPERATION(ctx, o) \ + do { \ + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { \ + pa_threaded_mainloop_wait(ctx->mainloop); \ + } \ + \ + pa_assert(pa_operation_get_state(o) == PA_OPERATION_DONE); \ + pa_operation_unref(o); \ + } while (false) + +typedef struct pa_test_context { + /* "Public" members */ + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + /* "Private" bookkeeping */ + pa_idxset *modules; + uint32_t module_idx, sink_idx; /* only used for module -> sink index lookup */ + void *data; + size_t length; +} pa_test_context; + +pa_test_context* pa_test_context_new(const char *name); +void pa_test_context_free(pa_test_context *ctx); + +/* Loads a null sink with provided params to test with */ +uint32_t pa_test_context_load_null_sink(pa_test_context *ctx, const char *modargs); + +/* A stream is created and started. The function doesn't wait for the data to + * be played back, playback will continue in the background. The data buffer + * will be played only once, after which an underflow callback will call + * pa_threaded_mainloop_signal() so pa_threaded_mainloop_wait() can be used to + * wait for the stream to finish playing. + */ +pa_stream* pa_test_context_create_stream(pa_test_context *ctx, const char *name, uint32_t sink_idx, pa_format_info *format, + pa_stream_flags_t flags, void *data, size_t length); +/* Clean up the stream */ +void pa_test_context_destroy_stream(pa_test_context *ctx, pa_stream *s); + +typedef bool (*pa_test_sink_info_pred_t)(const pa_sink_info *sink_info, void *userdata); + +/* Test the current state of the sink by providing a predicate function which + * can examine the sink's pa_sink_info for whatever condition is expected. */ +bool pa_test_context_check_sink(pa_test_context *ctx, uint32_t idx, pa_test_sink_info_pred_t predicate, void *userdata); + +#endif /* footestutilhfoo */