gstreamer/tests/check/elements/leaks.c
Edward Hervey 58859c8ee9 check: Avoid race with leaks test
The problem is that the taskpool might not have completely drained by the time
we check for leaks.

Instead, ensure all tasks have stopped before testing for valid results.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/523>
2020-06-08 09:47:03 +02:00

513 lines
16 KiB
C

/* GStreamer
*
* Unit test for leakstracer
*
* Copyright (C) <2019> Nirbheek Chauhan <nirbheek@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/check/gstcheck.h>
#define PROBE_TYPE GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BLOCK
#define NUM_BUFFERS 2
struct RetBufferCtx
{
GstBuffer *bufs[NUM_BUFFERS];
guint idx;
};
static void
ret_buffer_ctx_free (struct RetBufferCtx *ctx, gboolean free_bufs)
{
guint ii;
if (free_bufs)
for (ii = 0; ii < ctx->idx; ii++)
gst_buffer_unref (ctx->bufs[ii]);
g_free (ctx);
}
static GstPadProbeReturn
ref_buffer (GstPad * srcpad, GstPadProbeInfo * info, gpointer user_data)
{
GstBuffer *buffer;
struct RetBufferCtx *ctx = user_data;
if (!(GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER))
return GST_PAD_PROBE_PASS;
buffer = GST_PAD_PROBE_INFO_BUFFER (info);
/* ref buffer so it leaks */
gst_buffer_ref (buffer);
if (ctx) {
/* we can only store NUM_BUFFERS buffers */
fail_unless (ctx->idx < NUM_BUFFERS);
/* return the buffer so it can be freed later to avoid triggering valgrind
* in gst-validate */
ctx->bufs[ctx->idx] = buffer;
ctx->idx++;
}
return GST_PAD_PROBE_PASS;
}
static GstTracer *
get_tracer_by_name (const gchar * name)
{
GList *tracers, *l;
GstTracer *tracer = NULL;
tracers = gst_tracing_get_active_tracers ();
for (l = tracers; l; l = l->next)
if (g_strcmp0 (GST_OBJECT_NAME (l->data), name) == 0)
tracer = l->data;
g_list_free (tracers);
return tracer;
}
/* Test logging of live objects to debug logs */
GST_START_TEST (test_log_live_objects)
{
GstElement *pipe, *src, *sink;
GstPad *srcpad;
GstMessage *m;
struct RetBufferCtx *ctx = g_new0 (struct RetBufferCtx, 1);
pipe = gst_pipeline_new ("pipeline");
fail_unless (pipe);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src);
g_object_set (src, "num-buffers", NUM_BUFFERS, NULL);
sink = gst_element_factory_make ("fakesink", NULL);
fail_unless (sink);
gst_bin_add_many (GST_BIN (pipe), src, sink, NULL);
fail_unless (gst_element_link (src, sink));
srcpad = gst_element_get_static_pad (src, "src");
gst_pad_add_probe (srcpad, PROBE_TYPE, ref_buffer, ctx, NULL);
gst_object_unref (srcpad);
GST_DEBUG ("Setting pipeline to PLAYING");
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
GST_STATE_CHANGE_ASYNC);
m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
gst_message_unref (m);
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
GST_STATE_CHANGE_SUCCESS);
gst_object_unref (pipe);
/* Check the live-objects data returned by the tracer */
{
GstTracer *tracer = get_tracer_by_name ("plain");
fail_unless (tracer);
g_signal_emit_by_name (tracer, "log-live-objects");
gst_object_unref (tracer);
}
ret_buffer_ctx_free (ctx, TRUE);
}
GST_END_TEST;
/* Test fetching of live objects with no detail */
GST_START_TEST (test_get_live_objects)
{
GstElement *pipe, *src, *sink;
GstPad *srcpad;
GstMessage *m;
struct RetBufferCtx *ctx = g_new0 (struct RetBufferCtx, 1);
pipe = gst_pipeline_new ("pipeline");
fail_unless (pipe);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src);
g_object_set (src, "num-buffers", NUM_BUFFERS, NULL);
sink = gst_element_factory_make ("fakesink", NULL);
fail_unless (sink);
gst_bin_add_many (GST_BIN (pipe), src, sink, NULL);
fail_unless (gst_element_link (src, sink));
srcpad = gst_element_get_static_pad (src, "src");
gst_pad_add_probe (srcpad, PROBE_TYPE, ref_buffer, ctx, NULL);
gst_object_unref (srcpad);
GST_DEBUG ("Setting pipeline to PLAYING");
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
GST_STATE_CHANGE_ASYNC);
m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
gst_message_unref (m);
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
GST_STATE_CHANGE_SUCCESS);
gst_object_unref (pipe);
/* Force all leftover tasks to stop */
gst_task_cleanup_all ();
/* Check the live-objects data returned by the tracer */
{
guint ii, size;
GstStructure *info;
const GValue *leaks;
GstTracer *tracer = get_tracer_by_name ("plain");
fail_unless (tracer);
g_signal_emit_by_name (tracer, "get-live-objects", &info);
fail_unless_equals_int (gst_structure_n_fields (info), 1);
leaks = gst_structure_get_value (info, "live-objects-list");
fail_unless (G_VALUE_HOLDS (leaks, GST_TYPE_LIST));
size = gst_value_list_get_size (leaks);
fail_unless_equals_int (size, NUM_BUFFERS);
for (ii = 0; ii < size; ii++) {
const GValue *v;
const GstStructure *s;
guint ref_count;
v = gst_value_list_get_value (leaks, ii);
fail_unless (G_VALUE_HOLDS (v, GST_TYPE_STRUCTURE));
s = gst_value_get_structure (v);
fail_unless (gst_structure_has_field_typed (s, "object",
GST_TYPE_BUFFER));
fail_unless (gst_structure_has_field_typed (s, "ref-count", G_TYPE_UINT));
fail_unless (gst_structure_get_uint (s, "ref-count", &ref_count));
fail_unless_equals_int (ref_count, 1);
fail_unless (gst_structure_has_field_typed (s, "trace", G_TYPE_STRING));
fail_unless_equals_string (gst_structure_get_string (s, "trace"), NULL);
fail_unless (!gst_structure_has_field (s, "ref-infos"));
fail_unless_equals_int (gst_structure_n_fields (s), 3);
}
gst_structure_free (info);
gst_object_unref (tracer);
}
/* leaked buffers were freed above with @info */
ret_buffer_ctx_free (ctx, FALSE);
}
GST_END_TEST;
/* Test fetching of filtered live objects with full detail */
GST_START_TEST (test_get_live_objects_filtered_detailed)
{
GstElement *pipe, *src, *sink;
GstPad *srcpad;
GstMessage *m;
struct RetBufferCtx *ctx = g_new0 (struct RetBufferCtx, 1);
pipe = gst_pipeline_new ("pipeline");
fail_unless (pipe);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src);
g_object_set (src, "num-buffers", NUM_BUFFERS, NULL);
sink = gst_element_factory_make ("fakesink", NULL);
fail_unless (sink);
gst_bin_add_many (GST_BIN (pipe), src, sink, NULL);
fail_unless (gst_element_link (src, sink));
srcpad = gst_element_get_static_pad (src, "src");
gst_pad_add_probe (srcpad, PROBE_TYPE, ref_buffer, ctx, NULL);
/* leak srcpad on purpose */
gst_element_get_static_pad (sink, "sink");
/* leak sinkpad on purpose */
GST_DEBUG ("Setting pipeline to PLAYING");
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
GST_STATE_CHANGE_ASYNC);
m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
gst_message_unref (m);
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
GST_STATE_CHANGE_SUCCESS);
gst_object_unref (pipe);
/* Force all leftover tasks to stop */
gst_task_cleanup_all ();
/* Check the live-objects data returned by the tracer */
{
guint ii, jj, isize, jsize;
GstStructure *info;
const GValue *leaks;
GstTracer *tracer = get_tracer_by_name ("more");
fail_unless (tracer);
g_signal_emit_by_name (tracer, "get-live-objects", &info);
fail_unless_equals_int (gst_structure_n_fields (info), 1);
leaks = gst_structure_get_value (info, "live-objects-list");
fail_unless (G_VALUE_HOLDS (leaks, GST_TYPE_LIST));
isize = gst_value_list_get_size (leaks);
fail_unless_equals_int (isize, NUM_BUFFERS);
for (ii = 0; ii < isize; ii++) {
const GValue *v;
const GstStructure *s;
guint ref_count;
v = gst_value_list_get_value (leaks, ii);
fail_unless (G_VALUE_HOLDS (v, GST_TYPE_STRUCTURE));
s = gst_value_get_structure (v);
fail_unless (gst_structure_has_field_typed (s, "object", GST_TYPE_PAD));
fail_unless (gst_structure_has_field_typed (s, "ref-count", G_TYPE_UINT));
fail_unless (gst_structure_get_uint (s, "ref-count", &ref_count));
fail_unless_equals_int (ref_count, 1);
fail_unless (gst_structure_has_field_typed (s, "trace", G_TYPE_STRING));
fail_unless (gst_structure_get_string (s, "trace"));
fail_unless (gst_structure_has_field_typed (s, "ref-infos",
GST_TYPE_LIST));
fail_unless_equals_int (gst_structure_n_fields (s), 4);
v = gst_structure_get_value (s, "ref-infos");
jsize = gst_value_list_get_size (v);
for (jj = 0; jj < jsize; jj++) {
const GValue *rv;
const GstStructure *r;
rv = gst_value_list_get_value (v, jj);
fail_unless (G_VALUE_HOLDS (rv, GST_TYPE_STRUCTURE));
r = gst_value_get_structure (rv);
fail_unless (gst_structure_has_field_typed (r, "ts",
GST_TYPE_CLOCK_TIME));
fail_unless (gst_structure_has_field_typed (r, "desc", G_TYPE_STRING));
fail_unless (gst_structure_get_string (r, "desc"));
fail_unless (gst_structure_get_uint (r, "ref-count", &ref_count));
fail_unless (ref_count > 0);
fail_unless (gst_structure_has_field_typed (r, "trace", G_TYPE_STRING));
fail_unless (gst_structure_get_string (r, "trace"));
fail_unless_equals_int (gst_structure_n_fields (r), 4);
}
}
gst_structure_free (info);
gst_object_unref (tracer);
}
ret_buffer_ctx_free (ctx, TRUE);
/* leaked pads were freed above with @info */
}
GST_END_TEST;
/* Just start and stop tracking without any checkpoints */
GST_START_TEST (test_activity_start_stop)
{
GstElement *pipe, *src, *sink;
GstMessage *m;
GstTracer *tracer = get_tracer_by_name ("plain");
g_signal_emit_by_name (tracer, "activity-start-tracking");
pipe = gst_pipeline_new ("pipeline");
fail_unless (pipe);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src);
g_object_set (src, "num-buffers", NUM_BUFFERS, NULL);
sink = gst_element_factory_make ("fakesink", NULL);
fail_unless (sink);
gst_bin_add_many (GST_BIN (pipe), src, sink, NULL);
fail_unless (gst_element_link (src, sink));
GST_DEBUG ("Setting pipeline to PLAYING");
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
GST_STATE_CHANGE_ASYNC);
m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
gst_message_unref (m);
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
GST_STATE_CHANGE_SUCCESS);
gst_object_unref (pipe);
g_signal_emit_by_name (tracer, "activity-stop-tracking");
gst_object_unref (tracer);
}
GST_END_TEST;
/* Track objects, and checkpoint twice */
GST_START_TEST (test_activity_log_checkpoint)
{
GstElement *pipe, *src, *sink;
GstMessage *m;
GstTracer *tracer = get_tracer_by_name ("plain");
g_signal_emit_by_name (tracer, "activity-start-tracking");
pipe = gst_pipeline_new ("pipeline");
fail_unless (pipe);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src);
g_object_set (src, "num-buffers", NUM_BUFFERS, NULL);
sink = gst_element_factory_make ("fakesink", NULL);
fail_unless (sink);
gst_bin_add_many (GST_BIN (pipe), src, sink, NULL);
fail_unless (gst_element_link (src, sink));
g_signal_emit_by_name (tracer, "activity-log-checkpoint");
GST_DEBUG ("Setting pipeline to PLAYING");
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
GST_STATE_CHANGE_ASYNC);
m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
gst_message_unref (m);
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
GST_STATE_CHANGE_SUCCESS);
gst_object_unref (pipe);
g_signal_emit_by_name (tracer, "activity-log-checkpoint");
g_signal_emit_by_name (tracer, "activity-stop-tracking");
gst_object_unref (tracer);
}
GST_END_TEST;
/* Track objects, checkpoint once, and assert the format of the data */
GST_START_TEST (test_activity_get_checkpoint)
{
GstElement *pipe, *src, *sink;
GstMessage *m;
GstTracer *tracer = get_tracer_by_name ("more");
g_signal_emit_by_name (tracer, "activity-start-tracking");
pipe = gst_pipeline_new ("pipeline");
fail_unless (pipe);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src);
g_object_set (src, "num-buffers", NUM_BUFFERS, NULL);
sink = gst_element_factory_make ("fakesink", NULL);
fail_unless (sink);
gst_bin_add_many (GST_BIN (pipe), src, sink, NULL);
fail_unless (gst_element_link (src, sink));
GST_DEBUG ("Setting pipeline to PLAYING");
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
GST_STATE_CHANGE_ASYNC);
m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
gst_message_unref (m);
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
GST_STATE_CHANGE_SUCCESS);
gst_object_unref (pipe);
/* Force all leftover tasks to stop */
gst_task_cleanup_all ();
{
GstStructure *cpoint;
const GstStructure *cs, *rs;
const GValue *created, *removed;
g_signal_emit_by_name (tracer, "activity-get-checkpoint", &cpoint);
fail_unless_equals_int (gst_structure_n_fields (cpoint), 2);
created = gst_structure_get_value (cpoint, "objects-created-list");
fail_unless (G_VALUE_HOLDS (created, GST_TYPE_LIST));
created = gst_value_list_get_value (created, 0);
fail_unless (G_VALUE_HOLDS (created, GST_TYPE_STRUCTURE));
cs = gst_value_get_structure (created);
fail_unless (gst_structure_has_field_typed (cs, "type-name",
G_TYPE_STRING));
fail_unless (gst_structure_get_string (cs, "type-name"));
fail_unless (gst_structure_has_field_typed (cs, "address", G_TYPE_STRING));
fail_unless (gst_structure_get_string (cs, "address"));
removed = gst_structure_get_value (cpoint, "objects-removed-list");
fail_unless (G_VALUE_HOLDS (removed, GST_TYPE_LIST));
removed = gst_value_list_get_value (removed, 0);
fail_unless (G_VALUE_HOLDS (removed, GST_TYPE_STRUCTURE));
rs = gst_value_get_structure (removed);
fail_unless (gst_structure_has_field_typed (rs, "type-name",
G_TYPE_STRING));
fail_unless (gst_structure_get_string (rs, "type-name"));
fail_unless (gst_structure_has_field_typed (rs, "address", G_TYPE_STRING));
fail_unless (gst_structure_get_string (rs, "address"));
gst_structure_free (cpoint);
}
g_signal_emit_by_name (tracer, "activity-stop-tracking");
gst_object_unref (tracer);
}
GST_END_TEST;
static Suite *
leakstracer_suite (void)
{
Suite *s = suite_create ("leakstracer");
TCase *tc_chain_1 = tcase_create ("live-objects");
TCase *tc_chain_2 = tcase_create ("activity-tracking");
suite_add_tcase (s, tc_chain_1);
tcase_add_test (tc_chain_1, test_log_live_objects);
tcase_add_test (tc_chain_1, test_get_live_objects);
tcase_add_test (tc_chain_1, test_get_live_objects_filtered_detailed);
suite_add_tcase (s, tc_chain_2);
tcase_add_test (tc_chain_2, test_activity_start_stop);
tcase_add_test (tc_chain_2, test_activity_log_checkpoint);
tcase_add_test (tc_chain_2, test_activity_get_checkpoint);
return s;
}
/* Replacement for GST_CHECK_MAIN (leakstracer); because we need to set the
* env before gst_init() is called */
int
main (int argc, char **argv)
{
Suite *s;
g_setenv ("GST_TRACERS", "leaks(name=plain,log-leaks-on-deinit=false);"
"leaks(name=more,filters=GstPad,check-refs=true,stack-traces-flags=none,log-leaks-on-deinit=false);",
TRUE);
gst_check_init (&argc, &argv);
s = leakstracer_suite ();
return gst_check_run_suite (s, "leakstracer", __FILE__);
}