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

Message ID 20200506103346.3433-5-email@uajain.com
State Superseded
Headers show
Series
  • Introduce UVC hotplug support
Related show

Commit Message

Umang Jain May 6, 2020, 10:33 a.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

Patch

diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp
new file mode 100644
index 0000000..7f1e5a5
--- /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(Camera *cam)
+	{
+		cameraAddedPass_ = true;
+	}
+
+	void cameraRemovedHandler(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'],