[libcamera-devel] [PATCH v3 5/5] tests: Introduce hotplug hot-unplug unit test
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Mon Jun 8 00:24:36 CEST 2020
Hi Umang,
Thank you for the patch.
On Thu, May 21, 2020 at 01:54:27PM +0000, Umang Jain wrote:
> This test checks the code-paths for camera's hotplugged and
> unplugged support. It is based off bind/unbind of a UVC device
s/off/on/
> from sysfs. Hence, this tests required root permissions to run
s/tests required/test requires/
> and should have atleast one already bound UVC device present
s/atleast/at least/
> on the system.
s/on/in/
>
> Signed-off-by: Umang Jain <email at uajain.com>
> ---
> test/hotplug-cameras.cpp | 153 +++++++++++++++++++++++++++++++++++++++
> test/meson.build | 1 +
> 2 files changed, 154 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..72c370f
> --- /dev/null
> +++ b/test/hotplug-cameras.cpp
> @@ -0,0 +1,153 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2020, Umang Jain <email at uajain.com>
> + *
> + * hotplug-cameras.cpp - Emulate cameraAdded/cameraRemoved signals in CameraManager
Maybe s/Emulate/Test/ ?
> + */
> +
> +#include <dirent.h>
> +#include <fstream>
> +#include <iostream>
> +#include <string.h>
> +#include <unistd.h>
> +
No need for a blank line here, and you can then merge the two groups of
headers in alphabetical order.
> +#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 "libcamera/internal/file.h"
> +#include "libcamera/internal/thread.h"
> +
> +#include "test.h"
> +
> +using namespace std;
As you use the std namespace explicitly below, you can drop this line.
> +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_->newCameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
> + cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);
> +
> + uvc_toplevel_ = "/sys/module/uvcvideo/drivers/usb:uvcvideo/";
Maybe /sys/bus/usb/drivers/uvcvideo/ to avoid traversing symlinks ? It
makes little difference in practice I suppose.
s/uvc_toplevel_/uvcTopLevel_/
And I would even call this uvcDriverDir to make it more explicit. Now
that I think about it, as uvc_toplevel_ is constant, maybe we should
have
static const std::string uvcDriverDir_;
as a static class member.
> +
> + return 0;
> + }
> +
> + int run()
> + {
> + DIR *dir;
> + struct dirent *dirent, *dirent2;
> + std::string uvc_driver_dir;
> + bool uvc_driver_found = false;
camelCase please :-)
> +
> + dir = opendir(uvc_toplevel_.c_str());
> + /* Find a UVC device driver symlink, which we can bind/unbind */
s/unbind/unbind./ (same for the other comments below)
> + 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;
Can't this be simplified with
std::string child_dir = uvc_toplevel_ + dirent->d_name;
if (!File::exist(uvc_toplevel_ + dirent->d_name + "/video4linux"))
continue;
uvc_driver_dir = dirent->d_name;
break;
> + }
> + closedir(dir);
> +
> + /* If no UVC driver found, skip */
> + if (!uvc_driver_found)
And here,
if (uvc_driver_dir.empty())
to remove uvc_driver_found.
> + return TestSkip;
> +
> + /* Unbind a camera, process events */
> + int fd1 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/unbind", O_WRONLY);
Maybe replacing the hardcoded string with uvc_toplevel_ + "unbind" ?
This would become
int fd1 = open((uvcDriverDir_ + "unbind").c_str(), O_WRONLY);
> + write(fd1, uvc_driver_dir.c_str(), uvc_driver_dir.size());
> + close(fd1);
Blank line here ?
I wonder if using the C++ file API would be simpler here.
std::ofstream(uvcDriverDir_ + "unbind", std::ios::binary)
<< uvc_driver_dir;;
and that's it.
> + Timer timer;
> + timer.start(1000);
> + while (timer.isRunning())
while (timer.isRunning() && !cameraRemovedPass_)
to shorten the wait time. Same below.
> + Thread::current()->eventDispatcher()->processEvents();
> +
How about already testing for cameraRemovedPass_ here ?
if (!cameraRemovedPass_)
return TestFail;
> + /* \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.
> + */
We can merge the series without fixing this, but I think it needs to be
handled sooner than latter.
> + 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);
You don't need fd2, you can reuse fd1 (which should be renamed fd).
> + 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;
This would become
if (!cameraAddedPass_)
return TestFail;
return TestPass;
> + }
> +
> + void cleanup()
> + {
> + cm_->stop();
> + delete cm_;
> + }
> +
> +private:
> + CameraManager *cm_;
> + std::string uvc_toplevel_;
> + bool cameraRemovedPass_;
Maybe s/cameraRemovedPass_/cameraRemoved_/ ?
> + bool cameraAddedPass_;
Same here.
> +};
> +
> +TEST_REGISTER(HotplugTest)
> +
> diff --git a/test/meson.build b/test/meson.build
> index bd7da14..f7e27d7 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -31,6 +31,7 @@ internal_tests = [
> ['file', 'file.cpp'],
> ['file-descriptor', 'file-descriptor.cpp'],
> ['message', 'message.cpp'],
> + ['hotplug-cameras', 'hotplug-cameras.cpp'],
Alphabetical order ?
> ['object', 'object.cpp'],
> ['object-invoke', 'object-invoke.cpp'],
> ['signal-threads', 'signal-threads.cpp'],
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list