From d267fd6d89be8dd9274b058f05230712cf9773b8 Mon Sep 17 00:00:00 2001
From: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Date: Tue, 5 Mar 2024 10:30:58 -0500
Subject: test: gstreamer: Test memory lifetime

Test that everything works fine if a buffer outlives the pipeline.

[Kieran: Update test path with comments and clarify test case]
Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Signed-off-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 test/gstreamer/gstreamer_memory_lifetime_test.cpp | 90 +++++++++++++++++++++++
 test/gstreamer/meson.build                        |  8 +-
 2 files changed, 96 insertions(+), 2 deletions(-)
 create mode 100644 test/gstreamer/gstreamer_memory_lifetime_test.cpp

diff --git a/test/gstreamer/gstreamer_memory_lifetime_test.cpp b/test/gstreamer/gstreamer_memory_lifetime_test.cpp
new file mode 100644
index 00000000..1738cf56
--- /dev/null
+++ b/test/gstreamer/gstreamer_memory_lifetime_test.cpp
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Nicolas Dufresne
+ *
+ * gstreamer_memory_lifetime_test.cpp - GStreamer memory lifetime test
+ */
+
+#include <iostream>
+#include <unistd.h>
+
+#include <gst/app/app.h>
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+using namespace std;
+
+class GstreamerMemoryLifetimeTest : public GstreamerTest, public Test
+{
+public:
+	GstreamerMemoryLifetimeTest()
+		: GstreamerTest()
+	{
+	}
+
+protected:
+	int init() override
+	{
+		if (status_ != TestPass)
+			return status_;
+
+		appsink_ = gst_element_factory_make("appsink", nullptr);
+		if (!appsink_) {
+			g_printerr("Your installation is missing 'appsink'\n");
+			return TestFail;
+		}
+		g_object_ref_sink(appsink_);
+
+		return createPipeline();
+	}
+
+	int run() override
+	{
+		/* Build the pipeline */
+		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, appsink_, nullptr);
+		if (gst_element_link(libcameraSrc_, appsink_) != TRUE) {
+			g_printerr("Elements could not be linked.\n");
+			return TestFail;
+		}
+
+		if (startPipeline() != TestPass)
+			return TestFail;
+
+		sample_ = gst_app_sink_try_pull_sample(GST_APP_SINK(appsink_), GST_SECOND * 5);
+		if (!sample_) {
+			/* Failed to obtain a sample. Abort the test */
+			gst_element_set_state(pipeline_, GST_STATE_NULL);
+			return TestFail;
+		}
+
+		/*
+		 * Keep the sample referenced and set the pipeline state to
+		 * NULL. This causes the libcamerasrc element to synchronously
+		 * release resources it holds. The sample will be released
+		 * later in cleanup().
+		 *
+		 * The test case verifies that libcamerasrc keeps alive long
+		 * enough all the resources that are needed until memory
+		 * allocated for frames gets freed. We return TestPass at this
+		 * stage, and any use-after-free will be caught by the test
+		 * crashing in cleanup().
+		 */
+		gst_element_set_state(pipeline_, GST_STATE_NULL);
+
+		return TestPass;
+	}
+
+	void cleanup() override
+	{
+		g_clear_pointer(&sample_, gst_sample_unref);
+		g_clear_object(&appsink_);
+	}
+
+private:
+	GstElement *appsink_;
+	GstSample *sample_;
+};
+
+TEST_REGISTER(GstreamerMemoryLifetimeTest)
diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
index 37ad125e..b689f637 100644
--- a/test/gstreamer/meson.build
+++ b/test/gstreamer/meson.build
@@ -8,8 +8,11 @@ gstreamer_tests = [
     {'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']},
     {'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']},
     {'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']},
+    {'name': 'memory_lifetime_test', 'sources': ['gstreamer_memory_lifetime_test.cpp'],
+      'should_fail': true},
 ]
 gstreamer_dep = dependency('gstreamer-1.0', required : true)
+gstapp_dep = dependency('gstreamer-app-1.0', required : true)
 
 gstreamer_test_args = []
 
@@ -20,9 +23,10 @@ endif
 foreach test : gstreamer_tests
     exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp',
                      cpp_args : gstreamer_test_args,
-                     dependencies : [libcamera_private, gstreamer_dep],
+                     dependencies : [libcamera_private, gstreamer_dep, gstapp_dep],
                      link_with : test_libraries,
                      include_directories : test_includes_internal)
 
-    test(test['name'], exe, suite : 'gstreamer', is_parallel : false, env : gst_env)
+    test(test['name'], exe, suite : 'gstreamer', is_parallel : false,
+         env : gst_env, should_fail : test.get('should_fail', false))
 endforeach
-- 
cgit v1.2.1