[libcamera-devel,v11] test: gstreamer: Add test for gstreamer single stream
diff mbox series

Message ID 20210813234652.32200-1-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • [libcamera-devel,v11] test: gstreamer: Add test for gstreamer single stream
Related show

Commit Message

Laurent Pinchart Aug. 13, 2021, 11:46 p.m. UTC
From: Vedant Paranjape <vedantparanjape160201@gmail.com>

This patch adds a test to test if single stream using libcamera's
gstreamer element works.

We need to work around two distinct issues with ASan when enabled in the
build:

- glib has a known leak at initialization time. This is covered by the
  suppression file shipped with glib, but it's not clear how to use it
  automatically. For now, disable leak detection to avoid test failures.

- GStreamer spawns a child process to scan plugins. If GStreamer is
  compiled without ASan (which is likely) but libcamera is, dlopen()ing
  the libcamera plugin will cause an ASan link order verification
  failure. Disable the verification child processes to work around the
  problem. This requires gcc 8 or newer.

Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
This version incorporates changes coming from my review of v10, and
fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
independent reasons as explained in the commit message. I'd like to find
a way to use leak suppression files to handle the glib initialization
leak, but that's a big rabbit hole. The workaround for the second issue
is acceptable in my opinion.

The biggest trouble with these workarounds is that they don't work with
gcc version older than 8. As the stable version of the most common Linux
distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
is the oldest version that libcamera supports) is also acceptable in my
opinion.

Changes since v10:

- Disable ASan leak detection
- Disable ASan link order verification for child processes
- Include source_path.h
- Remove unneeded explicit std::string construction
- Declare variables at usage site
- Make constants constexpr
- Add a variable for the message type
- Blank space fixes
---
 .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
 test/gstreamer/meson.build                    |  19 ++
 test/meson.build                              |   1 +
 3 files changed, 208 insertions(+)
 create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
 create mode 100644 test/gstreamer/meson.build

Comments

Paul Elder Aug. 14, 2021, 5:13 a.m. UTC | #1
Hello,

On Sat, Aug 14, 2021 at 02:46:52AM +0300, Laurent Pinchart wrote:
> From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> 
> This patch adds a test to test if single stream using libcamera's
> gstreamer element works.
> 
> We need to work around two distinct issues with ASan when enabled in the
> build:
> 
> - glib has a known leak at initialization time. This is covered by the
>   suppression file shipped with glib, but it's not clear how to use it
>   automatically. For now, disable leak detection to avoid test failures.
> 
> - GStreamer spawns a child process to scan plugins. If GStreamer is
>   compiled without ASan (which is likely) but libcamera is, dlopen()ing
>   the libcamera plugin will cause an ASan link order verification
>   failure. Disable the verification child processes to work around the
>   problem. This requires gcc 8 or newer.
> 
> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> This version incorporates changes coming from my review of v10, and
> fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> independent reasons as explained in the commit message. I'd like to find
> a way to use leak suppression files to handle the glib initialization
> leak, but that's a big rabbit hole. The workaround for the second issue
> is acceptable in my opinion.
> 
> The biggest trouble with these workarounds is that they don't work with
> gcc version older than 8. As the stable version of the most common Linux
> distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> is the oldest version that libcamera supports) is also acceptable in my
> opinion.
> 
> Changes since v10:
> 
> - Disable ASan leak detection
> - Disable ASan link order verification for child processes
> - Include source_path.h
> - Remove unneeded explicit std::string construction
> - Declare variables at usage site
> - Make constants constexpr
> - Add a variable for the message type
> - Blank space fixes
> ---
>  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
>  test/gstreamer/meson.build                    |  19 ++
>  test/meson.build                              |   1 +
>  3 files changed, 208 insertions(+)
>  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
>  create mode 100644 test/gstreamer/meson.build
> 
> diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> new file mode 100644
> index 000000000000..e26673b3471a
> --- /dev/null
> +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> @@ -0,0 +1,188 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2021, Vedant Paranjape
> + *
> + * ipa_interface_test.cpp - Test the IPA interface

Maybe we should change the description.


Paul

> + */
> +
> +#include <iostream>
> +#include <unistd.h>
> +
> +#include <libcamera/base/utils.h>
> +
> +#include "libcamera/internal/source_paths.h"
> +
> +#include <gst/gst.h>
> +
> +#include "test.h"
> +
> +using namespace std;
> +
> +extern "C" {
> +const char *__asan_default_options()
> +{
> +	/*
> +	 * Disable leak detection due to a known global variable initialization
> +	 * leak in glib's g_quark_init(). This should ideally be handled by
> +	 * using a suppression file instead of disabling leak detection.
> +	 */
> +	return "detect_leaks=false";
> +}
> +}
> +
> +class GstreamerSingleStreamTest : public Test
> +{
> +protected:
> +	int init() override
> +	{
> +		/*
> +		 * GStreamer spawns a process to run the gst-plugin-scanner
> +		 * helper. If libcamera is compiled with ASan enabled, and as
> +		 * GStreamer is most likely not, this will cause the ASan link
> +		 * order check to fail when gst-plugin-scanner dlopen()s the
> +		 * plugin as many libraries will have already been loaded by
> +		 * then. Work around this issue by disabling the link order
> +		 * check. This will only affect child processes, as ASan is
> +		 * already loaded for this process by the time this code is
> +		 * executed, and should thus hopefully be safe.
> +		 *
> +		 * This option is not available in gcc older than 8, the only
> +		 * option in that case is to skip the test.
> +		 */
> +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> +		return TestSkip;
> +#endif
> +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> +
> +		/* Initialize GStreamer */
> +		GError *errInit = nullptr;
> +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> +			g_printerr("Could not initialize GStreamer: %s\n",
> +				   errInit ? errInit->message : "unknown error");
> +			if (errInit)
> +				g_error_free(errInit);
> +
> +			return TestFail;
> +		}
> +
> +		/*
> +		 * Remove the system libcamera plugin, if any, and add the
> +		 * plugin from the build directory.
> +		 */
> +		GstRegistry *registry = gst_registry_get();
> +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> +		if (plugin) {
> +			gst_registry_remove_plugin(registry, plugin);
> +			gst_object_unref(plugin);
> +		}
> +
> +		std::string path = libcamera::utils::libcameraBuildPath()
> +				 + "src/gstreamer";
> +		if (!gst_registry_scan_path(registry, path.c_str())) {
> +			g_printerr("Failed to add plugin to registry\n");
> +			gst_deinit();
> +			return TestFail;
> +		}
> +
> +		/* Create the elements */
> +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> +		sink0_ = gst_element_factory_make("fakesink", "sink0");
> +
> +		/* Create the empty pipeline_ */
> +		pipeline_ = gst_pipeline_new("test-pipeline");
> +
> +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> +			if (pipeline_)
> +				gst_object_unref(pipeline_);
> +			if (convert0_)
> +				gst_object_unref(convert0_);
> +			if (sink0_)
> +				gst_object_unref(sink0_);
> +			if (libcameraSrc_)
> +				gst_object_unref(libcameraSrc_);
> +			gst_deinit();
> +
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +	void cleanup() override
> +	{
> +		gst_object_unref(pipeline_);
> +		gst_deinit();
> +	}
> +
> +	int run() override
> +	{
> +		GstStateChangeReturn ret;
> +
> +		/* Build the pipeline */
> +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> +			g_printerr("Elements could not be linked.\n");
> +			return TestFail;
> +		}
> +
> +		/* Start playing */
> +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> +		if (ret == GST_STATE_CHANGE_FAILURE) {
> +			g_printerr("Unable to set the pipeline to the playing state.\n");
> +			return TestFail;
> +		}
> +
> +		/* Wait until error or EOS or timeout after 2 seconds */
> +		constexpr GstMessageType msgType =
> +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> +		constexpr GstClockTime timeout = 2000000000;
> +
> +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> +
> +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> +
> +		/* Parse error message */
> +		if (msg == NULL)
> +			return TestPass;
> +
> +		switch (GST_MESSAGE_TYPE(msg)) {
> +		case GST_MESSAGE_ERROR:
> +			gstreamer_print_error(msg);
> +			break;
> +		case GST_MESSAGE_EOS:
> +			g_print("End-Of-Stream reached.\n");
> +			break;
> +		default:
> +			g_printerr("Unexpected message received.\n");
> +			break;
> +		}
> +
> +		return TestFail;
> +	}
> +
> +private:
> +	void gstreamer_print_error(GstMessage *msg)
> +	{
> +		GError *err;
> +		gchar *debug_info;
> +
> +		gst_message_parse_error(msg, &err, &debug_info);
> +		g_printerr("Error received from element %s: %s\n",
> +			   GST_OBJECT_NAME(msg->src), err->message);
> +		g_printerr("Debugging information: %s\n",
> +			   debug_info ? debug_info : "none");
> +		g_clear_error(&err);
> +		g_free(debug_info);
> +	}
> +
> +	GstElement *pipeline_;
> +	GstElement *libcameraSrc_;
> +	GstElement *convert0_;
> +	GstElement *sink0_;
> +};
> +
> +TEST_REGISTER(GstreamerSingleStreamTest)
> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> new file mode 100644
> index 000000000000..b99aa0da0ba3
> --- /dev/null
> +++ b/test/gstreamer/meson.build
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +if not gst_enabled
> +    subdir_done()
> +endif
> +
> +gstreamer_tests = [
> +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> +]
> +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> +
> +foreach t : gstreamer_tests
> +    exe = executable(t[0], t[1],
> +                     dependencies : [libcamera_private, gstreamer_dep],
> +                     link_with : test_libraries,
> +                     include_directories : test_includes_internal)
> +
> +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> +endforeach
> diff --git a/test/meson.build b/test/meson.build
> index 3bceb5df586f..d0466f17d7b6 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -11,6 +11,7 @@ subdir('libtest')
>  
>  subdir('camera')
>  subdir('controls')
> +subdir('gstreamer')
>  subdir('ipa')
>  subdir('ipc')
>  subdir('log')
> -- 
> Regards,
> 
> Laurent Pinchart
>
Laurent Pinchart Aug. 14, 2021, 7:54 p.m. UTC | #2
Hi Paul,

On Sat, Aug 14, 2021 at 02:13:53PM +0900, paul.elder@ideasonboard.com wrote:
> On Sat, Aug 14, 2021 at 02:46:52AM +0300, Laurent Pinchart wrote:
> > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > 
> > This patch adds a test to test if single stream using libcamera's
> > gstreamer element works.
> > 
> > We need to work around two distinct issues with ASan when enabled in the
> > build:
> > 
> > - glib has a known leak at initialization time. This is covered by the
> >   suppression file shipped with glib, but it's not clear how to use it
> >   automatically. For now, disable leak detection to avoid test failures.
> > 
> > - GStreamer spawns a child process to scan plugins. If GStreamer is
> >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> >   the libcamera plugin will cause an ASan link order verification
> >   failure. Disable the verification child processes to work around the
> >   problem. This requires gcc 8 or newer.
> > 
> > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> > This version incorporates changes coming from my review of v10, and
> > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > independent reasons as explained in the commit message. I'd like to find
> > a way to use leak suppression files to handle the glib initialization
> > leak, but that's a big rabbit hole. The workaround for the second issue
> > is acceptable in my opinion.
> > 
> > The biggest trouble with these workarounds is that they don't work with
> > gcc version older than 8. As the stable version of the most common Linux
> > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > is the oldest version that libcamera supports) is also acceptable in my
> > opinion.
> > 
> > Changes since v10:
> > 
> > - Disable ASan leak detection
> > - Disable ASan link order verification for child processes
> > - Include source_path.h
> > - Remove unneeded explicit std::string construction
> > - Declare variables at usage site
> > - Make constants constexpr
> > - Add a variable for the message type
> > - Blank space fixes
> > ---
> >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> >  test/gstreamer/meson.build                    |  19 ++
> >  test/meson.build                              |   1 +
> >  3 files changed, 208 insertions(+)
> >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> >  create mode 100644 test/gstreamer/meson.build
> > 
> > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > new file mode 100644
> > index 000000000000..e26673b3471a
> > --- /dev/null
> > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > @@ -0,0 +1,188 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2021, Vedant Paranjape
> > + *
> > + * ipa_interface_test.cpp - Test the IPA interface
> 
> Maybe we should change the description.

As good point. Funny how this can stay unnoticed for 11 versions.

I'll update it to

 * gstreamer_single_stream_test.cpp - GStreamer single stream capture test

> > + */
> > +
> > +#include <iostream>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/base/utils.h>
> > +
> > +#include "libcamera/internal/source_paths.h"
> > +
> > +#include <gst/gst.h>
> > +
> > +#include "test.h"
> > +
> > +using namespace std;
> > +
> > +extern "C" {
> > +const char *__asan_default_options()
> > +{
> > +	/*
> > +	 * Disable leak detection due to a known global variable initialization
> > +	 * leak in glib's g_quark_init(). This should ideally be handled by
> > +	 * using a suppression file instead of disabling leak detection.
> > +	 */
> > +	return "detect_leaks=false";
> > +}
> > +}
> > +
> > +class GstreamerSingleStreamTest : public Test
> > +{
> > +protected:
> > +	int init() override
> > +	{
> > +		/*
> > +		 * GStreamer spawns a process to run the gst-plugin-scanner
> > +		 * helper. If libcamera is compiled with ASan enabled, and as
> > +		 * GStreamer is most likely not, this will cause the ASan link
> > +		 * order check to fail when gst-plugin-scanner dlopen()s the
> > +		 * plugin as many libraries will have already been loaded by
> > +		 * then. Work around this issue by disabling the link order
> > +		 * check. This will only affect child processes, as ASan is
> > +		 * already loaded for this process by the time this code is
> > +		 * executed, and should thus hopefully be safe.
> > +		 *
> > +		 * This option is not available in gcc older than 8, the only
> > +		 * option in that case is to skip the test.
> > +		 */
> > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> > +		return TestSkip;
> > +#endif
> > +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > +
> > +		/* Initialize GStreamer */
> > +		GError *errInit = nullptr;
> > +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > +			g_printerr("Could not initialize GStreamer: %s\n",
> > +				   errInit ? errInit->message : "unknown error");
> > +			if (errInit)
> > +				g_error_free(errInit);
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		/*
> > +		 * Remove the system libcamera plugin, if any, and add the
> > +		 * plugin from the build directory.
> > +		 */
> > +		GstRegistry *registry = gst_registry_get();
> > +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > +		if (plugin) {
> > +			gst_registry_remove_plugin(registry, plugin);
> > +			gst_object_unref(plugin);
> > +		}
> > +
> > +		std::string path = libcamera::utils::libcameraBuildPath()
> > +				 + "src/gstreamer";
> > +		if (!gst_registry_scan_path(registry, path.c_str())) {
> > +			g_printerr("Failed to add plugin to registry\n");
> > +			gst_deinit();
> > +			return TestFail;
> > +		}
> > +
> > +		/* Create the elements */
> > +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > +		sink0_ = gst_element_factory_make("fakesink", "sink0");
> > +
> > +		/* Create the empty pipeline_ */
> > +		pipeline_ = gst_pipeline_new("test-pipeline");
> > +
> > +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> > +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> > +			if (pipeline_)
> > +				gst_object_unref(pipeline_);
> > +			if (convert0_)
> > +				gst_object_unref(convert0_);
> > +			if (sink0_)
> > +				gst_object_unref(sink0_);
> > +			if (libcameraSrc_)
> > +				gst_object_unref(libcameraSrc_);
> > +			gst_deinit();
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		return TestPass;
> > +	}
> > +
> > +	void cleanup() override
> > +	{
> > +		gst_object_unref(pipeline_);
> > +		gst_deinit();
> > +	}
> > +
> > +	int run() override
> > +	{
> > +		GstStateChangeReturn ret;
> > +
> > +		/* Build the pipeline */
> > +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> > +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > +			g_printerr("Elements could not be linked.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Start playing */
> > +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > +		if (ret == GST_STATE_CHANGE_FAILURE) {
> > +			g_printerr("Unable to set the pipeline to the playing state.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Wait until error or EOS or timeout after 2 seconds */
> > +		constexpr GstMessageType msgType =
> > +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > +		constexpr GstClockTime timeout = 2000000000;
> > +
> > +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > +
> > +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> > +
> > +		/* Parse error message */
> > +		if (msg == NULL)
> > +			return TestPass;
> > +
> > +		switch (GST_MESSAGE_TYPE(msg)) {
> > +		case GST_MESSAGE_ERROR:
> > +			gstreamer_print_error(msg);
> > +			break;
> > +		case GST_MESSAGE_EOS:
> > +			g_print("End-Of-Stream reached.\n");
> > +			break;
> > +		default:
> > +			g_printerr("Unexpected message received.\n");
> > +			break;
> > +		}
> > +
> > +		return TestFail;
> > +	}
> > +
> > +private:
> > +	void gstreamer_print_error(GstMessage *msg)
> > +	{
> > +		GError *err;
> > +		gchar *debug_info;
> > +
> > +		gst_message_parse_error(msg, &err, &debug_info);
> > +		g_printerr("Error received from element %s: %s\n",
> > +			   GST_OBJECT_NAME(msg->src), err->message);
> > +		g_printerr("Debugging information: %s\n",
> > +			   debug_info ? debug_info : "none");
> > +		g_clear_error(&err);
> > +		g_free(debug_info);
> > +	}
> > +
> > +	GstElement *pipeline_;
> > +	GstElement *libcameraSrc_;
> > +	GstElement *convert0_;
> > +	GstElement *sink0_;
> > +};
> > +
> > +TEST_REGISTER(GstreamerSingleStreamTest)
> > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > new file mode 100644
> > index 000000000000..b99aa0da0ba3
> > --- /dev/null
> > +++ b/test/gstreamer/meson.build
> > @@ -0,0 +1,19 @@
> > +# SPDX-License-Identifier: CC0-1.0
> > +
> > +if not gst_enabled
> > +    subdir_done()
> > +endif
> > +
> > +gstreamer_tests = [
> > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > +]
> > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > +
> > +foreach t : gstreamer_tests
> > +    exe = executable(t[0], t[1],
> > +                     dependencies : [libcamera_private, gstreamer_dep],
> > +                     link_with : test_libraries,
> > +                     include_directories : test_includes_internal)
> > +
> > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > +endforeach
> > diff --git a/test/meson.build b/test/meson.build
> > index 3bceb5df586f..d0466f17d7b6 100644
> > --- a/test/meson.build
> > +++ b/test/meson.build
> > @@ -11,6 +11,7 @@ subdir('libtest')
> >  
> >  subdir('camera')
> >  subdir('controls')
> > +subdir('gstreamer')
> >  subdir('ipa')
> >  subdir('ipc')
> >  subdir('log')
Umang Jain Aug. 16, 2021, 5:47 a.m. UTC | #3
Hi Vedant and Laurent,

Thanks for the patch. Overall looks good to me,

a few comments below for cleanup paths :)

On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> From: Vedant Paranjape <vedantparanjape160201@gmail.com>
>
> This patch adds a test to test if single stream using libcamera's
> gstreamer element works.
>
> We need to work around two distinct issues with ASan when enabled in the
> build:
>
> - glib has a known leak at initialization time. This is covered by the
>    suppression file shipped with glib, but it's not clear how to use it
>    automatically. For now, disable leak detection to avoid test failures.
>
> - GStreamer spawns a child process to scan plugins. If GStreamer is
>    compiled without ASan (which is likely) but libcamera is, dlopen()ing
>    the libcamera plugin will cause an ASan link order verification
>    failure. Disable the verification child processes to work around the
>    problem. This requires gcc 8 or newer.
>
> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> This version incorporates changes coming from my review of v10, and
> fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> independent reasons as explained in the commit message. I'd like to find
> a way to use leak suppression files to handle the glib initialization
> leak, but that's a big rabbit hole. The workaround for the second issue
> is acceptable in my opinion.
>
> The biggest trouble with these workarounds is that they don't work with
> gcc version older than 8. As the stable version of the most common Linux
> distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> is the oldest version that libcamera supports) is also acceptable in my
> opinion.
>
> Changes since v10:
>
> - Disable ASan leak detection
> - Disable ASan link order verification for child processes
> - Include source_path.h
> - Remove unneeded explicit std::string construction
> - Declare variables at usage site
> - Make constants constexpr
> - Add a variable for the message type
> - Blank space fixes
> ---
>   .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
>   test/gstreamer/meson.build                    |  19 ++
>   test/meson.build                              |   1 +
>   3 files changed, 208 insertions(+)
>   create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
>   create mode 100644 test/gstreamer/meson.build
>
> diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> new file mode 100644
> index 000000000000..e26673b3471a
> --- /dev/null
> +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> @@ -0,0 +1,188 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2021, Vedant Paranjape
> + *
> + * ipa_interface_test.cpp - Test the IPA interface
> + */
> +
> +#include <iostream>
> +#include <unistd.h>
> +
> +#include <libcamera/base/utils.h>
> +
> +#include "libcamera/internal/source_paths.h"
> +
> +#include <gst/gst.h>
> +
> +#include "test.h"
> +
> +using namespace std;
> +
> +extern "C" {
> +const char *__asan_default_options()
> +{
> +	/*
> +	 * Disable leak detection due to a known global variable initialization
> +	 * leak in glib's g_quark_init(). This should ideally be handled by
> +	 * using a suppression file instead of disabling leak detection.
> +	 */
> +	return "detect_leaks=false";
> +}
> +}
> +
> +class GstreamerSingleStreamTest : public Test
> +{
> +protected:
> +	int init() override
> +	{
> +		/*
> +		 * GStreamer spawns a process to run the gst-plugin-scanner
> +		 * helper. If libcamera is compiled with ASan enabled, and as
> +		 * GStreamer is most likely not, this will cause the ASan link
> +		 * order check to fail when gst-plugin-scanner dlopen()s the
> +		 * plugin as many libraries will have already been loaded by
> +		 * then. Work around this issue by disabling the link order
> +		 * check. This will only affect child processes, as ASan is
> +		 * already loaded for this process by the time this code is
> +		 * executed, and should thus hopefully be safe.
> +		 *
> +		 * This option is not available in gcc older than 8, the only
> +		 * option in that case is to skip the test.
> +		 */
> +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> +		return TestSkip;
> +#endif
> +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> +
> +		/* Initialize GStreamer */
> +		GError *errInit = nullptr;
One can use:

     g_autoptr(GError) errInit = NULL;

...

> +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> +			g_printerr("Could not initialize GStreamer: %s\n",
> +				   errInit ? errInit->message : "unknown error");
> +			if (errInit)
> +				g_error_free(errInit);
And remove manual free here.
> +
> +			return TestFail;
> +		}
> +
> +		/*
> +		 * Remove the system libcamera plugin, if any, and add the
> +		 * plugin from the build directory.
> +		 */
> +		GstRegistry *registry = gst_registry_get();
> +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> +		if (plugin) {
> +			gst_registry_remove_plugin(registry, plugin);
> +			gst_object_unref(plugin);
> +		}
> +
> +		std::string path = libcamera::utils::libcameraBuildPath()
> +				 + "src/gstreamer";
> +		if (!gst_registry_scan_path(registry, path.c_str())) {
> +			g_printerr("Failed to add plugin to registry\n");
> +			gst_deinit();
> +			return TestFail;
> +		}
> +
> +		/* Create the elements */
> +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> +		sink0_ = gst_element_factory_make("fakesink", "sink0");
> +
> +		/* Create the empty pipeline_ */
> +		pipeline_ = gst_pipeline_new("test-pipeline");
> +
> +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> +			if (pipeline_)
> +				gst_object_unref(pipeline_);
> +			if (convert0_)
> +				gst_object_unref(convert0_);
> +			if (sink0_)
> +				gst_object_unref(sink0_);
> +			if (libcameraSrc_)
> +				gst_object_unref(libcameraSrc_);
> +			gst_deinit();
> +
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +	void cleanup() override
> +	{
> +		gst_object_unref(pipeline_);
> +		gst_deinit();
> +	}
> +
> +	int run() override
> +	{
> +		GstStateChangeReturn ret;
> +
> +		/* Build the pipeline */
> +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> +			g_printerr("Elements could not be linked.\n");
> +			return TestFail;
> +		}
> +
> +		/* Start playing */
> +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> +		if (ret == GST_STATE_CHANGE_FAILURE) {
> +			g_printerr("Unable to set the pipeline to the playing state.\n");
> +			return TestFail;
> +		}
> +
> +		/* Wait until error or EOS or timeout after 2 seconds */
> +		constexpr GstMessageType msgType =
> +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> +		constexpr GstClockTime timeout = 2000000000;
> +
> +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> +
> +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> +
> +		/* Parse error message */
> +		if (msg == NULL)
> +			return TestPass;
> +
> +		switch (GST_MESSAGE_TYPE(msg)) {
> +		case GST_MESSAGE_ERROR:
> +			gstreamer_print_error(msg);
> +			break;
> +		case GST_MESSAGE_EOS:
> +			g_print("End-Of-Stream reached.\n");
EOS doesn't sound like a error/Test-fail condition to me. If there is a 
possibility that EOS will be reached before timeout, what's ensures(in 
the first place) that the pipeline will be in playing state for entire 
timeout, before we request masked EOS/Error messages from the bus? What 
am I missing?
> +			break;
> +		default:
> +			g_printerr("Unexpected message received.\n");
> +			break;
> +		}
> +
> +		return TestFail;
> +	}
> +
> +private:
> +	void gstreamer_print_error(GstMessage *msg)
> +	{
> +		GError *err;

This can use:

     g_autoptr(GError) err = NULL;

> +		gchar *debug_info;

and

g_autofree char *debug_info = NULL;

> +
> +		gst_message_parse_error(msg, &err, &debug_info);
> +		g_printerr("Error received from element %s: %s\n",
> +			   GST_OBJECT_NAME(msg->src), err->message);
> +		g_printerr("Debugging information: %s\n",
> +			   debug_info ? debug_info : "none");
> +		g_clear_error(&err);
> +		g_free(debug_info);

So, that will lead us to dropping manual free of err and debug info


Minor things, so:

Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>

> +	}
> +
> +	GstElement *pipeline_;
> +	GstElement *libcameraSrc_;
> +	GstElement *convert0_;
> +	GstElement *sink0_;
> +};
> +
> +TEST_REGISTER(GstreamerSingleStreamTest)
> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> new file mode 100644
> index 000000000000..b99aa0da0ba3
> --- /dev/null
> +++ b/test/gstreamer/meson.build
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +if not gst_enabled
> +    subdir_done()
> +endif
> +
> +gstreamer_tests = [
> +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> +]
> +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> +
> +foreach t : gstreamer_tests
> +    exe = executable(t[0], t[1],
> +                     dependencies : [libcamera_private, gstreamer_dep],
> +                     link_with : test_libraries,
> +                     include_directories : test_includes_internal)
> +
> +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> +endforeach
> diff --git a/test/meson.build b/test/meson.build
> index 3bceb5df586f..d0466f17d7b6 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -11,6 +11,7 @@ subdir('libtest')
>   
>   subdir('camera')
>   subdir('controls')
> +subdir('gstreamer')
>   subdir('ipa')
>   subdir('ipc')
>   subdir('log')
Laurent Pinchart Aug. 16, 2021, 9:33 a.m. UTC | #4
Hi Umang,

On Mon, Aug 16, 2021 at 11:17:27AM +0530, Umang Jain wrote:
> Hi Vedant and Laurent,
> 
> Thanks for the patch. Overall looks good to me,
> 
> a few comments below for cleanup paths :)

The patch has already been merged I'm afraid.

Vedant, could you read these comments, and either submit follow-up
patches, or reply if you disagree ?

> On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> >
> > This patch adds a test to test if single stream using libcamera's
> > gstreamer element works.
> >
> > We need to work around two distinct issues with ASan when enabled in the
> > build:
> >
> > - glib has a known leak at initialization time. This is covered by the
> >    suppression file shipped with glib, but it's not clear how to use it
> >    automatically. For now, disable leak detection to avoid test failures.
> >
> > - GStreamer spawns a child process to scan plugins. If GStreamer is
> >    compiled without ASan (which is likely) but libcamera is, dlopen()ing
> >    the libcamera plugin will cause an ASan link order verification
> >    failure. Disable the verification child processes to work around the
> >    problem. This requires gcc 8 or newer.
> >
> > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> > This version incorporates changes coming from my review of v10, and
> > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > independent reasons as explained in the commit message. I'd like to find
> > a way to use leak suppression files to handle the glib initialization
> > leak, but that's a big rabbit hole. The workaround for the second issue
> > is acceptable in my opinion.
> >
> > The biggest trouble with these workarounds is that they don't work with
> > gcc version older than 8. As the stable version of the most common Linux
> > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > is the oldest version that libcamera supports) is also acceptable in my
> > opinion.
> >
> > Changes since v10:
> >
> > - Disable ASan leak detection
> > - Disable ASan link order verification for child processes
> > - Include source_path.h
> > - Remove unneeded explicit std::string construction
> > - Declare variables at usage site
> > - Make constants constexpr
> > - Add a variable for the message type
> > - Blank space fixes
> > ---
> >   .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> >   test/gstreamer/meson.build                    |  19 ++
> >   test/meson.build                              |   1 +
> >   3 files changed, 208 insertions(+)
> >   create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> >   create mode 100644 test/gstreamer/meson.build
> >
> > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > new file mode 100644
> > index 000000000000..e26673b3471a
> > --- /dev/null
> > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > @@ -0,0 +1,188 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2021, Vedant Paranjape
> > + *
> > + * ipa_interface_test.cpp - Test the IPA interface
> > + */
> > +
> > +#include <iostream>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/base/utils.h>
> > +
> > +#include "libcamera/internal/source_paths.h"
> > +
> > +#include <gst/gst.h>
> > +
> > +#include "test.h"
> > +
> > +using namespace std;
> > +
> > +extern "C" {
> > +const char *__asan_default_options()
> > +{
> > +	/*
> > +	 * Disable leak detection due to a known global variable initialization
> > +	 * leak in glib's g_quark_init(). This should ideally be handled by
> > +	 * using a suppression file instead of disabling leak detection.
> > +	 */
> > +	return "detect_leaks=false";
> > +}
> > +}
> > +
> > +class GstreamerSingleStreamTest : public Test
> > +{
> > +protected:
> > +	int init() override
> > +	{
> > +		/*
> > +		 * GStreamer spawns a process to run the gst-plugin-scanner
> > +		 * helper. If libcamera is compiled with ASan enabled, and as
> > +		 * GStreamer is most likely not, this will cause the ASan link
> > +		 * order check to fail when gst-plugin-scanner dlopen()s the
> > +		 * plugin as many libraries will have already been loaded by
> > +		 * then. Work around this issue by disabling the link order
> > +		 * check. This will only affect child processes, as ASan is
> > +		 * already loaded for this process by the time this code is
> > +		 * executed, and should thus hopefully be safe.
> > +		 *
> > +		 * This option is not available in gcc older than 8, the only
> > +		 * option in that case is to skip the test.
> > +		 */
> > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> > +		return TestSkip;
> > +#endif
> > +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > +
> > +		/* Initialize GStreamer */
> > +		GError *errInit = nullptr;
>
> One can use:
> 
>      g_autoptr(GError) errInit = NULL;
> 
> ...
> 
> > +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > +			g_printerr("Could not initialize GStreamer: %s\n",
> > +				   errInit ? errInit->message : "unknown error");
> > +			if (errInit)
> > +				g_error_free(errInit);
>
> And remove manual free here.
>
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		/*
> > +		 * Remove the system libcamera plugin, if any, and add the
> > +		 * plugin from the build directory.
> > +		 */
> > +		GstRegistry *registry = gst_registry_get();
> > +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > +		if (plugin) {
> > +			gst_registry_remove_plugin(registry, plugin);
> > +			gst_object_unref(plugin);
> > +		}
> > +
> > +		std::string path = libcamera::utils::libcameraBuildPath()
> > +				 + "src/gstreamer";
> > +		if (!gst_registry_scan_path(registry, path.c_str())) {
> > +			g_printerr("Failed to add plugin to registry\n");
> > +			gst_deinit();
> > +			return TestFail;
> > +		}
> > +
> > +		/* Create the elements */
> > +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > +		sink0_ = gst_element_factory_make("fakesink", "sink0");
> > +
> > +		/* Create the empty pipeline_ */
> > +		pipeline_ = gst_pipeline_new("test-pipeline");
> > +
> > +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> > +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> > +			if (pipeline_)
> > +				gst_object_unref(pipeline_);
> > +			if (convert0_)
> > +				gst_object_unref(convert0_);
> > +			if (sink0_)
> > +				gst_object_unref(sink0_);
> > +			if (libcameraSrc_)
> > +				gst_object_unref(libcameraSrc_);
> > +			gst_deinit();
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		return TestPass;
> > +	}
> > +
> > +	void cleanup() override
> > +	{
> > +		gst_object_unref(pipeline_);
> > +		gst_deinit();
> > +	}
> > +
> > +	int run() override
> > +	{
> > +		GstStateChangeReturn ret;
> > +
> > +		/* Build the pipeline */
> > +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> > +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > +			g_printerr("Elements could not be linked.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Start playing */
> > +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > +		if (ret == GST_STATE_CHANGE_FAILURE) {
> > +			g_printerr("Unable to set the pipeline to the playing state.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Wait until error or EOS or timeout after 2 seconds */
> > +		constexpr GstMessageType msgType =
> > +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > +		constexpr GstClockTime timeout = 2000000000;
> > +
> > +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > +
> > +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> > +
> > +		/* Parse error message */
> > +		if (msg == NULL)
> > +			return TestPass;
> > +
> > +		switch (GST_MESSAGE_TYPE(msg)) {
> > +		case GST_MESSAGE_ERROR:
> > +			gstreamer_print_error(msg);
> > +			break;
> > +		case GST_MESSAGE_EOS:
> > +			g_print("End-Of-Stream reached.\n");
>
> EOS doesn't sound like a error/Test-fail condition to me. If there is a 
> possibility that EOS will be reached before timeout, what's ensures(in 
> the first place) that the pipeline will be in playing state for entire 
> timeout, before we request masked EOS/Error messages from the bus? What 
> am I missing?
>
> > +			break;
> > +		default:
> > +			g_printerr("Unexpected message received.\n");
> > +			break;
> > +		}
> > +
> > +		return TestFail;
> > +	}
> > +
> > +private:
> > +	void gstreamer_print_error(GstMessage *msg)
> > +	{
> > +		GError *err;
> 
> This can use:
> 
>      g_autoptr(GError) err = NULL;
> 
> > +		gchar *debug_info;
> 
> and
> 
> g_autofree char *debug_info = NULL;
> 
> > +
> > +		gst_message_parse_error(msg, &err, &debug_info);
> > +		g_printerr("Error received from element %s: %s\n",
> > +			   GST_OBJECT_NAME(msg->src), err->message);
> > +		g_printerr("Debugging information: %s\n",
> > +			   debug_info ? debug_info : "none");
> > +		g_clear_error(&err);
> > +		g_free(debug_info);
> 
> So, that will lead us to dropping manual free of err and debug info
> 
> 
> Minor things, so:
> 
> Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> 
> > +	}
> > +
> > +	GstElement *pipeline_;
> > +	GstElement *libcameraSrc_;
> > +	GstElement *convert0_;
> > +	GstElement *sink0_;
> > +};
> > +
> > +TEST_REGISTER(GstreamerSingleStreamTest)
> > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > new file mode 100644
> > index 000000000000..b99aa0da0ba3
> > --- /dev/null
> > +++ b/test/gstreamer/meson.build
> > @@ -0,0 +1,19 @@
> > +# SPDX-License-Identifier: CC0-1.0
> > +
> > +if not gst_enabled
> > +    subdir_done()
> > +endif
> > +
> > +gstreamer_tests = [
> > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > +]
> > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > +
> > +foreach t : gstreamer_tests
> > +    exe = executable(t[0], t[1],
> > +                     dependencies : [libcamera_private, gstreamer_dep],
> > +                     link_with : test_libraries,
> > +                     include_directories : test_includes_internal)
> > +
> > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > +endforeach
> > diff --git a/test/meson.build b/test/meson.build
> > index 3bceb5df586f..d0466f17d7b6 100644
> > --- a/test/meson.build
> > +++ b/test/meson.build
> > @@ -11,6 +11,7 @@ subdir('libtest')
> >   
> >   subdir('camera')
> >   subdir('controls')
> > +subdir('gstreamer')
> >   subdir('ipa')
> >   subdir('ipc')
> >   subdir('log')
Vedant Paranjape Aug. 16, 2021, 10:31 a.m. UTC | #5
Hi Laurent and Umang,
Yes, I'll take a look soon, just finishing with classes.

Regards,
Vedant Paranjape


On Mon, 16 Aug, 2021, 15:03 Laurent Pinchart, <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Umang,
>
> On Mon, Aug 16, 2021 at 11:17:27AM +0530, Umang Jain wrote:
> > Hi Vedant and Laurent,
> >
> > Thanks for the patch. Overall looks good to me,
> >
> > a few comments below for cleanup paths :)
>
> The patch has already been merged I'm afraid.
>
> Vedant, could you read these comments, and either submit follow-up
> patches, or reply if you disagree ?
>
> > On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > >
> > > This patch adds a test to test if single stream using libcamera's
> > > gstreamer element works.
> > >
> > > We need to work around two distinct issues with ASan when enabled in
> the
> > > build:
> > >
> > > - glib has a known leak at initialization time. This is covered by the
> > >    suppression file shipped with glib, but it's not clear how to use it
> > >    automatically. For now, disable leak detection to avoid test
> failures.
> > >
> > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > >    compiled without ASan (which is likely) but libcamera is,
> dlopen()ing
> > >    the libcamera plugin will cause an ASan link order verification
> > >    failure. Disable the verification child processes to work around the
> > >    problem. This requires gcc 8 or newer.
> > >
> > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > > This version incorporates changes coming from my review of v10, and
> > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > > independent reasons as explained in the commit message. I'd like to
> find
> > > a way to use leak suppression files to handle the glib initialization
> > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > is acceptable in my opinion.
> > >
> > > The biggest trouble with these workarounds is that they don't work with
> > > gcc version older than 8. As the stable version of the most common
> Linux
> > > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > > is the oldest version that libcamera supports) is also acceptable in my
> > > opinion.
> > >
> > > Changes since v10:
> > >
> > > - Disable ASan leak detection
> > > - Disable ASan link order verification for child processes
> > > - Include source_path.h
> > > - Remove unneeded explicit std::string construction
> > > - Declare variables at usage site
> > > - Make constants constexpr
> > > - Add a variable for the message type
> > > - Blank space fixes
> > > ---
> > >   .../gstreamer_single_stream_test.cpp          | 188
> ++++++++++++++++++
> > >   test/gstreamer/meson.build                    |  19 ++
> > >   test/meson.build                              |   1 +
> > >   3 files changed, 208 insertions(+)
> > >   create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > >   create mode 100644 test/gstreamer/meson.build
> > >
> > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
> b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > new file mode 100644
> > > index 000000000000..e26673b3471a
> > > --- /dev/null
> > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > @@ -0,0 +1,188 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Vedant Paranjape
> > > + *
> > > + * ipa_interface_test.cpp - Test the IPA interface
> > > + */
> > > +
> > > +#include <iostream>
> > > +#include <unistd.h>
> > > +
> > > +#include <libcamera/base/utils.h>
> > > +
> > > +#include "libcamera/internal/source_paths.h"
> > > +
> > > +#include <gst/gst.h>
> > > +
> > > +#include "test.h"
> > > +
> > > +using namespace std;
> > > +
> > > +extern "C" {
> > > +const char *__asan_default_options()
> > > +{
> > > +   /*
> > > +    * Disable leak detection due to a known global variable
> initialization
> > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > +    * using a suppression file instead of disabling leak detection.
> > > +    */
> > > +   return "detect_leaks=false";
> > > +}
> > > +}
> > > +
> > > +class GstreamerSingleStreamTest : public Test
> > > +{
> > > +protected:
> > > +   int init() override
> > > +   {
> > > +           /*
> > > +            * GStreamer spawns a process to run the gst-plugin-scanner
> > > +            * helper. If libcamera is compiled with ASan enabled, and
> as
> > > +            * GStreamer is most likely not, this will cause the ASan
> link
> > > +            * order check to fail when gst-plugin-scanner dlopen()s
> the
> > > +            * plugin as many libraries will have already been loaded
> by
> > > +            * then. Work around this issue by disabling the link order
> > > +            * check. This will only affect child processes, as ASan is
> > > +            * already loaded for this process by the time this code is
> > > +            * executed, and should thus hopefully be safe.
> > > +            *
> > > +            * This option is not available in gcc older than 8, the
> only
> > > +            * option in that case is to skip the test.
> > > +            */
> > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__
> < 8
> > > +           return TestSkip;
> > > +#endif
> > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > > +
> > > +           /* Initialize GStreamer */
> > > +           GError *errInit = nullptr;
> >
> > One can use:
> >
> >      g_autoptr(GError) errInit = NULL;
> >
> > ...
> >
> > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > +                   g_printerr("Could not initialize GStreamer: %s\n",
> > > +                              errInit ? errInit->message : "unknown
> error");
> > > +                   if (errInit)
> > > +                           g_error_free(errInit);
> >
> > And remove manual free here.
> >
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /*
> > > +            * Remove the system libcamera plugin, if any, and add the
> > > +            * plugin from the build directory.
> > > +            */
> > > +           GstRegistry *registry = gst_registry_get();
> > > +           GstPlugin *plugin = gst_registry_lookup(registry,
> "libgstlibcamera.so");
> > > +           if (plugin) {
> > > +                   gst_registry_remove_plugin(registry, plugin);
> > > +                   gst_object_unref(plugin);
> > > +           }
> > > +
> > > +           std::string path = libcamera::utils::libcameraBuildPath()
> > > +                            + "src/gstreamer";
> > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > +                   g_printerr("Failed to add plugin to registry\n");
> > > +                   gst_deinit();
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Create the elements */
> > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc",
> "libcamera");
> > > +           convert0_ = gst_element_factory_make("videoconvert",
> "convert0");
> > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > > +
> > > +           /* Create the empty pipeline_ */
> > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > +
> > > +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_)
> {
> > > +                   g_printerr("Not all elements could be created.
> %p.%p.%p.%p\n",
> > > +                              pipeline_, convert0_, sink0_,
> libcameraSrc_);
> > > +                   if (pipeline_)
> > > +                           gst_object_unref(pipeline_);
> > > +                   if (convert0_)
> > > +                           gst_object_unref(convert0_);
> > > +                   if (sink0_)
> > > +                           gst_object_unref(sink0_);
> > > +                   if (libcameraSrc_)
> > > +                           gst_object_unref(libcameraSrc_);
> > > +                   gst_deinit();
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           return TestPass;
> > > +   }
> > > +
> > > +   void cleanup() override
> > > +   {
> > > +           gst_object_unref(pipeline_);
> > > +           gst_deinit();
> > > +   }
> > > +
> > > +   int run() override
> > > +   {
> > > +           GstStateChangeReturn ret;
> > > +
> > > +           /* Build the pipeline */
> > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> convert0_, sink0_, NULL);
> > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
> sink0_, NULL) != TRUE) {
> > > +                   g_printerr("Elements could not be linked.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Start playing */
> > > +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > +                   g_printerr("Unable to set the pipeline to the
> playing state.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > +           constexpr GstMessageType msgType =
> > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR |
> GST_MESSAGE_EOS);
> > > +           constexpr GstClockTime timeout = 2000000000;
> > > +
> > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > +           g_autoptr(GstMessage) msg =
> gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > +
> > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > +
> > > +           /* Parse error message */
> > > +           if (msg == NULL)
> > > +                   return TestPass;
> > > +
> > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > +           case GST_MESSAGE_ERROR:
> > > +                   gstreamer_print_error(msg);
> > > +                   break;
> > > +           case GST_MESSAGE_EOS:
> > > +                   g_print("End-Of-Stream reached.\n");
> >
> > EOS doesn't sound like a error/Test-fail condition to me. If there is a
> > possibility that EOS will be reached before timeout, what's ensures(in
> > the first place) that the pipeline will be in playing state for entire
> > timeout, before we request masked EOS/Error messages from the bus? What
> > am I missing?
> >
> > > +                   break;
> > > +           default:
> > > +                   g_printerr("Unexpected message received.\n");
> > > +                   break;
> > > +           }
> > > +
> > > +           return TestFail;
> > > +   }
> > > +
> > > +private:
> > > +   void gstreamer_print_error(GstMessage *msg)
> > > +   {
> > > +           GError *err;
> >
> > This can use:
> >
> >      g_autoptr(GError) err = NULL;
> >
> > > +           gchar *debug_info;
> >
> > and
> >
> > g_autofree char *debug_info = NULL;
> >
> > > +
> > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > +           g_printerr("Error received from element %s: %s\n",
> > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > +           g_printerr("Debugging information: %s\n",
> > > +                      debug_info ? debug_info : "none");
> > > +           g_clear_error(&err);
> > > +           g_free(debug_info);
> >
> > So, that will lead us to dropping manual free of err and debug info
> >
> >
> > Minor things, so:
> >
> > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> >
> > > +   }
> > > +
> > > +   GstElement *pipeline_;
> > > +   GstElement *libcameraSrc_;
> > > +   GstElement *convert0_;
> > > +   GstElement *sink0_;
> > > +};
> > > +
> > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > new file mode 100644
> > > index 000000000000..b99aa0da0ba3
> > > --- /dev/null
> > > +++ b/test/gstreamer/meson.build
> > > @@ -0,0 +1,19 @@
> > > +# SPDX-License-Identifier: CC0-1.0
> > > +
> > > +if not gst_enabled
> > > +    subdir_done()
> > > +endif
> > > +
> > > +gstreamer_tests = [
> > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > +]
> > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > +
> > > +foreach t : gstreamer_tests
> > > +    exe = executable(t[0], t[1],
> > > +                     dependencies : [libcamera_private,
> gstreamer_dep],
> > > +                     link_with : test_libraries,
> > > +                     include_directories : test_includes_internal)
> > > +
> > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > +endforeach
> > > diff --git a/test/meson.build b/test/meson.build
> > > index 3bceb5df586f..d0466f17d7b6 100644
> > > --- a/test/meson.build
> > > +++ b/test/meson.build
> > > @@ -11,6 +11,7 @@ subdir('libtest')
> > >
> > >   subdir('camera')
> > >   subdir('controls')
> > > +subdir('gstreamer')
> > >   subdir('ipa')
> > >   subdir('ipc')
> > >   subdir('log')
>
> --
> Regards,
>
> Laurent Pinchart
>
Vedant Paranjape Aug. 16, 2021, 1:34 p.m. UTC | #6
Hi Umang,
Thanks for the Review. I feel the changes are appropriate. I will send a
patch with the changes.

On Mon, Aug 16, 2021 at 11:17 AM Umang Jain <umang.jain@ideasonboard.com>
wrote:

> Hi Vedant and Laurent,
>
> Thanks for the patch. Overall looks good to me,
>
> a few comments below for cleanup paths :)
> On 8/14/21 5:16 AM, Laurent Pinchart wrote:
>
> From: Vedant Paranjape <vedantparanjape160201@gmail.com> <vedantparanjape160201@gmail.com>
>
> This patch adds a test to test if single stream using libcamera's
> gstreamer element works.
>
> We need to work around two distinct issues with ASan when enabled in the
> build:
>
> - glib has a known leak at initialization time. This is covered by the
>   suppression file shipped with glib, but it's not clear how to use it
>   automatically. For now, disable leak detection to avoid test failures.
>
> - GStreamer spawns a child process to scan plugins. If GStreamer is
>   compiled without ASan (which is likely) but libcamera is, dlopen()ing
>   the libcamera plugin will cause an ASan link order verification
>   failure. Disable the verification child processes to work around the
>   problem. This requires gcc 8 or newer.
>
> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com> <vedantparanjape160201@gmail.com>
> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> <paul.elder@ideasonboard.com>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> <kieran.bingham@ideasonboard.com>
> Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com> <kieran.bingham@@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> <laurent.pinchart@ideasonboard.com>
> ---
> This version incorporates changes coming from my review of v10, and
> fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> independent reasons as explained in the commit message. I'd like to find
> a way to use leak suppression files to handle the glib initialization
> leak, but that's a big rabbit hole. The workaround for the second issue
> is acceptable in my opinion.
>
> The biggest trouble with these workarounds is that they don't work with
> gcc version older than 8. As the stable version of the most common Linux
> distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> is the oldest version that libcamera supports) is also acceptable in my
> opinion.
>
> Changes since v10:
>
> - Disable ASan leak detection
> - Disable ASan link order verification for child processes
> - Include source_path.h
> - Remove unneeded explicit std::string construction
> - Declare variables at usage site
> - Make constants constexpr
> - Add a variable for the message type
> - Blank space fixes
> ---
>  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
>  test/gstreamer/meson.build                    |  19 ++
>  test/meson.build                              |   1 +
>  3 files changed, 208 insertions(+)
>  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
>  create mode 100644 test/gstreamer/meson.build
>
> diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> new file mode 100644
> index 000000000000..e26673b3471a
> --- /dev/null
> +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> @@ -0,0 +1,188 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2021, Vedant Paranjape
> + *
> + * ipa_interface_test.cpp - Test the IPA interface
> + */
> +
> +#include <iostream>
> +#include <unistd.h>
> +
> +#include <libcamera/base/utils.h>
> +
> +#include "libcamera/internal/source_paths.h"
> +
> +#include <gst/gst.h>
> +
> +#include "test.h"
> +
> +using namespace std;
> +
> +extern "C" {
> +const char *__asan_default_options()
> +{
> +	/*
> +	 * Disable leak detection due to a known global variable initialization
> +	 * leak in glib's g_quark_init(). This should ideally be handled by
> +	 * using a suppression file instead of disabling leak detection.
> +	 */
> +	return "detect_leaks=false";
> +}
> +}
> +
> +class GstreamerSingleStreamTest : public Test
> +{
> +protected:
> +	int init() override
> +	{
> +		/*
> +		 * GStreamer spawns a process to run the gst-plugin-scanner
> +		 * helper. If libcamera is compiled with ASan enabled, and as
> +		 * GStreamer is most likely not, this will cause the ASan link
> +		 * order check to fail when gst-plugin-scanner dlopen()s the
> +		 * plugin as many libraries will have already been loaded by
> +		 * then. Work around this issue by disabling the link order
> +		 * check. This will only affect child processes, as ASan is
> +		 * already loaded for this process by the time this code is
> +		 * executed, and should thus hopefully be safe.
> +		 *
> +		 * This option is not available in gcc older than 8, the only
> +		 * option in that case is to skip the test.
> +		 */
> +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> +		return TestSkip;
> +#endif
> +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> +
> +		/* Initialize GStreamer */
> +		GError *errInit = nullptr;
>
> One can use:
>
>     g_autoptr(GError) errInit = NULL;
>
> ...
>
> +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> +			g_printerr("Could not initialize GStreamer: %s\n",
> +				   errInit ? errInit->message : "unknown error");
> +			if (errInit)
> +				g_error_free(errInit);
>
> And remove manual free here.
>
> +
> +			return TestFail;
> +		}
> +
> +		/*
> +		 * Remove the system libcamera plugin, if any, and add the
> +		 * plugin from the build directory.
> +		 */
> +		GstRegistry *registry = gst_registry_get();
> +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> +		if (plugin) {
> +			gst_registry_remove_plugin(registry, plugin);
> +			gst_object_unref(plugin);
> +		}
> +
> +		std::string path = libcamera::utils::libcameraBuildPath()
> +				 + "src/gstreamer";
> +		if (!gst_registry_scan_path(registry, path.c_str())) {
> +			g_printerr("Failed to add plugin to registry\n");
> +			gst_deinit();
> +			return TestFail;
> +		}
> +
> +		/* Create the elements */
> +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> +		sink0_ = gst_element_factory_make("fakesink", "sink0");
> +
> +		/* Create the empty pipeline_ */
> +		pipeline_ = gst_pipeline_new("test-pipeline");
> +
> +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> +			if (pipeline_)
> +				gst_object_unref(pipeline_);
> +			if (convert0_)
> +				gst_object_unref(convert0_);
> +			if (sink0_)
> +				gst_object_unref(sink0_);
> +			if (libcameraSrc_)
> +				gst_object_unref(libcameraSrc_);
> +			gst_deinit();
> +
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +	void cleanup() override
> +	{
> +		gst_object_unref(pipeline_);
> +		gst_deinit();
> +	}
> +
> +	int run() override
> +	{
> +		GstStateChangeReturn ret;
> +
> +		/* Build the pipeline */
> +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> +			g_printerr("Elements could not be linked.\n");
> +			return TestFail;
> +		}
> +
> +		/* Start playing */
> +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> +		if (ret == GST_STATE_CHANGE_FAILURE) {
> +			g_printerr("Unable to set the pipeline to the playing state.\n");
> +			return TestFail;
> +		}
> +
> +		/* Wait until error or EOS or timeout after 2 seconds */
> +		constexpr GstMessageType msgType =
> +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> +		constexpr GstClockTime timeout = 2000000000;
> +
> +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> +
> +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> +
> +		/* Parse error message */
> +		if (msg == NULL)
> +			return TestPass;
> +
> +		switch (GST_MESSAGE_TYPE(msg)) {
> +		case GST_MESSAGE_ERROR:
> +			gstreamer_print_error(msg);
> +			break;
> +		case GST_MESSAGE_EOS:
> +			g_print("End-Of-Stream reached.\n");
>
> EOS doesn't sound like a error/Test-fail condition to me. If there is a
> possibility that EOS will be reached before timeout, what's ensures(in the
> first place) that the pipeline will be in playing state for entire timeout,
> before we request masked EOS/Error messages from the bus? What am I missing?
>

I am not a gstreamer expert, to the best of my knowledge, EOS won't be ever
sent by libcamerasrc, as it is the element which drives the pipeline. I
think this event can be dropped, I just want to confirm it with Nicolas
once he's back.

> It is important to note that *only elements driving the pipeline should
ever send an EOS event*. If your element is chain-based, it is not driving
the pipeline. Chain-based elements should just return GST_FLOW_EOS from
their chain function at the end of the stream (or the configured segment)

https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos

+			break;
> +		default:
> +			g_printerr("Unexpected message received.\n");
> +			break;
> +		}
> +
> +		return TestFail;
> +	}
> +
> +private:
> +	void gstreamer_print_error(GstMessage *msg)
> +	{
> +		GError *err;
>
> This can use:
>
>     g_autoptr(GError) err = NULL;
>
> +		gchar *debug_info;
>
> and
>
>      g_autofree char *debug_info = NULL;
>
> +
> +		gst_message_parse_error(msg, &err, &debug_info);
> +		g_printerr("Error received from element %s: %s\n",
> +			   GST_OBJECT_NAME(msg->src), err->message);
> +		g_printerr("Debugging information: %s\n",
> +			   debug_info ? debug_info : "none");
> +		g_clear_error(&err);
> +		g_free(debug_info);
>
> So, that will lead us to dropping manual free of err and debug info
>
>
> Minor things, so:
>
> Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> <umang.jain@ideasonboard.com>
>
> +	}
> +
> +	GstElement *pipeline_;
> +	GstElement *libcameraSrc_;
> +	GstElement *convert0_;
> +	GstElement *sink0_;
> +};
> +
> +TEST_REGISTER(GstreamerSingleStreamTest)
> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> new file mode 100644
> index 000000000000..b99aa0da0ba3
> --- /dev/null
> +++ b/test/gstreamer/meson.build
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +if not gst_enabled
> +    subdir_done()
> +endif
> +
> +gstreamer_tests = [
> +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> +]
> +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> +
> +foreach t : gstreamer_tests
> +    exe = executable(t[0], t[1],
> +                     dependencies : [libcamera_private, gstreamer_dep],
> +                     link_with : test_libraries,
> +                     include_directories : test_includes_internal)
> +
> +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> +endforeach
> diff --git a/test/meson.build b/test/meson.build
> index 3bceb5df586f..d0466f17d7b6 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -11,6 +11,7 @@ subdir('libtest')
>
>  subdir('camera')
>  subdir('controls')
> +subdir('gstreamer')
>  subdir('ipa')
>  subdir('ipc')
>  subdir('log')
>
>
Regards,
*Vedant Paranjape*
Laurent Pinchart Aug. 16, 2021, 1:58 p.m. UTC | #7
Hi Vedant,

On Mon, Aug 16, 2021 at 07:04:00PM +0530, Vedant Paranjape wrote:
> Hi Umang,
> Thanks for the Review. I feel the changes are appropriate. I will send a
> patch with the changes.

While at it, I'm wondering if it would make sense to select the vimc
camera explicitly from the libcamerasrc element instead of using
whatever camera happens to be the first. It would make tests more
reproducible.

> On Mon, Aug 16, 2021 at 11:17 AM Umang Jain wrote:
> 
> > Hi Vedant and Laurent,
> >
> > Thanks for the patch. Overall looks good to me,
> >
> > a few comments below for cleanup paths :)
> > On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> >
> > From: Vedant Paranjape <vedantparanjape160201@gmail.com> <vedantparanjape160201@gmail.com>
> >
> > This patch adds a test to test if single stream using libcamera's
> > gstreamer element works.
> >
> > We need to work around two distinct issues with ASan when enabled in the
> > build:
> >
> > - glib has a known leak at initialization time. This is covered by the
> >   suppression file shipped with glib, but it's not clear how to use it
> >   automatically. For now, disable leak detection to avoid test failures.
> >
> > - GStreamer spawns a child process to scan plugins. If GStreamer is
> >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> >   the libcamera plugin will cause an ASan link order verification
> >   failure. Disable the verification child processes to work around the
> >   problem. This requires gcc 8 or newer.
> >
> > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com> <vedantparanjape160201@gmail.com>
> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> <paul.elder@ideasonboard.com>
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> <kieran.bingham@ideasonboard.com>
> > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com> <kieran.bingham@@ideasonboard.com>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> <laurent.pinchart@ideasonboard.com>
> > ---
> > This version incorporates changes coming from my review of v10, and
> > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > independent reasons as explained in the commit message. I'd like to find
> > a way to use leak suppression files to handle the glib initialization
> > leak, but that's a big rabbit hole. The workaround for the second issue
> > is acceptable in my opinion.
> >
> > The biggest trouble with these workarounds is that they don't work with
> > gcc version older than 8. As the stable version of the most common Linux
> > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > is the oldest version that libcamera supports) is also acceptable in my
> > opinion.
> >
> > Changes since v10:
> >
> > - Disable ASan leak detection
> > - Disable ASan link order verification for child processes
> > - Include source_path.h
> > - Remove unneeded explicit std::string construction
> > - Declare variables at usage site
> > - Make constants constexpr
> > - Add a variable for the message type
> > - Blank space fixes
> > ---
> >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> >  test/gstreamer/meson.build                    |  19 ++
> >  test/meson.build                              |   1 +
> >  3 files changed, 208 insertions(+)
> >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> >  create mode 100644 test/gstreamer/meson.build
> >
> > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > new file mode 100644
> > index 000000000000..e26673b3471a
> > --- /dev/null
> > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > @@ -0,0 +1,188 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2021, Vedant Paranjape
> > + *
> > + * ipa_interface_test.cpp - Test the IPA interface
> > + */
> > +
> > +#include <iostream>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/base/utils.h>
> > +
> > +#include "libcamera/internal/source_paths.h"
> > +
> > +#include <gst/gst.h>
> > +
> > +#include "test.h"
> > +
> > +using namespace std;
> > +
> > +extern "C" {
> > +const char *__asan_default_options()
> > +{
> > +	/*
> > +	 * Disable leak detection due to a known global variable initialization
> > +	 * leak in glib's g_quark_init(). This should ideally be handled by
> > +	 * using a suppression file instead of disabling leak detection.
> > +	 */
> > +	return "detect_leaks=false";
> > +}
> > +}
> > +
> > +class GstreamerSingleStreamTest : public Test
> > +{
> > +protected:
> > +	int init() override
> > +	{
> > +		/*
> > +		 * GStreamer spawns a process to run the gst-plugin-scanner
> > +		 * helper. If libcamera is compiled with ASan enabled, and as
> > +		 * GStreamer is most likely not, this will cause the ASan link
> > +		 * order check to fail when gst-plugin-scanner dlopen()s the
> > +		 * plugin as many libraries will have already been loaded by
> > +		 * then. Work around this issue by disabling the link order
> > +		 * check. This will only affect child processes, as ASan is
> > +		 * already loaded for this process by the time this code is
> > +		 * executed, and should thus hopefully be safe.
> > +		 *
> > +		 * This option is not available in gcc older than 8, the only
> > +		 * option in that case is to skip the test.
> > +		 */
> > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> > +		return TestSkip;
> > +#endif
> > +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > +
> > +		/* Initialize GStreamer */
> > +		GError *errInit = nullptr;
> >
> > One can use:
> >
> >     g_autoptr(GError) errInit = NULL;
> >
> > ...
> >
> > +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > +			g_printerr("Could not initialize GStreamer: %s\n",
> > +				   errInit ? errInit->message : "unknown error");
> > +			if (errInit)
> > +				g_error_free(errInit);
> >
> > And remove manual free here.
> >
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		/*
> > +		 * Remove the system libcamera plugin, if any, and add the
> > +		 * plugin from the build directory.
> > +		 */
> > +		GstRegistry *registry = gst_registry_get();
> > +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > +		if (plugin) {
> > +			gst_registry_remove_plugin(registry, plugin);
> > +			gst_object_unref(plugin);
> > +		}
> > +
> > +		std::string path = libcamera::utils::libcameraBuildPath()
> > +				 + "src/gstreamer";
> > +		if (!gst_registry_scan_path(registry, path.c_str())) {
> > +			g_printerr("Failed to add plugin to registry\n");
> > +			gst_deinit();
> > +			return TestFail;
> > +		}
> > +
> > +		/* Create the elements */
> > +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > +		sink0_ = gst_element_factory_make("fakesink", "sink0");
> > +
> > +		/* Create the empty pipeline_ */
> > +		pipeline_ = gst_pipeline_new("test-pipeline");
> > +
> > +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> > +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> > +			if (pipeline_)
> > +				gst_object_unref(pipeline_);
> > +			if (convert0_)
> > +				gst_object_unref(convert0_);
> > +			if (sink0_)
> > +				gst_object_unref(sink0_);
> > +			if (libcameraSrc_)
> > +				gst_object_unref(libcameraSrc_);
> > +			gst_deinit();
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		return TestPass;
> > +	}
> > +
> > +	void cleanup() override
> > +	{
> > +		gst_object_unref(pipeline_);
> > +		gst_deinit();
> > +	}
> > +
> > +	int run() override
> > +	{
> > +		GstStateChangeReturn ret;
> > +
> > +		/* Build the pipeline */
> > +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> > +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > +			g_printerr("Elements could not be linked.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Start playing */
> > +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > +		if (ret == GST_STATE_CHANGE_FAILURE) {
> > +			g_printerr("Unable to set the pipeline to the playing state.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Wait until error or EOS or timeout after 2 seconds */
> > +		constexpr GstMessageType msgType =
> > +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > +		constexpr GstClockTime timeout = 2000000000;
> > +
> > +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > +
> > +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> > +
> > +		/* Parse error message */
> > +		if (msg == NULL)
> > +			return TestPass;
> > +
> > +		switch (GST_MESSAGE_TYPE(msg)) {
> > +		case GST_MESSAGE_ERROR:
> > +			gstreamer_print_error(msg);
> > +			break;
> > +		case GST_MESSAGE_EOS:
> > +			g_print("End-Of-Stream reached.\n");
> >
> > EOS doesn't sound like a error/Test-fail condition to me. If there is a
> > possibility that EOS will be reached before timeout, what's ensures(in the
> > first place) that the pipeline will be in playing state for entire timeout,
> > before we request masked EOS/Error messages from the bus? What am I missing?
> 
> I am not a gstreamer expert, to the best of my knowledge, EOS won't be ever
> sent by libcamerasrc, as it is the element which drives the pipeline. I
> think this event can be dropped, I just want to confirm it with Nicolas
> once he's back.
> 
> > It is important to note that *only elements driving the pipeline should
> ever send an EOS event*. If your element is chain-based, it is not driving
> the pipeline. Chain-based elements should just return GST_FLOW_EOS from
> their chain function at the end of the stream (or the configured segment)
> 
> https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
> 
> +			break;
> > +		default:
> > +			g_printerr("Unexpected message received.\n");
> > +			break;
> > +		}
> > +
> > +		return TestFail;
> > +	}
> > +
> > +private:
> > +	void gstreamer_print_error(GstMessage *msg)
> > +	{
> > +		GError *err;
> >
> > This can use:
> >
> >     g_autoptr(GError) err = NULL;
> >
> > +		gchar *debug_info;
> >
> > and
> >
> >      g_autofree char *debug_info = NULL;
> >
> > +
> > +		gst_message_parse_error(msg, &err, &debug_info);
> > +		g_printerr("Error received from element %s: %s\n",
> > +			   GST_OBJECT_NAME(msg->src), err->message);
> > +		g_printerr("Debugging information: %s\n",
> > +			   debug_info ? debug_info : "none");
> > +		g_clear_error(&err);
> > +		g_free(debug_info);
> >
> > So, that will lead us to dropping manual free of err and debug info
> >
> >
> > Minor things, so:
> >
> > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> >
> > +	}
> > +
> > +	GstElement *pipeline_;
> > +	GstElement *libcameraSrc_;
> > +	GstElement *convert0_;
> > +	GstElement *sink0_;
> > +};
> > +
> > +TEST_REGISTER(GstreamerSingleStreamTest)
> > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > new file mode 100644
> > index 000000000000..b99aa0da0ba3
> > --- /dev/null
> > +++ b/test/gstreamer/meson.build
> > @@ -0,0 +1,19 @@
> > +# SPDX-License-Identifier: CC0-1.0
> > +
> > +if not gst_enabled
> > +    subdir_done()
> > +endif
> > +
> > +gstreamer_tests = [
> > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > +]
> > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > +
> > +foreach t : gstreamer_tests
> > +    exe = executable(t[0], t[1],
> > +                     dependencies : [libcamera_private, gstreamer_dep],
> > +                     link_with : test_libraries,
> > +                     include_directories : test_includes_internal)
> > +
> > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > +endforeach
> > diff --git a/test/meson.build b/test/meson.build
> > index 3bceb5df586f..d0466f17d7b6 100644
> > --- a/test/meson.build
> > +++ b/test/meson.build
> > @@ -11,6 +11,7 @@ subdir('libtest')
> >
> >  subdir('camera')
> >  subdir('controls')
> > +subdir('gstreamer')
> >  subdir('ipa')
> >  subdir('ipc')
> >  subdir('log')
Vedant Paranjape Aug. 16, 2021, 2:14 p.m. UTC | #8
Hi Laurent,
Yes, what's the camera-name with which vimc camera can be selected ?
I could simply set the object property to use it. I think it would be nice
to have two separate tests, one which runs on real camera and other one on
vimc camera.

Regards,
*Vedant Paranjape*

On Mon, Aug 16, 2021 at 7:29 PM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Vedant,
>
> On Mon, Aug 16, 2021 at 07:04:00PM +0530, Vedant Paranjape wrote:
> > Hi Umang,
> > Thanks for the Review. I feel the changes are appropriate. I will send a
> > patch with the changes.
>
> While at it, I'm wondering if it would make sense to select the vimc
> camera explicitly from the libcamerasrc element instead of using
> whatever camera happens to be the first. It would make tests more
> reproducible.
>
> > On Mon, Aug 16, 2021 at 11:17 AM Umang Jain wrote:
> >
> > > Hi Vedant and Laurent,
> > >
> > > Thanks for the patch. Overall looks good to me,
> > >
> > > a few comments below for cleanup paths :)
> > > On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> > >
> > > From: Vedant Paranjape <vedantparanjape160201@gmail.com> <
> vedantparanjape160201@gmail.com>
> > >
> > > This patch adds a test to test if single stream using libcamera's
> > > gstreamer element works.
> > >
> > > We need to work around two distinct issues with ASan when enabled in
> the
> > > build:
> > >
> > > - glib has a known leak at initialization time. This is covered by the
> > >   suppression file shipped with glib, but it's not clear how to use it
> > >   automatically. For now, disable leak detection to avoid test
> failures.
> > >
> > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> > >   the libcamera plugin will cause an ASan link order verification
> > >   failure. Disable the verification child processes to work around the
> > >   problem. This requires gcc 8 or newer.
> > >
> > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com> <
> vedantparanjape160201@gmail.com>
> > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> <
> paul.elder@ideasonboard.com>
> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> <
> kieran.bingham@ideasonboard.com>
> > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> <kieran.bingham@@ideasonboard.com>
> > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> <
> laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> <
> laurent.pinchart@ideasonboard.com>
> > > ---
> > > This version incorporates changes coming from my review of v10, and
> > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > > independent reasons as explained in the commit message. I'd like to
> find
> > > a way to use leak suppression files to handle the glib initialization
> > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > is acceptable in my opinion.
> > >
> > > The biggest trouble with these workarounds is that they don't work with
> > > gcc version older than 8. As the stable version of the most common
> Linux
> > > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > > is the oldest version that libcamera supports) is also acceptable in my
> > > opinion.
> > >
> > > Changes since v10:
> > >
> > > - Disable ASan leak detection
> > > - Disable ASan link order verification for child processes
> > > - Include source_path.h
> > > - Remove unneeded explicit std::string construction
> > > - Declare variables at usage site
> > > - Make constants constexpr
> > > - Add a variable for the message type
> > > - Blank space fixes
> > > ---
> > >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> > >  test/gstreamer/meson.build                    |  19 ++
> > >  test/meson.build                              |   1 +
> > >  3 files changed, 208 insertions(+)
> > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > >  create mode 100644 test/gstreamer/meson.build
> > >
> > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
> b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > new file mode 100644
> > > index 000000000000..e26673b3471a
> > > --- /dev/null
> > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > @@ -0,0 +1,188 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Vedant Paranjape
> > > + *
> > > + * ipa_interface_test.cpp - Test the IPA interface
> > > + */
> > > +
> > > +#include <iostream>
> > > +#include <unistd.h>
> > > +
> > > +#include <libcamera/base/utils.h>
> > > +
> > > +#include "libcamera/internal/source_paths.h"
> > > +
> > > +#include <gst/gst.h>
> > > +
> > > +#include "test.h"
> > > +
> > > +using namespace std;
> > > +
> > > +extern "C" {
> > > +const char *__asan_default_options()
> > > +{
> > > +   /*
> > > +    * Disable leak detection due to a known global variable
> initialization
> > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > +    * using a suppression file instead of disabling leak detection.
> > > +    */
> > > +   return "detect_leaks=false";
> > > +}
> > > +}
> > > +
> > > +class GstreamerSingleStreamTest : public Test
> > > +{
> > > +protected:
> > > +   int init() override
> > > +   {
> > > +           /*
> > > +            * GStreamer spawns a process to run the gst-plugin-scanner
> > > +            * helper. If libcamera is compiled with ASan enabled, and
> as
> > > +            * GStreamer is most likely not, this will cause the ASan
> link
> > > +            * order check to fail when gst-plugin-scanner dlopen()s
> the
> > > +            * plugin as many libraries will have already been loaded
> by
> > > +            * then. Work around this issue by disabling the link order
> > > +            * check. This will only affect child processes, as ASan is
> > > +            * already loaded for this process by the time this code is
> > > +            * executed, and should thus hopefully be safe.
> > > +            *
> > > +            * This option is not available in gcc older than 8, the
> only
> > > +            * option in that case is to skip the test.
> > > +            */
> > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__
> < 8
> > > +           return TestSkip;
> > > +#endif
> > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > > +
> > > +           /* Initialize GStreamer */
> > > +           GError *errInit = nullptr;
> > >
> > > One can use:
> > >
> > >     g_autoptr(GError) errInit = NULL;
> > >
> > > ...
> > >
> > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > +                   g_printerr("Could not initialize GStreamer: %s\n",
> > > +                              errInit ? errInit->message : "unknown
> error");
> > > +                   if (errInit)
> > > +                           g_error_free(errInit);
> > >
> > > And remove manual free here.
> > >
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /*
> > > +            * Remove the system libcamera plugin, if any, and add the
> > > +            * plugin from the build directory.
> > > +            */
> > > +           GstRegistry *registry = gst_registry_get();
> > > +           GstPlugin *plugin = gst_registry_lookup(registry,
> "libgstlibcamera.so");
> > > +           if (plugin) {
> > > +                   gst_registry_remove_plugin(registry, plugin);
> > > +                   gst_object_unref(plugin);
> > > +           }
> > > +
> > > +           std::string path = libcamera::utils::libcameraBuildPath()
> > > +                            + "src/gstreamer";
> > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > +                   g_printerr("Failed to add plugin to registry\n");
> > > +                   gst_deinit();
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Create the elements */
> > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc",
> "libcamera");
> > > +           convert0_ = gst_element_factory_make("videoconvert",
> "convert0");
> > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > > +
> > > +           /* Create the empty pipeline_ */
> > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > +
> > > +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_)
> {
> > > +                   g_printerr("Not all elements could be created.
> %p.%p.%p.%p\n",
> > > +                              pipeline_, convert0_, sink0_,
> libcameraSrc_);
> > > +                   if (pipeline_)
> > > +                           gst_object_unref(pipeline_);
> > > +                   if (convert0_)
> > > +                           gst_object_unref(convert0_);
> > > +                   if (sink0_)
> > > +                           gst_object_unref(sink0_);
> > > +                   if (libcameraSrc_)
> > > +                           gst_object_unref(libcameraSrc_);
> > > +                   gst_deinit();
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           return TestPass;
> > > +   }
> > > +
> > > +   void cleanup() override
> > > +   {
> > > +           gst_object_unref(pipeline_);
> > > +           gst_deinit();
> > > +   }
> > > +
> > > +   int run() override
> > > +   {
> > > +           GstStateChangeReturn ret;
> > > +
> > > +           /* Build the pipeline */
> > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> convert0_, sink0_, NULL);
> > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
> sink0_, NULL) != TRUE) {
> > > +                   g_printerr("Elements could not be linked.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Start playing */
> > > +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > +                   g_printerr("Unable to set the pipeline to the
> playing state.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > +           constexpr GstMessageType msgType =
> > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR |
> GST_MESSAGE_EOS);
> > > +           constexpr GstClockTime timeout = 2000000000;
> > > +
> > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > +           g_autoptr(GstMessage) msg =
> gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > +
> > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > +
> > > +           /* Parse error message */
> > > +           if (msg == NULL)
> > > +                   return TestPass;
> > > +
> > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > +           case GST_MESSAGE_ERROR:
> > > +                   gstreamer_print_error(msg);
> > > +                   break;
> > > +           case GST_MESSAGE_EOS:
> > > +                   g_print("End-Of-Stream reached.\n");
> > >
> > > EOS doesn't sound like a error/Test-fail condition to me. If there is a
> > > possibility that EOS will be reached before timeout, what's ensures(in
> the
> > > first place) that the pipeline will be in playing state for entire
> timeout,
> > > before we request masked EOS/Error messages from the bus? What am I
> missing?
> >
> > I am not a gstreamer expert, to the best of my knowledge, EOS won't be
> ever
> > sent by libcamerasrc, as it is the element which drives the pipeline. I
> > think this event can be dropped, I just want to confirm it with Nicolas
> > once he's back.
> >
> > > It is important to note that *only elements driving the pipeline should
> > ever send an EOS event*. If your element is chain-based, it is not
> driving
> > the pipeline. Chain-based elements should just return GST_FLOW_EOS from
> > their chain function at the end of the stream (or the configured segment)
> >
> >
> https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
> >
> > +                     break;
> > > +           default:
> > > +                   g_printerr("Unexpected message received.\n");
> > > +                   break;
> > > +           }
> > > +
> > > +           return TestFail;
> > > +   }
> > > +
> > > +private:
> > > +   void gstreamer_print_error(GstMessage *msg)
> > > +   {
> > > +           GError *err;
> > >
> > > This can use:
> > >
> > >     g_autoptr(GError) err = NULL;
> > >
> > > +           gchar *debug_info;
> > >
> > > and
> > >
> > >      g_autofree char *debug_info = NULL;
> > >
> > > +
> > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > +           g_printerr("Error received from element %s: %s\n",
> > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > +           g_printerr("Debugging information: %s\n",
> > > +                      debug_info ? debug_info : "none");
> > > +           g_clear_error(&err);
> > > +           g_free(debug_info);
> > >
> > > So, that will lead us to dropping manual free of err and debug info
> > >
> > >
> > > Minor things, so:
> > >
> > > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> > >
> > > +   }
> > > +
> > > +   GstElement *pipeline_;
> > > +   GstElement *libcameraSrc_;
> > > +   GstElement *convert0_;
> > > +   GstElement *sink0_;
> > > +};
> > > +
> > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > new file mode 100644
> > > index 000000000000..b99aa0da0ba3
> > > --- /dev/null
> > > +++ b/test/gstreamer/meson.build
> > > @@ -0,0 +1,19 @@
> > > +# SPDX-License-Identifier: CC0-1.0
> > > +
> > > +if not gst_enabled
> > > +    subdir_done()
> > > +endif
> > > +
> > > +gstreamer_tests = [
> > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > +]
> > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > +
> > > +foreach t : gstreamer_tests
> > > +    exe = executable(t[0], t[1],
> > > +                     dependencies : [libcamera_private,
> gstreamer_dep],
> > > +                     link_with : test_libraries,
> > > +                     include_directories : test_includes_internal)
> > > +
> > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > +endforeach
> > > diff --git a/test/meson.build b/test/meson.build
> > > index 3bceb5df586f..d0466f17d7b6 100644
> > > --- a/test/meson.build
> > > +++ b/test/meson.build
> > > @@ -11,6 +11,7 @@ subdir('libtest')
> > >
> > >  subdir('camera')
> > >  subdir('controls')
> > > +subdir('gstreamer')
> > >  subdir('ipa')
> > >  subdir('ipc')
> > >  subdir('log')
>
> --
> Regards,
>
> Laurent Pinchart
>
Kieran Bingham Aug. 16, 2021, 2:22 p.m. UTC | #9
On 16/08/2021 15:14, Vedant Paranjape wrote:
> Hi Laurent,
> Yes, what's the camera-name with which vimc camera can be selected ?
> I could simply set the object property to use it. I think it would be
> nice to have two separate tests, one which runs on real camera and other
> one on vimc camera.


Or runs on all available cameras?

--
Kieran


> Regards,
> /*Vedant Paranjape*/
> 
> On Mon, Aug 16, 2021 at 7:29 PM Laurent Pinchart
> <laurent.pinchart@ideasonboard.com
> <mailto:laurent.pinchart@ideasonboard.com>> wrote:
> 
>     Hi Vedant,
> 
>     On Mon, Aug 16, 2021 at 07:04:00PM +0530, Vedant Paranjape wrote:
>     > Hi Umang,
>     > Thanks for the Review. I feel the changes are appropriate. I will
>     send a
>     > patch with the changes.
> 
>     While at it, I'm wondering if it would make sense to select the vimc
>     camera explicitly from the libcamerasrc element instead of using
>     whatever camera happens to be the first. It would make tests more
>     reproducible.
> 
>     > On Mon, Aug 16, 2021 at 11:17 AM Umang Jain wrote:
>     >
>     > > Hi Vedant and Laurent,
>     > >
>     > > Thanks for the patch. Overall looks good to me,
>     > >
>     > > a few comments below for cleanup paths :)
>     > > On 8/14/21 5:16 AM, Laurent Pinchart wrote:
>     > >
>     > > From: Vedant Paranjape <vedantparanjape160201@gmail.com
>     <mailto:vedantparanjape160201@gmail.com>>
>     <vedantparanjape160201@gmail.com
>     <mailto:vedantparanjape160201@gmail.com>>
>     > >
>     > > This patch adds a test to test if single stream using libcamera's
>     > > gstreamer element works.
>     > >
>     > > We need to work around two distinct issues with ASan when
>     enabled in the
>     > > build:
>     > >
>     > > - glib has a known leak at initialization time. This is covered
>     by the
>     > >   suppression file shipped with glib, but it's not clear how to
>     use it
>     > >   automatically. For now, disable leak detection to avoid test
>     failures.
>     > >
>     > > - GStreamer spawns a child process to scan plugins. If GStreamer is
>     > >   compiled without ASan (which is likely) but libcamera is,
>     dlopen()ing
>     > >   the libcamera plugin will cause an ASan link order verification
>     > >   failure. Disable the verification child processes to work
>     around the
>     > >   problem. This requires gcc 8 or newer.
>     > >
>     > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com
>     <mailto:vedantparanjape160201@gmail.com>>
>     <vedantparanjape160201@gmail.com
>     <mailto:vedantparanjape160201@gmail.com>>
>     > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com
>     <mailto:paul.elder@ideasonboard.com>> <paul.elder@ideasonboard.com
>     <mailto:paul.elder@ideasonboard.com>>
>     > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com
>     <mailto:kieran.bingham@ideasonboard.com>>
>     <kieran.bingham@ideasonboard.com
>     <mailto:kieran.bingham@ideasonboard.com>>
>     > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com
>     <http://ideasonboard.com>> <kieran.bingham@@ideasonboard.com
>     <http://ideasonboard.com>>
>     > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com
>     <mailto:laurent.pinchart@ideasonboard.com>>
>     <laurent.pinchart@ideasonboard.com
>     <mailto:laurent.pinchart@ideasonboard.com>>
>     > > Signed-off-by: Laurent Pinchart
>     <laurent.pinchart@ideasonboard.com
>     <mailto:laurent.pinchart@ideasonboard.com>>
>     <laurent.pinchart@ideasonboard.com
>     <mailto:laurent.pinchart@ideasonboard.com>>
>     > > ---
>     > > This version incorporates changes coming from my review of v10, and
>     > > fixes for ASan issues. ASan is a bit of a pain with GStreamer,
>     for two
>     > > independent reasons as explained in the commit message. I'd like
>     to find
>     > > a way to use leak suppression files to handle the glib
>     initialization
>     > > leak, but that's a big rabbit hole. The workaround for the
>     second issue
>     > > is acceptable in my opinion.
>     > >
>     > > The biggest trouble with these workarounds is that they don't
>     work with
>     > > gcc version older than 8. As the stable version of the most
>     common Linux
>     > > distributions ship gcc 8 or newer, skipping this test for gcc 7
>     (which
>     > > is the oldest version that libcamera supports) is also
>     acceptable in my
>     > > opinion.
>     > >
>     > > Changes since v10:
>     > >
>     > > - Disable ASan leak detection
>     > > - Disable ASan link order verification for child processes
>     > > - Include source_path.h
>     > > - Remove unneeded explicit std::string construction
>     > > - Declare variables at usage site
>     > > - Make constants constexpr
>     > > - Add a variable for the message type
>     > > - Blank space fixes
>     > > ---
>     > >  .../gstreamer_single_stream_test.cpp          | 188
>     ++++++++++++++++++
>     > >  test/gstreamer/meson.build                    |  19 ++
>     > >  test/meson.build                              |   1 +
>     > >  3 files changed, 208 insertions(+)
>     > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
>     > >  create mode 100644 test/gstreamer/meson.build
>     > >
>     > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
>     b/test/gstreamer/gstreamer_single_stream_test.cpp
>     > > new file mode 100644
>     > > index 000000000000..e26673b3471a
>     > > --- /dev/null
>     > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
>     > > @@ -0,0 +1,188 @@
>     > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
>     > > +/*
>     > > + * Copyright (C) 2021, Vedant Paranjape
>     > > + *
>     > > + * ipa_interface_test.cpp - Test the IPA interface
>     > > + */
>     > > +
>     > > +#include <iostream>
>     > > +#include <unistd.h>
>     > > +
>     > > +#include <libcamera/base/utils.h>
>     > > +
>     > > +#include "libcamera/internal/source_paths.h"
>     > > +
>     > > +#include <gst/gst.h>
>     > > +
>     > > +#include "test.h"
>     > > +
>     > > +using namespace std;
>     > > +
>     > > +extern "C" {
>     > > +const char *__asan_default_options()
>     > > +{
>     > > +   /*
>     > > +    * Disable leak detection due to a known global variable
>     initialization
>     > > +    * leak in glib's g_quark_init(). This should ideally be
>     handled by
>     > > +    * using a suppression file instead of disabling leak detection.
>     > > +    */
>     > > +   return "detect_leaks=false";
>     > > +}
>     > > +}
>     > > +
>     > > +class GstreamerSingleStreamTest : public Test
>     > > +{
>     > > +protected:
>     > > +   int init() override
>     > > +   {
>     > > +           /*
>     > > +            * GStreamer spawns a process to run the
>     gst-plugin-scanner
>     > > +            * helper. If libcamera is compiled with ASan
>     enabled, and as
>     > > +            * GStreamer is most likely not, this will cause the
>     ASan link
>     > > +            * order check to fail when gst-plugin-scanner
>     dlopen()s the
>     > > +            * plugin as many libraries will have already been
>     loaded by
>     > > +            * then. Work around this issue by disabling the
>     link order
>     > > +            * check. This will only affect child processes, as
>     ASan is
>     > > +            * already loaded for this process by the time this
>     code is
>     > > +            * executed, and should thus hopefully be safe.
>     > > +            *
>     > > +            * This option is not available in gcc older than 8,
>     the only
>     > > +            * option in that case is to skip the test.
>     > > +            */
>     > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) &&
>     __GNUC__ < 8
>     > > +           return TestSkip;
>     > > +#endif
>     > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
>     > > +
>     > > +           /* Initialize GStreamer */
>     > > +           GError *errInit = nullptr;
>     > >
>     > > One can use:
>     > >
>     > >     g_autoptr(GError) errInit = NULL;
>     > >
>     > > ...
>     > >
>     > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
>     > > +                   g_printerr("Could not initialize GStreamer:
>     %s\n",
>     > > +                              errInit ? errInit->message :
>     "unknown error");
>     > > +                   if (errInit)
>     > > +                           g_error_free(errInit);
>     > >
>     > > And remove manual free here.
>     > >
>     > > +
>     > > +                   return TestFail;
>     > > +           }
>     > > +
>     > > +           /*
>     > > +            * Remove the system libcamera plugin, if any, and
>     add the
>     > > +            * plugin from the build directory.
>     > > +            */
>     > > +           GstRegistry *registry = gst_registry_get();
>     > > +           GstPlugin *plugin = gst_registry_lookup(registry,
>     "libgstlibcamera.so");
>     > > +           if (plugin) {
>     > > +                   gst_registry_remove_plugin(registry, plugin);
>     > > +                   gst_object_unref(plugin);
>     > > +           }
>     > > +
>     > > +           std::string path =
>     libcamera::utils::libcameraBuildPath()
>     > > +                            + "src/gstreamer";
>     > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
>     > > +                   g_printerr("Failed to add plugin to
>     registry\n");
>     > > +                   gst_deinit();
>     > > +                   return TestFail;
>     > > +           }
>     > > +
>     > > +           /* Create the elements */
>     > > +           libcameraSrc_ =
>     gst_element_factory_make("libcamerasrc", "libcamera");
>     > > +           convert0_ = gst_element_factory_make("videoconvert",
>     "convert0");
>     > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
>     > > +
>     > > +           /* Create the empty pipeline_ */
>     > > +           pipeline_ = gst_pipeline_new("test-pipeline");
>     > > +
>     > > +           if (!pipeline_ || !convert0_ || !sink0_ ||
>     !libcameraSrc_) {
>     > > +                   g_printerr("Not all elements could be
>     created. %p.%p.%p.%p\n",
>     > > +                              pipeline_, convert0_, sink0_,
>     libcameraSrc_);
>     > > +                   if (pipeline_)
>     > > +                           gst_object_unref(pipeline_);
>     > > +                   if (convert0_)
>     > > +                           gst_object_unref(convert0_);
>     > > +                   if (sink0_)
>     > > +                           gst_object_unref(sink0_);
>     > > +                   if (libcameraSrc_)
>     > > +                           gst_object_unref(libcameraSrc_);
>     > > +                   gst_deinit();
>     > > +
>     > > +                   return TestFail;
>     > > +           }
>     > > +
>     > > +           return TestPass;
>     > > +   }
>     > > +
>     > > +   void cleanup() override
>     > > +   {
>     > > +           gst_object_unref(pipeline_);
>     > > +           gst_deinit();
>     > > +   }
>     > > +
>     > > +   int run() override
>     > > +   {
>     > > +           GstStateChangeReturn ret;
>     > > +
>     > > +           /* Build the pipeline */
>     > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
>     convert0_, sink0_, NULL);
>     > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
>     sink0_, NULL) != TRUE) {
>     > > +                   g_printerr("Elements could not be linked.\n");
>     > > +                   return TestFail;
>     > > +           }
>     > > +
>     > > +           /* Start playing */
>     > > +           ret = gst_element_set_state(pipeline_,
>     GST_STATE_PLAYING);
>     > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
>     > > +                   g_printerr("Unable to set the pipeline to
>     the playing state.\n");
>     > > +                   return TestFail;
>     > > +           }
>     > > +
>     > > +           /* Wait until error or EOS or timeout after 2 seconds */
>     > > +           constexpr GstMessageType msgType =
>     > > +                 
>      static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
>     > > +           constexpr GstClockTime timeout = 2000000000;
>     > > +
>     > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
>     > > +           g_autoptr(GstMessage) msg =
>     gst_bus_timed_pop_filtered(bus, timeout, msgType);
>     > > +
>     > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
>     > > +
>     > > +           /* Parse error message */
>     > > +           if (msg == NULL)
>     > > +                   return TestPass;
>     > > +
>     > > +           switch (GST_MESSAGE_TYPE(msg)) {
>     > > +           case GST_MESSAGE_ERROR:
>     > > +                   gstreamer_print_error(msg);
>     > > +                   break;
>     > > +           case GST_MESSAGE_EOS:
>     > > +                   g_print("End-Of-Stream reached.\n");
>     > >
>     > > EOS doesn't sound like a error/Test-fail condition to me. If
>     there is a
>     > > possibility that EOS will be reached before timeout, what's
>     ensures(in the
>     > > first place) that the pipeline will be in playing state for
>     entire timeout,
>     > > before we request masked EOS/Error messages from the bus? What
>     am I missing?
>     >
>     > I am not a gstreamer expert, to the best of my knowledge, EOS
>     won't be ever
>     > sent by libcamerasrc, as it is the element which drives the
>     pipeline. I
>     > think this event can be dropped, I just want to confirm it with
>     Nicolas
>     > once he's back.
>     >
>     > > It is important to note that *only elements driving the pipeline
>     should
>     > ever send an EOS event*. If your element is chain-based, it is not
>     driving
>     > the pipeline. Chain-based elements should just return GST_FLOW_EOS
>     from
>     > their chain function at the end of the stream (or the configured
>     segment)
>     >
>     >
>     https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
>     <https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos>
>     >
>     > +                     break;
>     > > +           default:
>     > > +                   g_printerr("Unexpected message received.\n");
>     > > +                   break;
>     > > +           }
>     > > +
>     > > +           return TestFail;
>     > > +   }
>     > > +
>     > > +private:
>     > > +   void gstreamer_print_error(GstMessage *msg)
>     > > +   {
>     > > +           GError *err;
>     > >
>     > > This can use:
>     > >
>     > >     g_autoptr(GError) err = NULL;
>     > >
>     > > +           gchar *debug_info;
>     > >
>     > > and
>     > >
>     > >      g_autofree char *debug_info = NULL;
>     > >
>     > > +
>     > > +           gst_message_parse_error(msg, &err, &debug_info);
>     > > +           g_printerr("Error received from element %s: %s\n",
>     > > +                      GST_OBJECT_NAME(msg->src), err->message);
>     > > +           g_printerr("Debugging information: %s\n",
>     > > +                      debug_info ? debug_info : "none");
>     > > +           g_clear_error(&err);
>     > > +           g_free(debug_info);
>     > >
>     > > So, that will lead us to dropping manual free of err and debug info
>     > >
>     > >
>     > > Minor things, so:
>     > >
>     > > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com
>     <mailto:umang.jain@ideasonboard.com>>
>     > >
>     > > +   }
>     > > +
>     > > +   GstElement *pipeline_;
>     > > +   GstElement *libcameraSrc_;
>     > > +   GstElement *convert0_;
>     > > +   GstElement *sink0_;
>     > > +};
>     > > +
>     > > +TEST_REGISTER(GstreamerSingleStreamTest)
>     > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
>     > > new file mode 100644
>     > > index 000000000000..b99aa0da0ba3
>     > > --- /dev/null
>     > > +++ b/test/gstreamer/meson.build
>     > > @@ -0,0 +1,19 @@
>     > > +# SPDX-License-Identifier: CC0-1.0
>     > > +
>     > > +if not gst_enabled
>     > > +    subdir_done()
>     > > +endif
>     > > +
>     > > +gstreamer_tests = [
>     > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
>     > > +]
>     > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
>     > > +
>     > > +foreach t : gstreamer_tests
>     > > +    exe = executable(t[0], t[1],
>     > > +                     dependencies : [libcamera_private,
>     gstreamer_dep],
>     > > +                     link_with : test_libraries,
>     > > +                     include_directories : test_includes_internal)
>     > > +
>     > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
>     > > +endforeach
>     > > diff --git a/test/meson.build b/test/meson.build
>     > > index 3bceb5df586f..d0466f17d7b6 100644
>     > > --- a/test/meson.build
>     > > +++ b/test/meson.build
>     > > @@ -11,6 +11,7 @@ subdir('libtest')
>     > >
>     > >  subdir('camera')
>     > >  subdir('controls')
>     > > +subdir('gstreamer')
>     > >  subdir('ipa')
>     > >  subdir('ipc')
>     > >  subdir('log')
> 
>     -- 
>     Regards,
> 
>     Laurent Pinchart
>
Vedant Paranjape Aug. 16, 2021, 2:27 p.m. UTC | #10
Hi Kieran,
Sounds like a good idea. Once I figure how to use GstDeviceProvider, I'll
try to do this.

Regards,
Vedant Paranjape

On Mon, 16 Aug, 2021, 19:52 Kieran Bingham, <kieran.bingham@ideasonboard.com>
wrote:

> On 16/08/2021 15:14, Vedant Paranjape wrote:
> > Hi Laurent,
> > Yes, what's the camera-name with which vimc camera can be selected ?
> > I could simply set the object property to use it. I think it would be
> > nice to have two separate tests, one which runs on real camera and other
> > one on vimc camera.
>
>
> Or runs on all available cameras?
>
> --
> Kieran
>
>
> > Regards,
> > /*Vedant Paranjape*/
> >
> > On Mon, Aug 16, 2021 at 7:29 PM Laurent Pinchart
> > <laurent.pinchart@ideasonboard.com
> > <mailto:laurent.pinchart@ideasonboard.com>> wrote:
> >
> >     Hi Vedant,
> >
> >     On Mon, Aug 16, 2021 at 07:04:00PM +0530, Vedant Paranjape wrote:
> >     > Hi Umang,
> >     > Thanks for the Review. I feel the changes are appropriate. I will
> >     send a
> >     > patch with the changes.
> >
> >     While at it, I'm wondering if it would make sense to select the vimc
> >     camera explicitly from the libcamerasrc element instead of using
> >     whatever camera happens to be the first. It would make tests more
> >     reproducible.
> >
> >     > On Mon, Aug 16, 2021 at 11:17 AM Umang Jain wrote:
> >     >
> >     > > Hi Vedant and Laurent,
> >     > >
> >     > > Thanks for the patch. Overall looks good to me,
> >     > >
> >     > > a few comments below for cleanup paths :)
> >     > > On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> >     > >
> >     > > From: Vedant Paranjape <vedantparanjape160201@gmail.com
> >     <mailto:vedantparanjape160201@gmail.com>>
> >     <vedantparanjape160201@gmail.com
> >     <mailto:vedantparanjape160201@gmail.com>>
> >     > >
> >     > > This patch adds a test to test if single stream using libcamera's
> >     > > gstreamer element works.
> >     > >
> >     > > We need to work around two distinct issues with ASan when
> >     enabled in the
> >     > > build:
> >     > >
> >     > > - glib has a known leak at initialization time. This is covered
> >     by the
> >     > >   suppression file shipped with glib, but it's not clear how to
> >     use it
> >     > >   automatically. For now, disable leak detection to avoid test
> >     failures.
> >     > >
> >     > > - GStreamer spawns a child process to scan plugins. If GStreamer
> is
> >     > >   compiled without ASan (which is likely) but libcamera is,
> >     dlopen()ing
> >     > >   the libcamera plugin will cause an ASan link order verification
> >     > >   failure. Disable the verification child processes to work
> >     around the
> >     > >   problem. This requires gcc 8 or newer.
> >     > >
> >     > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com
> >     <mailto:vedantparanjape160201@gmail.com>>
> >     <vedantparanjape160201@gmail.com
> >     <mailto:vedantparanjape160201@gmail.com>>
> >     > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com
> >     <mailto:paul.elder@ideasonboard.com>> <paul.elder@ideasonboard.com
> >     <mailto:paul.elder@ideasonboard.com>>
> >     > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com
> >     <mailto:kieran.bingham@ideasonboard.com>>
> >     <kieran.bingham@ideasonboard.com
> >     <mailto:kieran.bingham@ideasonboard.com>>
> >     > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com
> >     <http://ideasonboard.com>> <kieran.bingham@@ideasonboard.com
> >     <http://ideasonboard.com>>
> >     > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com
> >     <mailto:laurent.pinchart@ideasonboard.com>>
> >     <laurent.pinchart@ideasonboard.com
> >     <mailto:laurent.pinchart@ideasonboard.com>>
> >     > > Signed-off-by: Laurent Pinchart
> >     <laurent.pinchart@ideasonboard.com
> >     <mailto:laurent.pinchart@ideasonboard.com>>
> >     <laurent.pinchart@ideasonboard.com
> >     <mailto:laurent.pinchart@ideasonboard.com>>
> >     > > ---
> >     > > This version incorporates changes coming from my review of v10,
> and
> >     > > fixes for ASan issues. ASan is a bit of a pain with GStreamer,
> >     for two
> >     > > independent reasons as explained in the commit message. I'd like
> >     to find
> >     > > a way to use leak suppression files to handle the glib
> >     initialization
> >     > > leak, but that's a big rabbit hole. The workaround for the
> >     second issue
> >     > > is acceptable in my opinion.
> >     > >
> >     > > The biggest trouble with these workarounds is that they don't
> >     work with
> >     > > gcc version older than 8. As the stable version of the most
> >     common Linux
> >     > > distributions ship gcc 8 or newer, skipping this test for gcc 7
> >     (which
> >     > > is the oldest version that libcamera supports) is also
> >     acceptable in my
> >     > > opinion.
> >     > >
> >     > > Changes since v10:
> >     > >
> >     > > - Disable ASan leak detection
> >     > > - Disable ASan link order verification for child processes
> >     > > - Include source_path.h
> >     > > - Remove unneeded explicit std::string construction
> >     > > - Declare variables at usage site
> >     > > - Make constants constexpr
> >     > > - Add a variable for the message type
> >     > > - Blank space fixes
> >     > > ---
> >     > >  .../gstreamer_single_stream_test.cpp          | 188
> >     ++++++++++++++++++
> >     > >  test/gstreamer/meson.build                    |  19 ++
> >     > >  test/meson.build                              |   1 +
> >     > >  3 files changed, 208 insertions(+)
> >     > >  create mode 100644
> test/gstreamer/gstreamer_single_stream_test.cpp
> >     > >  create mode 100644 test/gstreamer/meson.build
> >     > >
> >     > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
> >     b/test/gstreamer/gstreamer_single_stream_test.cpp
> >     > > new file mode 100644
> >     > > index 000000000000..e26673b3471a
> >     > > --- /dev/null
> >     > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> >     > > @@ -0,0 +1,188 @@
> >     > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> >     > > +/*
> >     > > + * Copyright (C) 2021, Vedant Paranjape
> >     > > + *
> >     > > + * ipa_interface_test.cpp - Test the IPA interface
> >     > > + */
> >     > > +
> >     > > +#include <iostream>
> >     > > +#include <unistd.h>
> >     > > +
> >     > > +#include <libcamera/base/utils.h>
> >     > > +
> >     > > +#include "libcamera/internal/source_paths.h"
> >     > > +
> >     > > +#include <gst/gst.h>
> >     > > +
> >     > > +#include "test.h"
> >     > > +
> >     > > +using namespace std;
> >     > > +
> >     > > +extern "C" {
> >     > > +const char *__asan_default_options()
> >     > > +{
> >     > > +   /*
> >     > > +    * Disable leak detection due to a known global variable
> >     initialization
> >     > > +    * leak in glib's g_quark_init(). This should ideally be
> >     handled by
> >     > > +    * using a suppression file instead of disabling leak
> detection.
> >     > > +    */
> >     > > +   return "detect_leaks=false";
> >     > > +}
> >     > > +}
> >     > > +
> >     > > +class GstreamerSingleStreamTest : public Test
> >     > > +{
> >     > > +protected:
> >     > > +   int init() override
> >     > > +   {
> >     > > +           /*
> >     > > +            * GStreamer spawns a process to run the
> >     gst-plugin-scanner
> >     > > +            * helper. If libcamera is compiled with ASan
> >     enabled, and as
> >     > > +            * GStreamer is most likely not, this will cause the
> >     ASan link
> >     > > +            * order check to fail when gst-plugin-scanner
> >     dlopen()s the
> >     > > +            * plugin as many libraries will have already been
> >     loaded by
> >     > > +            * then. Work around this issue by disabling the
> >     link order
> >     > > +            * check. This will only affect child processes, as
> >     ASan is
> >     > > +            * already loaded for this process by the time this
> >     code is
> >     > > +            * executed, and should thus hopefully be safe.
> >     > > +            *
> >     > > +            * This option is not available in gcc older than 8,
> >     the only
> >     > > +            * option in that case is to skip the test.
> >     > > +            */
> >     > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) &&
> >     __GNUC__ < 8
> >     > > +           return TestSkip;
> >     > > +#endif
> >     > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0",
> 1);
> >     > > +
> >     > > +           /* Initialize GStreamer */
> >     > > +           GError *errInit = nullptr;
> >     > >
> >     > > One can use:
> >     > >
> >     > >     g_autoptr(GError) errInit = NULL;
> >     > >
> >     > > ...
> >     > >
> >     > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> >     > > +                   g_printerr("Could not initialize GStreamer:
> >     %s\n",
> >     > > +                              errInit ? errInit->message :
> >     "unknown error");
> >     > > +                   if (errInit)
> >     > > +                           g_error_free(errInit);
> >     > >
> >     > > And remove manual free here.
> >     > >
> >     > > +
> >     > > +                   return TestFail;
> >     > > +           }
> >     > > +
> >     > > +           /*
> >     > > +            * Remove the system libcamera plugin, if any, and
> >     add the
> >     > > +            * plugin from the build directory.
> >     > > +            */
> >     > > +           GstRegistry *registry = gst_registry_get();
> >     > > +           GstPlugin *plugin = gst_registry_lookup(registry,
> >     "libgstlibcamera.so");
> >     > > +           if (plugin) {
> >     > > +                   gst_registry_remove_plugin(registry, plugin);
> >     > > +                   gst_object_unref(plugin);
> >     > > +           }
> >     > > +
> >     > > +           std::string path =
> >     libcamera::utils::libcameraBuildPath()
> >     > > +                            + "src/gstreamer";
> >     > > +           if (!gst_registry_scan_path(registry, path.c_str()))
> {
> >     > > +                   g_printerr("Failed to add plugin to
> >     registry\n");
> >     > > +                   gst_deinit();
> >     > > +                   return TestFail;
> >     > > +           }
> >     > > +
> >     > > +           /* Create the elements */
> >     > > +           libcameraSrc_ =
> >     gst_element_factory_make("libcamerasrc", "libcamera");
> >     > > +           convert0_ = gst_element_factory_make("videoconvert",
> >     "convert0");
> >     > > +           sink0_ = gst_element_factory_make("fakesink",
> "sink0");
> >     > > +
> >     > > +           /* Create the empty pipeline_ */
> >     > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> >     > > +
> >     > > +           if (!pipeline_ || !convert0_ || !sink0_ ||
> >     !libcameraSrc_) {
> >     > > +                   g_printerr("Not all elements could be
> >     created. %p.%p.%p.%p\n",
> >     > > +                              pipeline_, convert0_, sink0_,
> >     libcameraSrc_);
> >     > > +                   if (pipeline_)
> >     > > +                           gst_object_unref(pipeline_);
> >     > > +                   if (convert0_)
> >     > > +                           gst_object_unref(convert0_);
> >     > > +                   if (sink0_)
> >     > > +                           gst_object_unref(sink0_);
> >     > > +                   if (libcameraSrc_)
> >     > > +                           gst_object_unref(libcameraSrc_);
> >     > > +                   gst_deinit();
> >     > > +
> >     > > +                   return TestFail;
> >     > > +           }
> >     > > +
> >     > > +           return TestPass;
> >     > > +   }
> >     > > +
> >     > > +   void cleanup() override
> >     > > +   {
> >     > > +           gst_object_unref(pipeline_);
> >     > > +           gst_deinit();
> >     > > +   }
> >     > > +
> >     > > +   int run() override
> >     > > +   {
> >     > > +           GstStateChangeReturn ret;
> >     > > +
> >     > > +           /* Build the pipeline */
> >     > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> >     convert0_, sink0_, NULL);
> >     > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
> >     sink0_, NULL) != TRUE) {
> >     > > +                   g_printerr("Elements could not be
> linked.\n");
> >     > > +                   return TestFail;
> >     > > +           }
> >     > > +
> >     > > +           /* Start playing */
> >     > > +           ret = gst_element_set_state(pipeline_,
> >     GST_STATE_PLAYING);
> >     > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> >     > > +                   g_printerr("Unable to set the pipeline to
> >     the playing state.\n");
> >     > > +                   return TestFail;
> >     > > +           }
> >     > > +
> >     > > +           /* Wait until error or EOS or timeout after 2
> seconds */
> >     > > +           constexpr GstMessageType msgType =
> >     > > +
> >      static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> >     > > +           constexpr GstClockTime timeout = 2000000000;
> >     > > +
> >     > > +           g_autoptr(GstBus) bus =
> gst_element_get_bus(pipeline_);
> >     > > +           g_autoptr(GstMessage) msg =
> >     gst_bus_timed_pop_filtered(bus, timeout, msgType);
> >     > > +
> >     > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> >     > > +
> >     > > +           /* Parse error message */
> >     > > +           if (msg == NULL)
> >     > > +                   return TestPass;
> >     > > +
> >     > > +           switch (GST_MESSAGE_TYPE(msg)) {
> >     > > +           case GST_MESSAGE_ERROR:
> >     > > +                   gstreamer_print_error(msg);
> >     > > +                   break;
> >     > > +           case GST_MESSAGE_EOS:
> >     > > +                   g_print("End-Of-Stream reached.\n");
> >     > >
> >     > > EOS doesn't sound like a error/Test-fail condition to me. If
> >     there is a
> >     > > possibility that EOS will be reached before timeout, what's
> >     ensures(in the
> >     > > first place) that the pipeline will be in playing state for
> >     entire timeout,
> >     > > before we request masked EOS/Error messages from the bus? What
> >     am I missing?
> >     >
> >     > I am not a gstreamer expert, to the best of my knowledge, EOS
> >     won't be ever
> >     > sent by libcamerasrc, as it is the element which drives the
> >     pipeline. I
> >     > think this event can be dropped, I just want to confirm it with
> >     Nicolas
> >     > once he's back.
> >     >
> >     > > It is important to note that *only elements driving the pipeline
> >     should
> >     > ever send an EOS event*. If your element is chain-based, it is not
> >     driving
> >     > the pipeline. Chain-based elements should just return GST_FLOW_EOS
> >     from
> >     > their chain function at the end of the stream (or the configured
> >     segment)
> >     >
> >     >
> >
> https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
> >     <
> https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
> >
> >     >
> >     > +                     break;
> >     > > +           default:
> >     > > +                   g_printerr("Unexpected message received.\n");
> >     > > +                   break;
> >     > > +           }
> >     > > +
> >     > > +           return TestFail;
> >     > > +   }
> >     > > +
> >     > > +private:
> >     > > +   void gstreamer_print_error(GstMessage *msg)
> >     > > +   {
> >     > > +           GError *err;
> >     > >
> >     > > This can use:
> >     > >
> >     > >     g_autoptr(GError) err = NULL;
> >     > >
> >     > > +           gchar *debug_info;
> >     > >
> >     > > and
> >     > >
> >     > >      g_autofree char *debug_info = NULL;
> >     > >
> >     > > +
> >     > > +           gst_message_parse_error(msg, &err, &debug_info);
> >     > > +           g_printerr("Error received from element %s: %s\n",
> >     > > +                      GST_OBJECT_NAME(msg->src), err->message);
> >     > > +           g_printerr("Debugging information: %s\n",
> >     > > +                      debug_info ? debug_info : "none");
> >     > > +           g_clear_error(&err);
> >     > > +           g_free(debug_info);
> >     > >
> >     > > So, that will lead us to dropping manual free of err and debug
> info
> >     > >
> >     > >
> >     > > Minor things, so:
> >     > >
> >     > > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com
> >     <mailto:umang.jain@ideasonboard.com>>
> >     > >
> >     > > +   }
> >     > > +
> >     > > +   GstElement *pipeline_;
> >     > > +   GstElement *libcameraSrc_;
> >     > > +   GstElement *convert0_;
> >     > > +   GstElement *sink0_;
> >     > > +};
> >     > > +
> >     > > +TEST_REGISTER(GstreamerSingleStreamTest)
> >     > > diff --git a/test/gstreamer/meson.build
> b/test/gstreamer/meson.build
> >     > > new file mode 100644
> >     > > index 000000000000..b99aa0da0ba3
> >     > > --- /dev/null
> >     > > +++ b/test/gstreamer/meson.build
> >     > > @@ -0,0 +1,19 @@
> >     > > +# SPDX-License-Identifier: CC0-1.0
> >     > > +
> >     > > +if not gst_enabled
> >     > > +    subdir_done()
> >     > > +endif
> >     > > +
> >     > > +gstreamer_tests = [
> >     > > +    ['single_stream_test',
>  'gstreamer_single_stream_test.cpp'],
> >     > > +]
> >     > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> >     > > +
> >     > > +foreach t : gstreamer_tests
> >     > > +    exe = executable(t[0], t[1],
> >     > > +                     dependencies : [libcamera_private,
> >     gstreamer_dep],
> >     > > +                     link_with : test_libraries,
> >     > > +                     include_directories :
> test_includes_internal)
> >     > > +
> >     > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> >     > > +endforeach
> >     > > diff --git a/test/meson.build b/test/meson.build
> >     > > index 3bceb5df586f..d0466f17d7b6 100644
> >     > > --- a/test/meson.build
> >     > > +++ b/test/meson.build
> >     > > @@ -11,6 +11,7 @@ subdir('libtest')
> >     > >
> >     > >  subdir('camera')
> >     > >  subdir('controls')
> >     > > +subdir('gstreamer')
> >     > >  subdir('ipa')
> >     > >  subdir('ipc')
> >     > >  subdir('log')
> >
> >     --
> >     Regards,
> >
> >     Laurent Pinchart
> >
>
Laurent Pinchart Aug. 16, 2021, 4:30 p.m. UTC | #11
Hi Vedant,

On Mon, Aug 16, 2021 at 07:44:34PM +0530, Vedant Paranjape wrote:
> Hi Laurent,
> Yes, what's the camera-name with which vimc camera can be selected ?

You can find it with 'cam -l' of you have the vimc driver loaded :-)
It's the value between parentheses.

> I could simply set the object property to use it. I think it would be nice
> to have two separate tests, one which runs on real camera and other one on
> vimc camera.
> 
> On Mon, Aug 16, 2021 at 7:29 PM Laurent Pinchart wrote:
> > On Mon, Aug 16, 2021 at 07:04:00PM +0530, Vedant Paranjape wrote:
> > > Hi Umang,
> > > Thanks for the Review. I feel the changes are appropriate. I will send a
> > > patch with the changes.
> >
> > While at it, I'm wondering if it would make sense to select the vimc
> > camera explicitly from the libcamerasrc element instead of using
> > whatever camera happens to be the first. It would make tests more
> > reproducible.
> >
> > > On Mon, Aug 16, 2021 at 11:17 AM Umang Jain wrote:
> > >
> > > > Hi Vedant and Laurent,
> > > >
> > > > Thanks for the patch. Overall looks good to me,
> > > >
> > > > a few comments below for cleanup paths :)
> > > > On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> > > >
> > > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > >
> > > > This patch adds a test to test if single stream using libcamera's
> > > > gstreamer element works.
> > > >
> > > > We need to work around two distinct issues with ASan when enabled in the
> > > > build:
> > > >
> > > > - glib has a known leak at initialization time. This is covered by the
> > > >   suppression file shipped with glib, but it's not clear how to use it
> > > >   automatically. For now, disable leak detection to avoid test failures.
> > > >
> > > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > > >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> > > >   the libcamera plugin will cause an ASan link order verification
> > > >   failure. Disable the verification child processes to work around the
> > > >   problem. This requires gcc 8 or newer.
> > > >
> > > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > ---
> > > > This version incorporates changes coming from my review of v10, and
> > > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > > > independent reasons as explained in the commit message. I'd like to find
> > > > a way to use leak suppression files to handle the glib initialization
> > > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > > is acceptable in my opinion.
> > > >
> > > > The biggest trouble with these workarounds is that they don't work with
> > > > gcc version older than 8. As the stable version of the most common Linux
> > > > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > > > is the oldest version that libcamera supports) is also acceptable in my
> > > > opinion.
> > > >
> > > > Changes since v10:
> > > >
> > > > - Disable ASan leak detection
> > > > - Disable ASan link order verification for child processes
> > > > - Include source_path.h
> > > > - Remove unneeded explicit std::string construction
> > > > - Declare variables at usage site
> > > > - Make constants constexpr
> > > > - Add a variable for the message type
> > > > - Blank space fixes
> > > > ---
> > > >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> > > >  test/gstreamer/meson.build                    |  19 ++
> > > >  test/meson.build                              |   1 +
> > > >  3 files changed, 208 insertions(+)
> > > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > > >  create mode 100644 test/gstreamer/meson.build
> > > >
> > > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > new file mode 100644
> > > > index 000000000000..e26673b3471a
> > > > --- /dev/null
> > > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > @@ -0,0 +1,188 @@
> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > +/*
> > > > + * Copyright (C) 2021, Vedant Paranjape
> > > > + *
> > > > + * ipa_interface_test.cpp - Test the IPA interface
> > > > + */
> > > > +
> > > > +#include <iostream>
> > > > +#include <unistd.h>
> > > > +
> > > > +#include <libcamera/base/utils.h>
> > > > +
> > > > +#include "libcamera/internal/source_paths.h"
> > > > +
> > > > +#include <gst/gst.h>
> > > > +
> > > > +#include "test.h"
> > > > +
> > > > +using namespace std;
> > > > +
> > > > +extern "C" {
> > > > +const char *__asan_default_options()
> > > > +{
> > > > +   /*
> > > > +    * Disable leak detection due to a known global variable initialization
> > > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > > +    * using a suppression file instead of disabling leak detection.
> > > > +    */
> > > > +   return "detect_leaks=false";
> > > > +}
> > > > +}
> > > > +
> > > > +class GstreamerSingleStreamTest : public Test
> > > > +{
> > > > +protected:
> > > > +   int init() override
> > > > +   {
> > > > +           /*
> > > > +            * GStreamer spawns a process to run the gst-plugin-scanner
> > > > +            * helper. If libcamera is compiled with ASan enabled, and as
> > > > +            * GStreamer is most likely not, this will cause the ASan link
> > > > +            * order check to fail when gst-plugin-scanner dlopen()s the
> > > > +            * plugin as many libraries will have already been loaded by
> > > > +            * then. Work around this issue by disabling the link order
> > > > +            * check. This will only affect child processes, as ASan is
> > > > +            * already loaded for this process by the time this code is
> > > > +            * executed, and should thus hopefully be safe.
> > > > +            *
> > > > +            * This option is not available in gcc older than 8, the only
> > > > +            * option in that case is to skip the test.
> > > > +            */
> > > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> > > > +           return TestSkip;
> > > > +#endif
> > > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > > > +
> > > > +           /* Initialize GStreamer */
> > > > +           GError *errInit = nullptr;
> > > >
> > > > One can use:
> > > >
> > > >     g_autoptr(GError) errInit = NULL;
> > > >
> > > > ...
> > > >
> > > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > > +                   g_printerr("Could not initialize GStreamer: %s\n",
> > > > +                              errInit ? errInit->message : "unknown error");
> > > > +                   if (errInit)
> > > > +                           g_error_free(errInit);
> > > >
> > > > And remove manual free here.
> > > >
> > > > +
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /*
> > > > +            * Remove the system libcamera plugin, if any, and add the
> > > > +            * plugin from the build directory.
> > > > +            */
> > > > +           GstRegistry *registry = gst_registry_get();
> > > > +           GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > > > +           if (plugin) {
> > > > +                   gst_registry_remove_plugin(registry, plugin);
> > > > +                   gst_object_unref(plugin);
> > > > +           }
> > > > +
> > > > +           std::string path = libcamera::utils::libcameraBuildPath()
> > > > +                            + "src/gstreamer";
> > > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > > +                   g_printerr("Failed to add plugin to registry\n");
> > > > +                   gst_deinit();
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /* Create the elements */
> > > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > > > +           convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > > > +
> > > > +           /* Create the empty pipeline_ */
> > > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > > +
> > > > +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> > > > +                   g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > > > +                              pipeline_, convert0_, sink0_, libcameraSrc_);
> > > > +                   if (pipeline_)
> > > > +                           gst_object_unref(pipeline_);
> > > > +                   if (convert0_)
> > > > +                           gst_object_unref(convert0_);
> > > > +                   if (sink0_)
> > > > +                           gst_object_unref(sink0_);
> > > > +                   if (libcameraSrc_)
> > > > +                           gst_object_unref(libcameraSrc_);
> > > > +                   gst_deinit();
> > > > +
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           return TestPass;
> > > > +   }
> > > > +
> > > > +   void cleanup() override
> > > > +   {
> > > > +           gst_object_unref(pipeline_);
> > > > +           gst_deinit();
> > > > +   }
> > > > +
> > > > +   int run() override
> > > > +   {
> > > > +           GstStateChangeReturn ret;
> > > > +
> > > > +           /* Build the pipeline */
> > > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> > > > +           if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > > > +                   g_printerr("Elements could not be linked.\n");
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /* Start playing */
> > > > +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > > +                   g_printerr("Unable to set the pipeline to the playing state.\n");
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > > +           constexpr GstMessageType msgType =
> > > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > > > +           constexpr GstClockTime timeout = 2000000000;
> > > > +
> > > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > > +           g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > > +
> > > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > > +
> > > > +           /* Parse error message */
> > > > +           if (msg == NULL)
> > > > +                   return TestPass;
> > > > +
> > > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > > +           case GST_MESSAGE_ERROR:
> > > > +                   gstreamer_print_error(msg);
> > > > +                   break;
> > > > +           case GST_MESSAGE_EOS:
> > > > +                   g_print("End-Of-Stream reached.\n");
> > > >
> > > > EOS doesn't sound like a error/Test-fail condition to me. If there is a
> > > > possibility that EOS will be reached before timeout, what's ensures(in the
> > > > first place) that the pipeline will be in playing state for entire timeout,
> > > > before we request masked EOS/Error messages from the bus? What am I missing?
> > >
> > > I am not a gstreamer expert, to the best of my knowledge, EOS won't be ever
> > > sent by libcamerasrc, as it is the element which drives the pipeline. I
> > > think this event can be dropped, I just want to confirm it with Nicolas
> > > once he's back.
> > >
> > > > It is important to note that *only elements driving the pipeline should
> > > ever send an EOS event*. If your element is chain-based, it is not driving
> > > the pipeline. Chain-based elements should just return GST_FLOW_EOS from
> > > their chain function at the end of the stream (or the configured segment)
> > >
> > > https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
> > >
> > > +                     break;
> > > > +           default:
> > > > +                   g_printerr("Unexpected message received.\n");
> > > > +                   break;
> > > > +           }
> > > > +
> > > > +           return TestFail;
> > > > +   }
> > > > +
> > > > +private:
> > > > +   void gstreamer_print_error(GstMessage *msg)
> > > > +   {
> > > > +           GError *err;
> > > >
> > > > This can use:
> > > >
> > > >     g_autoptr(GError) err = NULL;
> > > >
> > > > +           gchar *debug_info;
> > > >
> > > > and
> > > >
> > > >      g_autofree char *debug_info = NULL;
> > > >
> > > > +
> > > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > > +           g_printerr("Error received from element %s: %s\n",
> > > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > > +           g_printerr("Debugging information: %s\n",
> > > > +                      debug_info ? debug_info : "none");
> > > > +           g_clear_error(&err);
> > > > +           g_free(debug_info);
> > > >
> > > > So, that will lead us to dropping manual free of err and debug info
> > > >
> > > >
> > > > Minor things, so:
> > > >
> > > > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> > > >
> > > > +   }
> > > > +
> > > > +   GstElement *pipeline_;
> > > > +   GstElement *libcameraSrc_;
> > > > +   GstElement *convert0_;
> > > > +   GstElement *sink0_;
> > > > +};
> > > > +
> > > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > > new file mode 100644
> > > > index 000000000000..b99aa0da0ba3
> > > > --- /dev/null
> > > > +++ b/test/gstreamer/meson.build
> > > > @@ -0,0 +1,19 @@
> > > > +# SPDX-License-Identifier: CC0-1.0
> > > > +
> > > > +if not gst_enabled
> > > > +    subdir_done()
> > > > +endif
> > > > +
> > > > +gstreamer_tests = [
> > > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > > +]
> > > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > > +
> > > > +foreach t : gstreamer_tests
> > > > +    exe = executable(t[0], t[1],
> > > > +                     dependencies : [libcamera_private, gstreamer_dep],
> > > > +                     link_with : test_libraries,
> > > > +                     include_directories : test_includes_internal)
> > > > +
> > > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > > +endforeach
> > > > diff --git a/test/meson.build b/test/meson.build
> > > > index 3bceb5df586f..d0466f17d7b6 100644
> > > > --- a/test/meson.build
> > > > +++ b/test/meson.build
> > > > @@ -11,6 +11,7 @@ subdir('libtest')
> > > >
> > > >  subdir('camera')
> > > >  subdir('controls')
> > > > +subdir('gstreamer')
> > > >  subdir('ipa')
> > > >  subdir('ipc')
> > > >  subdir('log')
Laurent Pinchart Aug. 16, 2021, 4:34 p.m. UTC | #12
On Mon, Aug 16, 2021 at 03:22:29PM +0100, Kieran Bingham wrote:
> On 16/08/2021 15:14, Vedant Paranjape wrote:
> > Hi Laurent,
> > Yes, what's the camera-name with which vimc camera can be selected ?
> > I could simply set the object property to use it. I think it would be
> > nice to have two separate tests, one which runs on real camera and other
> > one on vimc camera.
> 
> Or runs on all available cameras?

I'm in two minds about this. We don't run all our other tests against
all cameras, and instead consider that pipeline handler testing is the
job of lc-compliance. For the GStreamer element, it could be different
if we wanted to unit-test features that don't work with vimc, but for
the single stream test, I'm not sure testing all cameras would be the
best option.

> > On Mon, Aug 16, 2021 at 7:29 PM Laurent Pinchart wrote:
> > > On Mon, Aug 16, 2021 at 07:04:00PM +0530, Vedant Paranjape wrote:
> > > > Hi Umang,
> > > > Thanks for the Review. I feel the changes are appropriate. I will send a
> > > > patch with the changes.
> > >
> > > While at it, I'm wondering if it would make sense to select the
> > > vimc camera explicitly from the libcamerasrc element instead of
> > > using whatever camera happens to be the first. It would make tests
> > > more reproducible.
> > >
> > > > On Mon, Aug 16, 2021 at 11:17 AM Umang Jain wrote:
> > > >
> > > > > Hi Vedant and Laurent,
> > > > >
> > > > > Thanks for the patch. Overall looks good to me,
> > > > >
> > > > > a few comments below for cleanup paths :)
> > > > > On 8/14/21 5:16 AM, Laurent Pinchart wrote:
> > > > >
> > > > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > >
> > > > > This patch adds a test to test if single stream using libcamera's
> > > > > gstreamer element works.
> > > > >
> > > > > We need to work around two distinct issues with ASan when enabled in the
> > > > > build:
> > > > >
> > > > > - glib has a known leak at initialization time. This is covered by the
> > > > >   suppression file shipped with glib, but it's not clear how to use it
> > > > >   automatically. For now, disable leak detection to avoid test failures.
> > > > >
> > > > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > > > >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> > > > >   the libcamera plugin will cause an ASan link order verification
> > > > >   failure. Disable the verification child processes to work around the
> > > > >   problem. This requires gcc 8 or newer.
> > > > >
> > > > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > ---
> > > > > This version incorporates changes coming from my review of v10, and
> > > > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > > > > independent reasons as explained in the commit message. I'd like to find
> > > > > a way to use leak suppression files to handle the glib initialization
> > > > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > > > is acceptable in my opinion.
> > > > >
> > > > > The biggest trouble with these workarounds is that they don't work with
> > > > > gcc version older than 8. As the stable version of the most common Linux
> > > > > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > > > > is the oldest version that libcamera supports) is also acceptable in my
> > > > > opinion.
> > > > >
> > > > > Changes since v10:
> > > > >
> > > > > - Disable ASan leak detection
> > > > > - Disable ASan link order verification for child processes
> > > > > - Include source_path.h
> > > > > - Remove unneeded explicit std::string construction
> > > > > - Declare variables at usage site
> > > > > - Make constants constexpr
> > > > > - Add a variable for the message type
> > > > > - Blank space fixes
> > > > > ---
> > > > >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> > > > >  test/gstreamer/meson.build                    |  19 ++
> > > > >  test/meson.build                              |   1 +
> > > > >  3 files changed, 208 insertions(+)
> > > > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > > > >  create mode 100644 test/gstreamer/meson.build
> > > > >
> > > > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > new file mode 100644
> > > > > index 000000000000..e26673b3471a
> > > > > --- /dev/null
> > > > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > @@ -0,0 +1,188 @@
> > > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > > +/*
> > > > > + * Copyright (C) 2021, Vedant Paranjape
> > > > > + *
> > > > > + * ipa_interface_test.cpp - Test the IPA interface
> > > > > + */
> > > > > +
> > > > > +#include <iostream>
> > > > > +#include <unistd.h>
> > > > > +
> > > > > +#include <libcamera/base/utils.h>
> > > > > +
> > > > > +#include "libcamera/internal/source_paths.h"
> > > > > +
> > > > > +#include <gst/gst.h>
> > > > > +
> > > > > +#include "test.h"
> > > > > +
> > > > > +using namespace std;
> > > > > +
> > > > > +extern "C" {
> > > > > +const char *__asan_default_options()
> > > > > +{
> > > > > +   /*
> > > > > +    * Disable leak detection due to a known global variable initialization
> > > > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > > > +    * using a suppression file instead of disabling leak detection.
> > > > > +    */
> > > > > +   return "detect_leaks=false";
> > > > > +}
> > > > > +}
> > > > > +
> > > > > +class GstreamerSingleStreamTest : public Test
> > > > > +{
> > > > > +protected:
> > > > > +   int init() override
> > > > > +   {
> > > > > +           /*
> > > > > +            * GStreamer spawns a process to run the gst-plugin-scanner
> > > > > +            * helper. If libcamera is compiled with ASan enabled, and as
> > > > > +            * GStreamer is most likely not, this will cause the ASan link
> > > > > +            * order check to fail when gst-plugin-scanner dlopen()s the
> > > > > +            * plugin as many libraries will have already been loaded by
> > > > > +            * then. Work around this issue by disabling the link order
> > > > > +            * check. This will only affect child processes, as ASan is
> > > > > +            * already loaded for this process by the time this code is
> > > > > +            * executed, and should thus hopefully be safe.
> > > > > +            *
> > > > > +            * This option is not available in gcc older than 8, the only
> > > > > +            * option in that case is to skip the test.
> > > > > +            */
> > > > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> > > > > +           return TestSkip;
> > > > > +#endif
> > > > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > > > > +
> > > > > +           /* Initialize GStreamer */
> > > > > +           GError *errInit = nullptr;
> > > > >
> > > > > One can use:
> > > > >
> > > > >     g_autoptr(GError) errInit = NULL;
> > > > >
> > > > > ...
> > > > >
> > > > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > > > +                   g_printerr("Could not initialize GStreamer: %s\n",
> > > > > +                              errInit ? errInit->message : "unknown error");
> > > > > +                   if (errInit)
> > > > > +                           g_error_free(errInit);
> > > > >
> > > > > And remove manual free here.
> > > > >
> > > > > +
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /*
> > > > > +            * Remove the system libcamera plugin, if any, and add the
> > > > > +            * plugin from the build directory.
> > > > > +            */
> > > > > +           GstRegistry *registry = gst_registry_get();
> > > > > +           GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > > > > +           if (plugin) {
> > > > > +                   gst_registry_remove_plugin(registry, plugin);
> > > > > +                   gst_object_unref(plugin);
> > > > > +           }
> > > > > +
> > > > > +           std::string path = libcamera::utils::libcameraBuildPath()
> > > > > +                            + "src/gstreamer";
> > > > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > > > +                   g_printerr("Failed to add plugin to registry\n");
> > > > > +                   gst_deinit();
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /* Create the elements */
> > > > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > > > > +           convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > > > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > > > > +
> > > > > +           /* Create the empty pipeline_ */
> > > > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > > > +
> > > > > +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> > > > > +                   g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > > > > +                              pipeline_, convert0_, sink0_, libcameraSrc_);
> > > > > +                   if (pipeline_)
> > > > > +                           gst_object_unref(pipeline_);
> > > > > +                   if (convert0_)
> > > > > +                           gst_object_unref(convert0_);
> > > > > +                   if (sink0_)
> > > > > +                           gst_object_unref(sink0_);
> > > > > +                   if (libcameraSrc_)
> > > > > +                           gst_object_unref(libcameraSrc_);
> > > > > +                   gst_deinit();
> > > > > +
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           return TestPass;
> > > > > +   }
> > > > > +
> > > > > +   void cleanup() override
> > > > > +   {
> > > > > +           gst_object_unref(pipeline_);
> > > > > +           gst_deinit();
> > > > > +   }
> > > > > +
> > > > > +   int run() override
> > > > > +   {
> > > > > +           GstStateChangeReturn ret;
> > > > > +
> > > > > +           /* Build the pipeline */
> > > > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> > > > > +           if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > > > > +                   g_printerr("Elements could not be linked.\n");
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /* Start playing */
> > > > > +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > > > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > > > +                   g_printerr("Unable to set the pipeline to the playing state.\n");
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > > > +           constexpr GstMessageType msgType =
> > > > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > > > > +           constexpr GstClockTime timeout = 2000000000;
> > > > > +
> > > > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > > > +           g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > > > +
> > > > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > > > +
> > > > > +           /* Parse error message */
> > > > > +           if (msg == NULL)
> > > > > +                   return TestPass;
> > > > > +
> > > > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > > > +           case GST_MESSAGE_ERROR:
> > > > > +                   gstreamer_print_error(msg);
> > > > > +                   break;
> > > > > +           case GST_MESSAGE_EOS:
> > > > > +                   g_print("End-Of-Stream reached.\n");
> > > > >
> > > > > EOS doesn't sound like a error/Test-fail condition to me. If there is a
> > > > > possibility that EOS will be reached before timeout, what's ensures(in the
> > > > > first place) that the pipeline will be in playing state for entire timeout,
> > > > > before we request masked EOS/Error messages from the bus? What am I missing?
> > > >
> > > > I am not a gstreamer expert, to the best of my knowledge, EOS won't be ever
> > > > sent by libcamerasrc, as it is the element which drives the pipeline. I
> > > > think this event can be dropped, I just want to confirm it with Nicolas
> > > > once he's back.
> > > >
> > > > > It is important to note that *only elements driving the pipeline should
> > > > ever send an EOS event*. If your element is chain-based, it is not driving
> > > > the pipeline. Chain-based elements should just return GST_FLOW_EOS from
> > > > their chain function at the end of the stream (or the configured segment)
> > > >
> > > > https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
> > > >
> > > > +                     break;
> > > > > +           default:
> > > > > +                   g_printerr("Unexpected message received.\n");
> > > > > +                   break;
> > > > > +           }
> > > > > +
> > > > > +           return TestFail;
> > > > > +   }
> > > > > +
> > > > > +private:
> > > > > +   void gstreamer_print_error(GstMessage *msg)
> > > > > +   {
> > > > > +           GError *err;
> > > > >
> > > > > This can use:
> > > > >
> > > > >     g_autoptr(GError) err = NULL;
> > > > >
> > > > > +           gchar *debug_info;
> > > > >
> > > > > and
> > > > >
> > > > >      g_autofree char *debug_info = NULL;
> > > > >
> > > > > +
> > > > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > > > +           g_printerr("Error received from element %s: %s\n",
> > > > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > > > +           g_printerr("Debugging information: %s\n",
> > > > > +                      debug_info ? debug_info : "none");
> > > > > +           g_clear_error(&err);
> > > > > +           g_free(debug_info);
> > > > >
> > > > > So, that will lead us to dropping manual free of err and debug info
> > > > >
> > > > >
> > > > > Minor things, so:
> > > > >
> > > > > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> > > > >
> > > > > +   }
> > > > > +
> > > > > +   GstElement *pipeline_;
> > > > > +   GstElement *libcameraSrc_;
> > > > > +   GstElement *convert0_;
> > > > > +   GstElement *sink0_;
> > > > > +};
> > > > > +
> > > > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > > > new file mode 100644
> > > > > index 000000000000..b99aa0da0ba3
> > > > > --- /dev/null
> > > > > +++ b/test/gstreamer/meson.build
> > > > > @@ -0,0 +1,19 @@
> > > > > +# SPDX-License-Identifier: CC0-1.0
> > > > > +
> > > > > +if not gst_enabled
> > > > > +    subdir_done()
> > > > > +endif
> > > > > +
> > > > > +gstreamer_tests = [
> > > > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > > > +]
> > > > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > > > +
> > > > > +foreach t : gstreamer_tests
> > > > > +    exe = executable(t[0], t[1],
> > > > > +                     dependencies : [libcamera_private, gstreamer_dep],
> > > > > +                     link_with : test_libraries,
> > > > > +                     include_directories : test_includes_internal)
> > > > > +
> > > > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > > > +endforeach
> > > > > diff --git a/test/meson.build b/test/meson.build
> > > > > index 3bceb5df586f..d0466f17d7b6 100644
> > > > > --- a/test/meson.build
> > > > > +++ b/test/meson.build
> > > > > @@ -11,6 +11,7 @@ subdir('libtest')
> > > > >
> > > > >  subdir('camera')
> > > > >  subdir('controls')
> > > > > +subdir('gstreamer')
> > > > >  subdir('ipa')
> > > > >  subdir('ipc')
> > > > >  subdir('log')
Kieran Bingham Aug. 16, 2021, 7:43 p.m. UTC | #13
On 16/08/2021 17:34, Laurent Pinchart wrote:
> On Mon, Aug 16, 2021 at 03:22:29PM +0100, Kieran Bingham wrote:
>> On 16/08/2021 15:14, Vedant Paranjape wrote:
>>> Hi Laurent,
>>> Yes, what's the camera-name with which vimc camera can be selected ?
>>> I could simply set the object property to use it. I think it would be
>>> nice to have two separate tests, one which runs on real camera and other
>>> one on vimc camera.
>>
>> Or runs on all available cameras?
> 
> I'm in two minds about this. We don't run all our other tests against
> all cameras, and instead consider that pipeline handler testing is the
> job of lc-compliance. For the GStreamer element, it could be different
> if we wanted to unit-test features that don't work with vimc, but for
> the single stream test, I'm not sure testing all cameras would be the
> best option.

Actually, I agree - for consistancy and reproducability of the unit
tests, I think fixing to VIMC is better.

I believe we have defined VIMC as a requirement for the unit tests, if
not explicitly - then certainly defined by code already.

Using VIMC makes the most sense.


I do think there is some benefit in some test being able to run on 'all
cameras' though, but that's more about being able to test multiple
cameras - which would then /require/ multiple cameras to run the test
... so - lets not worry about that for now ;-)

--
Kieran


>>> On Mon, Aug 16, 2021 at 7:29 PM Laurent Pinchart wrote:
>>>> On Mon, Aug 16, 2021 at 07:04:00PM +0530, Vedant Paranjape wrote:
>>>>> Hi Umang,
>>>>> Thanks for the Review. I feel the changes are appropriate. I will send a
>>>>> patch with the changes.
>>>>
>>>> While at it, I'm wondering if it would make sense to select the
>>>> vimc camera explicitly from the libcamerasrc element instead of
>>>> using whatever camera happens to be the first. It would make tests
>>>> more reproducible.
>>>>
>>>>> On Mon, Aug 16, 2021 at 11:17 AM Umang Jain wrote:
>>>>>
>>>>>> Hi Vedant and Laurent,
>>>>>>
>>>>>> Thanks for the patch. Overall looks good to me,
>>>>>>
>>>>>> a few comments below for cleanup paths :)
>>>>>> On 8/14/21 5:16 AM, Laurent Pinchart wrote:
>>>>>>
>>>>>> From: Vedant Paranjape <vedantparanjape160201@gmail.com>
>>>>>>
>>>>>> This patch adds a test to test if single stream using libcamera's
>>>>>> gstreamer element works.
>>>>>>
>>>>>> We need to work around two distinct issues with ASan when enabled in the
>>>>>> build:
>>>>>>
>>>>>> - glib has a known leak at initialization time. This is covered by the
>>>>>>    suppression file shipped with glib, but it's not clear how to use it
>>>>>>    automatically. For now, disable leak detection to avoid test failures.
>>>>>>
>>>>>> - GStreamer spawns a child process to scan plugins. If GStreamer is
>>>>>>    compiled without ASan (which is likely) but libcamera is, dlopen()ing
>>>>>>    the libcamera plugin will cause an ASan link order verification
>>>>>>    failure. Disable the verification child processes to work around the
>>>>>>    problem. This requires gcc 8 or newer.
>>>>>>
>>>>>> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
>>>>>> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
>>>>>> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>>>>>> Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
>>>>>> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>> ---
>>>>>> This version incorporates changes coming from my review of v10, and
>>>>>> fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
>>>>>> independent reasons as explained in the commit message. I'd like to find
>>>>>> a way to use leak suppression files to handle the glib initialization
>>>>>> leak, but that's a big rabbit hole. The workaround for the second issue
>>>>>> is acceptable in my opinion.
>>>>>>
>>>>>> The biggest trouble with these workarounds is that they don't work with
>>>>>> gcc version older than 8. As the stable version of the most common Linux
>>>>>> distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
>>>>>> is the oldest version that libcamera supports) is also acceptable in my
>>>>>> opinion.
>>>>>>
>>>>>> Changes since v10:
>>>>>>
>>>>>> - Disable ASan leak detection
>>>>>> - Disable ASan link order verification for child processes
>>>>>> - Include source_path.h
>>>>>> - Remove unneeded explicit std::string construction
>>>>>> - Declare variables at usage site
>>>>>> - Make constants constexpr
>>>>>> - Add a variable for the message type
>>>>>> - Blank space fixes
>>>>>> ---
>>>>>>   .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
>>>>>>   test/gstreamer/meson.build                    |  19 ++
>>>>>>   test/meson.build                              |   1 +
>>>>>>   3 files changed, 208 insertions(+)
>>>>>>   create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
>>>>>>   create mode 100644 test/gstreamer/meson.build
>>>>>>
>>>>>> diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
>>>>>> new file mode 100644
>>>>>> index 000000000000..e26673b3471a
>>>>>> --- /dev/null
>>>>>> +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
>>>>>> @@ -0,0 +1,188 @@
>>>>>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>>>>>> +/*
>>>>>> + * Copyright (C) 2021, Vedant Paranjape
>>>>>> + *
>>>>>> + * ipa_interface_test.cpp - Test the IPA interface
>>>>>> + */
>>>>>> +
>>>>>> +#include <iostream>
>>>>>> +#include <unistd.h>
>>>>>> +
>>>>>> +#include <libcamera/base/utils.h>
>>>>>> +
>>>>>> +#include "libcamera/internal/source_paths.h"
>>>>>> +
>>>>>> +#include <gst/gst.h>
>>>>>> +
>>>>>> +#include "test.h"
>>>>>> +
>>>>>> +using namespace std;
>>>>>> +
>>>>>> +extern "C" {
>>>>>> +const char *__asan_default_options()
>>>>>> +{
>>>>>> +   /*
>>>>>> +    * Disable leak detection due to a known global variable initialization
>>>>>> +    * leak in glib's g_quark_init(). This should ideally be handled by
>>>>>> +    * using a suppression file instead of disabling leak detection.
>>>>>> +    */
>>>>>> +   return "detect_leaks=false";
>>>>>> +}
>>>>>> +}
>>>>>> +
>>>>>> +class GstreamerSingleStreamTest : public Test
>>>>>> +{
>>>>>> +protected:
>>>>>> +   int init() override
>>>>>> +   {
>>>>>> +           /*
>>>>>> +            * GStreamer spawns a process to run the gst-plugin-scanner
>>>>>> +            * helper. If libcamera is compiled with ASan enabled, and as
>>>>>> +            * GStreamer is most likely not, this will cause the ASan link
>>>>>> +            * order check to fail when gst-plugin-scanner dlopen()s the
>>>>>> +            * plugin as many libraries will have already been loaded by
>>>>>> +            * then. Work around this issue by disabling the link order
>>>>>> +            * check. This will only affect child processes, as ASan is
>>>>>> +            * already loaded for this process by the time this code is
>>>>>> +            * executed, and should thus hopefully be safe.
>>>>>> +            *
>>>>>> +            * This option is not available in gcc older than 8, the only
>>>>>> +            * option in that case is to skip the test.
>>>>>> +            */
>>>>>> +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
>>>>>> +           return TestSkip;
>>>>>> +#endif
>>>>>> +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
>>>>>> +
>>>>>> +           /* Initialize GStreamer */
>>>>>> +           GError *errInit = nullptr;
>>>>>>
>>>>>> One can use:
>>>>>>
>>>>>>      g_autoptr(GError) errInit = NULL;
>>>>>>
>>>>>> ...
>>>>>>
>>>>>> +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
>>>>>> +                   g_printerr("Could not initialize GStreamer: %s\n",
>>>>>> +                              errInit ? errInit->message : "unknown error");
>>>>>> +                   if (errInit)
>>>>>> +                           g_error_free(errInit);
>>>>>>
>>>>>> And remove manual free here.
>>>>>>
>>>>>> +
>>>>>> +                   return TestFail;
>>>>>> +           }
>>>>>> +
>>>>>> +           /*
>>>>>> +            * Remove the system libcamera plugin, if any, and add the
>>>>>> +            * plugin from the build directory.
>>>>>> +            */
>>>>>> +           GstRegistry *registry = gst_registry_get();
>>>>>> +           GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
>>>>>> +           if (plugin) {
>>>>>> +                   gst_registry_remove_plugin(registry, plugin);
>>>>>> +                   gst_object_unref(plugin);
>>>>>> +           }
>>>>>> +
>>>>>> +           std::string path = libcamera::utils::libcameraBuildPath()
>>>>>> +                            + "src/gstreamer";
>>>>>> +           if (!gst_registry_scan_path(registry, path.c_str())) {
>>>>>> +                   g_printerr("Failed to add plugin to registry\n");
>>>>>> +                   gst_deinit();
>>>>>> +                   return TestFail;
>>>>>> +           }
>>>>>> +
>>>>>> +           /* Create the elements */
>>>>>> +           libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
>>>>>> +           convert0_ = gst_element_factory_make("videoconvert", "convert0");
>>>>>> +           sink0_ = gst_element_factory_make("fakesink", "sink0");
>>>>>> +
>>>>>> +           /* Create the empty pipeline_ */
>>>>>> +           pipeline_ = gst_pipeline_new("test-pipeline");
>>>>>> +
>>>>>> +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
>>>>>> +                   g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
>>>>>> +                              pipeline_, convert0_, sink0_, libcameraSrc_);
>>>>>> +                   if (pipeline_)
>>>>>> +                           gst_object_unref(pipeline_);
>>>>>> +                   if (convert0_)
>>>>>> +                           gst_object_unref(convert0_);
>>>>>> +                   if (sink0_)
>>>>>> +                           gst_object_unref(sink0_);
>>>>>> +                   if (libcameraSrc_)
>>>>>> +                           gst_object_unref(libcameraSrc_);
>>>>>> +                   gst_deinit();
>>>>>> +
>>>>>> +                   return TestFail;
>>>>>> +           }
>>>>>> +
>>>>>> +           return TestPass;
>>>>>> +   }
>>>>>> +
>>>>>> +   void cleanup() override
>>>>>> +   {
>>>>>> +           gst_object_unref(pipeline_);
>>>>>> +           gst_deinit();
>>>>>> +   }
>>>>>> +
>>>>>> +   int run() override
>>>>>> +   {
>>>>>> +           GstStateChangeReturn ret;
>>>>>> +
>>>>>> +           /* Build the pipeline */
>>>>>> +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
>>>>>> +           if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
>>>>>> +                   g_printerr("Elements could not be linked.\n");
>>>>>> +                   return TestFail;
>>>>>> +           }
>>>>>> +
>>>>>> +           /* Start playing */
>>>>>> +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
>>>>>> +           if (ret == GST_STATE_CHANGE_FAILURE) {
>>>>>> +                   g_printerr("Unable to set the pipeline to the playing state.\n");
>>>>>> +                   return TestFail;
>>>>>> +           }
>>>>>> +
>>>>>> +           /* Wait until error or EOS or timeout after 2 seconds */
>>>>>> +           constexpr GstMessageType msgType =
>>>>>> +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
>>>>>> +           constexpr GstClockTime timeout = 2000000000;
>>>>>> +
>>>>>> +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
>>>>>> +           g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
>>>>>> +
>>>>>> +           gst_element_set_state(pipeline_, GST_STATE_NULL);
>>>>>> +
>>>>>> +           /* Parse error message */
>>>>>> +           if (msg == NULL)
>>>>>> +                   return TestPass;
>>>>>> +
>>>>>> +           switch (GST_MESSAGE_TYPE(msg)) {
>>>>>> +           case GST_MESSAGE_ERROR:
>>>>>> +                   gstreamer_print_error(msg);
>>>>>> +                   break;
>>>>>> +           case GST_MESSAGE_EOS:
>>>>>> +                   g_print("End-Of-Stream reached.\n");
>>>>>>
>>>>>> EOS doesn't sound like a error/Test-fail condition to me. If there is a
>>>>>> possibility that EOS will be reached before timeout, what's ensures(in the
>>>>>> first place) that the pipeline will be in playing state for entire timeout,
>>>>>> before we request masked EOS/Error messages from the bus? What am I missing?
>>>>>
>>>>> I am not a gstreamer expert, to the best of my knowledge, EOS won't be ever
>>>>> sent by libcamerasrc, as it is the element which drives the pipeline. I
>>>>> think this event can be dropped, I just want to confirm it with Nicolas
>>>>> once he's back.
>>>>>
>>>>>> It is important to note that *only elements driving the pipeline should
>>>>> ever send an EOS event*. If your element is chain-based, it is not driving
>>>>> the pipeline. Chain-based elements should just return GST_FLOW_EOS from
>>>>> their chain function at the end of the stream (or the configured segment)
>>>>>
>>>>> https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/events.html?gi-language=c#end-of-stream-eos
>>>>>
>>>>> +                     break;
>>>>>> +           default:
>>>>>> +                   g_printerr("Unexpected message received.\n");
>>>>>> +                   break;
>>>>>> +           }
>>>>>> +
>>>>>> +           return TestFail;
>>>>>> +   }
>>>>>> +
>>>>>> +private:
>>>>>> +   void gstreamer_print_error(GstMessage *msg)
>>>>>> +   {
>>>>>> +           GError *err;
>>>>>>
>>>>>> This can use:
>>>>>>
>>>>>>      g_autoptr(GError) err = NULL;
>>>>>>
>>>>>> +           gchar *debug_info;
>>>>>>
>>>>>> and
>>>>>>
>>>>>>       g_autofree char *debug_info = NULL;
>>>>>>
>>>>>> +
>>>>>> +           gst_message_parse_error(msg, &err, &debug_info);
>>>>>> +           g_printerr("Error received from element %s: %s\n",
>>>>>> +                      GST_OBJECT_NAME(msg->src), err->message);
>>>>>> +           g_printerr("Debugging information: %s\n",
>>>>>> +                      debug_info ? debug_info : "none");
>>>>>> +           g_clear_error(&err);
>>>>>> +           g_free(debug_info);
>>>>>>
>>>>>> So, that will lead us to dropping manual free of err and debug info
>>>>>>
>>>>>>
>>>>>> Minor things, so:
>>>>>>
>>>>>> Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
>>>>>>
>>>>>> +   }
>>>>>> +
>>>>>> +   GstElement *pipeline_;
>>>>>> +   GstElement *libcameraSrc_;
>>>>>> +   GstElement *convert0_;
>>>>>> +   GstElement *sink0_;
>>>>>> +};
>>>>>> +
>>>>>> +TEST_REGISTER(GstreamerSingleStreamTest)
>>>>>> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
>>>>>> new file mode 100644
>>>>>> index 000000000000..b99aa0da0ba3
>>>>>> --- /dev/null
>>>>>> +++ b/test/gstreamer/meson.build
>>>>>> @@ -0,0 +1,19 @@
>>>>>> +# SPDX-License-Identifier: CC0-1.0
>>>>>> +
>>>>>> +if not gst_enabled
>>>>>> +    subdir_done()
>>>>>> +endif
>>>>>> +
>>>>>> +gstreamer_tests = [
>>>>>> +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
>>>>>> +]
>>>>>> +gstreamer_dep = dependency('gstreamer-1.0', required: true)
>>>>>> +
>>>>>> +foreach t : gstreamer_tests
>>>>>> +    exe = executable(t[0], t[1],
>>>>>> +                     dependencies : [libcamera_private, gstreamer_dep],
>>>>>> +                     link_with : test_libraries,
>>>>>> +                     include_directories : test_includes_internal)
>>>>>> +
>>>>>> +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
>>>>>> +endforeach
>>>>>> diff --git a/test/meson.build b/test/meson.build
>>>>>> index 3bceb5df586f..d0466f17d7b6 100644
>>>>>> --- a/test/meson.build
>>>>>> +++ b/test/meson.build
>>>>>> @@ -11,6 +11,7 @@ subdir('libtest')
>>>>>>
>>>>>>   subdir('camera')
>>>>>>   subdir('controls')
>>>>>> +subdir('gstreamer')
>>>>>>   subdir('ipa')
>>>>>>   subdir('ipc')
>>>>>>   subdir('log')
>
Nicolas Dufresne Aug. 17, 2021, 4:24 p.m. UTC | #14
Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> 
> This patch adds a test to test if single stream using libcamera's
> gstreamer element works.
> 
> We need to work around two distinct issues with ASan when enabled in the
> build:
> 
> - glib has a known leak at initialization time. This is covered by the
>   suppression file shipped with glib, but it's not clear how to use it
>   automatically. For now, disable leak detection to avoid test failures.

Are Valgrind suppression usable for Asan ? Glib installs it into:

  /usr/share/glib-2.0/valgrind/glib.supp

For GStreamer suppressions, we don't install them (yet, under discussion). They
are located in each repos. I would enable GStreamer leak tracer though, it does
not have such a high run-time overhead, and will detect any GObject or
GstMiniObject leak, as long as the test calls gst_deinit() at the end (all tests
should).

> 
> - GStreamer spawns a child process to scan plugins. If GStreamer is
>   compiled without ASan (which is likely) but libcamera is, dlopen()ing
>   the libcamera plugin will cause an ASan link order verification
>   failure. Disable the verification child processes to work around the
>   problem. This requires gcc 8 or newer.

Have you considered simply disabling forks ? See gst_registry_fork_set_enabled()

> 
> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> This version incorporates changes coming from my review of v10, and
> fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> independent reasons as explained in the commit message. I'd like to find
> a way to use leak suppression files to handle the glib initialization
> leak, but that's a big rabbit hole. The workaround for the second issue
> is acceptable in my opinion.
> 
> The biggest trouble with these workarounds is that they don't work with
> gcc version older than 8. As the stable version of the most common Linux
> distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> is the oldest version that libcamera supports) is also acceptable in my
> opinion.
> 
> Changes since v10:
> 
> - Disable ASan leak detection
> - Disable ASan link order verification for child processes
> - Include source_path.h
> - Remove unneeded explicit std::string construction
> - Declare variables at usage site
> - Make constants constexpr
> - Add a variable for the message type
> - Blank space fixes
> ---
>  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
>  test/gstreamer/meson.build                    |  19 ++
>  test/meson.build                              |   1 +
>  3 files changed, 208 insertions(+)
>  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
>  create mode 100644 test/gstreamer/meson.build
> 
> diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> new file mode 100644
> index 000000000000..e26673b3471a
> --- /dev/null
> +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> @@ -0,0 +1,188 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2021, Vedant Paranjape
> + *
> + * ipa_interface_test.cpp - Test the IPA interface
> + */
> +
> +#include <iostream>
> +#include <unistd.h>
> +
> +#include <libcamera/base/utils.h>
> +
> +#include "libcamera/internal/source_paths.h"
> +
> +#include <gst/gst.h>
> +
> +#include "test.h"
> +
> +using namespace std;
> +
> +extern "C" {
> +const char *__asan_default_options()
> +{
> +	/*
> +	 * Disable leak detection due to a known global variable initialization
> +	 * leak in glib's g_quark_init(). This should ideally be handled by
> +	 * using a suppression file instead of disabling leak detection.
> +	 */
> +	return "detect_leaks=false";

Have you consider GST_TRACERS=leaks ? It will heck for refcounted object leaks
on gstreamer/glib side, with very low overhead, and will abort on gst_deinit()
if it found some leaks.


> +}
> +}
> +
> +class GstreamerSingleStreamTest : public Test
> +{
> +protected:
> +	int init() override
> +	{
> +		/*
> +		 * GStreamer spawns a process to run the gst-plugin-scanner
> +		 * helper. If libcamera is compiled with ASan enabled, and as
> +		 * GStreamer is most likely not, this will cause the ASan link
> +		 * order check to fail when gst-plugin-scanner dlopen()s the
> +		 * plugin as many libraries will have already been loaded by
> +		 * then. Work around this issue by disabling the link order
> +		 * check. This will only affect child processes, as ASan is
> +		 * already loaded for this process by the time this code is
> +		 * executed, and should thus hopefully be safe.
> +		 *
> +		 * This option is not available in gcc older than 8, the only
> +		 * option in that case is to skip the test.
> +		 */
> +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> +		return TestSkip;
> +#endif
> +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);

I think gst_registry_fork_set_enabled(FALSE) would be much cleaner, what do you
think ?

> +
> +		/* Initialize GStreamer */
> +		GError *errInit = nullptr;
> +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> +			g_printerr("Could not initialize GStreamer: %s\n",
> +				   errInit ? errInit->message : "unknown error");
> +			if (errInit)
> +				g_error_free(errInit);
> +
> +			return TestFail;
> +		}
> +
> +		/*
> +		 * Remove the system libcamera plugin, if any, and add the
> +		 * plugin from the build directory.
> +		 */
> +		GstRegistry *registry = gst_registry_get();
> +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> +		if (plugin) {
> +			gst_registry_remove_plugin(registry, plugin);
> +			gst_object_unref(plugin);
> +		}
> +
> +		std::string path = libcamera::utils::libcameraBuildPath()
> +				 + "src/gstreamer";
> +		if (!gst_registry_scan_path(registry, path.c_str())) {
> +			g_printerr("Failed to add plugin to registry\n");
> +			gst_deinit();
> +			return TestFail;
> +		}
> +
> +		/* Create the elements */
> +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> +		sink0_ = gst_element_factory_make("fakesink", "sink0");

Please use fakevideosink instead.

> +
> +		/* Create the empty pipeline_ */
> +		pipeline_ = gst_pipeline_new("test-pipeline");
> +
> +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {

pipeline_ cannot be NULL, glib will abort if malloc failed.

> +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> +			if (pipeline_)
> +				gst_object_unref(pipeline_);
> +			if (convert0_)
> +				gst_object_unref(convert0_);
> +			if (sink0_)
> +				gst_object_unref(sink0_);
> +			if (libcameraSrc_)
> +				gst_object_unref(libcameraSrc_);

Use local g_autoptr() and g_steal_pointer() on success perhaps ? This will
reduce a lot the about of error prone code in failure case. Note that in other
software I've seen, an abort is used instead, as clean exit is just coding
overhead for a test.

An alternative, add them immediately to the pipeline, they are not owned by your
class anyway.

> +			gst_deinit();
> +
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +	void cleanup() override
> +	{
> +		gst_object_unref(pipeline_);
> +		gst_deinit();
> +	}
> +
> +	int run() override
> +	{
> +		GstStateChangeReturn ret;
> +
> +		/* Build the pipeline */
> +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);

This can fail (e.g. duplicate name).

> +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> +			g_printerr("Elements could not be linked.\n");
> +			return TestFail;
> +		}
> +
> +		/* Start playing */
> +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> +		if (ret == GST_STATE_CHANGE_FAILURE) {
> +			g_printerr("Unable to set the pipeline to the playing state.\n");
> +			return TestFail;
> +		}
> +
> +		/* Wait until error or EOS or timeout after 2 seconds */
> +		constexpr GstMessageType msgType =
> +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> +		constexpr GstClockTime timeout = 2000000000;
Perhaps 2 * GST_SECOND.
> +
> +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> +
> +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> +
> +		/* Parse error message */
> +		if (msg == NULL)
> +			return TestPass;

I would like some minimal validation. I would expect that after 2s some frames
got "rendered" properly. You can read the GstStructure property "stats" from
fakevideosink / fakesink, and read the "rendered" field. Make sure this not zero
perhaps ?

> +
> +		switch (GST_MESSAGE_TYPE(msg)) {
> +		case GST_MESSAGE_ERROR:
> +			gstreamer_print_error(msg);
> +			break;
> +		case GST_MESSAGE_EOS:
> +			g_print("End-Of-Stream reached.\n");
> +			break;
> +		default:
> +			g_printerr("Unexpected message received.\n");
> +			break;
> +		}
> +
> +		return TestFail;
> +	}
> +
> +private:
> +	void gstreamer_print_error(GstMessage *msg)
> +	{
> +		GError *err;
> +		gchar *debug_info;
> +
> +		gst_message_parse_error(msg, &err, &debug_info);
> +		g_printerr("Error received from element %s: %s\n",
> +			   GST_OBJECT_NAME(msg->src), err->message);
> +		g_printerr("Debugging information: %s\n",
> +			   debug_info ? debug_info : "none");
> +		g_clear_error(&err);
> +		g_free(debug_info);
> +	}
> +
> +	GstElement *pipeline_;
> +	GstElement *libcameraSrc_;
> +	GstElement *convert0_;
> +	GstElement *sink0_;
> +};
> +
> +TEST_REGISTER(GstreamerSingleStreamTest)
> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> new file mode 100644
> index 000000000000..b99aa0da0ba3
> --- /dev/null
> +++ b/test/gstreamer/meson.build
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +if not gst_enabled
> +    subdir_done()
> +endif
> +
> +gstreamer_tests = [
> +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> +]
> +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> +
> +foreach t : gstreamer_tests
> +    exe = executable(t[0], t[1],
> +                     dependencies : [libcamera_private, gstreamer_dep],
> +                     link_with : test_libraries,
> +                     include_directories : test_includes_internal)
> +
> +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> +endforeach
> diff --git a/test/meson.build b/test/meson.build
> index 3bceb5df586f..d0466f17d7b6 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -11,6 +11,7 @@ subdir('libtest')
>  
>  subdir('camera')
>  subdir('controls')
> +subdir('gstreamer')
>  subdir('ipa')
>  subdir('ipc')
>  subdir('log')
Laurent Pinchart Aug. 17, 2021, 6:34 p.m. UTC | #15
Hi Nicolas,

On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:
> Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > 
> > This patch adds a test to test if single stream using libcamera's
> > gstreamer element works.
> > 
> > We need to work around two distinct issues with ASan when enabled in the
> > build:
> > 
> > - glib has a known leak at initialization time. This is covered by the
> >   suppression file shipped with glib, but it's not clear how to use it
> >   automatically. For now, disable leak detection to avoid test failures.
> 
> Are Valgrind suppression usable for Asan ? Glib installs it into:
> 
>   /usr/share/glib-2.0/valgrind/glib.supp
> 
> For GStreamer suppressions, we don't install them (yet, under discussion). They
> are located in each repos. I would enable GStreamer leak tracer though, it does
> not have such a high run-time overhead, and will detect any GObject or
> GstMiniObject leak, as long as the test calls gst_deinit() at the end (all tests
> should).

Yes, I think they are usable. I was however unsure whether the path to
the supression file was standard, especially considering embedded
systems. I thus went for a workaround. It would be nice if there was a
standard mechanism to pick suppressions automatically.

> > - GStreamer spawns a child process to scan plugins. If GStreamer is
> >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> >   the libcamera plugin will cause an ASan link order verification
> >   failure. Disable the verification child processes to work around the
> >   problem. This requires gcc 8 or newer.
> 
> Have you considered simply disabling forks ? See gst_registry_fork_set_enabled()

No, as I had no idea that existed :-)

> > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> > This version incorporates changes coming from my review of v10, and
> > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > independent reasons as explained in the commit message. I'd like to find
> > a way to use leak suppression files to handle the glib initialization
> > leak, but that's a big rabbit hole. The workaround for the second issue
> > is acceptable in my opinion.
> > 
> > The biggest trouble with these workarounds is that they don't work with
> > gcc version older than 8. As the stable version of the most common Linux
> > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > is the oldest version that libcamera supports) is also acceptable in my
> > opinion.
> > 
> > Changes since v10:
> > 
> > - Disable ASan leak detection
> > - Disable ASan link order verification for child processes
> > - Include source_path.h
> > - Remove unneeded explicit std::string construction
> > - Declare variables at usage site
> > - Make constants constexpr
> > - Add a variable for the message type
> > - Blank space fixes
> > ---
> >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> >  test/gstreamer/meson.build                    |  19 ++
> >  test/meson.build                              |   1 +
> >  3 files changed, 208 insertions(+)
> >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> >  create mode 100644 test/gstreamer/meson.build
> > 
> > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > new file mode 100644
> > index 000000000000..e26673b3471a
> > --- /dev/null
> > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > @@ -0,0 +1,188 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2021, Vedant Paranjape
> > + *
> > + * ipa_interface_test.cpp - Test the IPA interface
> > + */
> > +
> > +#include <iostream>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/base/utils.h>
> > +
> > +#include "libcamera/internal/source_paths.h"
> > +
> > +#include <gst/gst.h>
> > +
> > +#include "test.h"
> > +
> > +using namespace std;
> > +
> > +extern "C" {
> > +const char *__asan_default_options()
> > +{
> > +	/*
> > +	 * Disable leak detection due to a known global variable initialization
> > +	 * leak in glib's g_quark_init(). This should ideally be handled by
> > +	 * using a suppression file instead of disabling leak detection.
> > +	 */
> > +	return "detect_leaks=false";
> 
> Have you consider GST_TRACERS=leaks ? It will heck for refcounted object leaks
> on gstreamer/glib side, with very low overhead, and will abort on gst_deinit()
> if it found some leaks.

Again, no idea it existed :-) It could be enabled in init() below.

> > +}
> > +}
> > +
> > +class GstreamerSingleStreamTest : public Test
> > +{
> > +protected:
> > +	int init() override
> > +	{
> > +		/*
> > +		 * GStreamer spawns a process to run the gst-plugin-scanner
> > +		 * helper. If libcamera is compiled with ASan enabled, and as
> > +		 * GStreamer is most likely not, this will cause the ASan link
> > +		 * order check to fail when gst-plugin-scanner dlopen()s the
> > +		 * plugin as many libraries will have already been loaded by
> > +		 * then. Work around this issue by disabling the link order
> > +		 * check. This will only affect child processes, as ASan is
> > +		 * already loaded for this process by the time this code is
> > +		 * executed, and should thus hopefully be safe.
> > +		 *
> > +		 * This option is not available in gcc older than 8, the only
> > +		 * option in that case is to skip the test.
> > +		 */
> > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> > +		return TestSkip;
> > +#endif
> > +		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> 
> I think gst_registry_fork_set_enabled(FALSE) would be much cleaner, what do you
> think ?

If it does the job and has no drawback, that's fine with me.

> > +
> > +		/* Initialize GStreamer */
> > +		GError *errInit = nullptr;
> > +		if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > +			g_printerr("Could not initialize GStreamer: %s\n",
> > +				   errInit ? errInit->message : "unknown error");
> > +			if (errInit)
> > +				g_error_free(errInit);
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		/*
> > +		 * Remove the system libcamera plugin, if any, and add the
> > +		 * plugin from the build directory.
> > +		 */
> > +		GstRegistry *registry = gst_registry_get();
> > +		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > +		if (plugin) {
> > +			gst_registry_remove_plugin(registry, plugin);
> > +			gst_object_unref(plugin);
> > +		}
> > +
> > +		std::string path = libcamera::utils::libcameraBuildPath()
> > +				 + "src/gstreamer";
> > +		if (!gst_registry_scan_path(registry, path.c_str())) {
> > +			g_printerr("Failed to add plugin to registry\n");
> > +			gst_deinit();
> > +			return TestFail;
> > +		}
> > +
> > +		/* Create the elements */
> > +		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > +		convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > +		sink0_ = gst_element_factory_make("fakesink", "sink0");
> 
> Please use fakevideosink instead.

That's what Vedant was doing, and the element wasn't present on Kieran's
system. For my own culture, what's the advantage of using fakevideosink
over fakesink ?

> > +
> > +		/* Create the empty pipeline_ */
> > +		pipeline_ = gst_pipeline_new("test-pipeline");
> > +
> > +		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> 
> pipeline_ cannot be NULL, glib will abort if malloc failed.

Good point.

> > +			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > +				   pipeline_, convert0_, sink0_, libcameraSrc_);
> > +			if (pipeline_)
> > +				gst_object_unref(pipeline_);
> > +			if (convert0_)
> > +				gst_object_unref(convert0_);
> > +			if (sink0_)
> > +				gst_object_unref(sink0_);
> > +			if (libcameraSrc_)
> > +				gst_object_unref(libcameraSrc_);
> 
> Use local g_autoptr() and g_steal_pointer() on success perhaps ? This will
> reduce a lot the about of error prone code in failure case. Note that in other
> software I've seen, an abort is used instead, as clean exit is just coding
> overhead for a test.
> 
> An alternative, add them immediately to the pipeline, they are not owned by your
> class anyway.
> 
> > +			gst_deinit();
> > +
> > +			return TestFail;
> > +		}
> > +
> > +		return TestPass;
> > +	}
> > +
> > +	void cleanup() override
> > +	{
> > +		gst_object_unref(pipeline_);
> > +		gst_deinit();
> > +	}
> > +
> > +	int run() override
> > +	{
> > +		GstStateChangeReturn ret;
> > +
> > +		/* Build the pipeline */
> > +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> 
> This can fail (e.g. duplicate name).
> 
> > +		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > +			g_printerr("Elements could not be linked.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Start playing */
> > +		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > +		if (ret == GST_STATE_CHANGE_FAILURE) {
> > +			g_printerr("Unable to set the pipeline to the playing state.\n");
> > +			return TestFail;
> > +		}
> > +
> > +		/* Wait until error or EOS or timeout after 2 seconds */
> > +		constexpr GstMessageType msgType =
> > +			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > +		constexpr GstClockTime timeout = 2000000000;

> Perhaps 2 * GST_SECOND.

Much better.

> > +
> > +		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > +		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > +
> > +		gst_element_set_state(pipeline_, GST_STATE_NULL);
> > +
> > +		/* Parse error message */
> > +		if (msg == NULL)
> > +			return TestPass;
> 
> I would like some minimal validation. I would expect that after 2s some frames
> got "rendered" properly. You can read the GstStructure property "stats" from
> fakevideosink / fakesink, and read the "rendered" field. Make sure this not zero
> perhaps ?

Very good idea.

Vedant, I think there's work for you :-)

> > +
> > +		switch (GST_MESSAGE_TYPE(msg)) {
> > +		case GST_MESSAGE_ERROR:
> > +			gstreamer_print_error(msg);
> > +			break;
> > +		case GST_MESSAGE_EOS:
> > +			g_print("End-Of-Stream reached.\n");
> > +			break;
> > +		default:
> > +			g_printerr("Unexpected message received.\n");
> > +			break;
> > +		}
> > +
> > +		return TestFail;
> > +	}
> > +
> > +private:
> > +	void gstreamer_print_error(GstMessage *msg)
> > +	{
> > +		GError *err;
> > +		gchar *debug_info;
> > +
> > +		gst_message_parse_error(msg, &err, &debug_info);
> > +		g_printerr("Error received from element %s: %s\n",
> > +			   GST_OBJECT_NAME(msg->src), err->message);
> > +		g_printerr("Debugging information: %s\n",
> > +			   debug_info ? debug_info : "none");
> > +		g_clear_error(&err);
> > +		g_free(debug_info);
> > +	}
> > +
> > +	GstElement *pipeline_;
> > +	GstElement *libcameraSrc_;
> > +	GstElement *convert0_;
> > +	GstElement *sink0_;
> > +};
> > +
> > +TEST_REGISTER(GstreamerSingleStreamTest)
> > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > new file mode 100644
> > index 000000000000..b99aa0da0ba3
> > --- /dev/null
> > +++ b/test/gstreamer/meson.build
> > @@ -0,0 +1,19 @@
> > +# SPDX-License-Identifier: CC0-1.0
> > +
> > +if not gst_enabled
> > +    subdir_done()
> > +endif
> > +
> > +gstreamer_tests = [
> > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > +]
> > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > +
> > +foreach t : gstreamer_tests
> > +    exe = executable(t[0], t[1],
> > +                     dependencies : [libcamera_private, gstreamer_dep],
> > +                     link_with : test_libraries,
> > +                     include_directories : test_includes_internal)
> > +
> > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > +endforeach
> > diff --git a/test/meson.build b/test/meson.build
> > index 3bceb5df586f..d0466f17d7b6 100644
> > --- a/test/meson.build
> > +++ b/test/meson.build
> > @@ -11,6 +11,7 @@ subdir('libtest')
> >  
> >  subdir('camera')
> >  subdir('controls')
> > +subdir('gstreamer')
> >  subdir('ipa')
> >  subdir('ipc')
> >  subdir('log')
Vedant Paranjape Aug. 17, 2021, 6:38 p.m. UTC | #16
Hi Laurent,
Will address these changes in addition to one Umang suggested within this
week itself.

Regards,
*Vedant Paranjape*

On Wed, Aug 18, 2021 at 12:04 AM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Nicolas,
>
> On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:
> > Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > >
> > > This patch adds a test to test if single stream using libcamera's
> > > gstreamer element works.
> > >
> > > We need to work around two distinct issues with ASan when enabled in
> the
> > > build:
> > >
> > > - glib has a known leak at initialization time. This is covered by the
> > >   suppression file shipped with glib, but it's not clear how to use it
> > >   automatically. For now, disable leak detection to avoid test
> failures.
> >
> > Are Valgrind suppression usable for Asan ? Glib installs it into:
> >
> >   /usr/share/glib-2.0/valgrind/glib.supp
> >
> > For GStreamer suppressions, we don't install them (yet, under
> discussion). They
> > are located in each repos. I would enable GStreamer leak tracer though,
> it does
> > not have such a high run-time overhead, and will detect any GObject or
> > GstMiniObject leak, as long as the test calls gst_deinit() at the end
> (all tests
> > should).
>
> Yes, I think they are usable. I was however unsure whether the path to
> the supression file was standard, especially considering embedded
> systems. I thus went for a workaround. It would be nice if there was a
> standard mechanism to pick suppressions automatically.
>
> > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> > >   the libcamera plugin will cause an ASan link order verification
> > >   failure. Disable the verification child processes to work around the
> > >   problem. This requires gcc 8 or newer.
> >
> > Have you considered simply disabling forks ? See
> gst_registry_fork_set_enabled()
>
> No, as I had no idea that existed :-)
>
> > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > > This version incorporates changes coming from my review of v10, and
> > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > > independent reasons as explained in the commit message. I'd like to
> find
> > > a way to use leak suppression files to handle the glib initialization
> > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > is acceptable in my opinion.
> > >
> > > The biggest trouble with these workarounds is that they don't work with
> > > gcc version older than 8. As the stable version of the most common
> Linux
> > > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > > is the oldest version that libcamera supports) is also acceptable in my
> > > opinion.
> > >
> > > Changes since v10:
> > >
> > > - Disable ASan leak detection
> > > - Disable ASan link order verification for child processes
> > > - Include source_path.h
> > > - Remove unneeded explicit std::string construction
> > > - Declare variables at usage site
> > > - Make constants constexpr
> > > - Add a variable for the message type
> > > - Blank space fixes
> > > ---
> > >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> > >  test/gstreamer/meson.build                    |  19 ++
> > >  test/meson.build                              |   1 +
> > >  3 files changed, 208 insertions(+)
> > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > >  create mode 100644 test/gstreamer/meson.build
> > >
> > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
> b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > new file mode 100644
> > > index 000000000000..e26673b3471a
> > > --- /dev/null
> > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > @@ -0,0 +1,188 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Vedant Paranjape
> > > + *
> > > + * ipa_interface_test.cpp - Test the IPA interface
> > > + */
> > > +
> > > +#include <iostream>
> > > +#include <unistd.h>
> > > +
> > > +#include <libcamera/base/utils.h>
> > > +
> > > +#include "libcamera/internal/source_paths.h"
> > > +
> > > +#include <gst/gst.h>
> > > +
> > > +#include "test.h"
> > > +
> > > +using namespace std;
> > > +
> > > +extern "C" {
> > > +const char *__asan_default_options()
> > > +{
> > > +   /*
> > > +    * Disable leak detection due to a known global variable
> initialization
> > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > +    * using a suppression file instead of disabling leak detection.
> > > +    */
> > > +   return "detect_leaks=false";
> >
> > Have you consider GST_TRACERS=leaks ? It will heck for refcounted object
> leaks
> > on gstreamer/glib side, with very low overhead, and will abort on
> gst_deinit()
> > if it found some leaks.
>
> Again, no idea it existed :-) It could be enabled in init() below.
>
> > > +}
> > > +}
> > > +
> > > +class GstreamerSingleStreamTest : public Test
> > > +{
> > > +protected:
> > > +   int init() override
> > > +   {
> > > +           /*
> > > +            * GStreamer spawns a process to run the gst-plugin-scanner
> > > +            * helper. If libcamera is compiled with ASan enabled, and
> as
> > > +            * GStreamer is most likely not, this will cause the ASan
> link
> > > +            * order check to fail when gst-plugin-scanner dlopen()s
> the
> > > +            * plugin as many libraries will have already been loaded
> by
> > > +            * then. Work around this issue by disabling the link order
> > > +            * check. This will only affect child processes, as ASan is
> > > +            * already loaded for this process by the time this code is
> > > +            * executed, and should thus hopefully be safe.
> > > +            *
> > > +            * This option is not available in gcc older than 8, the
> only
> > > +            * option in that case is to skip the test.
> > > +            */
> > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__
> < 8
> > > +           return TestSkip;
> > > +#endif
> > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> >
> > I think gst_registry_fork_set_enabled(FALSE) would be much cleaner, what
> do you
> > think ?
>
> If it does the job and has no drawback, that's fine with me.
>
> > > +
> > > +           /* Initialize GStreamer */
> > > +           GError *errInit = nullptr;
> > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > +                   g_printerr("Could not initialize GStreamer: %s\n",
> > > +                              errInit ? errInit->message : "unknown
> error");
> > > +                   if (errInit)
> > > +                           g_error_free(errInit);
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /*
> > > +            * Remove the system libcamera plugin, if any, and add the
> > > +            * plugin from the build directory.
> > > +            */
> > > +           GstRegistry *registry = gst_registry_get();
> > > +           GstPlugin *plugin = gst_registry_lookup(registry,
> "libgstlibcamera.so");
> > > +           if (plugin) {
> > > +                   gst_registry_remove_plugin(registry, plugin);
> > > +                   gst_object_unref(plugin);
> > > +           }
> > > +
> > > +           std::string path = libcamera::utils::libcameraBuildPath()
> > > +                            + "src/gstreamer";
> > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > +                   g_printerr("Failed to add plugin to registry\n");
> > > +                   gst_deinit();
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Create the elements */
> > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc",
> "libcamera");
> > > +           convert0_ = gst_element_factory_make("videoconvert",
> "convert0");
> > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> >
> > Please use fakevideosink instead.
>
> That's what Vedant was doing, and the element wasn't present on Kieran's
> system. For my own culture, what's the advantage of using fakevideosink
> over fakesink ?
>
> > > +
> > > +           /* Create the empty pipeline_ */
> > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > +
> > > +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_)
> {
> >
> > pipeline_ cannot be NULL, glib will abort if malloc failed.
>
> Good point.
>
> > > +                   g_printerr("Not all elements could be created.
> %p.%p.%p.%p\n",
> > > +                              pipeline_, convert0_, sink0_,
> libcameraSrc_);
> > > +                   if (pipeline_)
> > > +                           gst_object_unref(pipeline_);
> > > +                   if (convert0_)
> > > +                           gst_object_unref(convert0_);
> > > +                   if (sink0_)
> > > +                           gst_object_unref(sink0_);
> > > +                   if (libcameraSrc_)
> > > +                           gst_object_unref(libcameraSrc_);
> >
> > Use local g_autoptr() and g_steal_pointer() on success perhaps ? This
> will
> > reduce a lot the about of error prone code in failure case. Note that in
> other
> > software I've seen, an abort is used instead, as clean exit is just
> coding
> > overhead for a test.
> >
> > An alternative, add them immediately to the pipeline, they are not owned
> by your
> > class anyway.
> >
> > > +                   gst_deinit();
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           return TestPass;
> > > +   }
> > > +
> > > +   void cleanup() override
> > > +   {
> > > +           gst_object_unref(pipeline_);
> > > +           gst_deinit();
> > > +   }
> > > +
> > > +   int run() override
> > > +   {
> > > +           GstStateChangeReturn ret;
> > > +
> > > +           /* Build the pipeline */
> > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> convert0_, sink0_, NULL);
> >
> > This can fail (e.g. duplicate name).
> >
> > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
> sink0_, NULL) != TRUE) {
> > > +                   g_printerr("Elements could not be linked.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Start playing */
> > > +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > +                   g_printerr("Unable to set the pipeline to the
> playing state.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > +           constexpr GstMessageType msgType =
> > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR |
> GST_MESSAGE_EOS);
> > > +           constexpr GstClockTime timeout = 2000000000;
>
> > Perhaps 2 * GST_SECOND.
>
> Much better.
>
> > > +
> > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > +           g_autoptr(GstMessage) msg =
> gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > +
> > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > +
> > > +           /* Parse error message */
> > > +           if (msg == NULL)
> > > +                   return TestPass;
> >
> > I would like some minimal validation. I would expect that after 2s some
> frames
> > got "rendered" properly. You can read the GstStructure property "stats"
> from
> > fakevideosink / fakesink, and read the "rendered" field. Make sure this
> not zero
> > perhaps ?
>
> Very good idea.
>
> Vedant, I think there's work for you :-)
>
> > > +
> > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > +           case GST_MESSAGE_ERROR:
> > > +                   gstreamer_print_error(msg);
> > > +                   break;
> > > +           case GST_MESSAGE_EOS:
> > > +                   g_print("End-Of-Stream reached.\n");
> > > +                   break;
> > > +           default:
> > > +                   g_printerr("Unexpected message received.\n");
> > > +                   break;
> > > +           }
> > > +
> > > +           return TestFail;
> > > +   }
> > > +
> > > +private:
> > > +   void gstreamer_print_error(GstMessage *msg)
> > > +   {
> > > +           GError *err;
> > > +           gchar *debug_info;
> > > +
> > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > +           g_printerr("Error received from element %s: %s\n",
> > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > +           g_printerr("Debugging information: %s\n",
> > > +                      debug_info ? debug_info : "none");
> > > +           g_clear_error(&err);
> > > +           g_free(debug_info);
> > > +   }
> > > +
> > > +   GstElement *pipeline_;
> > > +   GstElement *libcameraSrc_;
> > > +   GstElement *convert0_;
> > > +   GstElement *sink0_;
> > > +};
> > > +
> > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > new file mode 100644
> > > index 000000000000..b99aa0da0ba3
> > > --- /dev/null
> > > +++ b/test/gstreamer/meson.build
> > > @@ -0,0 +1,19 @@
> > > +# SPDX-License-Identifier: CC0-1.0
> > > +
> > > +if not gst_enabled
> > > +    subdir_done()
> > > +endif
> > > +
> > > +gstreamer_tests = [
> > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > +]
> > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > +
> > > +foreach t : gstreamer_tests
> > > +    exe = executable(t[0], t[1],
> > > +                     dependencies : [libcamera_private,
> gstreamer_dep],
> > > +                     link_with : test_libraries,
> > > +                     include_directories : test_includes_internal)
> > > +
> > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > +endforeach
> > > diff --git a/test/meson.build b/test/meson.build
> > > index 3bceb5df586f..d0466f17d7b6 100644
> > > --- a/test/meson.build
> > > +++ b/test/meson.build
> > > @@ -11,6 +11,7 @@ subdir('libtest')
> > >
> > >  subdir('camera')
> > >  subdir('controls')
> > > +subdir('gstreamer')
> > >  subdir('ipa')
> > >  subdir('ipc')
> > >  subdir('log')
>
> --
> Regards,
>
> Laurent Pinchart
>
Vedant Paranjape Aug. 18, 2021, 11:24 a.m. UTC | #17
Hi Laurent,


On Wed, Aug 18, 2021 at 12:04 AM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Nicolas,
>
> On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:
> > Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > >
> > > This patch adds a test to test if single stream using libcamera's
> > > gstreamer element works.
> > >
> > > We need to work around two distinct issues with ASan when enabled in
> the
> > > build:
> > >
> > > - glib has a known leak at initialization time. This is covered by the
> > >   suppression file shipped with glib, but it's not clear how to use it
> > >   automatically. For now, disable leak detection to avoid test
> failures.
> >
> > Are Valgrind suppression usable for Asan ? Glib installs it into:
> >
> >   /usr/share/glib-2.0/valgrind/glib.supp
> >
> > For GStreamer suppressions, we don't install them (yet, under
> discussion). They
> > are located in each repos. I would enable GStreamer leak tracer though,
> it does
> > not have such a high run-time overhead, and will detect any GObject or
> > GstMiniObject leak, as long as the test calls gst_deinit() at the end
> (all tests
> > should).
>
> Yes, I think they are usable. I was however unsure whether the path to
> the supression file was standard, especially considering embedded
> systems. I thus went for a workaround. It would be nice if there was a
> standard mechanism to pick suppressions automatically.
>
> > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> > >   the libcamera plugin will cause an ASan link order verification
> > >   failure. Disable the verification child processes to work around the
> > >   problem. This requires gcc 8 or newer.
> >
> > Have you considered simply disabling forks ? See
> gst_registry_fork_set_enabled()
>
> No, as I had no idea that existed :-)
>

Tested this, it works as expected. Now need to figure out to patch
gstreamer as it still leaks memory

=================================================================
==676137==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 16384 byte(s) in 1 object(s) allocated from:
    #0 0x7f4faf3b8bc8 in malloc
(/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
    #1 0x7f4fae763e98 in g_malloc
(/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x57e98)

SUMMARY: AddressSanitizer: 16384 byte(s) leaked in 1 allocation(s).
――――――――――――――――――――――――――――――――――――――――

> > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > > This version incorporates changes coming from my review of v10, and
> > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > > independent reasons as explained in the commit message. I'd like to
> find
> > > a way to use leak suppression files to handle the glib initialization
> > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > is acceptable in my opinion.
> > >
> > > The biggest trouble with these workarounds is that they don't work with
> > > gcc version older than 8. As the stable version of the most common
> Linux
> > > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > > is the oldest version that libcamera supports) is also acceptable in my
> > > opinion.
> > >
> > > Changes since v10:
> > >
> > > - Disable ASan leak detection
> > > - Disable ASan link order verification for child processes
> > > - Include source_path.h
> > > - Remove unneeded explicit std::string construction
> > > - Declare variables at usage site
> > > - Make constants constexpr
> > > - Add a variable for the message type
> > > - Blank space fixes
> > > ---
> > >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> > >  test/gstreamer/meson.build                    |  19 ++
> > >  test/meson.build                              |   1 +
> > >  3 files changed, 208 insertions(+)
> > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > >  create mode 100644 test/gstreamer/meson.build
> > >
> > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
> b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > new file mode 100644
> > > index 000000000000..e26673b3471a
> > > --- /dev/null
> > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > @@ -0,0 +1,188 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Vedant Paranjape
> > > + *
> > > + * ipa_interface_test.cpp - Test the IPA interface
> > > + */
> > > +
> > > +#include <iostream>
> > > +#include <unistd.h>
> > > +
> > > +#include <libcamera/base/utils.h>
> > > +
> > > +#include "libcamera/internal/source_paths.h"
> > > +
> > > +#include <gst/gst.h>
> > > +
> > > +#include "test.h"
> > > +
> > > +using namespace std;
> > > +
> > > +extern "C" {
> > > +const char *__asan_default_options()
> > > +{
> > > +   /*
> > > +    * Disable leak detection due to a known global variable
> initialization
> > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > +    * using a suppression file instead of disabling leak detection.
> > > +    */
> > > +   return "detect_leaks=false";
> >
> > Have you consider GST_TRACERS=leaks ? It will heck for refcounted object
> leaks
> > on gstreamer/glib side, with very low overhead, and will abort on
> gst_deinit()
> > if it found some leaks.
>
> Again, no idea it existed :-) It could be enabled in init() below.
>
> > > +}
> > > +}
> > > +
> > > +class GstreamerSingleStreamTest : public Test
> > > +{
> > > +protected:
> > > +   int init() override
> > > +   {
> > > +           /*
> > > +            * GStreamer spawns a process to run the gst-plugin-scanner
> > > +            * helper. If libcamera is compiled with ASan enabled, and
> as
> > > +            * GStreamer is most likely not, this will cause the ASan
> link
> > > +            * order check to fail when gst-plugin-scanner dlopen()s
> the
> > > +            * plugin as many libraries will have already been loaded
> by
> > > +            * then. Work around this issue by disabling the link order
> > > +            * check. This will only affect child processes, as ASan is
> > > +            * already loaded for this process by the time this code is
> > > +            * executed, and should thus hopefully be safe.
> > > +            *
> > > +            * This option is not available in gcc older than 8, the
> only
> > > +            * option in that case is to skip the test.
> > > +            */
> > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__
> < 8
> > > +           return TestSkip;
> > > +#endif
> > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> >
> > I think gst_registry_fork_set_enabled(FALSE) would be much cleaner, what
> do you
> > think ?
>
> If it does the job and has no drawback, that's fine with me.
>
> > > +
> > > +           /* Initialize GStreamer */
> > > +           GError *errInit = nullptr;
> > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > +                   g_printerr("Could not initialize GStreamer: %s\n",
> > > +                              errInit ? errInit->message : "unknown
> error");
> > > +                   if (errInit)
> > > +                           g_error_free(errInit);
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /*
> > > +            * Remove the system libcamera plugin, if any, and add the
> > > +            * plugin from the build directory.
> > > +            */
> > > +           GstRegistry *registry = gst_registry_get();
> > > +           GstPlugin *plugin = gst_registry_lookup(registry,
> "libgstlibcamera.so");
> > > +           if (plugin) {
> > > +                   gst_registry_remove_plugin(registry, plugin);
> > > +                   gst_object_unref(plugin);
> > > +           }
> > > +
> > > +           std::string path = libcamera::utils::libcameraBuildPath()
> > > +                            + "src/gstreamer";
> > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > +                   g_printerr("Failed to add plugin to registry\n");
> > > +                   gst_deinit();
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Create the elements */
> > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc",
> "libcamera");
> > > +           convert0_ = gst_element_factory_make("videoconvert",
> "convert0");
> > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> >
> > Please use fakevideosink instead.
>
> That's what Vedant was doing, and the element wasn't present on Kieran's
> system. For my own culture, what's the advantage of using fakevideosink
> over fakesink ?
>
> > > +
> > > +           /* Create the empty pipeline_ */
> > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > +
> > > +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_)
> {
> >
> > pipeline_ cannot be NULL, glib will abort if malloc failed.
>
> Good point.
>
> > > +                   g_printerr("Not all elements could be created.
> %p.%p.%p.%p\n",
> > > +                              pipeline_, convert0_, sink0_,
> libcameraSrc_);
> > > +                   if (pipeline_)
> > > +                           gst_object_unref(pipeline_);
> > > +                   if (convert0_)
> > > +                           gst_object_unref(convert0_);
> > > +                   if (sink0_)
> > > +                           gst_object_unref(sink0_);
> > > +                   if (libcameraSrc_)
> > > +                           gst_object_unref(libcameraSrc_);
> >
> > Use local g_autoptr() and g_steal_pointer() on success perhaps ? This
> will
> > reduce a lot the about of error prone code in failure case. Note that in
> other
> > software I've seen, an abort is used instead, as clean exit is just
> coding
> > overhead for a test.
> >
> > An alternative, add them immediately to the pipeline, they are not owned
> by your
> > class anyway.
> >
> > > +                   gst_deinit();
> > > +
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           return TestPass;
> > > +   }
> > > +
> > > +   void cleanup() override
> > > +   {
> > > +           gst_object_unref(pipeline_);
> > > +           gst_deinit();
> > > +   }
> > > +
> > > +   int run() override
> > > +   {
> > > +           GstStateChangeReturn ret;
> > > +
> > > +           /* Build the pipeline */
> > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> convert0_, sink0_, NULL);
> >
> > This can fail (e.g. duplicate name).
> >
> > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
> sink0_, NULL) != TRUE) {
> > > +                   g_printerr("Elements could not be linked.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Start playing */
> > > +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > +                   g_printerr("Unable to set the pipeline to the
> playing state.\n");
> > > +                   return TestFail;
> > > +           }
> > > +
> > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > +           constexpr GstMessageType msgType =
> > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR |
> GST_MESSAGE_EOS);
> > > +           constexpr GstClockTime timeout = 2000000000;
>
> > Perhaps 2 * GST_SECOND.
>
> Much better.
>
> > > +
> > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > +           g_autoptr(GstMessage) msg =
> gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > +
> > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > +
> > > +           /* Parse error message */
> > > +           if (msg == NULL)
> > > +                   return TestPass;
> >
> > I would like some minimal validation. I would expect that after 2s some
> frames
> > got "rendered" properly. You can read the GstStructure property "stats"
> from
> > fakevideosink / fakesink, and read the "rendered" field. Make sure this
> not zero
> > perhaps ?
>
> Very good idea.
>
> Vedant, I think there's work for you :-)
>
> > > +
> > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > +           case GST_MESSAGE_ERROR:
> > > +                   gstreamer_print_error(msg);
> > > +                   break;
> > > +           case GST_MESSAGE_EOS:
> > > +                   g_print("End-Of-Stream reached.\n");
> > > +                   break;
> > > +           default:
> > > +                   g_printerr("Unexpected message received.\n");
> > > +                   break;
> > > +           }
> > > +
> > > +           return TestFail;
> > > +   }
> > > +
> > > +private:
> > > +   void gstreamer_print_error(GstMessage *msg)
> > > +   {
> > > +           GError *err;
> > > +           gchar *debug_info;
> > > +
> > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > +           g_printerr("Error received from element %s: %s\n",
> > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > +           g_printerr("Debugging information: %s\n",
> > > +                      debug_info ? debug_info : "none");
> > > +           g_clear_error(&err);
> > > +           g_free(debug_info);
> > > +   }
> > > +
> > > +   GstElement *pipeline_;
> > > +   GstElement *libcameraSrc_;
> > > +   GstElement *convert0_;
> > > +   GstElement *sink0_;
> > > +};
> > > +
> > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > new file mode 100644
> > > index 000000000000..b99aa0da0ba3
> > > --- /dev/null
> > > +++ b/test/gstreamer/meson.build
> > > @@ -0,0 +1,19 @@
> > > +# SPDX-License-Identifier: CC0-1.0
> > > +
> > > +if not gst_enabled
> > > +    subdir_done()
> > > +endif
> > > +
> > > +gstreamer_tests = [
> > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > +]
> > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > +
> > > +foreach t : gstreamer_tests
> > > +    exe = executable(t[0], t[1],
> > > +                     dependencies : [libcamera_private,
> gstreamer_dep],
> > > +                     link_with : test_libraries,
> > > +                     include_directories : test_includes_internal)
> > > +
> > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > +endforeach
> > > diff --git a/test/meson.build b/test/meson.build
> > > index 3bceb5df586f..d0466f17d7b6 100644
> > > --- a/test/meson.build
> > > +++ b/test/meson.build
> > > @@ -11,6 +11,7 @@ subdir('libtest')
> > >
> > >  subdir('camera')
> > >  subdir('controls')
> > > +subdir('gstreamer')
> > >  subdir('ipa')
> > >  subdir('ipc')
> > >  subdir('log')
>
> --
> Regards,
>
> Laurent Pinchart
>

Regards,
Vedant Paranjape
Laurent Pinchart Aug. 18, 2021, 11:35 a.m. UTC | #18
Hi Vedant,

On Wed, Aug 18, 2021 at 04:54:45PM +0530, Vedant Paranjape wrote:
> On Wed, Aug 18, 2021 at 12:04 AM Laurent Pinchart wrote:
> > On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:
> > > Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> > > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > >
> > > > This patch adds a test to test if single stream using libcamera's
> > > > gstreamer element works.
> > > >
> > > > We need to work around two distinct issues with ASan when enabled in the
> > > > build:
> > > >
> > > > - glib has a known leak at initialization time. This is covered by the
> > > >   suppression file shipped with glib, but it's not clear how to use it
> > > >   automatically. For now, disable leak detection to avoid test failures.
> > >
> > > Are Valgrind suppression usable for Asan ? Glib installs it into:
> > >
> > >   /usr/share/glib-2.0/valgrind/glib.supp
> > >
> > > For GStreamer suppressions, we don't install them (yet, under discussion). They
> > > are located in each repos. I would enable GStreamer leak tracer though, it does
> > > not have such a high run-time overhead, and will detect any GObject or
> > > GstMiniObject leak, as long as the test calls gst_deinit() at the end (all tests
> > > should).
> >
> > Yes, I think they are usable. I was however unsure whether the path to
> > the supression file was standard, especially considering embedded
> > systems. I thus went for a workaround. It would be nice if there was a
> > standard mechanism to pick suppressions automatically.
> >
> > > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > > >   compiled without ASan (which is likely) but libcamera is, dlopen()ing
> > > >   the libcamera plugin will cause an ASan link order verification
> > > >   failure. Disable the verification child processes to work around the
> > > >   problem. This requires gcc 8 or newer.
> > >
> > > Have you considered simply disabling forks ? See gst_registry_fork_set_enabled()
> >
> > No, as I had no idea that existed :-)
> 
> Tested this, it works as expected. Now need to figure out to patch
> gstreamer as it still leaks memory
> 
> =================================================================
> ==676137==ERROR: LeakSanitizer: detected memory leaks
> 
> Direct leak of 16384 byte(s) in 1 object(s) allocated from:
>     #0 0x7f4faf3b8bc8 in malloc
> (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
>     #1 0x7f4fae763e98 in g_malloc
> (/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x57e98)
> 
> SUMMARY: AddressSanitizer: 16384 byte(s) leaked in 1 allocation(s).
> ――――――――――――――――――――――――――――――――――――――――

That's a known glib issue, and that's why ASan leak detection is
disabled (in addition to disabling ASan link order verification, which
could be dropped in favour of using gst_registry_fork_set_enabled()).
glib has a valgrind suppression file that could be used by ASan as well,
but I wasn't sure how to integrate it correctly in a way that would work
on all distributions.

> > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > ---
> > > > This version incorporates changes coming from my review of v10, and
> > > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two
> > > > independent reasons as explained in the commit message. I'd like to find
> > > > a way to use leak suppression files to handle the glib initialization
> > > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > > is acceptable in my opinion.
> > > >
> > > > The biggest trouble with these workarounds is that they don't work with
> > > > gcc version older than 8. As the stable version of the most common Linux
> > > > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which
> > > > is the oldest version that libcamera supports) is also acceptable in my
> > > > opinion.
> > > >
> > > > Changes since v10:
> > > >
> > > > - Disable ASan leak detection
> > > > - Disable ASan link order verification for child processes
> > > > - Include source_path.h
> > > > - Remove unneeded explicit std::string construction
> > > > - Declare variables at usage site
> > > > - Make constants constexpr
> > > > - Add a variable for the message type
> > > > - Blank space fixes
> > > > ---
> > > >  .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> > > >  test/gstreamer/meson.build                    |  19 ++
> > > >  test/meson.build                              |   1 +
> > > >  3 files changed, 208 insertions(+)
> > > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > > >  create mode 100644 test/gstreamer/meson.build
> > > >
> > > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > new file mode 100644
> > > > index 000000000000..e26673b3471a
> > > > --- /dev/null
> > > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > @@ -0,0 +1,188 @@
> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > +/*
> > > > + * Copyright (C) 2021, Vedant Paranjape
> > > > + *
> > > > + * ipa_interface_test.cpp - Test the IPA interface
> > > > + */
> > > > +
> > > > +#include <iostream>
> > > > +#include <unistd.h>
> > > > +
> > > > +#include <libcamera/base/utils.h>
> > > > +
> > > > +#include "libcamera/internal/source_paths.h"
> > > > +
> > > > +#include <gst/gst.h>
> > > > +
> > > > +#include "test.h"
> > > > +
> > > > +using namespace std;
> > > > +
> > > > +extern "C" {
> > > > +const char *__asan_default_options()
> > > > +{
> > > > +   /*
> > > > +    * Disable leak detection due to a known global variable initialization
> > > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > > +    * using a suppression file instead of disabling leak detection.
> > > > +    */
> > > > +   return "detect_leaks=false";
> > >
> > > Have you consider GST_TRACERS=leaks ? It will heck for refcounted object leaks
> > > on gstreamer/glib side, with very low overhead, and will abort on gst_deinit()
> > > if it found some leaks.
> >
> > Again, no idea it existed :-) It could be enabled in init() below.
> >
> > > > +}
> > > > +}
> > > > +
> > > > +class GstreamerSingleStreamTest : public Test
> > > > +{
> > > > +protected:
> > > > +   int init() override
> > > > +   {
> > > > +           /*
> > > > +            * GStreamer spawns a process to run the gst-plugin-scanner
> > > > +            * helper. If libcamera is compiled with ASan enabled, and as
> > > > +            * GStreamer is most likely not, this will cause the ASan link
> > > > +            * order check to fail when gst-plugin-scanner dlopen()s the
> > > > +            * plugin as many libraries will have already been loaded by
> > > > +            * then. Work around this issue by disabling the link order
> > > > +            * check. This will only affect child processes, as ASan is
> > > > +            * already loaded for this process by the time this code is
> > > > +            * executed, and should thus hopefully be safe.
> > > > +            *
> > > > +            * This option is not available in gcc older than 8, the only
> > > > +            * option in that case is to skip the test.
> > > > +            */
> > > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
> > > > +           return TestSkip;
> > > > +#endif
> > > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > >
> > > I think gst_registry_fork_set_enabled(FALSE) would be much cleaner, what do you
> > > think ?
> >
> > If it does the job and has no drawback, that's fine with me.
> >
> > > > +
> > > > +           /* Initialize GStreamer */
> > > > +           GError *errInit = nullptr;
> > > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > > +                   g_printerr("Could not initialize GStreamer: %s\n",
> > > > +                              errInit ? errInit->message : "unknown error");
> > > > +                   if (errInit)
> > > > +                           g_error_free(errInit);
> > > > +
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /*
> > > > +            * Remove the system libcamera plugin, if any, and add the
> > > > +            * plugin from the build directory.
> > > > +            */
> > > > +           GstRegistry *registry = gst_registry_get();
> > > > +           GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > > > +           if (plugin) {
> > > > +                   gst_registry_remove_plugin(registry, plugin);
> > > > +                   gst_object_unref(plugin);
> > > > +           }
> > > > +
> > > > +           std::string path = libcamera::utils::libcameraBuildPath()
> > > > +                            + "src/gstreamer";
> > > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > > +                   g_printerr("Failed to add plugin to registry\n");
> > > > +                   gst_deinit();
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /* Create the elements */
> > > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > > > +           convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > >
> > > Please use fakevideosink instead.
> >
> > That's what Vedant was doing, and the element wasn't present on Kieran's
> > system. For my own culture, what's the advantage of using fakevideosink
> > over fakesink ?
> >
> > > > +
> > > > +           /* Create the empty pipeline_ */
> > > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > > +
> > > > +           if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
> > >
> > > pipeline_ cannot be NULL, glib will abort if malloc failed.
> >
> > Good point.
> >
> > > > +                   g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > > > +                              pipeline_, convert0_, sink0_, libcameraSrc_);
> > > > +                   if (pipeline_)
> > > > +                           gst_object_unref(pipeline_);
> > > > +                   if (convert0_)
> > > > +                           gst_object_unref(convert0_);
> > > > +                   if (sink0_)
> > > > +                           gst_object_unref(sink0_);
> > > > +                   if (libcameraSrc_)
> > > > +                           gst_object_unref(libcameraSrc_);
> > >
> > > Use local g_autoptr() and g_steal_pointer() on success perhaps ? This will
> > > reduce a lot the about of error prone code in failure case. Note that in other
> > > software I've seen, an abort is used instead, as clean exit is just coding
> > > overhead for a test.
> > >
> > > An alternative, add them immediately to the pipeline, they are not owned by your
> > > class anyway.
> > >
> > > > +                   gst_deinit();
> > > > +
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           return TestPass;
> > > > +   }
> > > > +
> > > > +   void cleanup() override
> > > > +   {
> > > > +           gst_object_unref(pipeline_);
> > > > +           gst_deinit();
> > > > +   }
> > > > +
> > > > +   int run() override
> > > > +   {
> > > > +           GstStateChangeReturn ret;
> > > > +
> > > > +           /* Build the pipeline */
> > > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> > >
> > > This can fail (e.g. duplicate name).
> > >
> > > > +           if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > > > +                   g_printerr("Elements could not be linked.\n");
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /* Start playing */
> > > > +           ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
> > > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > > +                   g_printerr("Unable to set the pipeline to the playing state.\n");
> > > > +                   return TestFail;
> > > > +           }
> > > > +
> > > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > > +           constexpr GstMessageType msgType =
> > > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > > > +           constexpr GstClockTime timeout = 2000000000;
> > >
> > > Perhaps 2 * GST_SECOND.
> >
> > Much better.
> >
> > > > +
> > > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > > +           g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > > +
> > > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > > +
> > > > +           /* Parse error message */
> > > > +           if (msg == NULL)
> > > > +                   return TestPass;
> > >
> > > I would like some minimal validation. I would expect that after 2s some frames
> > > got "rendered" properly. You can read the GstStructure property "stats" from
> > > fakevideosink / fakesink, and read the "rendered" field. Make sure this not zero
> > > perhaps ?
> >
> > Very good idea.
> >
> > Vedant, I think there's work for you :-)
> >
> > > > +
> > > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > > +           case GST_MESSAGE_ERROR:
> > > > +                   gstreamer_print_error(msg);
> > > > +                   break;
> > > > +           case GST_MESSAGE_EOS:
> > > > +                   g_print("End-Of-Stream reached.\n");
> > > > +                   break;
> > > > +           default:
> > > > +                   g_printerr("Unexpected message received.\n");
> > > > +                   break;
> > > > +           }
> > > > +
> > > > +           return TestFail;
> > > > +   }
> > > > +
> > > > +private:
> > > > +   void gstreamer_print_error(GstMessage *msg)
> > > > +   {
> > > > +           GError *err;
> > > > +           gchar *debug_info;
> > > > +
> > > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > > +           g_printerr("Error received from element %s: %s\n",
> > > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > > +           g_printerr("Debugging information: %s\n",
> > > > +                      debug_info ? debug_info : "none");
> > > > +           g_clear_error(&err);
> > > > +           g_free(debug_info);
> > > > +   }
> > > > +
> > > > +   GstElement *pipeline_;
> > > > +   GstElement *libcameraSrc_;
> > > > +   GstElement *convert0_;
> > > > +   GstElement *sink0_;
> > > > +};
> > > > +
> > > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > > new file mode 100644
> > > > index 000000000000..b99aa0da0ba3
> > > > --- /dev/null
> > > > +++ b/test/gstreamer/meson.build
> > > > @@ -0,0 +1,19 @@
> > > > +# SPDX-License-Identifier: CC0-1.0
> > > > +
> > > > +if not gst_enabled
> > > > +    subdir_done()
> > > > +endif
> > > > +
> > > > +gstreamer_tests = [
> > > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > > +]
> > > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > > +
> > > > +foreach t : gstreamer_tests
> > > > +    exe = executable(t[0], t[1],
> > > > +                     dependencies : [libcamera_private, gstreamer_dep],
> > > > +                     link_with : test_libraries,
> > > > +                     include_directories : test_includes_internal)
> > > > +
> > > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > > +endforeach
> > > > diff --git a/test/meson.build b/test/meson.build
> > > > index 3bceb5df586f..d0466f17d7b6 100644
> > > > --- a/test/meson.build
> > > > +++ b/test/meson.build
> > > > @@ -11,6 +11,7 @@ subdir('libtest')
> > > >
> > > >  subdir('camera')
> > > >  subdir('controls')
> > > > +subdir('gstreamer')
> > > >  subdir('ipa')
> > > >  subdir('ipc')
> > > >  subdir('log')
Vedant Paranjape Aug. 18, 2021, 12:24 p.m. UTC | #19
Hi,
How to use the suppression files ?

Regards
Vedant

On Wed, Aug 18, 2021 at 5:05 PM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Vedant,
>
> On Wed, Aug 18, 2021 at 04:54:45PM +0530, Vedant Paranjape wrote:
> > On Wed, Aug 18, 2021 at 12:04 AM Laurent Pinchart wrote:
> > > On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:
> > > > Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> > > > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > >
> > > > > This patch adds a test to test if single stream using libcamera's
> > > > > gstreamer element works.
> > > > >
> > > > > We need to work around two distinct issues with ASan when enabled
> in the
> > > > > build:
> > > > >
> > > > > - glib has a known leak at initialization time. This is covered by
> the
> > > > >   suppression file shipped with glib, but it's not clear how to
> use it
> > > > >   automatically. For now, disable leak detection to avoid test
> failures.
> > > >
> > > > Are Valgrind suppression usable for Asan ? Glib installs it into:
> > > >
> > > >   /usr/share/glib-2.0/valgrind/glib.supp
> > > >
> > > > For GStreamer suppressions, we don't install them (yet, under
> discussion). They
> > > > are located in each repos. I would enable GStreamer leak tracer
> though, it does
> > > > not have such a high run-time overhead, and will detect any GObject
> or
> > > > GstMiniObject leak, as long as the test calls gst_deinit() at the
> end (all tests
> > > > should).
> > >
> > > Yes, I think they are usable. I was however unsure whether the path to
> > > the supression file was standard, especially considering embedded
> > > systems. I thus went for a workaround. It would be nice if there was a
> > > standard mechanism to pick suppressions automatically.
> > >
> > > > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > > > >   compiled without ASan (which is likely) but libcamera is,
> dlopen()ing
> > > > >   the libcamera plugin will cause an ASan link order verification
> > > > >   failure. Disable the verification child processes to work around
> the
> > > > >   problem. This requires gcc 8 or newer.
> > > >
> > > > Have you considered simply disabling forks ? See
> gst_registry_fork_set_enabled()
> > >
> > > No, as I had no idea that existed :-)
> >
> > Tested this, it works as expected. Now need to figure out to patch
> > gstreamer as it still leaks memory
> >
> > =================================================================
> > ==676137==ERROR: LeakSanitizer: detected memory leaks
> >
> > Direct leak of 16384 byte(s) in 1 object(s) allocated from:
> >     #0 0x7f4faf3b8bc8 in malloc
> > (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
> >     #1 0x7f4fae763e98 in g_malloc
> > (/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x57e98)
> >
> > SUMMARY: AddressSanitizer: 16384 byte(s) leaked in 1 allocation(s).
> > ――――――――――――――――――――――――――――――――――――――――
>
> That's a known glib issue, and that's why ASan leak detection is
> disabled (in addition to disabling ASan link order verification, which
> could be dropped in favour of using gst_registry_fork_set_enabled()).
> glib has a valgrind suppression file that could be used by ASan as well,
> but I wasn't sure how to integrate it correctly in a way that would work
> on all distributions.
>
> > > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com
> >
> > > > > ---
> > > > > This version incorporates changes coming from my review of v10, and
> > > > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for
> two
> > > > > independent reasons as explained in the commit message. I'd like
> to find
> > > > > a way to use leak suppression files to handle the glib
> initialization
> > > > > leak, but that's a big rabbit hole. The workaround for the second
> issue
> > > > > is acceptable in my opinion.
> > > > >
> > > > > The biggest trouble with these workarounds is that they don't work
> with
> > > > > gcc version older than 8. As the stable version of the most common
> Linux
> > > > > distributions ship gcc 8 or newer, skipping this test for gcc 7
> (which
> > > > > is the oldest version that libcamera supports) is also acceptable
> in my
> > > > > opinion.
> > > > >
> > > > > Changes since v10:
> > > > >
> > > > > - Disable ASan leak detection
> > > > > - Disable ASan link order verification for child processes
> > > > > - Include source_path.h
> > > > > - Remove unneeded explicit std::string construction
> > > > > - Declare variables at usage site
> > > > > - Make constants constexpr
> > > > > - Add a variable for the message type
> > > > > - Blank space fixes
> > > > > ---
> > > > >  .../gstreamer_single_stream_test.cpp          | 188
> ++++++++++++++++++
> > > > >  test/gstreamer/meson.build                    |  19 ++
> > > > >  test/meson.build                              |   1 +
> > > > >  3 files changed, 208 insertions(+)
> > > > >  create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > > > >  create mode 100644 test/gstreamer/meson.build
> > > > >
> > > > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
> b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > new file mode 100644
> > > > > index 000000000000..e26673b3471a
> > > > > --- /dev/null
> > > > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > @@ -0,0 +1,188 @@
> > > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > > +/*
> > > > > + * Copyright (C) 2021, Vedant Paranjape
> > > > > + *
> > > > > + * ipa_interface_test.cpp - Test the IPA interface
> > > > > + */
> > > > > +
> > > > > +#include <iostream>
> > > > > +#include <unistd.h>
> > > > > +
> > > > > +#include <libcamera/base/utils.h>
> > > > > +
> > > > > +#include "libcamera/internal/source_paths.h"
> > > > > +
> > > > > +#include <gst/gst.h>
> > > > > +
> > > > > +#include "test.h"
> > > > > +
> > > > > +using namespace std;
> > > > > +
> > > > > +extern "C" {
> > > > > +const char *__asan_default_options()
> > > > > +{
> > > > > +   /*
> > > > > +    * Disable leak detection due to a known global variable
> initialization
> > > > > +    * leak in glib's g_quark_init(). This should ideally be
> handled by
> > > > > +    * using a suppression file instead of disabling leak
> detection.
> > > > > +    */
> > > > > +   return "detect_leaks=false";
> > > >
> > > > Have you consider GST_TRACERS=leaks ? It will heck for refcounted
> object leaks
> > > > on gstreamer/glib side, with very low overhead, and will abort on
> gst_deinit()
> > > > if it found some leaks.
> > >
> > > Again, no idea it existed :-) It could be enabled in init() below.
> > >
> > > > > +}
> > > > > +}
> > > > > +
> > > > > +class GstreamerSingleStreamTest : public Test
> > > > > +{
> > > > > +protected:
> > > > > +   int init() override
> > > > > +   {
> > > > > +           /*
> > > > > +            * GStreamer spawns a process to run the
> gst-plugin-scanner
> > > > > +            * helper. If libcamera is compiled with ASan enabled,
> and as
> > > > > +            * GStreamer is most likely not, this will cause the
> ASan link
> > > > > +            * order check to fail when gst-plugin-scanner
> dlopen()s the
> > > > > +            * plugin as many libraries will have already been
> loaded by
> > > > > +            * then. Work around this issue by disabling the link
> order
> > > > > +            * check. This will only affect child processes, as
> ASan is
> > > > > +            * already loaded for this process by the time this
> code is
> > > > > +            * executed, and should thus hopefully be safe.
> > > > > +            *
> > > > > +            * This option is not available in gcc older than 8,
> the only
> > > > > +            * option in that case is to skip the test.
> > > > > +            */
> > > > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) &&
> __GNUC__ < 8
> > > > > +           return TestSkip;
> > > > > +#endif
> > > > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > > >
> > > > I think gst_registry_fork_set_enabled(FALSE) would be much cleaner,
> what do you
> > > > think ?
> > >
> > > If it does the job and has no drawback, that's fine with me.
> > >
> > > > > +
> > > > > +           /* Initialize GStreamer */
> > > > > +           GError *errInit = nullptr;
> > > > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > > > +                   g_printerr("Could not initialize GStreamer:
> %s\n",
> > > > > +                              errInit ? errInit->message :
> "unknown error");
> > > > > +                   if (errInit)
> > > > > +                           g_error_free(errInit);
> > > > > +
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /*
> > > > > +            * Remove the system libcamera plugin, if any, and add
> the
> > > > > +            * plugin from the build directory.
> > > > > +            */
> > > > > +           GstRegistry *registry = gst_registry_get();
> > > > > +           GstPlugin *plugin = gst_registry_lookup(registry,
> "libgstlibcamera.so");
> > > > > +           if (plugin) {
> > > > > +                   gst_registry_remove_plugin(registry, plugin);
> > > > > +                   gst_object_unref(plugin);
> > > > > +           }
> > > > > +
> > > > > +           std::string path =
> libcamera::utils::libcameraBuildPath()
> > > > > +                            + "src/gstreamer";
> > > > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > > > +                   g_printerr("Failed to add plugin to
> registry\n");
> > > > > +                   gst_deinit();
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /* Create the elements */
> > > > > +           libcameraSrc_ =
> gst_element_factory_make("libcamerasrc", "libcamera");
> > > > > +           convert0_ = gst_element_factory_make("videoconvert",
> "convert0");
> > > > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > > >
> > > > Please use fakevideosink instead.
> > >
> > > That's what Vedant was doing, and the element wasn't present on
> Kieran's
> > > system. For my own culture, what's the advantage of using fakevideosink
> > > over fakesink ?
> > >
> > > > > +
> > > > > +           /* Create the empty pipeline_ */
> > > > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > > > +
> > > > > +           if (!pipeline_ || !convert0_ || !sink0_ ||
> !libcameraSrc_) {
> > > >
> > > > pipeline_ cannot be NULL, glib will abort if malloc failed.
> > >
> > > Good point.
> > >
> > > > > +                   g_printerr("Not all elements could be created.
> %p.%p.%p.%p\n",
> > > > > +                              pipeline_, convert0_, sink0_,
> libcameraSrc_);
> > > > > +                   if (pipeline_)
> > > > > +                           gst_object_unref(pipeline_);
> > > > > +                   if (convert0_)
> > > > > +                           gst_object_unref(convert0_);
> > > > > +                   if (sink0_)
> > > > > +                           gst_object_unref(sink0_);
> > > > > +                   if (libcameraSrc_)
> > > > > +                           gst_object_unref(libcameraSrc_);
> > > >
> > > > Use local g_autoptr() and g_steal_pointer() on success perhaps ?
> This will
> > > > reduce a lot the about of error prone code in failure case. Note
> that in other
> > > > software I've seen, an abort is used instead, as clean exit is just
> coding
> > > > overhead for a test.
> > > >
> > > > An alternative, add them immediately to the pipeline, they are not
> owned by your
> > > > class anyway.
> > > >
> > > > > +                   gst_deinit();
> > > > > +
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           return TestPass;
> > > > > +   }
> > > > > +
> > > > > +   void cleanup() override
> > > > > +   {
> > > > > +           gst_object_unref(pipeline_);
> > > > > +           gst_deinit();
> > > > > +   }
> > > > > +
> > > > > +   int run() override
> > > > > +   {
> > > > > +           GstStateChangeReturn ret;
> > > > > +
> > > > > +           /* Build the pipeline */
> > > > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> convert0_, sink0_, NULL);
> > > >
> > > > This can fail (e.g. duplicate name).
> > > >
> > > > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
> sink0_, NULL) != TRUE) {
> > > > > +                   g_printerr("Elements could not be linked.\n");
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /* Start playing */
> > > > > +           ret = gst_element_set_state(pipeline_,
> GST_STATE_PLAYING);
> > > > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > > > +                   g_printerr("Unable to set the pipeline to the
> playing state.\n");
> > > > > +                   return TestFail;
> > > > > +           }
> > > > > +
> > > > > +           /* Wait until error or EOS or timeout after 2 seconds
> */
> > > > > +           constexpr GstMessageType msgType =
> > > > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR
> | GST_MESSAGE_EOS);
> > > > > +           constexpr GstClockTime timeout = 2000000000;
> > > >
> > > > Perhaps 2 * GST_SECOND.
> > >
> > > Much better.
> > >
> > > > > +
> > > > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > > > +           g_autoptr(GstMessage) msg =
> gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > > > +
> > > > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > > > +
> > > > > +           /* Parse error message */
> > > > > +           if (msg == NULL)
> > > > > +                   return TestPass;
> > > >
> > > > I would like some minimal validation. I would expect that after 2s
> some frames
> > > > got "rendered" properly. You can read the GstStructure property
> "stats" from
> > > > fakevideosink / fakesink, and read the "rendered" field. Make sure
> this not zero
> > > > perhaps ?
> > >
> > > Very good idea.
> > >
> > > Vedant, I think there's work for you :-)
> > >
> > > > > +
> > > > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > > > +           case GST_MESSAGE_ERROR:
> > > > > +                   gstreamer_print_error(msg);
> > > > > +                   break;
> > > > > +           case GST_MESSAGE_EOS:
> > > > > +                   g_print("End-Of-Stream reached.\n");
> > > > > +                   break;
> > > > > +           default:
> > > > > +                   g_printerr("Unexpected message received.\n");
> > > > > +                   break;
> > > > > +           }
> > > > > +
> > > > > +           return TestFail;
> > > > > +   }
> > > > > +
> > > > > +private:
> > > > > +   void gstreamer_print_error(GstMessage *msg)
> > > > > +   {
> > > > > +           GError *err;
> > > > > +           gchar *debug_info;
> > > > > +
> > > > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > > > +           g_printerr("Error received from element %s: %s\n",
> > > > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > > > +           g_printerr("Debugging information: %s\n",
> > > > > +                      debug_info ? debug_info : "none");
> > > > > +           g_clear_error(&err);
> > > > > +           g_free(debug_info);
> > > > > +   }
> > > > > +
> > > > > +   GstElement *pipeline_;
> > > > > +   GstElement *libcameraSrc_;
> > > > > +   GstElement *convert0_;
> > > > > +   GstElement *sink0_;
> > > > > +};
> > > > > +
> > > > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > > > diff --git a/test/gstreamer/meson.build
> b/test/gstreamer/meson.build
> > > > > new file mode 100644
> > > > > index 000000000000..b99aa0da0ba3
> > > > > --- /dev/null
> > > > > +++ b/test/gstreamer/meson.build
> > > > > @@ -0,0 +1,19 @@
> > > > > +# SPDX-License-Identifier: CC0-1.0
> > > > > +
> > > > > +if not gst_enabled
> > > > > +    subdir_done()
> > > > > +endif
> > > > > +
> > > > > +gstreamer_tests = [
> > > > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > > > +]
> > > > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > > > +
> > > > > +foreach t : gstreamer_tests
> > > > > +    exe = executable(t[0], t[1],
> > > > > +                     dependencies : [libcamera_private,
> gstreamer_dep],
> > > > > +                     link_with : test_libraries,
> > > > > +                     include_directories : test_includes_internal)
> > > > > +
> > > > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > > > +endforeach
> > > > > diff --git a/test/meson.build b/test/meson.build
> > > > > index 3bceb5df586f..d0466f17d7b6 100644
> > > > > --- a/test/meson.build
> > > > > +++ b/test/meson.build
> > > > > @@ -11,6 +11,7 @@ subdir('libtest')
> > > > >
> > > > >  subdir('camera')
> > > > >  subdir('controls')
> > > > > +subdir('gstreamer')
> > > > >  subdir('ipa')
> > > > >  subdir('ipc')
> > > > >  subdir('log')
>
> --
> Regards,
>
> Laurent Pinchart
>
Nicolas Dufresne Aug. 18, 2021, 1:42 p.m. UTC | #20
Le mercredi 18 août 2021 à 17:54 +0530, Vedant Paranjape a écrit :
> Hi,
> How to use the suppression files ?

With valgrind, you pass it through command line argument. The path is standard,
read the lib prefix from its pc file, and so <prefix>/share/glib-
2.0/valgrind/glib.supp

> 
> Regards
> Vedant
> 
> On Wed, Aug 18, 2021 at 5:05 PM Laurent Pinchart
> <laurent.pinchart@ideasonboard.com> wrote:
> > Hi Vedant,
> > 
> > On Wed, Aug 18, 2021 at 04:54:45PM +0530, Vedant Paranjape wrote:
> > > On Wed, Aug 18, 2021 at 12:04 AM Laurent Pinchart wrote:
> > > > On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:
> > > > > Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> > > > > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > > > 
> > > > > > This patch adds a test to test if single stream using libcamera's
> > > > > > gstreamer element works.
> > > > > > 
> > > > > > We need to work around two distinct issues with ASan when enabled in
> > the
> > > > > > build:
> > > > > > 
> > > > > > - glib has a known leak at initialization time. This is covered by
> > > > > > the
> > > > > >    suppression file shipped with glib, but it's not clear how to use
> > > > > > it
> > > > > >    automatically. For now, disable leak detection to avoid test
> > failures.
> > > > > 
> > > > > Are Valgrind suppression usable for Asan ? Glib installs it into:
> > > > > 
> > > > >    /usr/share/glib-2.0/valgrind/glib.supp
> > > > > 
> > > > > For GStreamer suppressions, we don't install them (yet, under
> > discussion). They
> > > > > are located in each repos. I would enable GStreamer leak tracer
> > > > > though,
> > it does
> > > > > not have such a high run-time overhead, and will detect any GObject or
> > > > > GstMiniObject leak, as long as the test calls gst_deinit() at the end
> > (all tests
> > > > > should).
> > > > 
> > > > Yes, I think they are usable. I was however unsure whether the path to
> > > > the supression file was standard, especially considering embedded
> > > > systems. I thus went for a workaround. It would be nice if there was a
> > > > standard mechanism to pick suppressions automatically.
> > > > 
> > > > > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > > > > >    compiled without ASan (which is likely) but libcamera is,
> > dlopen()ing
> > > > > >    the libcamera plugin will cause an ASan link order verification
> > > > > >    failure. Disable the verification child processes to work around
> > > > > > the
> > > > > >    problem. This requires gcc 8 or newer.
> > > > > 
> > > > > Have you considered simply disabling forks ? See
> > gst_registry_fork_set_enabled()
> > > > 
> > > > No, as I had no idea that existed :-)
> > > 
> > > Tested this, it works as expected. Now need to figure out to patch
> > > gstreamer as it still leaks memory
> > > 
> > > =================================================================
> > > ==676137==ERROR: LeakSanitizer: detected memory leaks
> > > 
> > > Direct leak of 16384 byte(s) in 1 object(s) allocated from:
> > >      #0 0x7f4faf3b8bc8 in malloc
> > > (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
> > >      #1 0x7f4fae763e98 in g_malloc
> > > (/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x57e98)
> > > 
> > > SUMMARY: AddressSanitizer: 16384 byte(s) leaked in 1 allocation(s).
> > > ――――――――――――――――――――――――――――――――――――――――
> > 
> > That's a known glib issue, and that's why ASan leak detection is
> > disabled (in addition to disabling ASan link order verification, which
> > could be dropped in favour of using gst_registry_fork_set_enabled()).
> > glib has a valgrind suppression file that could be used by ASan as well,
> > but I wasn't sure how to integrate it correctly in a way that would work
> > on all distributions.
> > 
> > > > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > > > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > ---
> > > > > > This version incorporates changes coming from my review of v10, and
> > > > > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for
> > > > > > two
> > > > > > independent reasons as explained in the commit message. I'd like to
> > find
> > > > > > a way to use leak suppression files to handle the glib
> > > > > > initialization
> > > > > > leak, but that's a big rabbit hole. The workaround for the second
> > issue
> > > > > > is acceptable in my opinion.
> > > > > > 
> > > > > > The biggest trouble with these workarounds is that they don't work
> > with
> > > > > > gcc version older than 8. As the stable version of the most common
> > Linux
> > > > > > distributions ship gcc 8 or newer, skipping this test for gcc 7
> > > > > > (which
> > > > > > is the oldest version that libcamera supports) is also acceptable in
> > my
> > > > > > opinion.
> > > > > > 
> > > > > > Changes since v10:
> > > > > > 
> > > > > > - Disable ASan leak detection
> > > > > > - Disable ASan link order verification for child processes
> > > > > > - Include source_path.h
> > > > > > - Remove unneeded explicit std::string construction
> > > > > > - Declare variables at usage site
> > > > > > - Make constants constexpr
> > > > > > - Add a variable for the message type
> > > > > > - Blank space fixes
> > > > > > ---
> > > > > >   .../gstreamer_single_stream_test.cpp          | 188
> > ++++++++++++++++++
> > > > > >   test/gstreamer/meson.build                    |  19 ++
> > > > > >   test/meson.build                              |   1 +
> > > > > >   3 files changed, 208 insertions(+)
> > > > > >   create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > >   create mode 100644 test/gstreamer/meson.build
> > > > > > 
> > > > > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp
> > b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > > new file mode 100644
> > > > > > index 000000000000..e26673b3471a
> > > > > > --- /dev/null
> > > > > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > > @@ -0,0 +1,188 @@
> > > > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > > > +/*
> > > > > > + * Copyright (C) 2021, Vedant Paranjape
> > > > > > + *
> > > > > > + * ipa_interface_test.cpp - Test the IPA interface
> > > > > > + */
> > > > > > +
> > > > > > +#include <iostream>
> > > > > > +#include <unistd.h>
> > > > > > +
> > > > > > +#include <libcamera/base/utils.h>
> > > > > > +
> > > > > > +#include "libcamera/internal/source_paths.h"
> > > > > > +
> > > > > > +#include <gst/gst.h>
> > > > > > +
> > > > > > +#include "test.h"
> > > > > > +
> > > > > > +using namespace std;
> > > > > > +
> > > > > > +extern "C" {
> > > > > > +const char *__asan_default_options()
> > > > > > +{
> > > > > > +   /*
> > > > > > +    * Disable leak detection due to a known global variable
> > initialization
> > > > > > +    * leak in glib's g_quark_init(). This should ideally be handled
> > by
> > > > > > +    * using a suppression file instead of disabling leak detection.
> > > > > > +    */
> > > > > > +   return "detect_leaks=false";
> > > > > 
> > > > > Have you consider GST_TRACERS=leaks ? It will heck for refcounted
> > > > > object
> > leaks
> > > > > on gstreamer/glib side, with very low overhead, and will abort on
> > gst_deinit()
> > > > > if it found some leaks.
> > > > 
> > > > Again, no idea it existed :-) It could be enabled in init() below.
> > > > 
> > > > > > +}
> > > > > > +}
> > > > > > +
> > > > > > +class GstreamerSingleStreamTest : public Test
> > > > > > +{
> > > > > > +protected:
> > > > > > +   int init() override
> > > > > > +   {
> > > > > > +           /*
> > > > > > +            * GStreamer spawns a process to run the gst-plugin-
> > scanner
> > > > > > +            * helper. If libcamera is compiled with ASan enabled,
> > > > > > and
> > as
> > > > > > +            * GStreamer is most likely not, this will cause the
> > > > > > ASan
> > link
> > > > > > +            * order check to fail when gst-plugin-scanner dlopen()s
> > the
> > > > > > +            * plugin as many libraries will have already been
> > > > > > loaded
> > by
> > > > > > +            * then. Work around this issue by disabling the link
> > order
> > > > > > +            * check. This will only affect child processes, as ASan
> > is
> > > > > > +            * already loaded for this process by the time this code
> > is
> > > > > > +            * executed, and should thus hopefully be safe.
> > > > > > +            *
> > > > > > +            * This option is not available in gcc older than 8, the
> > only
> > > > > > +            * option in that case is to skip the test.
> > > > > > +            */
> > > > > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) &&
> > > > > > __GNUC__
> > < 8
> > > > > > +           return TestSkip;
> > > > > > +#endif
> > > > > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > > > > 
> > > > > I think gst_registry_fork_set_enabled(FALSE) would be much cleaner,
> > > > > what
> > do you
> > > > > think ?
> > > > 
> > > > If it does the job and has no drawback, that's fine with me.
> > > > 
> > > > > > +
> > > > > > +           /* Initialize GStreamer */
> > > > > > +           GError *errInit = nullptr;
> > > > > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > > > > +                   g_printerr("Could not initialize GStreamer:
> > > > > > %s\n",
> > > > > > +                              errInit ? errInit->message : "unknown
> > error");
> > > > > > +                   if (errInit)
> > > > > > +                           g_error_free(errInit);
> > > > > > +
> > > > > > +                   return TestFail;
> > > > > > +           }
> > > > > > +
> > > > > > +           /*
> > > > > > +            * Remove the system libcamera plugin, if any, and add
> > > > > > the
> > > > > > +            * plugin from the build directory.
> > > > > > +            */
> > > > > > +           GstRegistry *registry = gst_registry_get();
> > > > > > +           GstPlugin *plugin = gst_registry_lookup(registry,
> > "libgstlibcamera.so");
> > > > > > +           if (plugin) {
> > > > > > +                   gst_registry_remove_plugin(registry, plugin);
> > > > > > +                   gst_object_unref(plugin);
> > > > > > +           }
> > > > > > +
> > > > > > +           std::string path =
> > > > > > libcamera::utils::libcameraBuildPath()
> > > > > > +                            + "src/gstreamer";
> > > > > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > > > > +                   g_printerr("Failed to add plugin to
> > > > > > registry\n");
> > > > > > +                   gst_deinit();
> > > > > > +                   return TestFail;
> > > > > > +           }
> > > > > > +
> > > > > > +           /* Create the elements */
> > > > > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc",
> > "libcamera");
> > > > > > +           convert0_ = gst_element_factory_make("videoconvert",
> > "convert0");
> > > > > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > > > > 
> > > > > Please use fakevideosink instead.
> > > > 
> > > > That's what Vedant was doing, and the element wasn't present on Kieran's
> > > > system. For my own culture, what's the advantage of using fakevideosink
> > > > over fakesink ?
> > > > 
> > > > > > +
> > > > > > +           /* Create the empty pipeline_ */
> > > > > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > > > > +
> > > > > > +           if (!pipeline_ || !convert0_ || !sink0_ ||
> > > > > > !libcameraSrc_)
> > {
> > > > > 
> > > > > pipeline_ cannot be NULL, glib will abort if malloc failed.
> > > > 
> > > > Good point.
> > > > 
> > > > > > +                   g_printerr("Not all elements could be created.
> > %p.%p.%p.%p\n",
> > > > > > +                              pipeline_, convert0_, sink0_,
> > libcameraSrc_);
> > > > > > +                   if (pipeline_)
> > > > > > +                           gst_object_unref(pipeline_);
> > > > > > +                   if (convert0_)
> > > > > > +                           gst_object_unref(convert0_);
> > > > > > +                   if (sink0_)
> > > > > > +                           gst_object_unref(sink0_);
> > > > > > +                   if (libcameraSrc_)
> > > > > > +                           gst_object_unref(libcameraSrc_);
> > > > > 
> > > > > Use local g_autoptr() and g_steal_pointer() on success perhaps ? This
> > will
> > > > > reduce a lot the about of error prone code in failure case. Note that
> > > > > in
> > other
> > > > > software I've seen, an abort is used instead, as clean exit is just
> > coding
> > > > > overhead for a test.
> > > > > 
> > > > > An alternative, add them immediately to the pipeline, they are not
> > > > > owned
> > by your
> > > > > class anyway.
> > > > > 
> > > > > > +                   gst_deinit();
> > > > > > +
> > > > > > +                   return TestFail;
> > > > > > +           }
> > > > > > +
> > > > > > +           return TestPass;
> > > > > > +   }
> > > > > > +
> > > > > > +   void cleanup() override
> > > > > > +   {
> > > > > > +           gst_object_unref(pipeline_);
> > > > > > +           gst_deinit();
> > > > > > +   }
> > > > > > +
> > > > > > +   int run() override
> > > > > > +   {
> > > > > > +           GstStateChangeReturn ret;
> > > > > > +
> > > > > > +           /* Build the pipeline */
> > > > > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> > convert0_, sink0_, NULL);
> > > > > 
> > > > > This can fail (e.g. duplicate name).
> > > > > 
> > > > > > +           if (gst_element_link_many(libcameraSrc_, convert0_,
> > sink0_, NULL) != TRUE) {
> > > > > > +                   g_printerr("Elements could not be linked.\n");
> > > > > > +                   return TestFail;
> > > > > > +           }
> > > > > > +
> > > > > > +           /* Start playing */
> > > > > > +           ret = gst_element_set_state(pipeline_,
> > > > > > GST_STATE_PLAYING);
> > > > > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > > > > +                   g_printerr("Unable to set the pipeline to the
> > playing state.\n");
> > > > > > +                   return TestFail;
> > > > > > +           }
> > > > > > +
> > > > > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > > > > +           constexpr GstMessageType msgType =
> > > > > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR |
> > GST_MESSAGE_EOS);
> > > > > > +           constexpr GstClockTime timeout = 2000000000;
> > > > > 
> > > > > Perhaps 2 * GST_SECOND.
> > > > 
> > > > Much better.
> > > > 
> > > > > > +
> > > > > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > > > > +           g_autoptr(GstMessage) msg =
> > gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > > > > +
> > > > > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > > > > +
> > > > > > +           /* Parse error message */
> > > > > > +           if (msg == NULL)
> > > > > > +                   return TestPass;
> > > > > 
> > > > > I would like some minimal validation. I would expect that after 2s
> > > > > some
> > frames
> > > > > got "rendered" properly. You can read the GstStructure property
> > > > > "stats"
> > from
> > > > > fakevideosink / fakesink, and read the "rendered" field. Make sure
> > > > > this
> > not zero
> > > > > perhaps ?
> > > > 
> > > > Very good idea.
> > > > 
> > > > Vedant, I think there's work for you :-)
> > > > 
> > > > > > +
> > > > > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > > > > +           case GST_MESSAGE_ERROR:
> > > > > > +                   gstreamer_print_error(msg);
> > > > > > +                   break;
> > > > > > +           case GST_MESSAGE_EOS:
> > > > > > +                   g_print("End-Of-Stream reached.\n");
> > > > > > +                   break;
> > > > > > +           default:
> > > > > > +                   g_printerr("Unexpected message received.\n");
> > > > > > +                   break;
> > > > > > +           }
> > > > > > +
> > > > > > +           return TestFail;
> > > > > > +   }
> > > > > > +
> > > > > > +private:
> > > > > > +   void gstreamer_print_error(GstMessage *msg)
> > > > > > +   {
> > > > > > +           GError *err;
> > > > > > +           gchar *debug_info;
> > > > > > +
> > > > > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > > > > +           g_printerr("Error received from element %s: %s\n",
> > > > > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > > > > +           g_printerr("Debugging information: %s\n",
> > > > > > +                      debug_info ? debug_info : "none");
> > > > > > +           g_clear_error(&err);
> > > > > > +           g_free(debug_info);
> > > > > > +   }
> > > > > > +
> > > > > > +   GstElement *pipeline_;
> > > > > > +   GstElement *libcameraSrc_;
> > > > > > +   GstElement *convert0_;
> > > > > > +   GstElement *sink0_;
> > > > > > +};
> > > > > > +
> > > > > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > > > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > > > > new file mode 100644
> > > > > > index 000000000000..b99aa0da0ba3
> > > > > > --- /dev/null
> > > > > > +++ b/test/gstreamer/meson.build
> > > > > > @@ -0,0 +1,19 @@
> > > > > > +# SPDX-License-Identifier: CC0-1.0
> > > > > > +
> > > > > > +if not gst_enabled
> > > > > > +    subdir_done()
> > > > > > +endif
> > > > > > +
> > > > > > +gstreamer_tests = [
> > > > > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > > > > +]
> > > > > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > > > > +
> > > > > > +foreach t : gstreamer_tests
> > > > > > +    exe = executable(t[0], t[1],
> > > > > > +                     dependencies : [libcamera_private,
> > gstreamer_dep],
> > > > > > +                     link_with : test_libraries,
> > > > > > +                     include_directories : test_includes_internal)
> > > > > > +
> > > > > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > > > > +endforeach
> > > > > > diff --git a/test/meson.build b/test/meson.build
> > > > > > index 3bceb5df586f..d0466f17d7b6 100644
> > > > > > --- a/test/meson.build
> > > > > > +++ b/test/meson.build
> > > > > > @@ -11,6 +11,7 @@ subdir('libtest')
> > > > > > 
> > > > > >   subdir('camera')
> > > > > >   subdir('controls')
> > > > > > +subdir('gstreamer')
> > > > > >   subdir('ipa')
> > > > > >   subdir('ipc')
> > > > > >   subdir('log')
> >
Laurent Pinchart Aug. 18, 2021, 4:26 p.m. UTC | #21
On Wed, Aug 18, 2021 at 09:42:23AM -0400, Nicolas Dufresne wrote:
> Le mercredi 18 août 2021 à 17:54 +0530, Vedant Paranjape a écrit :
> > Hi,
> > How to use the suppression files ?
> 
> With valgrind, you pass it through command line argument. The path is standard,
> read the lib prefix from its pc file, and so <prefix>/share/glib-
> 2.0/valgrind/glib.supp

ASan has a similar option, see
https://github.com/google/sanitizers/wiki/AddressSanitizerFlags#run-time-flags.

> > On Wed, Aug 18, 2021 at 5:05 PM Laurent Pinchart wrote:
> > > On Wed, Aug 18, 2021 at 04:54:45PM +0530, Vedant Paranjape wrote:
> > > > On Wed, Aug 18, 2021 at 12:04 AM Laurent Pinchart wrote:
> > > > > On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:
> > > > > > Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :
> > > > > > > From: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > > > > 
> > > > > > > This patch adds a test to test if single stream using libcamera's
> > > > > > > gstreamer element works.
> > > > > > > 
> > > > > > > We need to work around two distinct issues with ASan when enabled in the
> > > > > > > build:
> > > > > > > 
> > > > > > > - glib has a known leak at initialization time. This is covered by
> > > > > > > the
> > > > > > >    suppression file shipped with glib, but it's not clear how to use
> > > > > > > it
> > > > > > >    automatically. For now, disable leak detection to avoid test failures.
> > > > > > 
> > > > > > Are Valgrind suppression usable for Asan ? Glib installs it into:
> > > > > > 
> > > > > >    /usr/share/glib-2.0/valgrind/glib.supp
> > > > > > 
> > > > > > For GStreamer suppressions, we don't install them (yet, under discussion). They
> > > > > > are located in each repos. I would enable GStreamer leak tracer
> > > > > > though, it does
> > > > > > not have such a high run-time overhead, and will detect any GObject or
> > > > > > GstMiniObject leak, as long as the test calls gst_deinit() at the end (all tests
> > > > > > should).
> > > > > 
> > > > > Yes, I think they are usable. I was however unsure whether the path to
> > > > > the supression file was standard, especially considering embedded
> > > > > systems. I thus went for a workaround. It would be nice if there was a
> > > > > standard mechanism to pick suppressions automatically.
> > > > > 
> > > > > > > - GStreamer spawns a child process to scan plugins. If GStreamer is
> > > > > > >    compiled without ASan (which is likely) but libcamera is, dlopen()ing
> > > > > > >    the libcamera plugin will cause an ASan link order verification
> > > > > > >    failure. Disable the verification child processes to work around
> > > > > > > the
> > > > > > >    problem. This requires gcc 8 or newer.
> > > > > > 
> > > > > > Have you considered simply disabling forks ? See gst_registry_fork_set_enabled()
> > > > > 
> > > > > No, as I had no idea that existed :-)
> > > > 
> > > > Tested this, it works as expected. Now need to figure out to patch
> > > > gstreamer as it still leaks memory
> > > > 
> > > > =================================================================
> > > > ==676137==ERROR: LeakSanitizer: detected memory leaks
> > > > 
> > > > Direct leak of 16384 byte(s) in 1 object(s) allocated from:
> > > >      #0 0x7f4faf3b8bc8 in malloc
> > > > (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
> > > >      #1 0x7f4fae763e98 in g_malloc
> > > > (/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x57e98)
> > > > 
> > > > SUMMARY: AddressSanitizer: 16384 byte(s) leaked in 1 allocation(s).
> > > > ――――――――――――――――――――――――――――――――――――――――
> > > 
> > > That's a known glib issue, and that's why ASan leak detection is
> > > disabled (in addition to disabling ASan link order verification, which
> > > could be dropped in favour of using gst_registry_fork_set_enabled()).
> > > glib has a valgrind suppression file that could be used by ASan as well,
> > > but I wasn't sure how to integrate it correctly in a way that would work
> > > on all distributions.
> > > 
> > > > > > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > > > > > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > > > > Tested-by: Kieran Bingham <kieran.bingham@@ideasonboard.com>
> > > > > > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > > ---
> > > > > > > This version incorporates changes coming from my review of v10, and
> > > > > > > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for
> > > > > > > two
> > > > > > > independent reasons as explained in the commit message. I'd like to find
> > > > > > > a way to use leak suppression files to handle the glib
> > > > > > > initialization
> > > > > > > leak, but that's a big rabbit hole. The workaround for the second issue
> > > > > > > is acceptable in my opinion.
> > > > > > > 
> > > > > > > The biggest trouble with these workarounds is that they don't work with
> > > > > > > gcc version older than 8. As the stable version of the most common Linux
> > > > > > > distributions ship gcc 8 or newer, skipping this test for gcc 7
> > > > > > > (which
> > > > > > > is the oldest version that libcamera supports) is also acceptable in my
> > > > > > > opinion.
> > > > > > > 
> > > > > > > Changes since v10:
> > > > > > > 
> > > > > > > - Disable ASan leak detection
> > > > > > > - Disable ASan link order verification for child processes
> > > > > > > - Include source_path.h
> > > > > > > - Remove unneeded explicit std::string construction
> > > > > > > - Declare variables at usage site
> > > > > > > - Make constants constexpr
> > > > > > > - Add a variable for the message type
> > > > > > > - Blank space fixes
> > > > > > > ---
> > > > > > >   .../gstreamer_single_stream_test.cpp          | 188 ++++++++++++++++++
> > > > > > >   test/gstreamer/meson.build                    |  19 ++
> > > > > > >   test/meson.build                              |   1 +
> > > > > > >   3 files changed, 208 insertions(+)
> > > > > > >   create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > > >   create mode 100644 test/gstreamer/meson.build
> > > > > > > 
> > > > > > > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > > > new file mode 100644
> > > > > > > index 000000000000..e26673b3471a
> > > > > > > --- /dev/null
> > > > > > > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp
> > > > > > > @@ -0,0 +1,188 @@
> > > > > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > > > > +/*
> > > > > > > + * Copyright (C) 2021, Vedant Paranjape
> > > > > > > + *
> > > > > > > + * ipa_interface_test.cpp - Test the IPA interface
> > > > > > > + */
> > > > > > > +
> > > > > > > +#include <iostream>
> > > > > > > +#include <unistd.h>
> > > > > > > +
> > > > > > > +#include <libcamera/base/utils.h>
> > > > > > > +
> > > > > > > +#include "libcamera/internal/source_paths.h"
> > > > > > > +
> > > > > > > +#include <gst/gst.h>
> > > > > > > +
> > > > > > > +#include "test.h"
> > > > > > > +
> > > > > > > +using namespace std;
> > > > > > > +
> > > > > > > +extern "C" {
> > > > > > > +const char *__asan_default_options()
> > > > > > > +{
> > > > > > > +   /*
> > > > > > > +    * Disable leak detection due to a known global variable initialization
> > > > > > > +    * leak in glib's g_quark_init(). This should ideally be handled by
> > > > > > > +    * using a suppression file instead of disabling leak detection.
> > > > > > > +    */
> > > > > > > +   return "detect_leaks=false";
> > > > > > 
> > > > > > Have you consider GST_TRACERS=leaks ? It will heck for refcounted
> > > > > > object leaks
> > > > > > on gstreamer/glib side, with very low overhead, and will abort on gst_deinit()
> > > > > > if it found some leaks.
> > > > > 
> > > > > Again, no idea it existed :-) It could be enabled in init() below.
> > > > > 
> > > > > > > +}
> > > > > > > +}
> > > > > > > +
> > > > > > > +class GstreamerSingleStreamTest : public Test
> > > > > > > +{
> > > > > > > +protected:
> > > > > > > +   int init() override
> > > > > > > +   {
> > > > > > > +           /*
> > > > > > > +            * GStreamer spawns a process to run the gst-plugin- scanner
> > > > > > > +            * helper. If libcamera is compiled with ASan enabled,
> > > > > > > and as
> > > > > > > +            * GStreamer is most likely not, this will cause the
> > > > > > > ASan link
> > > > > > > +            * order check to fail when gst-plugin-scanner dlopen()s the
> > > > > > > +            * plugin as many libraries will have already been
> > > > > > > loaded by
> > > > > > > +            * then. Work around this issue by disabling the link order
> > > > > > > +            * check. This will only affect child processes, as ASan is
> > > > > > > +            * already loaded for this process by the time this code is
> > > > > > > +            * executed, and should thus hopefully be safe.
> > > > > > > +            *
> > > > > > > +            * This option is not available in gcc older than 8, the only
> > > > > > > +            * option in that case is to skip the test.
> > > > > > > +            */
> > > > > > > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) &&
> > > > > > > __GNUC__ < 8
> > > > > > > +           return TestSkip;
> > > > > > > +#endif
> > > > > > > +           setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
> > > > > > 
> > > > > > I think gst_registry_fork_set_enabled(FALSE) would be much cleaner,
> > > > > > what do you
> > > > > > think ?
> > > > > 
> > > > > If it does the job and has no drawback, that's fine with me.
> > > > > 
> > > > > > > +
> > > > > > > +           /* Initialize GStreamer */
> > > > > > > +           GError *errInit = nullptr;
> > > > > > > +           if (!gst_init_check(nullptr, nullptr, &errInit)) {
> > > > > > > +                   g_printerr("Could not initialize GStreamer:
> > > > > > > %s\n",
> > > > > > > +                              errInit ? errInit->message : "unknown error");
> > > > > > > +                   if (errInit)
> > > > > > > +                           g_error_free(errInit);
> > > > > > > +
> > > > > > > +                   return TestFail;
> > > > > > > +           }
> > > > > > > +
> > > > > > > +           /*
> > > > > > > +            * Remove the system libcamera plugin, if any, and add
> > > > > > > the
> > > > > > > +            * plugin from the build directory.
> > > > > > > +            */
> > > > > > > +           GstRegistry *registry = gst_registry_get();
> > > > > > > +           GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
> > > > > > > +           if (plugin) {
> > > > > > > +                   gst_registry_remove_plugin(registry, plugin);
> > > > > > > +                   gst_object_unref(plugin);
> > > > > > > +           }
> > > > > > > +
> > > > > > > +           std::string path =
> > > > > > > libcamera::utils::libcameraBuildPath()
> > > > > > > +                            + "src/gstreamer";
> > > > > > > +           if (!gst_registry_scan_path(registry, path.c_str())) {
> > > > > > > +                   g_printerr("Failed to add plugin to
> > > > > > > registry\n");
> > > > > > > +                   gst_deinit();
> > > > > > > +                   return TestFail;
> > > > > > > +           }
> > > > > > > +
> > > > > > > +           /* Create the elements */
> > > > > > > +           libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
> > > > > > > +           convert0_ = gst_element_factory_make("videoconvert", "convert0");
> > > > > > > +           sink0_ = gst_element_factory_make("fakesink", "sink0");
> > > > > > 
> > > > > > Please use fakevideosink instead.
> > > > > 
> > > > > That's what Vedant was doing, and the element wasn't present on Kieran's
> > > > > system. For my own culture, what's the advantage of using fakevideosink
> > > > > over fakesink ?
> > > > > 
> > > > > > > +
> > > > > > > +           /* Create the empty pipeline_ */
> > > > > > > +           pipeline_ = gst_pipeline_new("test-pipeline");
> > > > > > > +
> > > > > > > +           if (!pipeline_ || !convert0_ || !sink0_ ||
> > > > > > > !libcameraSrc_) {
> > > > > > 
> > > > > > pipeline_ cannot be NULL, glib will abort if malloc failed.
> > > > > 
> > > > > Good point.
> > > > > 
> > > > > > > +                   g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
> > > > > > > +                              pipeline_, convert0_, sink0_, libcameraSrc_);
> > > > > > > +                   if (pipeline_)
> > > > > > > +                           gst_object_unref(pipeline_);
> > > > > > > +                   if (convert0_)
> > > > > > > +                           gst_object_unref(convert0_);
> > > > > > > +                   if (sink0_)
> > > > > > > +                           gst_object_unref(sink0_);
> > > > > > > +                   if (libcameraSrc_)
> > > > > > > +                           gst_object_unref(libcameraSrc_);
> > > > > > 
> > > > > > Use local g_autoptr() and g_steal_pointer() on success perhaps ? This will
> > > > > > reduce a lot the about of error prone code in failure case. Note that
> > > > > > in other
> > > > > > software I've seen, an abort is used instead, as clean exit is just coding
> > > > > > overhead for a test.
> > > > > > 
> > > > > > An alternative, add them immediately to the pipeline, they are not
> > > > > > owned by your
> > > > > > class anyway.
> > > > > > 
> > > > > > > +                   gst_deinit();
> > > > > > > +
> > > > > > > +                   return TestFail;
> > > > > > > +           }
> > > > > > > +
> > > > > > > +           return TestPass;
> > > > > > > +   }
> > > > > > > +
> > > > > > > +   void cleanup() override
> > > > > > > +   {
> > > > > > > +           gst_object_unref(pipeline_);
> > > > > > > +           gst_deinit();
> > > > > > > +   }
> > > > > > > +
> > > > > > > +   int run() override
> > > > > > > +   {
> > > > > > > +           GstStateChangeReturn ret;
> > > > > > > +
> > > > > > > +           /* Build the pipeline */
> > > > > > > +           gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
> > > > > > 
> > > > > > This can fail (e.g. duplicate name).
> > > > > > 
> > > > > > > +           if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
> > > > > > > +                   g_printerr("Elements could not be linked.\n");
> > > > > > > +                   return TestFail;
> > > > > > > +           }
> > > > > > > +
> > > > > > > +           /* Start playing */
> > > > > > > +           ret = gst_element_set_state(pipeline_,
> > > > > > > GST_STATE_PLAYING);
> > > > > > > +           if (ret == GST_STATE_CHANGE_FAILURE) {
> > > > > > > +                   g_printerr("Unable to set the pipeline to the playing state.\n");
> > > > > > > +                   return TestFail;
> > > > > > > +           }
> > > > > > > +
> > > > > > > +           /* Wait until error or EOS or timeout after 2 seconds */
> > > > > > > +           constexpr GstMessageType msgType =
> > > > > > > +                   static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
> > > > > > > +           constexpr GstClockTime timeout = 2000000000;
> > > > > > 
> > > > > > Perhaps 2 * GST_SECOND.
> > > > > 
> > > > > Much better.
> > > > > 
> > > > > > > +
> > > > > > > +           g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
> > > > > > > +           g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
> > > > > > > +
> > > > > > > +           gst_element_set_state(pipeline_, GST_STATE_NULL);
> > > > > > > +
> > > > > > > +           /* Parse error message */
> > > > > > > +           if (msg == NULL)
> > > > > > > +                   return TestPass;
> > > > > > 
> > > > > > I would like some minimal validation. I would expect that after 2s
> > > > > > some frames
> > > > > > got "rendered" properly. You can read the GstStructure property
> > > > > > "stats" from
> > > > > > fakevideosink / fakesink, and read the "rendered" field. Make sure
> > > > > > this not zero
> > > > > > perhaps ?
> > > > > 
> > > > > Very good idea.
> > > > > 
> > > > > Vedant, I think there's work for you :-)
> > > > > 
> > > > > > > +
> > > > > > > +           switch (GST_MESSAGE_TYPE(msg)) {
> > > > > > > +           case GST_MESSAGE_ERROR:
> > > > > > > +                   gstreamer_print_error(msg);
> > > > > > > +                   break;
> > > > > > > +           case GST_MESSAGE_EOS:
> > > > > > > +                   g_print("End-Of-Stream reached.\n");
> > > > > > > +                   break;
> > > > > > > +           default:
> > > > > > > +                   g_printerr("Unexpected message received.\n");
> > > > > > > +                   break;
> > > > > > > +           }
> > > > > > > +
> > > > > > > +           return TestFail;
> > > > > > > +   }
> > > > > > > +
> > > > > > > +private:
> > > > > > > +   void gstreamer_print_error(GstMessage *msg)
> > > > > > > +   {
> > > > > > > +           GError *err;
> > > > > > > +           gchar *debug_info;
> > > > > > > +
> > > > > > > +           gst_message_parse_error(msg, &err, &debug_info);
> > > > > > > +           g_printerr("Error received from element %s: %s\n",
> > > > > > > +                      GST_OBJECT_NAME(msg->src), err->message);
> > > > > > > +           g_printerr("Debugging information: %s\n",
> > > > > > > +                      debug_info ? debug_info : "none");
> > > > > > > +           g_clear_error(&err);
> > > > > > > +           g_free(debug_info);
> > > > > > > +   }
> > > > > > > +
> > > > > > > +   GstElement *pipeline_;
> > > > > > > +   GstElement *libcameraSrc_;
> > > > > > > +   GstElement *convert0_;
> > > > > > > +   GstElement *sink0_;
> > > > > > > +};
> > > > > > > +
> > > > > > > +TEST_REGISTER(GstreamerSingleStreamTest)
> > > > > > > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > > > > > > new file mode 100644
> > > > > > > index 000000000000..b99aa0da0ba3
> > > > > > > --- /dev/null
> > > > > > > +++ b/test/gstreamer/meson.build
> > > > > > > @@ -0,0 +1,19 @@
> > > > > > > +# SPDX-License-Identifier: CC0-1.0
> > > > > > > +
> > > > > > > +if not gst_enabled
> > > > > > > +    subdir_done()
> > > > > > > +endif
> > > > > > > +
> > > > > > > +gstreamer_tests = [
> > > > > > > +    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > > > > > > +]
> > > > > > > +gstreamer_dep = dependency('gstreamer-1.0', required: true)
> > > > > > > +
> > > > > > > +foreach t : gstreamer_tests
> > > > > > > +    exe = executable(t[0], t[1],
> > > > > > > +                     dependencies : [libcamera_private, gstreamer_dep],
> > > > > > > +                     link_with : test_libraries,
> > > > > > > +                     include_directories : test_includes_internal)
> > > > > > > +
> > > > > > > +    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
> > > > > > > +endforeach
> > > > > > > diff --git a/test/meson.build b/test/meson.build
> > > > > > > index 3bceb5df586f..d0466f17d7b6 100644
> > > > > > > --- a/test/meson.build
> > > > > > > +++ b/test/meson.build
> > > > > > > @@ -11,6 +11,7 @@ subdir('libtest')
> > > > > > > 
> > > > > > >   subdir('camera')
> > > > > > >   subdir('controls')
> > > > > > > +subdir('gstreamer')
> > > > > > >   subdir('ipa')
> > > > > > >   subdir('ipc')
> > > > > > >   subdir('log')

Patch
diff mbox series

diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
new file mode 100644
index 000000000000..e26673b3471a
--- /dev/null
+++ b/test/gstreamer/gstreamer_single_stream_test.cpp
@@ -0,0 +1,188 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Vedant Paranjape
+ *
+ * ipa_interface_test.cpp - Test the IPA interface
+ */
+
+#include <iostream>
+#include <unistd.h>
+
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/source_paths.h"
+
+#include <gst/gst.h>
+
+#include "test.h"
+
+using namespace std;
+
+extern "C" {
+const char *__asan_default_options()
+{
+	/*
+	 * Disable leak detection due to a known global variable initialization
+	 * leak in glib's g_quark_init(). This should ideally be handled by
+	 * using a suppression file instead of disabling leak detection.
+	 */
+	return "detect_leaks=false";
+}
+}
+
+class GstreamerSingleStreamTest : public Test
+{
+protected:
+	int init() override
+	{
+		/*
+		 * GStreamer spawns a process to run the gst-plugin-scanner
+		 * helper. If libcamera is compiled with ASan enabled, and as
+		 * GStreamer is most likely not, this will cause the ASan link
+		 * order check to fail when gst-plugin-scanner dlopen()s the
+		 * plugin as many libraries will have already been loaded by
+		 * then. Work around this issue by disabling the link order
+		 * check. This will only affect child processes, as ASan is
+		 * already loaded for this process by the time this code is
+		 * executed, and should thus hopefully be safe.
+		 *
+		 * This option is not available in gcc older than 8, the only
+		 * option in that case is to skip the test.
+		 */
+#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8
+		return TestSkip;
+#endif
+		setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);
+
+		/* Initialize GStreamer */
+		GError *errInit = nullptr;
+		if (!gst_init_check(nullptr, nullptr, &errInit)) {
+			g_printerr("Could not initialize GStreamer: %s\n",
+				   errInit ? errInit->message : "unknown error");
+			if (errInit)
+				g_error_free(errInit);
+
+			return TestFail;
+		}
+
+		/*
+		 * Remove the system libcamera plugin, if any, and add the
+		 * plugin from the build directory.
+		 */
+		GstRegistry *registry = gst_registry_get();
+		GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
+		if (plugin) {
+			gst_registry_remove_plugin(registry, plugin);
+			gst_object_unref(plugin);
+		}
+
+		std::string path = libcamera::utils::libcameraBuildPath()
+				 + "src/gstreamer";
+		if (!gst_registry_scan_path(registry, path.c_str())) {
+			g_printerr("Failed to add plugin to registry\n");
+			gst_deinit();
+			return TestFail;
+		}
+
+		/* Create the elements */
+		libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
+		convert0_ = gst_element_factory_make("videoconvert", "convert0");
+		sink0_ = gst_element_factory_make("fakesink", "sink0");
+
+		/* Create the empty pipeline_ */
+		pipeline_ = gst_pipeline_new("test-pipeline");
+
+		if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {
+			g_printerr("Not all elements could be created. %p.%p.%p.%p\n",
+				   pipeline_, convert0_, sink0_, libcameraSrc_);
+			if (pipeline_)
+				gst_object_unref(pipeline_);
+			if (convert0_)
+				gst_object_unref(convert0_);
+			if (sink0_)
+				gst_object_unref(sink0_);
+			if (libcameraSrc_)
+				gst_object_unref(libcameraSrc_);
+			gst_deinit();
+
+			return TestFail;
+		}
+
+		return TestPass;
+	}
+
+	void cleanup() override
+	{
+		gst_object_unref(pipeline_);
+		gst_deinit();
+	}
+
+	int run() override
+	{
+		GstStateChangeReturn ret;
+
+		/* Build the pipeline */
+		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);
+		if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {
+			g_printerr("Elements could not be linked.\n");
+			return TestFail;
+		}
+
+		/* Start playing */
+		ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
+		if (ret == GST_STATE_CHANGE_FAILURE) {
+			g_printerr("Unable to set the pipeline to the playing state.\n");
+			return TestFail;
+		}
+
+		/* Wait until error or EOS or timeout after 2 seconds */
+		constexpr GstMessageType msgType =
+			static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
+		constexpr GstClockTime timeout = 2000000000;
+
+		g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
+		g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
+
+		gst_element_set_state(pipeline_, GST_STATE_NULL);
+
+		/* Parse error message */
+		if (msg == NULL)
+			return TestPass;
+
+		switch (GST_MESSAGE_TYPE(msg)) {
+		case GST_MESSAGE_ERROR:
+			gstreamer_print_error(msg);
+			break;
+		case GST_MESSAGE_EOS:
+			g_print("End-Of-Stream reached.\n");
+			break;
+		default:
+			g_printerr("Unexpected message received.\n");
+			break;
+		}
+
+		return TestFail;
+	}
+
+private:
+	void gstreamer_print_error(GstMessage *msg)
+	{
+		GError *err;
+		gchar *debug_info;
+
+		gst_message_parse_error(msg, &err, &debug_info);
+		g_printerr("Error received from element %s: %s\n",
+			   GST_OBJECT_NAME(msg->src), err->message);
+		g_printerr("Debugging information: %s\n",
+			   debug_info ? debug_info : "none");
+		g_clear_error(&err);
+		g_free(debug_info);
+	}
+
+	GstElement *pipeline_;
+	GstElement *libcameraSrc_;
+	GstElement *convert0_;
+	GstElement *sink0_;
+};
+
+TEST_REGISTER(GstreamerSingleStreamTest)
diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
new file mode 100644
index 000000000000..b99aa0da0ba3
--- /dev/null
+++ b/test/gstreamer/meson.build
@@ -0,0 +1,19 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+if not gst_enabled
+    subdir_done()
+endif
+
+gstreamer_tests = [
+    ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
+]
+gstreamer_dep = dependency('gstreamer-1.0', required: true)
+
+foreach t : gstreamer_tests
+    exe = executable(t[0], t[1],
+                     dependencies : [libcamera_private, gstreamer_dep],
+                     link_with : test_libraries,
+                     include_directories : test_includes_internal)
+
+    test(t[0], exe, suite : 'gstreamer', is_parallel : false)
+endforeach
diff --git a/test/meson.build b/test/meson.build
index 3bceb5df586f..d0466f17d7b6 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -11,6 +11,7 @@  subdir('libtest')
 
 subdir('camera')
 subdir('controls')
+subdir('gstreamer')
 subdir('ipa')
 subdir('ipc')
 subdir('log')