From 3892cc4c2df7634a3e70fb41727a2c727b116fb8 Mon Sep 17 00:00:00 2001 From: Havard Graff Date: Mon, 16 Dec 2013 10:01:37 +0100 Subject: [PATCH] testclock: add support for waiting and releasing multiple GstClockIDs In order to be deterministic, multiple waiting GstClockIDs needs to be released at the same time, or else one can get into the situation that the one being released first can add itself back again before the next one waiting is released. Test added for new API and old tests rewritten to comply. --- libs/gst/check/Makefile.am | 9 +- libs/gst/check/gsttestclock.c | 235 ++++++++++++++++++++++++++++++---------- libs/gst/check/gsttestclock.h | 45 ++++++-- tests/check/libs/gsttestclock.c | 74 ++++++++++++- 4 files changed, 290 insertions(+), 73 deletions(-) diff --git a/libs/gst/check/Makefile.am b/libs/gst/check/Makefile.am index 155bff8..73c0cdf 100644 --- a/libs/gst/check/Makefile.am +++ b/libs/gst/check/Makefile.am @@ -102,9 +102,14 @@ LIBGSTCHECK_EXPORTED_FUNCS = \ gst_test_clock_has_id \ gst_test_clock_peek_next_pending_id \ gst_test_clock_wait_for_next_pending_id \ - gst_test_clock_wait_for_pending_id_count \ + gst_test_clock_wait_for_multiple_pending_ids \ gst_test_clock_process_next_clock_id \ - gst_test_clock_get_next_entry_time + gst_test_clock_get_next_entry_time \ + gst_test_clock_wait_for_multiple_pending_ids \ + gst_test_clock_process_id_list \ + gst_test_clock_id_list_get_latest_time \ + gst_test_clock_id_list_free + LIBGSTCHECK_EXPORTED_SYMBOLS = \ $(LIBGSTCHECK_EXPORTED_VARS) \ diff --git a/libs/gst/check/gsttestclock.c b/libs/gst/check/gsttestclock.c index 4b3ee8a..685330c 100644 --- a/libs/gst/check/gsttestclock.c +++ b/libs/gst/check/gsttestclock.c @@ -2,6 +2,8 @@ * * Copyright (C) 2008 Ole André Vadla Ravnås * Copyright (C) 2012 Sebastian Rasmussen + * Copyright (C) 2012 Havard Graff + * Copyright (C) 2013 Haakon Sporsheim * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -62,7 +64,7 @@ * * #GstTestClock keeps track of these clock notifications. By calling * gst_test_clock_wait_for_next_pending_id() or - * gst_test_clock_wait_for_pending_id_count() a unit tests may wait for the + * gst_test_clock_wait_for_multiple_pending_ids() a unit tests may wait for the * next one or several clock notifications to be requested. Additionally unit * tests may release blocked waits in a controlled fashion by calling * gst_test_clock_process_next_clock_id(). This way a unit test can control the @@ -76,7 +78,7 @@ * * N.B.: When a unit test waits for a certain amount of clock notifications to * be requested in gst_test_clock_wait_for_next_pending_id() or - * gst_test_clock_wait_for_pending_id_count() then these functions may block + * gst_test_clock_wait_for_multiple_pending_ids() then these functions may block * for a long time. If they block forever then the expected clock notifications * were never requested from #GstTestClock, and so the assumptions in the code * of the unit test are wrong. The unit test case runner in gstcheck is @@ -388,6 +390,7 @@ gst_test_clock_set_property (GObject * object, guint property_id, static GstClockTime gst_test_clock_get_resolution (GstClock * clock) { + (void) clock; return 1; } @@ -592,6 +595,55 @@ gst_clock_entry_context_compare_func (gconstpointer a, gconstpointer b) return gst_clock_id_compare_func (ctx_a->clock_entry, ctx_b->clock_entry); } +static void +process_entry_context_unlocked (GstTestClock * test_clock, + GstClockEntryContext * ctx) +{ + GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock); + GstClockEntry *entry = ctx->clock_entry; + + if (ctx->time_diff >= 0) + GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_OK; + else + GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_EARLY; + + if (entry->func != NULL) { + GST_OBJECT_UNLOCK (test_clock); + entry->func (GST_CLOCK (test_clock), priv->internal_time, entry, + entry->user_data); + GST_OBJECT_LOCK (test_clock); + } + + gst_test_clock_remove_entry (test_clock, entry); + + if (GST_CLOCK_ENTRY_TYPE (entry) == GST_CLOCK_ENTRY_PERIODIC) { + GST_CLOCK_ENTRY_TIME (entry) += GST_CLOCK_ENTRY_INTERVAL (entry); + + if (entry->func != NULL) + gst_test_clock_add_entry (test_clock, entry, NULL); + } +} + +static GstTestClockIDList * +gst_test_clock_get_pending_id_list_unlocked (GstTestClock * test_clock) +{ + GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock); + GstTestClockIDList *result = g_new0 (GstTestClockIDList, 1); + + if (priv->entry_contexts != NULL) { + GList *cur; + for (cur = priv->entry_contexts; cur != NULL; cur = cur->next) { + GstClockEntryContext *ctx = cur->data; + + result->cur = + g_list_append (result->cur, gst_clock_id_ref (ctx->clock_entry)); + } + } + result->length = g_list_length (result->cur); + + return result; +} + /** * gst_test_clock_new: * @@ -824,37 +876,6 @@ gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock, } /** - * gst_test_clock_wait_for_pending_id_count: - * @test_clock: #GstTestClock for which to await having enough pending clock - * @count: the number of pending clock notifications to wait for - * - * Blocks until at least @count clock notifications have been requested from - * @test_clock. There is no timeout for this wait, see the main description of - * #GstTestClock. - * - * MT safe. - * - * Since: 1.2 - */ -void -gst_test_clock_wait_for_pending_id_count (GstTestClock * test_clock, - guint count) -{ - GstTestClockPrivate *priv; - - g_return_if_fail (GST_IS_TEST_CLOCK (test_clock)); - - priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock); - - GST_OBJECT_LOCK (test_clock); - - while (gst_test_clock_peek_id_count_unlocked (test_clock) < count) - g_cond_wait (&priv->entry_added_cond, GST_OBJECT_GET_LOCK (test_clock)); - - GST_OBJECT_UNLOCK (test_clock); -} - -/** * gst_test_clock_process_next_clock_id: * @test_clock: a #GstTestClock for which to retrieve the next pending clock * notification @@ -888,30 +909,8 @@ gst_test_clock_process_next_clock_id (GstTestClock * test_clock) result = gst_clock_id_ref (ctx->clock_entry); } - if (result != NULL) { - GstClockEntry *entry = ctx->clock_entry; - - if (ctx->time_diff >= 0) - GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_OK; - else - GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_EARLY; - - if (entry->func != NULL) { - GST_OBJECT_UNLOCK (test_clock); - entry->func (GST_CLOCK (test_clock), priv->internal_time, entry, - entry->user_data); - GST_OBJECT_LOCK (test_clock); - } - - gst_test_clock_remove_entry (test_clock, entry); - - if (GST_CLOCK_ENTRY_TYPE (entry) == GST_CLOCK_ENTRY_PERIODIC) { - GST_CLOCK_ENTRY_TIME (entry) += GST_CLOCK_ENTRY_INTERVAL (entry); - - if (entry->func != NULL) - gst_test_clock_add_entry (test_clock, entry, NULL); - } - } + if (result != NULL) + process_entry_context_unlocked (test_clock, ctx); GST_OBJECT_UNLOCK (test_clock); @@ -957,3 +956,125 @@ gst_test_clock_get_next_entry_time (GstTestClock * test_clock) return result; } + +/** + * gst_test_clock_wait_for_multiple_pending_ids: + * @test_clock: #GstTestClock for which to await having enough pending clock + * @count: the number of pending clock notifications to wait for + * @pending_list: A #GstTestClockIDList with pending #GstClockIDs + * + * Blocks until at least @count clock notifications have been requested from + * @test_clock. There is no timeout for this wait, see the main description of + * #GstTestClock. + * + * MT safe. + * + * Since: 1.2 + */ +void +gst_test_clock_wait_for_multiple_pending_ids (GstTestClock * test_clock, + guint count, GstTestClockIDList ** pending_list) +{ + GstTestClockPrivate *priv; + + g_return_if_fail (GST_IS_TEST_CLOCK (test_clock)); + priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock); + + GST_OBJECT_LOCK (test_clock); + + while (g_list_length (priv->entry_contexts) < count) + g_cond_wait (&priv->entry_added_cond, GST_OBJECT_GET_LOCK (test_clock)); + + if (pending_list) + *pending_list = gst_test_clock_get_pending_id_list_unlocked (test_clock); + + GST_OBJECT_UNLOCK (test_clock); +} + +/** + * gst_test_clock_process_id_list: + * @test_clock: #GstTestClock for which to process the pending IDs + * @pending_list: A #GstTestClockIDList with pending #GstClockIDs + * + * Processes and releases the pending IDs in #GstTestClockIDList + * + * MT safe. + * + * Since: 1.2 + */ +guint +gst_test_clock_process_id_list (GstTestClock * test_clock, + GstTestClockIDList * pending_list) +{ + GstTestClockPrivate *priv; + GList *cur; + guint result = 0; + + g_return_val_if_fail (GST_IS_TEST_CLOCK (test_clock), 0); + priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock); + + GST_OBJECT_LOCK (test_clock); + + for (cur = pending_list->cur; cur != NULL; cur = cur->next) { + GstClockID pending_id = cur->data; + GstClockEntryContext *ctx = + gst_test_clock_lookup_entry_context (test_clock, pending_id); + if (ctx) { + process_entry_context_unlocked (test_clock, ctx); + result++; + } + } + GST_OBJECT_UNLOCK (test_clock); + + return result; +} + +/** + * gst_test_clock_id_list_get_latest_time: + * @pending_list: A #GstTestClockIDList with pending #GstClockIDs + * + * Finds the latest time inside the #GstTestClockIDList + * + * MT safe. + * + * Since: 1.2 + */ +GstClockTime +gst_test_clock_id_list_get_latest_time (GstTestClockIDList * pending_list) +{ + GList *cur; + GstClockTime result = 0; + + for (cur = pending_list->cur; cur != NULL; cur = cur->next) { + GstClockID *pending_id = cur->data; + GstClockTime time = gst_clock_id_get_time (pending_id); + if (time > result) + result = time; + } + + return result; +} + +/** + * gst_test_clock_id_list_free: + * @pending_list: A #GstTestClockIDList with pending #GstClockIDs + * + * Free the supplied #GstTestClockIDList + * + * MT safe. + * + * Since: 1.2 + */ +void +gst_test_clock_id_list_free (GstTestClockIDList * pending_list) +{ + GList *cur; + + for (cur = pending_list->cur; cur != NULL; cur = cur->next) { + GstClockID *pending_id = cur->data; + gst_clock_id_unref (pending_id); + } + + g_list_free (pending_list->cur); + g_free (pending_list); +} diff --git a/libs/gst/check/gsttestclock.h b/libs/gst/check/gsttestclock.h index 699e169..2b54b22 100644 --- a/libs/gst/check/gsttestclock.h +++ b/libs/gst/check/gsttestclock.h @@ -2,6 +2,8 @@ * * Copyright (C) 2008 Ole André Vadla Ravnås * Copyright (C) 2012 Sebastian Rasmussen + * Copyright (C) 2012 Havard Graff + * Copyright (C) 2013 Haakon Sporsheim * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -43,6 +45,8 @@ typedef struct _GstTestClock GstTestClock; typedef struct _GstTestClockClass GstTestClockClass; typedef struct _GstTestClockPrivate GstTestClockPrivate; +typedef struct _GstTestClockIDList GstTestClockIDList; + /** * GstTestClock: * @@ -72,17 +76,32 @@ struct _GstTestClockClass GstClockClass parent_class; }; +/** + * GstTestClockIDList: + * @cur: A #GList with all pending #GstClockID + * @length: A #guint with the length of the list + * + * A #GstTestClockIDList structure, which is returned when waiting for multiple IDs + * + * Since: 1.2 + */ + struct _GstTestClockIDList +{ + GList * cur; + guint length; +}; + GType gst_test_clock_get_type (void); GstClock * gst_test_clock_new (void); GstClock * gst_test_clock_new_with_start_time (GstClockTime start_time); -void gst_test_clock_set_time (GstTestClock * test_clock, - GstClockTime new_time); +void gst_test_clock_set_time (GstTestClock * test_clock, + GstClockTime new_time); -void gst_test_clock_advance_time (GstTestClock * test_clock, - GstClockTimeDiff delta); +void gst_test_clock_advance_time (GstTestClock * test_clock, + GstClockTimeDiff delta); guint gst_test_clock_peek_id_count (GstTestClock * test_clock); @@ -91,16 +110,24 @@ gboolean gst_test_clock_has_id (GstTestClock * test_clock, GstClockID id); gboolean gst_test_clock_peek_next_pending_id (GstTestClock * test_clock, GstClockID * pending_id); -void gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock, - GstClockID * pending_id); - -void gst_test_clock_wait_for_pending_id_count (GstTestClock * test_clock, - guint count); +void gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock, + GstClockID * pending_id); GstClockID gst_test_clock_process_next_clock_id (GstTestClock * test_clock); GstClockTime gst_test_clock_get_next_entry_time (GstTestClock * test_clock); +void gst_test_clock_wait_for_multiple_pending_ids (GstTestClock * test_clock, + guint count, + GstTestClockIDList ** pending_list); + +guint gst_test_clock_process_id_list (GstTestClock * test_clock, + GstTestClockIDList * pending_list); + +GstClockTime gst_test_clock_id_list_get_latest_time (GstTestClockIDList * list); + +void gst_test_clock_id_list_free (GstTestClockIDList * list); + G_END_DECLS #endif /* __GST_TEST_CLOCK_H__ */ diff --git a/tests/check/libs/gsttestclock.c b/tests/check/libs/gsttestclock.c index ab0c13d..97a04c2 100644 --- a/tests/check/libs/gsttestclock.c +++ b/tests/check/libs/gsttestclock.c @@ -630,13 +630,13 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout) context_b.clock_id = gst_clock_id_ref (clock_id_b); context_b.jitter = 0; - gst_test_clock_wait_for_pending_id_count (test_clock, 0); + gst_test_clock_wait_for_multiple_pending_ids (test_clock, 0, NULL); worker_thread_b = g_thread_new ("worker_thread_b", test_wait_pending_single_shot_id_sync_worker, &context_b); - gst_test_clock_wait_for_pending_id_count (test_clock, 1); + gst_test_clock_wait_for_multiple_pending_ids (test_clock, 1, NULL); gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id); assert_pending_id (pending_id, clock_id_b, GST_CLOCK_ENTRY_SINGLE, 6 * GST_SECOND); @@ -646,7 +646,7 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout) g_thread_new ("worker_thread_a", test_wait_pending_single_shot_id_sync_worker, &context_a); - gst_test_clock_wait_for_pending_id_count (test_clock, 2); + gst_test_clock_wait_for_multiple_pending_ids (test_clock, 2, NULL); gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id); assert_pending_id (pending_id, clock_id_a, GST_CLOCK_ENTRY_SINGLE, 5 * GST_SECOND); @@ -660,7 +660,7 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout) GST_CLOCK_OK); gst_clock_id_unref (processed_id); - gst_test_clock_wait_for_pending_id_count (test_clock, 1); + gst_test_clock_wait_for_multiple_pending_ids (test_clock, 1, NULL); gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id); assert_pending_id (pending_id, clock_id_b, GST_CLOCK_ENTRY_SINGLE, 6 * GST_SECOND); @@ -674,7 +674,7 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout) GST_CLOCK_OK); gst_clock_id_unref (processed_id); - gst_test_clock_wait_for_pending_id_count (test_clock, 0); + gst_test_clock_wait_for_multiple_pending_ids (test_clock, 0, NULL); g_thread_join (worker_thread_a); g_thread_join (worker_thread_b); @@ -690,7 +690,70 @@ GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout) gst_object_unref (clock); } +GST_END_TEST; + +GST_START_TEST (test_processing_multiple_ids) +{ + GstClock *clock; + GstTestClock *test_clock; + GstClockID clock_id_a; + GstClockID clock_id_b; + SyncClockWaitContext context_a; + SyncClockWaitContext context_b; + GThread *worker_thread_a; + GThread *worker_thread_b; + GstTestClockIDList * pending_list; + + clock = gst_test_clock_new_with_start_time (GST_SECOND); + test_clock = GST_TEST_CLOCK (clock); + + /* register a wait for 5 seconds */ + clock_id_a = gst_clock_new_single_shot_id (clock, 5 * GST_SECOND); + context_a.clock_id = gst_clock_id_ref (clock_id_a); + context_a.jitter = 0; + worker_thread_a = + g_thread_new ("worker_thread_a", + test_wait_pending_single_shot_id_sync_worker, &context_a); + + /* register another wait for 6 seconds */ + clock_id_b = gst_clock_new_single_shot_id (clock, 6 * GST_SECOND); + context_b.clock_id = gst_clock_id_ref (clock_id_b); + context_b.jitter = 0; + worker_thread_b = + g_thread_new ("worker_thread_b", + test_wait_pending_single_shot_id_sync_worker, &context_b); + + /* wait for two waits */ + gst_test_clock_wait_for_multiple_pending_ids (test_clock, 2, &pending_list); + /* assert they are correct */ + assert_pending_id (pending_list->cur->data, clock_id_a, GST_CLOCK_ENTRY_SINGLE, + 5 * GST_SECOND); + assert_pending_id (pending_list->cur->next->data, clock_id_b, GST_CLOCK_ENTRY_SINGLE, + 6 * GST_SECOND); + + /* verify we are waiting for 6 seconds as the latest time */ + fail_unless_equals_int64 (6 * GST_SECOND, + gst_test_clock_id_list_get_latest_time (pending_list)); + + /* process both ID's at the same time */ + gst_test_clock_process_id_list (test_clock, pending_list); + gst_test_clock_id_list_free (pending_list); + + g_thread_join (worker_thread_a); + g_thread_join (worker_thread_b); + + fail_unless_equals_int64 (-4 * GST_SECOND, context_a.jitter); + fail_unless_equals_int64 (-5 * GST_SECOND, context_b.jitter); + + gst_clock_id_unref (context_a.clock_id); + gst_clock_id_unref (context_b.clock_id); + + gst_clock_id_unref (clock_id_a); + gst_clock_id_unref (clock_id_b); + + gst_object_unref (clock); +} GST_END_TEST; GST_START_TEST (test_single_shot_async_past) @@ -954,6 +1017,7 @@ gst_test_clock_suite (void) tcase_add_test (tc_chain, test_wait_pending_single_shot_id); tcase_add_test (tc_chain, test_wait_pending_periodic_id); tcase_add_test (tc_chain, test_single_shot_sync_simultaneous_no_timeout); + tcase_add_test (tc_chain, test_processing_multiple_ids); tcase_add_test (tc_chain, test_single_shot_sync_past); tcase_add_test (tc_chain, test_single_shot_sync_present); tcase_add_test (tc_chain, test_single_shot_sync_future); -- 2.7.4