[libcamera-devel,v2,5/5] tests: Introduce hotplug hot-unplug unit test

Message ID 20200513172950.72685-6-email@uajain.com
State Accepted
Headers show
Series
  • Introduce UVC hotplug support
Related show

Commit Message

Umang Jain May 13, 2020, 5:30 p.m. UTC
Signed-off-by: Umang Jain <email@uajain.com>
---
 test/hotplug-cameras.cpp | 152 +++++++++++++++++++++++++++++++++++++++
 test/meson.build         |   1 +
 2 files changed, 153 insertions(+)
 create mode 100644 test/hotplug-cameras.cpp

Comments

Kieran Bingham May 18, 2020, 3:57 p.m. UTC | #1
Hi Umang,

I'm happy to see a hotplug test here, and I know this one is quite a
pain to write ;-)

On 13/05/2020 18:30, Umang Jain wrote:

A bit of blurb about what happens in the test is always useful in the
commit message here.

It's also particularly worth mentioning that the test will skip if not
run as root due to the requirement to modify the bind state of the
device, and of course that it requires a UVC device to be connected for
test.


> Signed-off-by: Umang Jain <email@uajain.com>
> ---
>  test/hotplug-cameras.cpp | 152 +++++++++++++++++++++++++++++++++++++++
>  test/meson.build         |   1 +
>  2 files changed, 153 insertions(+)
>  create mode 100644 test/hotplug-cameras.cpp
> 
> diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp
> new file mode 100644
> index 0000000..cbf68af
> --- /dev/null
> +++ b/test/hotplug-cameras.cpp
> @@ -0,0 +1,152 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2020, Umang Jain <email@uajain.com>
> + *
> + * hotplug-cameras.cpp - Emulate cameraAdded/cameraRemoved signals in CameraManager
> + */
> +
> +#include <dirent.h>
> +#include <fstream>
> +#include <iostream>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <fcntl.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +
> +#include <libcamera/camera.h>
> +#include <libcamera/camera_manager.h>
> +#include <libcamera/event_dispatcher.h>
> +#include <libcamera/timer.h>
> +
> +#include "file.h"
> +#include "test.h"
> +#include "thread.h"
> +
> +using namespace std;
> +using namespace libcamera;
> +
> +class HotplugTest : public Test
> +{
> +protected:
> +	void cameraAddedHandler(std::shared_ptr<Camera> cam)
> +	{
> +		cameraAddedPass_ = true;
> +	}
> +
> +	void cameraRemovedHandler(std::shared_ptr<Camera> cam)
> +	{
> +		cameraRemovedPass_ = true;
> +	}
> +
> +	int init()
> +	{
> +		if (!File::exists("/sys/module/uvcvideo")) {
> +			std::cout << "uvcvideo driver is not loaded, skipping" << std::endl;
> +			return TestSkip;
> +		}
> +
> +		if (geteuid() != 0) {
> +			std::cout << "This test requires root permissions, skipping" << std::endl;
> +			return TestSkip;
> +		}
> +
> +		cm_ = new CameraManager();
> +		if (cm_->start()) {
> +			std::cout << "Failed to start camera manager" << std::endl;
> +			return TestFail;
> +		}
> +
> +		cameraAddedPass_ = false;
> +		cameraRemovedPass_ = false;
> +
> +		cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
> +		cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);
> +
> +		uvc_toplevel_ = "/sys/module/uvcvideo/drivers/usb:uvcvideo/";
> +
> +		return 0;
> +	}
> +
> +	int run()
> +	{
> +		DIR *dir;
> +		struct dirent *dirent, *dirent2;
> +		std::string uvc_driver_dir;
> +		bool uvc_driver_found = false;
> +
> +		dir = opendir(uvc_toplevel_.c_str());
> +		/* Find a UVC device driver symlink, which we can bind/unbind */
> +		while ((dirent = readdir(dir)) != nullptr) {
> +			if (dirent->d_type != DT_LNK)
> +				continue;
> +
> +			std::string child_dir = uvc_toplevel_ + dirent->d_name;
> +			DIR *device_driver = opendir(child_dir.c_str());
> +			while ((dirent2 = readdir(device_driver)) != nullptr) {
> +				if (strncmp(dirent2->d_name, "video4linux", 11) == 0) {
> +					uvc_driver_dir = dirent->d_name;
> +					uvc_driver_found = true;
> +					break;
> +				}
> +			}
> +			closedir(device_driver);
> +
> +			if (uvc_driver_found)
> +				break;
> +		}
> +		closedir(dir);
> +
> +		/* If no UVC driver found, skip */
> +		if (!uvc_driver_found)
> +			return TestSkip;
> +
> +		/* Unbind a camera, process events */
> +		int fd1 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/unbind", O_WRONLY);
> +		write(fd1, uvc_driver_dir.c_str(), uvc_driver_dir.size());
> +		close(fd1);
> +		Timer timer;
> +		timer.start(1000);
> +		while (timer.isRunning())
> +			Thread::current()->eventDispatcher()->processEvents();
> +
> +		/* \todo: Fix this workaround of stopping and starting the camera-manager.
> +		 *  We need to do this, so that cm_ release all references to the uvc media symlinks.

Eep, I can't get my head around this just yet. You mean having the
camera manager running, it has open handles to the media symlinks in
sysfs perhaps?

I guess it's implying that something in the cm_ is preventing the clean
unbind of the camera - but I don't understand what that is yet to know
if we must fix that, or if it's something unavoidable?

But we're not going to expect applications to stop and restart the
camera manager, so I certainly hope this is just because of the fact
we're using the unbind/bind mechanism?

If not - then we really need to fix it ;-)

> +		 */
> +		cm_->stop();
> +		if (cm_->start()) {
> +			std::cout << "Failed to restart camera-manager" << std::endl;
> +			return TestFail;
> +		}
> +
> +		/* Bind the camera again, process events */
> +		int fd2 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/bind", O_WRONLY);
> +		write(fd2, uvc_driver_dir.c_str(), uvc_driver_dir.size());
> +		close(fd2);
> +
> +		timer.start(1000);
> +		while (timer.isRunning())
> +			Thread::current()->eventDispatcher()->processEvents();
> +
> +		if (cameraAddedPass_ && cameraRemovedPass_)
> +			return TestPass;
> +		else
> +			return TestFail;
> +	}
> +
> +	void cleanup()
> +	{
> +		cm_->stop();
> +		delete cm_;
> +	}
> +
> +private:
> +	CameraManager *cm_;
> +	std::string uvc_toplevel_;
> +	bool cameraRemovedPass_;
> +	bool cameraAddedPass_;
> +};
> +
> +TEST_REGISTER(HotplugTest)
> +
> diff --git a/test/meson.build b/test/meson.build
> index 5a45a85..383a7ea 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -29,6 +29,7 @@ internal_tests = [
>      ['file',                            'file.cpp'],
>      ['file-descriptor',                 'file-descriptor.cpp'],
>      ['message',                         'message.cpp'],
> +    ['hotplug-cameras',                 'hotplug-cameras.cpp'],
>      ['object',                          'object.cpp'],
>      ['object-invoke',                   'object-invoke.cpp'],
>      ['signal-threads',                  'signal-threads.cpp'],
>
Umang Jain May 19, 2020, 2:15 p.m. UTC | #2
Hi Kieran,

On Mon, 2020-05-18 at 16:57 +0100, Kieran Bingham wrote:
> Hi Umang,
> 
> I'm happy to see a hotplug test here, and I know this one is quite a
> pain to write ;-)
> 
> On 13/05/2020 18:30, Umang Jain wrote:
> 
> A bit of blurb about what happens in the test is always useful in the
> commit message here.
> 
> It's also particularly worth mentioning that the test will skip if
> not
> run as root due to the requirement to modify the bind state of the
> device, and of course that it requires a UVC device to be connected
> for
> test.
> 
> 
> > Signed-off-by: Umang Jain <email@uajain.com>
> > ---
> >  test/hotplug-cameras.cpp | 152
> > +++++++++++++++++++++++++++++++++++++++
> >  test/meson.build         |   1 +
> >  2 files changed, 153 insertions(+)
> >  create mode 100644 test/hotplug-cameras.cpp
> > 
> > diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp
> > new file mode 100644
> > index 0000000..cbf68af
> > --- /dev/null
> > +++ b/test/hotplug-cameras.cpp
> > @@ -0,0 +1,152 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2020, Umang Jain <email@uajain.com>
> > + *
> > + * hotplug-cameras.cpp - Emulate cameraAdded/cameraRemoved signals
> > in CameraManager
> > + */
> > +
> > +#include <dirent.h>
> > +#include <fstream>
> > +#include <iostream>
> > +#include <string.h>
> > +#include <unistd.h>
> > +
> > +#include <fcntl.h>
> > +#include <sys/stat.h>
> > +#include <sys/types.h>
> > +
> > +#include <libcamera/camera.h>
> > +#include <libcamera/camera_manager.h>
> > +#include <libcamera/event_dispatcher.h>
> > +#include <libcamera/timer.h>
> > +
> > +#include "file.h"
> > +#include "test.h"
> > +#include "thread.h"
> > +
> > +using namespace std;
> > +using namespace libcamera;
> > +
> > +class HotplugTest : public Test
> > +{
> > +protected:
> > +	void cameraAddedHandler(std::shared_ptr<Camera> cam)
> > +	{
> > +		cameraAddedPass_ = true;
> > +	}
> > +
> > +	void cameraRemovedHandler(std::shared_ptr<Camera> cam)
> > +	{
> > +		cameraRemovedPass_ = true;
> > +	}
> > +
> > +	int init()
> > +	{
> > +		if (!File::exists("/sys/module/uvcvideo")) {
> > +			std::cout << "uvcvideo driver is not loaded,
> > skipping" << std::endl;
> > +			return TestSkip;
> > +		}
> > +
> > +		if (geteuid() != 0) {
> > +			std::cout << "This test requires root
> > permissions, skipping" << std::endl;
> > +			return TestSkip;
> > +		}
> > +
> > +		cm_ = new CameraManager();
> > +		if (cm_->start()) {
> > +			std::cout << "Failed to start camera manager"
> > << std::endl;
> > +			return TestFail;
> > +		}
> > +
> > +		cameraAddedPass_ = false;
> > +		cameraRemovedPass_ = false;
> > +
> > +		cm_->cameraAdded.connect(this,
> > &HotplugTest::cameraAddedHandler);
> > +		cm_->cameraRemoved.connect(this,
> > &HotplugTest::cameraRemovedHandler);
> > +
> > +		uvc_toplevel_ =
> > "/sys/module/uvcvideo/drivers/usb:uvcvideo/";
> > +
> > +		return 0;
> > +	}
> > +
> > +	int run()
> > +	{
> > +		DIR *dir;
> > +		struct dirent *dirent, *dirent2;
> > +		std::string uvc_driver_dir;
> > +		bool uvc_driver_found = false;
> > +
> > +		dir = opendir(uvc_toplevel_.c_str());
> > +		/* Find a UVC device driver symlink, which we can
> > bind/unbind */
> > +		while ((dirent = readdir(dir)) != nullptr) {
> > +			if (dirent->d_type != DT_LNK)
> > +				continue;
> > +
> > +			std::string child_dir = uvc_toplevel_ + dirent-
> > >d_name;
> > +			DIR *device_driver =
> > opendir(child_dir.c_str());
> > +			while ((dirent2 = readdir(device_driver)) !=
> > nullptr) {
> > +				if (strncmp(dirent2->d_name,
> > "video4linux", 11) == 0) {
> > +					uvc_driver_dir = dirent-
> > >d_name;
> > +					uvc_driver_found = true;
> > +					break;
> > +				}
> > +			}
> > +			closedir(device_driver);
> > +
> > +			if (uvc_driver_found)
> > +				break;
> > +		}
> > +		closedir(dir);
> > +
> > +		/* If no UVC driver found, skip */
> > +		if (!uvc_driver_found)
> > +			return TestSkip;
> > +
> > +		/* Unbind a camera, process events */
> > +		int fd1 =
> > open("/sys/module/uvcvideo/drivers/usb:uvcvideo/unbind", O_WRONLY);
> > +		write(fd1, uvc_driver_dir.c_str(),
> > uvc_driver_dir.size());
> > +		close(fd1);
> > +		Timer timer;
> > +		timer.start(1000);
> > +		while (timer.isRunning())
> > +			Thread::current()->eventDispatcher()-
> > >processEvents();
> > +
> > +		/* \todo: Fix this workaround of stopping and starting
> > the camera-manager.
> > +		 *  We need to do this, so that cm_ release all
> > references to the uvc media symlinks.
> 
> Eep, I can't get my head around this just yet. You mean having the
> camera manager running, it has open handles to the media symlinks in
> sysfs perhaps?
> 
> I guess it's implying that something in the cm_ is preventing the
> clean
> unbind of the camera - but I don't understand what that is yet to
> know
> if we must fix that, or if it's something unavoidable?

Yes, let me explain with an observation from the 'master' branch of
libcamera(i.e. without these patches).

I have a UVC camera and is denoted by "1-1:1.0" and "1-1:1.1" symlinks
in '/sys/module/uvcvideo/drivers/usb:uvcvideo'. Now, with qcam
streaming from this device, try to unbind and re-bind the "1-1:1.0"
symlink (which  is the primary video device node). Obviously, qcam
won't pick up the re-binding again but notice what happens when you
exit the qcam and try to re-run qcam again; qcam won't notice the
device when it's restarted.

Yet another observation while following the above mentioned process,
when I have qcam running and I unbind "1-1:1.0", the symlink "1-1:1.1"
doesn't get dis-appeared(apparently it should). It only gets disappered
(i.e. device has been cleanly unbind) only when qcam is closed at this
point; which explains CameraManager is holding some kind of ref on this
symlink.
> 
> But we're not going to expect applications to stop and restart the
> camera manager, so I certainly hope this is just because of the fact
> we're using the unbind/bind mechanism?

Yes, that's my observation as well. This problem(and hence the
workaround in the test) is specific to bind/unbind mechanism. I tested
with a handy USB webcam around, and it doesn't leave any dangling
symlinks.

> If not - then we really need to fix it ;-)
> 
I am still looking into it, it might be a silly leak somewhere but
getting hard to find :)

> > +		 */
> > +		cm_->stop();
> > +		if (cm_->start()) {
> > +			std::cout << "Failed to restart camera-manager" 
> > << std::endl;
> > +			return TestFail;
> > +		}
> > +
> > +		/* Bind the camera again, process events */
> > +		int fd2 =
> > open("/sys/module/uvcvideo/drivers/usb:uvcvideo/bind", O_WRONLY);
> > +		write(fd2, uvc_driver_dir.c_str(),
> > uvc_driver_dir.size());
> > +		close(fd2);
> > +
> > +		timer.start(1000);
> > +		while (timer.isRunning())
> > +			Thread::current()->eventDispatcher()-
> > >processEvents();
> > +
> > +		if (cameraAddedPass_ && cameraRemovedPass_)
> > +			return TestPass;
> > +		else
> > +			return TestFail;
> > +	}
> > +
> > +	void cleanup()
> > +	{
> > +		cm_->stop();
> > +		delete cm_;
> > +	}
> > +
> > +private:
> > +	CameraManager *cm_;
> > +	std::string uvc_toplevel_;
> > +	bool cameraRemovedPass_;
> > +	bool cameraAddedPass_;
> > +};
> > +
> > +TEST_REGISTER(HotplugTest)
> > +
> > diff --git a/test/meson.build b/test/meson.build
> > index 5a45a85..383a7ea 100644
> > --- a/test/meson.build
> > +++ b/test/meson.build
> > @@ -29,6 +29,7 @@ internal_tests = [
> >      ['file',                            'file.cpp'],
> >      ['file-descriptor',                 'file-descriptor.cpp'],
> >      ['message',                         'message.cpp'],
> > +    ['hotplug-cameras',                 'hotplug-cameras.cpp'],
> >      ['object',                          'object.cpp'],
> >      ['object-invoke',                   'object-invoke.cpp'],
> >      ['signal-threads',                  'signal-threads.cpp'],
> >
Laurent Pinchart June 7, 2020, 5:21 p.m. UTC | #3
Hi Umang,

On Tue, May 19, 2020 at 02:15:26PM +0000, Umang Jain wrote:
> On Mon, 2020-05-18 at 16:57 +0100, Kieran Bingham wrote:
> > Hi Umang,
> > 
> > I'm happy to see a hotplug test here, and I know this one is quite a
> > pain to write ;-)
> > 
> > On 13/05/2020 18:30, Umang Jain wrote:
> > 
> > A bit of blurb about what happens in the test is always useful in the
> > commit message here.
> > 
> > It's also particularly worth mentioning that the test will skip if not
> > run as root due to the requirement to modify the bind state of the
> > device, and of course that it requires a UVC device to be connected for
> > test.
> > 
> > > Signed-off-by: Umang Jain <email@uajain.com>
> > > ---
> > >  test/hotplug-cameras.cpp | 152 +++++++++++++++++++++++++++++++++++++++
> > >  test/meson.build         |   1 +
> > >  2 files changed, 153 insertions(+)
> > >  create mode 100644 test/hotplug-cameras.cpp
> > > 
> > > diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp
> > > new file mode 100644
> > > index 0000000..cbf68af
> > > --- /dev/null
> > > +++ b/test/hotplug-cameras.cpp
> > > @@ -0,0 +1,152 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +/*
> > > + * Copyright (C) 2020, Umang Jain <email@uajain.com>
> > > + *
> > > + * hotplug-cameras.cpp - Emulate cameraAdded/cameraRemoved signals
> > > in CameraManager
> > > + */
> > > +
> > > +#include <dirent.h>
> > > +#include <fstream>
> > > +#include <iostream>
> > > +#include <string.h>
> > > +#include <unistd.h>
> > > +
> > > +#include <fcntl.h>
> > > +#include <sys/stat.h>
> > > +#include <sys/types.h>
> > > +
> > > +#include <libcamera/camera.h>
> > > +#include <libcamera/camera_manager.h>
> > > +#include <libcamera/event_dispatcher.h>
> > > +#include <libcamera/timer.h>
> > > +
> > > +#include "file.h"
> > > +#include "test.h"
> > > +#include "thread.h"
> > > +
> > > +using namespace std;
> > > +using namespace libcamera;
> > > +
> > > +class HotplugTest : public Test
> > > +{
> > > +protected:
> > > +	void cameraAddedHandler(std::shared_ptr<Camera> cam)
> > > +	{
> > > +		cameraAddedPass_ = true;
> > > +	}
> > > +
> > > +	void cameraRemovedHandler(std::shared_ptr<Camera> cam)
> > > +	{
> > > +		cameraRemovedPass_ = true;
> > > +	}
> > > +
> > > +	int init()
> > > +	{
> > > +		if (!File::exists("/sys/module/uvcvideo")) {
> > > +			std::cout << "uvcvideo driver is not loaded,
> > > skipping" << std::endl;
> > > +			return TestSkip;
> > > +		}
> > > +
> > > +		if (geteuid() != 0) {
> > > +			std::cout << "This test requires root permissions, skipping" << std::endl;
> > > +			return TestSkip;
> > > +		}
> > > +
> > > +		cm_ = new CameraManager();
> > > +		if (cm_->start()) {
> > > +			std::cout << "Failed to start camera manager" << std::endl;
> > > +			return TestFail;
> > > +		}
> > > +
> > > +		cameraAddedPass_ = false;
> > > +		cameraRemovedPass_ = false;
> > > +
> > > +		cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
> > > +		cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);
> > > +
> > > +		uvc_toplevel_ = "/sys/module/uvcvideo/drivers/usb:uvcvideo/";
> > > +
> > > +		return 0;
> > > +	}
> > > +
> > > +	int run()
> > > +	{
> > > +		DIR *dir;
> > > +		struct dirent *dirent, *dirent2;
> > > +		std::string uvc_driver_dir;
> > > +		bool uvc_driver_found = false;
> > > +
> > > +		dir = opendir(uvc_toplevel_.c_str());
> > > +		/* Find a UVC device driver symlink, which we can bind/unbind */
> > > +		while ((dirent = readdir(dir)) != nullptr) {
> > > +			if (dirent->d_type != DT_LNK)
> > > +				continue;
> > > +
> > > +			std::string child_dir = uvc_toplevel_ + dirent- >d_name;
> > > +			DIR *device_driver = opendir(child_dir.c_str());
> > > +			while ((dirent2 = readdir(device_driver)) != nullptr) {
> > > +				if (strncmp(dirent2->d_name, "video4linux", 11) == 0) {
> > > +					uvc_driver_dir = dirent- >d_name;
> > > +					uvc_driver_found = true;
> > > +					break;
> > > +				}
> > > +			}
> > > +			closedir(device_driver);
> > > +
> > > +			if (uvc_driver_found)
> > > +				break;
> > > +		}
> > > +		closedir(dir);
> > > +
> > > +		/* If no UVC driver found, skip */
> > > +		if (!uvc_driver_found)
> > > +			return TestSkip;
> > > +
> > > +		/* Unbind a camera, process events */
> > > +		int fd1 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/unbind", O_WRONLY);
> > > +		write(fd1, uvc_driver_dir.c_str(), uvc_driver_dir.size());
> > > +		close(fd1);
> > > +		Timer timer;
> > > +		timer.start(1000);
> > > +		while (timer.isRunning())
> > > +			Thread::current()->eventDispatcher()- >processEvents();
> > > +
> > > +		/* \todo: Fix this workaround of stopping and starting the camera-manager.
> > > +		 *  We need to do this, so that cm_ release all references to the uvc media symlinks.
> > 
> > Eep, I can't get my head around this just yet. You mean having the
> > camera manager running, it has open handles to the media symlinks in
> > sysfs perhaps?
> > 
> > I guess it's implying that something in the cm_ is preventing the clean
> > unbind of the camera - but I don't understand what that is yet to know
> > if we must fix that, or if it's something unavoidable?
> 
> Yes, let me explain with an observation from the 'master' branch of
> libcamera(i.e. without these patches).
> 
> I have a UVC camera and is denoted by "1-1:1.0" and "1-1:1.1" symlinks
> in '/sys/module/uvcvideo/drivers/usb:uvcvideo'. Now, with qcam
> streaming from this device, try to unbind and re-bind the "1-1:1.0"
> symlink (which  is the primary video device node). Obviously, qcam
> won't pick up the re-binding again but notice what happens when you
> exit the qcam and try to re-run qcam again; qcam won't notice the
> device when it's restarted.
> 
> Yet another observation while following the above mentioned process,
> when I have qcam running and I unbind "1-1:1.0", the symlink "1-1:1.1"
> doesn't get dis-appeared(apparently it should). It only gets disappered
> (i.e. device has been cleanly unbind) only when qcam is closed at this
> point; which explains CameraManager is holding some kind of ref on this
> symlink.
>
> > But we're not going to expect applications to stop and restart the
> > camera manager, so I certainly hope this is just because of the fact
> > we're using the unbind/bind mechanism?
> 
> Yes, that's my observation as well. This problem(and hence the
> workaround in the test) is specific to bind/unbind mechanism. I tested
> with a handy USB webcam around, and it doesn't leave any dangling
> symlinks.
> 
> > If not - then we really need to fix it ;-)
>
> I am still looking into it, it might be a silly leak somewhere but
> getting hard to find :)

Thanks for the analysis. Have you managed to figure out why this
happens, or is it still an open question ?

> > > +		 */
> > > +		cm_->stop();
> > > +		if (cm_->start()) {
> > > +			std::cout << "Failed to restart camera-manager"  << std::endl;
> > > +			return TestFail;
> > > +		}
> > > +
> > > +		/* Bind the camera again, process events */
> > > +		int fd2 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/bind", O_WRONLY);
> > > +		write(fd2, uvc_driver_dir.c_str(), uvc_driver_dir.size());
> > > +		close(fd2);
> > > +
> > > +		timer.start(1000);
> > > +		while (timer.isRunning())
> > > +			Thread::current()->eventDispatcher()- >processEvents();
> > > +
> > > +		if (cameraAddedPass_ && cameraRemovedPass_)
> > > +			return TestPass;
> > > +		else
> > > +			return TestFail;
> > > +	}
> > > +
> > > +	void cleanup()
> > > +	{
> > > +		cm_->stop();
> > > +		delete cm_;
> > > +	}
> > > +
> > > +private:
> > > +	CameraManager *cm_;
> > > +	std::string uvc_toplevel_;
> > > +	bool cameraRemovedPass_;
> > > +	bool cameraAddedPass_;
> > > +};
> > > +
> > > +TEST_REGISTER(HotplugTest)
> > > +
> > > diff --git a/test/meson.build b/test/meson.build
> > > index 5a45a85..383a7ea 100644
> > > --- a/test/meson.build
> > > +++ b/test/meson.build
> > > @@ -29,6 +29,7 @@ internal_tests = [
> > >      ['file',                            'file.cpp'],
> > >      ['file-descriptor',                 'file-descriptor.cpp'],
> > >      ['message',                         'message.cpp'],
> > > +    ['hotplug-cameras',                 'hotplug-cameras.cpp'],
> > >      ['object',                          'object.cpp'],
> > >      ['object-invoke',                   'object-invoke.cpp'],
> > >      ['signal-threads',                  'signal-threads.cpp'],

Patch

diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp
new file mode 100644
index 0000000..cbf68af
--- /dev/null
+++ b/test/hotplug-cameras.cpp
@@ -0,0 +1,152 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Umang Jain <email@uajain.com>
+ *
+ * hotplug-cameras.cpp - Emulate cameraAdded/cameraRemoved signals in CameraManager
+ */
+
+#include <dirent.h>
+#include <fstream>
+#include <iostream>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/event_dispatcher.h>
+#include <libcamera/timer.h>
+
+#include "file.h"
+#include "test.h"
+#include "thread.h"
+
+using namespace std;
+using namespace libcamera;
+
+class HotplugTest : public Test
+{
+protected:
+	void cameraAddedHandler(std::shared_ptr<Camera> cam)
+	{
+		cameraAddedPass_ = true;
+	}
+
+	void cameraRemovedHandler(std::shared_ptr<Camera> cam)
+	{
+		cameraRemovedPass_ = true;
+	}
+
+	int init()
+	{
+		if (!File::exists("/sys/module/uvcvideo")) {
+			std::cout << "uvcvideo driver is not loaded, skipping" << std::endl;
+			return TestSkip;
+		}
+
+		if (geteuid() != 0) {
+			std::cout << "This test requires root permissions, skipping" << std::endl;
+			return TestSkip;
+		}
+
+		cm_ = new CameraManager();
+		if (cm_->start()) {
+			std::cout << "Failed to start camera manager" << std::endl;
+			return TestFail;
+		}
+
+		cameraAddedPass_ = false;
+		cameraRemovedPass_ = false;
+
+		cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
+		cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);
+
+		uvc_toplevel_ = "/sys/module/uvcvideo/drivers/usb:uvcvideo/";
+
+		return 0;
+	}
+
+	int run()
+	{
+		DIR *dir;
+		struct dirent *dirent, *dirent2;
+		std::string uvc_driver_dir;
+		bool uvc_driver_found = false;
+
+		dir = opendir(uvc_toplevel_.c_str());
+		/* Find a UVC device driver symlink, which we can bind/unbind */
+		while ((dirent = readdir(dir)) != nullptr) {
+			if (dirent->d_type != DT_LNK)
+				continue;
+
+			std::string child_dir = uvc_toplevel_ + dirent->d_name;
+			DIR *device_driver = opendir(child_dir.c_str());
+			while ((dirent2 = readdir(device_driver)) != nullptr) {
+				if (strncmp(dirent2->d_name, "video4linux", 11) == 0) {
+					uvc_driver_dir = dirent->d_name;
+					uvc_driver_found = true;
+					break;
+				}
+			}
+			closedir(device_driver);
+
+			if (uvc_driver_found)
+				break;
+		}
+		closedir(dir);
+
+		/* If no UVC driver found, skip */
+		if (!uvc_driver_found)
+			return TestSkip;
+
+		/* Unbind a camera, process events */
+		int fd1 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/unbind", O_WRONLY);
+		write(fd1, uvc_driver_dir.c_str(), uvc_driver_dir.size());
+		close(fd1);
+		Timer timer;
+		timer.start(1000);
+		while (timer.isRunning())
+			Thread::current()->eventDispatcher()->processEvents();
+
+		/* \todo: Fix this workaround of stopping and starting the camera-manager.
+		 *  We need to do this, so that cm_ release all references to the uvc media symlinks.
+		 */
+		cm_->stop();
+		if (cm_->start()) {
+			std::cout << "Failed to restart camera-manager" << std::endl;
+			return TestFail;
+		}
+
+		/* Bind the camera again, process events */
+		int fd2 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/bind", O_WRONLY);
+		write(fd2, uvc_driver_dir.c_str(), uvc_driver_dir.size());
+		close(fd2);
+
+		timer.start(1000);
+		while (timer.isRunning())
+			Thread::current()->eventDispatcher()->processEvents();
+
+		if (cameraAddedPass_ && cameraRemovedPass_)
+			return TestPass;
+		else
+			return TestFail;
+	}
+
+	void cleanup()
+	{
+		cm_->stop();
+		delete cm_;
+	}
+
+private:
+	CameraManager *cm_;
+	std::string uvc_toplevel_;
+	bool cameraRemovedPass_;
+	bool cameraAddedPass_;
+};
+
+TEST_REGISTER(HotplugTest)
+
diff --git a/test/meson.build b/test/meson.build
index 5a45a85..383a7ea 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -29,6 +29,7 @@  internal_tests = [
     ['file',                            'file.cpp'],
     ['file-descriptor',                 'file-descriptor.cpp'],
     ['message',                         'message.cpp'],
+    ['hotplug-cameras',                 'hotplug-cameras.cpp'],
     ['object',                          'object.cpp'],
     ['object-invoke',                   'object-invoke.cpp'],
     ['signal-threads',                  'signal-threads.cpp'],