summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/bayer-format.cpp211
-rw-r--r--test/byte-stream-buffer.cpp12
-rw-r--r--test/camera-sensor.cpp46
-rw-r--r--test/camera/buffer_import.cpp56
-rw-r--r--test/camera/camera_reconfigure.cpp264
-rw-r--r--test/camera/capture.cpp56
-rw-r--r--test/camera/configuration_default.cpp5
-rw-r--r--test/camera/configuration_set.cpp5
-rw-r--r--test/camera/meson.build21
-rw-r--r--test/camera/statemachine.cpp29
-rw-r--r--test/color-space.cpp105
-rw-r--r--test/controls/control_info.cpp45
-rw-r--r--test/controls/control_info_map.cpp13
-rw-r--r--test/controls/control_list.cpp137
-rw-r--r--test/controls/control_value.cpp82
-rw-r--r--test/controls/meson.build18
-rw-r--r--test/delayed_controls.cpp303
-rw-r--r--test/event-dispatcher.cpp17
-rw-r--r--test/event-thread.cpp50
-rw-r--r--test/event.cpp38
-rw-r--r--test/fence.cpp361
-rw-r--r--test/file.cpp383
-rw-r--r--test/flags.cpp193
-rw-r--r--test/geometry.cpp409
-rw-r--r--test/gstreamer/gstreamer_device_provider_test.cpp70
-rw-r--r--test/gstreamer/gstreamer_memory_lifetime_test.cpp90
-rw-r--r--test/gstreamer/gstreamer_multi_stream_test.cpp111
-rw-r--r--test/gstreamer/gstreamer_single_stream_test.cpp69
-rw-r--r--test/gstreamer/gstreamer_test.cpp174
-rw-r--r--test/gstreamer/gstreamer_test.h34
-rw-r--r--test/gstreamer/meson.build31
-rw-r--r--test/hotplug-cameras.cpp129
-rw-r--r--test/ipa/ipa_interface_test.cpp109
-rw-r--r--test/ipa/ipa_module_test.cpp9
-rw-r--r--test/ipa/ipa_wrappers_test.cpp394
-rw-r--r--test/ipa/libipa/fixedpoint.cpp108
-rw-r--r--test/ipa/libipa/interpolator.cpp54
-rw-r--r--test/ipa/libipa/meson.build18
-rw-r--r--test/ipa/libipa/vector.cpp100
-rw-r--r--test/ipa/meson.build21
-rw-r--r--test/ipc/meson.build13
-rw-r--r--test/ipc/unixsocket.cpp66
-rw-r--r--test/ipc/unixsocket_ipc.cpp233
-rw-r--r--test/libtest/buffer_source.cpp9
-rw-r--r--test/libtest/buffer_source.h22
-rw-r--r--test/libtest/camera_test.cpp5
-rw-r--r--test/libtest/camera_test.h19
-rw-r--r--test/libtest/meson.build5
-rw-r--r--test/libtest/test.cpp7
-rw-r--r--test/libtest/test.h21
-rw-r--r--test/list-cameras.cpp51
-rw-r--r--test/log/log_api.cpp7
-rw-r--r--test/log/log_process.cpp42
-rw-r--r--test/log/meson.build14
-rw-r--r--test/mapped-buffer.cpp117
-rw-r--r--test/media_device/media_device_acquire.cpp2
-rw-r--r--test/media_device/media_device_link_test.cpp6
-rw-r--r--test/media_device/media_device_print_test.cpp10
-rw-r--r--test/media_device/media_device_test.cpp2
-rw-r--r--test/media_device/media_device_test.h18
-rw-r--r--test/media_device/meson.build18
-rw-r--r--test/meson.build127
-rw-r--r--test/message.cpp79
-rw-r--r--test/object-delete.cpp116
-rw-r--r--test/object-invoke.cpp10
-rw-r--r--test/object.cpp9
-rw-r--r--test/pipeline/ipu3/ipu3_pipeline_test.cpp125
-rw-r--r--test/pipeline/ipu3/meson.build12
-rw-r--r--test/pipeline/meson.build2
-rw-r--r--test/pipeline/rkisp1/meson.build12
-rw-r--r--test/pipeline/rkisp1/rkisp1_pipeline_test.cpp114
-rw-r--r--test/pixel-format.cpp51
-rw-r--r--test/process/meson.build12
-rw-r--r--test/process/process_test.cpp32
-rw-r--r--test/public-api.cpp25
-rw-r--r--test/py/meson.build42
-rwxr-xr-xtest/py/unittests.py365
-rw-r--r--test/serialization/control_serialization.cpp26
-rw-r--r--test/serialization/generated_serializer/generated_serializer_test.cpp182
-rw-r--r--test/serialization/generated_serializer/include/libcamera/ipa/meson.build43
-rw-r--r--test/serialization/generated_serializer/include/libcamera/ipa/test.mojom43
-rw-r--r--test/serialization/generated_serializer/meson.build19
-rw-r--r--test/serialization/ipa_data_serializer_test.cpp436
-rw-r--r--test/serialization/meson.build15
-rw-r--r--test/serialization/serialization_test.cpp2
-rw-r--r--test/serialization/serialization_test.h20
-rw-r--r--test/shared-fd.cpp (renamed from test/file-descriptor.cpp)117
-rw-r--r--test/signal-threads.cpp33
-rw-r--r--test/signal.cpp66
-rw-r--r--test/span.cpp52
-rw-r--r--test/stream/meson.build17
-rw-r--r--test/stream/stream_colorspace.cpp96
-rw-r--r--test/stream/stream_formats.cpp6
-rw-r--r--test/threads.cpp111
-rw-r--r--test/timer-fail.cpp109
-rw-r--r--test/timer-thread.cpp52
-rw-r--r--test/timer.cpp41
-rw-r--r--test/transform.cpp329
-rw-r--r--test/unique-fd.cpp220
-rw-r--r--test/utils.cpp188
-rw-r--r--test/v4l2_compat/meson.build29
-rwxr-xr-xtest/v4l2_compat/v4l2_compat_test.py180
-rw-r--r--test/v4l2_subdevice/list_formats.cpp27
-rw-r--r--test/v4l2_subdevice/meson.build14
-rw-r--r--test/v4l2_subdevice/test_formats.cpp7
-rw-r--r--test/v4l2_subdevice/v4l2_subdevice_test.cpp9
-rw-r--r--test/v4l2_subdevice/v4l2_subdevice_test.h24
-rw-r--r--test/v4l2_videodevice/buffer_cache.cpp41
-rw-r--r--test/v4l2_videodevice/buffer_sharing.cpp16
-rw-r--r--test/v4l2_videodevice/capture_async.cpp25
-rw-r--r--test/v4l2_videodevice/controls.cpp37
-rw-r--r--test/v4l2_videodevice/dequeue_watchdog.cpp102
-rw-r--r--test/v4l2_videodevice/double_open.cpp2
-rw-r--r--test/v4l2_videodevice/formats.cpp13
-rw-r--r--test/v4l2_videodevice/meson.build29
-rw-r--r--test/v4l2_videodevice/request_buffers.cpp2
-rw-r--r--test/v4l2_videodevice/stream_on_off.cpp2
-rw-r--r--test/v4l2_videodevice/v4l2_m2mdevice.cpp29
-rw-r--r--test/v4l2_videodevice/v4l2_videodevice_test.cpp18
-rw-r--r--test/v4l2_videodevice/v4l2_videodevice_test.h36
-rw-r--r--test/yaml-parser.cpp620
121 files changed, 8245 insertions, 1440 deletions
diff --git a/test/bayer-format.cpp b/test/bayer-format.cpp
new file mode 100644
index 00000000..f8d19804
--- /dev/null
+++ b/test/bayer-format.cpp
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Sebastian Fricke
+ *
+ * BayerFormat class tests
+ */
+
+#include <iostream>
+
+#include <libcamera/transform.h>
+
+#include "libcamera/internal/bayer_format.h"
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class BayerFormatTest : public Test
+{
+protected:
+ int run()
+ {
+ /* An empty Bayer format has to be invalid. */
+ BayerFormat bayerFmt;
+ if (bayerFmt.isValid()) {
+ cerr << "An empty BayerFormat has to be invalid."
+ << endl;
+ return TestFail;
+ }
+
+ /* A correct Bayer format has to be valid. */
+ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
+ if (!bayerFmt.isValid()) {
+ cerr << "A correct BayerFormat has to be valid."
+ << endl;
+ return TestFail;
+ }
+
+ /*
+ * Two bayer formats created with the same order and bit depth
+ * have to be equal.
+ */
+ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
+ BayerFormat bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 8,
+ BayerFormat::Packing::None);
+ if (bayerFmt != bayerFmtExpect) {
+ cerr << "Comparison of identical formats failed."
+ << endl;
+ return TestFail;
+ }
+
+ /*
+ * Two Bayer formats created with the same order but with a
+ * different bitDepth are not equal.
+ */
+ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
+ bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 12,
+ BayerFormat::Packing::None);
+ if (bayerFmt == bayerFmtExpect) {
+ cerr << "Comparison of different formats failed."
+ << endl;
+ return TestFail;
+ }
+
+ /*
+ * Create a Bayer format with a V4L2PixelFormat and check if we
+ * get the same format after converting back to the V4L2 Format.
+ */
+ V4L2PixelFormat v4l2FmtExpect = V4L2PixelFormat(
+ V4L2_PIX_FMT_SBGGR8);
+ bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2FmtExpect);
+ V4L2PixelFormat v4l2Fmt = bayerFmt.toV4L2PixelFormat();
+ if (v4l2Fmt != v4l2FmtExpect) {
+ cerr << "Expected: '" << v4l2FmtExpect
+ << "' got: '" << v4l2Fmt << "'" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Use an empty Bayer format and verify that no matching
+ * V4L2PixelFormat is found.
+ */
+ v4l2FmtExpect = V4L2PixelFormat();
+ bayerFmt = BayerFormat();
+ v4l2Fmt = bayerFmt.toV4L2PixelFormat();
+ if (v4l2Fmt != v4l2FmtExpect) {
+ cerr << "Expected: empty V4L2PixelFormat got: '"
+ << v4l2Fmt << "'" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Check if we get the expected Bayer format BGGR8
+ * when we convert the V4L2PixelFormat (V4L2_PIX_FMT_SBGGR8)
+ * to a Bayer format.
+ */
+ bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 8,
+ BayerFormat::Packing::None);
+ v4l2Fmt = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8);
+ bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2Fmt);
+ if (bayerFmt != bayerFmtExpect) {
+ cerr << "Expected BayerFormat '"
+ << bayerFmtExpect << "', got: '"
+ << bayerFmt << "'" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Confirm that a V4L2PixelFormat that is not found in
+ * the conversion table, doesn't yield a Bayer format.
+ */
+ V4L2PixelFormat v4l2FmtUnknown = V4L2PixelFormat(
+ V4L2_PIX_FMT_BGRA444);
+ bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2FmtUnknown);
+ if (bayerFmt.isValid()) {
+ cerr << "Expected empty BayerFormat got: '"
+ << bayerFmt << "'" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Test if a valid Bayer format can be converted to a
+ * string representation.
+ */
+ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
+ if (bayerFmt.toString() != "BGGR-8") {
+ cerr << "String representation != 'BGGR-8' (got: '"
+ << bayerFmt.toString() << "' ) " << endl;
+ return TestFail;
+ }
+
+ /*
+ * Determine if an empty Bayer format results in no
+ * string representation.
+ */
+ bayerFmt = BayerFormat();
+ if (bayerFmt.toString() != "INVALID") {
+ cerr << "String representation != 'INVALID' (got: '"
+ << bayerFmt.toString() << "' ) " << endl;
+ return TestFail;
+ }
+
+ /*
+ * Perform a horizontal Flip and make sure that the
+ * order is adjusted accordingly.
+ */
+ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
+ bayerFmtExpect = BayerFormat(BayerFormat::GBRG, 8,
+ BayerFormat::Packing::None);
+ BayerFormat hFlipFmt = bayerFmt.transform(Transform::HFlip);
+ if (hFlipFmt != bayerFmtExpect) {
+ cerr << "Horizontal flip of 'BGGR-8' should result in '"
+ << bayerFmtExpect << "', got: '"
+ << hFlipFmt << "'" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Perform a vertical Flip and make sure that
+ * the order is adjusted accordingly.
+ */
+ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
+ bayerFmtExpect = BayerFormat(BayerFormat::GRBG, 8,
+ BayerFormat::Packing::None);
+ BayerFormat vFlipFmt = bayerFmt.transform(Transform::VFlip);
+ if (vFlipFmt != bayerFmtExpect) {
+ cerr << "Vertical flip of 'BGGR-8' should result in '"
+ << bayerFmtExpect << "', got: '"
+ << vFlipFmt << "'" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Perform a transposition on a pixel order with both green
+ * pixels on the bottom left to top right diagonal and make
+ * sure, that it doesn't change.
+ */
+ bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
+ BayerFormat transposeFmt = bayerFmt.transform(
+ Transform::Transpose);
+ if (transposeFmt != bayerFmt) {
+ cerr << "Transpose with both green pixels on the "
+ << "antidiagonal should not change the order "
+ << "(got '" << transposeFmt << "')"
+ << endl;
+ return TestFail;
+ }
+
+ /*
+ * Perform a transposition on an pixel order with red and blue
+ * on the bottom left to top right diagonal and make sure
+ * that their positions are switched.
+ */
+ bayerFmt = BayerFormat(BayerFormat::GBRG, 8, BayerFormat::Packing::None);
+ bayerFmtExpect = BayerFormat(BayerFormat::GRBG, 8,
+ BayerFormat::Packing::None);
+ transposeFmt = bayerFmt.transform(Transform::Transpose);
+ if (transposeFmt != bayerFmtExpect) {
+ cerr << "Transpose with the red & blue pixels on the "
+ << "antidiagonal should switch their position "
+ << "(got '" << transposeFmt << "')"
+ << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(BayerFormatTest)
diff --git a/test/byte-stream-buffer.cpp b/test/byte-stream-buffer.cpp
index bc1d462e..04aab3d2 100644
--- a/test/byte-stream-buffer.cpp
+++ b/test/byte-stream-buffer.cpp
@@ -2,13 +2,14 @@
/*
* Copyright (C) 2018, Google Inc.
*
- * byte_stream_buffer.cpp - ByteStreamBuffer tests
+ * ByteStreamBuffer tests
*/
#include <array>
#include <iostream>
-#include "byte_stream_buffer.h"
+#include "libcamera/internal/byte_stream_buffer.h"
+
#include "test.h"
using namespace std;
@@ -19,7 +20,12 @@ class ByteStreamBufferTest : public Test
protected:
int run()
{
- std::array<uint8_t, 100> data;
+ /*
+ * gcc 11.1.0 incorrectly raises a maybe-uninitialized warning
+ * when calling data.size() below (if the address sanitizer is
+ * disabled). Silence it by initializing the array.
+ */
+ std::array<uint8_t, 100> data = {};
unsigned int i;
uint32_t value;
int ret;
diff --git a/test/camera-sensor.cpp b/test/camera-sensor.cpp
index 27c190fe..869c7889 100644
--- a/test/camera-sensor.cpp
+++ b/test/camera-sensor.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * camera-sensor.cpp - Camera sensor tests
+ * Camera sensor tests
*/
#include <algorithm>
@@ -10,11 +10,13 @@
#include <linux/media-bus-format.h>
-#include "camera_sensor.h"
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "utils.h"
-#include "v4l2_subdevice.h"
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/camera_lens.h"
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_subdevice.h"
#include "test.h"
@@ -50,17 +52,27 @@ protected:
return TestFail;
}
- sensor_ = new CameraSensor(entity);
- if (sensor_->init() < 0) {
+ sensor_ = CameraSensorFactoryBase::create(entity);
+ if (!sensor_) {
cerr << "Unable to initialise camera sensor" << endl;
return TestFail;
}
+ lens_ = sensor_->focusLens();
+ if (lens_)
+ cout << "Found lens controller" << endl;
+
return TestPass;
}
int run()
{
+ if (sensor_->model() != "Sensor A") {
+ cerr << "Incorrect sensor model '" << sensor_->model()
+ << "'" << endl;
+ return TestFail;
+ }
+
const std::vector<unsigned int> &codes = sensor_->mbusCodes();
auto iter = std::find(codes.begin(), codes.end(),
MEDIA_BUS_FMT_ARGB8888_1X32);
@@ -69,7 +81,7 @@ protected:
return TestFail;
}
- const std::vector<Size> &sizes = sensor_->sizes();
+ const std::vector<Size> &sizes = sensor_->sizes(*iter);
auto iter2 = std::find(sizes.begin(), sizes.end(),
Size(4096, 2160));
if (iter2 == sizes.end()) {
@@ -79,8 +91,7 @@ protected:
const Size &resolution = sensor_->resolution();
if (resolution != Size(4096, 2160)) {
- cerr << "Incorrect sensor resolution "
- << resolution.toString() << endl;
+ cerr << "Incorrect sensor resolution " << resolution << endl;
return TestFail;
}
@@ -89,11 +100,16 @@ protected:
MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_BGR888_1X24 },
Size(1024, 768));
- if (format.mbus_code != MEDIA_BUS_FMT_SBGGR10_1X10 ||
+ if (format.code != MEDIA_BUS_FMT_SBGGR10_1X10 ||
format.size != Size(4096, 2160)) {
cerr << "Failed to get a suitable format, expected 4096x2160-0x"
<< utils::hex(MEDIA_BUS_FMT_SBGGR10_1X10)
- << ", got " << format.toString() << endl;
+ << ", got " << format << endl;
+ return TestFail;
+ }
+
+ if (lens_ && lens_->setFocusPosition(10)) {
+ cerr << "Failed to set lens focus position" << endl;
return TestFail;
}
@@ -102,13 +118,13 @@ protected:
void cleanup()
{
- delete sensor_;
}
private:
std::unique_ptr<DeviceEnumerator> enumerator_;
std::shared_ptr<MediaDevice> media_;
- CameraSensor *sensor_;
+ std::unique_ptr<CameraSensor> sensor_;
+ CameraLens *lens_;
};
TEST_REGISTER(CameraSensorTest)
diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp
index 3f392cdc..815d1cae 100644
--- a/test/camera/buffer_import.cpp
+++ b/test/camera/buffer_import.cpp
@@ -12,15 +12,20 @@
#include <numeric>
#include <vector>
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "v4l2_videodevice.h"
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_videodevice.h"
#include "buffer_source.h"
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
+using namespace std::chrono_literals;
namespace {
@@ -28,12 +33,13 @@ class BufferImportTest : public CameraTest, public Test
{
public:
BufferImportTest()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
- void bufferComplete(Request *request, FrameBuffer *buffer)
+ void bufferComplete([[maybe_unused]] Request *request,
+ FrameBuffer *buffer)
{
if (buffer->metadata().status != FrameMetadata::FrameSuccess)
return;
@@ -46,17 +52,19 @@ protected:
if (request->status() != Request::RequestComplete)
return;
- const std::map<Stream *, FrameBuffer *> &buffers = request->buffers();
+ const Request::BufferMap &buffers = request->buffers();
completeRequestsCount_++;
/* Create a new request. */
- Stream *stream = buffers.begin()->first;
+ const Stream *stream = buffers.begin()->first;
FrameBuffer *buffer = buffers.begin()->second;
- request = camera_->createRequest();
+ request->reuse();
request->addBuffer(stream, buffer);
camera_->queueRequest(request);
+
+ dispatcher_->interrupt();
}
int init() override
@@ -70,6 +78,8 @@ protected:
return TestFail;
}
+ dispatcher_ = Thread::current()->eventDispatcher();
+
return TestPass;
}
@@ -94,9 +104,8 @@ protected:
if (ret != TestPass)
return ret;
- std::vector<Request *> requests;
for (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {
- Request *request = camera_->createRequest();
+ std::unique_ptr<Request> request = camera_->createRequest();
if (!request) {
std::cout << "Failed to create request" << std::endl;
return TestFail;
@@ -107,7 +116,7 @@ protected:
return TestFail;
}
- requests.push_back(request);
+ requests_.push_back(std::move(request));
}
completeRequestsCount_ = 0;
@@ -121,24 +130,27 @@ protected:
return TestFail;
}
- for (Request *request : requests) {
- if (camera_->queueRequest(request)) {
+ for (std::unique_ptr<Request> &request : requests_) {
+ if (camera_->queueRequest(request.get())) {
std::cout << "Failed to queue request" << std::endl;
return TestFail;
}
}
- EventDispatcher *dispatcher = cm_->eventDispatcher();
+ const unsigned int nFrames = cfg.bufferCount * 2;
Timer timer;
- timer.start(1000);
- while (timer.isRunning())
- dispatcher->processEvents();
+ timer.start(500ms * nFrames);
+ while (timer.isRunning()) {
+ dispatcher_->processEvents();
+ if (completeRequestsCount_ > nFrames)
+ break;
+ }
- if (completeRequestsCount_ <= cfg.bufferCount * 2) {
+ if (completeRequestsCount_ < nFrames) {
std::cout << "Failed to capture enough frames (got "
<< completeRequestsCount_ << " expected at least "
- << cfg.bufferCount * 2 << ")" << std::endl;
+ << nFrames << ")" << std::endl;
return TestFail;
}
@@ -156,6 +168,10 @@ protected:
}
private:
+ EventDispatcher *dispatcher_;
+
+ std::vector<std::unique_ptr<Request>> requests_;
+
unsigned int completeBuffersCount_;
unsigned int completeRequestsCount_;
std::unique_ptr<CameraConfiguration> config_;
@@ -163,4 +179,4 @@ private:
} /* namespace */
-TEST_REGISTER(BufferImportTest);
+TEST_REGISTER(BufferImportTest)
diff --git a/test/camera/camera_reconfigure.cpp b/test/camera/camera_reconfigure.cpp
new file mode 100644
index 00000000..06c87730
--- /dev/null
+++ b/test/camera/camera_reconfigure.cpp
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * Test:
+ * - Multiple reconfigurations of the Camera without stopping the CameraManager
+ * - Validate there are no file descriptor leaks when using IPC
+ */
+
+#include <dirent.h>
+#include <fstream>
+#include <iostream>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/file.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include <libcamera/framebuffer_allocator.h>
+
+#include "camera_test.h"
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
+
+namespace {
+
+class CameraReconfigure : public CameraTest, public Test
+{
+public:
+ /* Initialize CameraTest with isolated IPA */
+ CameraReconfigure()
+ : CameraTest(kCamId_, true)
+ {
+ }
+
+private:
+ static constexpr const char *kCamId_ = "platform/vimc.0 Sensor B";
+ static constexpr const char *kIpaProxyName_ = "vimc_ipa_proxy";
+ static constexpr unsigned int kNumOfReconfigures_ = 10;
+
+ void requestComplete(Request *request)
+ {
+ if (request->status() != Request::RequestComplete)
+ return;
+
+ const Request::BufferMap &buffers = request->buffers();
+
+ const Stream *stream = buffers.begin()->first;
+ FrameBuffer *buffer = buffers.begin()->second;
+
+ /* Reuse the request and re-queue it with the same buffers. */
+ request->reuse();
+ request->addBuffer(stream, buffer);
+ camera_->queueRequest(request);
+ }
+
+ int startAndStop()
+ {
+ StreamConfiguration &cfg = config_->at(0);
+
+ if (camera_->acquire()) {
+ cerr << "Failed to acquire the camera" << endl;
+ return TestFail;
+ }
+
+ if (camera_->configure(config_.get())) {
+ cerr << "Failed to set default configuration" << endl;
+ return TestFail;
+ }
+
+ Stream *stream = cfg.stream();
+
+ /*
+ * The configuration is consistent so we can re-use the
+ * same buffer allocation for each run.
+ */
+ if (!allocated_) {
+ int ret = allocator_->allocate(stream);
+ if (ret < 0) {
+ cerr << "Failed to allocate buffers" << endl;
+ return TestFail;
+ }
+ allocated_ = true;
+ }
+
+ for (const unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
+ unique_ptr<Request> request = camera_->createRequest();
+ if (!request) {
+ cerr << "Failed to create request" << endl;
+ return TestFail;
+ }
+
+ if (request->addBuffer(stream, buffer.get())) {
+ cerr << "Failed to associate buffer with request" << endl;
+ return TestFail;
+ }
+
+ requests_.push_back(std::move(request));
+ }
+
+ camera_->requestCompleted.connect(this, &CameraReconfigure::requestComplete);
+
+ if (camera_->start()) {
+ cerr << "Failed to start camera" << endl;
+ return TestFail;
+ }
+
+ for (unique_ptr<Request> &request : requests_) {
+ if (camera_->queueRequest(request.get())) {
+ cerr << "Failed to queue request" << endl;
+ return TestFail;
+ }
+ }
+
+ EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
+
+ Timer timer;
+ timer.start(100ms);
+ while (timer.isRunning())
+ dispatcher->processEvents();
+
+ if (camera_->stop()) {
+ cerr << "Failed to stop camera" << endl;
+ return TestFail;
+ }
+
+ if (camera_->release()) {
+ cerr << "Failed to release camera" << endl;
+ return TestFail;
+ }
+
+ camera_->requestCompleted.disconnect(this);
+
+ requests_.clear();
+
+ return 0;
+ }
+
+ int fdsOpen(pid_t pid)
+ {
+ string proxyFdPath = "/proc/" + to_string(pid) + "/fd";
+ DIR *dir;
+ struct dirent *ptr;
+ unsigned int openFds = 0;
+
+ dir = opendir(proxyFdPath.c_str());
+ if (dir == nullptr) {
+ int err = errno;
+ cerr << "Error opening " << proxyFdPath << ": "
+ << strerror(-err) << endl;
+ return 0;
+ }
+
+ while ((ptr = readdir(dir)) != nullptr) {
+ if ((strcmp(ptr->d_name, ".") == 0) ||
+ (strcmp(ptr->d_name, "..") == 0))
+ continue;
+
+ openFds++;
+ }
+ closedir(dir);
+
+ return openFds;
+ }
+
+ pid_t findProxyPid()
+ {
+ string proxyPid;
+ string proxyName(kIpaProxyName_);
+ DIR *dir;
+ struct dirent *ptr;
+
+ dir = opendir("/proc");
+ while ((ptr = readdir(dir)) != nullptr) {
+ if (ptr->d_type != DT_DIR)
+ continue;
+
+ string pname("/proc/" + string(ptr->d_name) + "/comm");
+ if (File::exists(pname)) {
+ ifstream pfile(pname.c_str());
+ string comm;
+ getline(pfile, comm);
+ pfile.close();
+
+ proxyPid = comm == proxyName ? string(ptr->d_name) : "";
+ }
+
+ if (!proxyPid.empty())
+ break;
+ }
+ closedir(dir);
+
+ if (!proxyPid.empty())
+ return atoi(proxyPid.c_str());
+
+ return -1;
+ }
+
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ config_ = camera_->generateConfiguration({ StreamRole::StillCapture });
+ if (!config_ || config_->size() != 1) {
+ cerr << "Failed to generate default configuration" << endl;
+ return TestFail;
+ }
+
+ allocator_ = make_unique<FrameBufferAllocator>(camera_);
+ allocated_ = false;
+
+ return TestPass;
+ }
+
+ int run() override
+ {
+ unsigned int openFdsAtStart = 0;
+ unsigned int openFds = 0;
+
+ pid_t proxyPid = findProxyPid();
+ if (proxyPid < 0) {
+ cerr << "Cannot find " << kIpaProxyName_
+ << " pid, exiting" << endl;
+ return TestFail;
+ }
+
+ openFdsAtStart = fdsOpen(proxyPid);
+ for (unsigned int i = 0; i < kNumOfReconfigures_; i++) {
+ startAndStop();
+ openFds = fdsOpen(proxyPid);
+ if (openFds == 0) {
+ cerr << "No open fds found whereas "
+ << "open fds at start: " << openFdsAtStart
+ << endl;
+ return TestFail;
+ }
+
+ if (openFds != openFdsAtStart) {
+ cerr << "Leaking fds for " << kIpaProxyName_
+ << " - Open fds: " << openFds << " vs "
+ << "Open fds at start: " << openFdsAtStart
+ << endl;
+ return TestFail;
+ }
+ }
+
+ return TestPass;
+ }
+
+ bool allocated_;
+
+ vector<unique_ptr<Request>> requests_;
+
+ unique_ptr<CameraConfiguration> config_;
+ unique_ptr<FrameBufferAllocator> allocator_;
+};
+
+} /* namespace */
+
+TEST_REGISTER(CameraReconfigure)
diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp
index f6b2f348..8766fb19 100644
--- a/test/camera/capture.cpp
+++ b/test/camera/capture.cpp
@@ -7,10 +7,18 @@
#include <iostream>
+#include <libcamera/framebuffer_allocator.h>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
#include "camera_test.h"
#include "test.h"
+using namespace libcamera;
using namespace std;
+using namespace std::chrono_literals;
namespace {
@@ -18,7 +26,7 @@ class Capture : public CameraTest, public Test
{
public:
Capture()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
@@ -26,7 +34,8 @@ protected:
unsigned int completeBuffersCount_;
unsigned int completeRequestsCount_;
- void bufferComplete(Request *request, FrameBuffer *buffer)
+ void bufferComplete([[maybe_unused]] Request *request,
+ FrameBuffer *buffer)
{
if (buffer->metadata().status != FrameMetadata::FrameSuccess)
return;
@@ -39,17 +48,19 @@ protected:
if (request->status() != Request::RequestComplete)
return;
- const std::map<Stream *, FrameBuffer *> &buffers = request->buffers();
+ const Request::BufferMap &buffers = request->buffers();
completeRequestsCount_++;
/* Create a new request. */
- Stream *stream = buffers.begin()->first;
+ const Stream *stream = buffers.begin()->first;
FrameBuffer *buffer = buffers.begin()->second;
- request = camera_->createRequest();
+ request->reuse();
request->addBuffer(stream, buffer);
camera_->queueRequest(request);
+
+ dispatcher_->interrupt();
}
int init() override
@@ -64,6 +75,7 @@ protected:
}
allocator_ = new FrameBufferAllocator(camera_);
+ dispatcher_ = Thread::current()->eventDispatcher();
return TestPass;
}
@@ -93,20 +105,19 @@ protected:
if (ret < 0)
return TestFail;
- std::vector<Request *> requests;
for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
- Request *request = camera_->createRequest();
+ std::unique_ptr<Request> request = camera_->createRequest();
if (!request) {
cout << "Failed to create request" << endl;
return TestFail;
}
if (request->addBuffer(stream, buffer.get())) {
- cout << "Failed to associating buffer with request" << endl;
+ cout << "Failed to associate buffer with request" << endl;
return TestFail;
}
- requests.push_back(request);
+ requests_.push_back(std::move(request));
}
completeRequestsCount_ = 0;
@@ -120,26 +131,27 @@ protected:
return TestFail;
}
- for (Request *request : requests) {
- if (camera_->queueRequest(request)) {
+ for (std::unique_ptr<Request> &request : requests_) {
+ if (camera_->queueRequest(request.get())) {
cout << "Failed to queue request" << endl;
return TestFail;
}
}
- EventDispatcher *dispatcher = cm_->eventDispatcher();
+ unsigned int nFrames = allocator_->buffers(stream).size() * 2;
Timer timer;
- timer.start(1000);
- while (timer.isRunning())
- dispatcher->processEvents();
-
- unsigned int nbuffers = allocator_->buffers(stream).size();
+ timer.start(500ms * nFrames);
+ while (timer.isRunning()) {
+ dispatcher_->processEvents();
+ if (completeRequestsCount_ > nFrames)
+ break;
+ }
- if (completeRequestsCount_ <= nbuffers * 2) {
+ if (completeRequestsCount_ < nFrames) {
cout << "Failed to capture enough frames (got "
<< completeRequestsCount_ << " expected at least "
- << nbuffers * 2 << ")" << endl;
+ << nFrames * 2 << ")" << endl;
return TestFail;
}
@@ -156,10 +168,14 @@ protected:
return TestPass;
}
+ EventDispatcher *dispatcher_;
+
+ std::vector<std::unique_ptr<Request>> requests_;
+
std::unique_ptr<CameraConfiguration> config_;
FrameBufferAllocator *allocator_;
};
} /* namespace */
-TEST_REGISTER(Capture);
+TEST_REGISTER(Capture)
diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp
index 31c908d2..209eb6a5 100644
--- a/test/camera/configuration_default.cpp
+++ b/test/camera/configuration_default.cpp
@@ -10,6 +10,7 @@
#include "camera_test.h"
#include "test.h"
+using namespace libcamera;
using namespace std;
namespace {
@@ -18,7 +19,7 @@ class ConfigurationDefault : public CameraTest, public Test
{
public:
ConfigurationDefault()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
@@ -56,4 +57,4 @@ protected:
} /* namespace */
-TEST_REGISTER(ConfigurationDefault);
+TEST_REGISTER(ConfigurationDefault)
diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp
index b4b59681..4281a1c4 100644
--- a/test/camera/configuration_set.cpp
+++ b/test/camera/configuration_set.cpp
@@ -10,6 +10,7 @@
#include "camera_test.h"
#include "test.h"
+using namespace libcamera;
using namespace std;
namespace {
@@ -18,7 +19,7 @@ class ConfigurationSet : public CameraTest, public Test
{
public:
ConfigurationSet()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
@@ -102,4 +103,4 @@ protected:
} /* namespace */
-TEST_REGISTER(ConfigurationSet);
+TEST_REGISTER(ConfigurationSet)
diff --git a/test/camera/meson.build b/test/camera/meson.build
index e2a6660a..4f9f8c8c 100644
--- a/test/camera/meson.build
+++ b/test/camera/meson.build
@@ -1,17 +1,20 @@
+# SPDX-License-Identifier: CC0-1.0
+
# Tests are listed in order of complexity.
# They are not alphabetically sorted.
camera_tests = [
- [ 'configuration_default', 'configuration_default.cpp' ],
- [ 'configuration_set', 'configuration_set.cpp' ],
- [ 'buffer_import', 'buffer_import.cpp' ],
- [ 'statemachine', 'statemachine.cpp' ],
- [ 'capture', 'capture.cpp' ],
+ {'name': 'configuration_default', 'sources': ['configuration_default.cpp']},
+ {'name': 'configuration_set', 'sources': ['configuration_set.cpp']},
+ {'name': 'buffer_import', 'sources': ['buffer_import.cpp']},
+ {'name': 'statemachine', 'sources': ['statemachine.cpp']},
+ {'name': 'capture', 'sources': ['capture.cpp']},
+ {'name': 'camera_reconfigure', 'sources': ['camera_reconfigure.cpp']},
]
-foreach t : camera_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : camera_tests
+ exe = executable(test['name'], test['sources'],
+ dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'camera', is_parallel : false)
+ test(test['name'], exe, suite : 'camera', is_parallel : false)
endforeach
diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp
index 325b4674..9c2b0c6a 100644
--- a/test/camera/statemachine.cpp
+++ b/test/camera/statemachine.cpp
@@ -7,9 +7,12 @@
#include <iostream>
+#include <libcamera/framebuffer_allocator.h>
+
#include "camera_test.h"
#include "test.h"
+using namespace libcamera;
using namespace std;
namespace {
@@ -18,7 +21,7 @@ class Statemachine : public CameraTest, public Test
{
public:
Statemachine()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
@@ -39,13 +42,13 @@ protected:
if (camera_->queueRequest(&request) != -EACCES)
return TestFail;
- if (camera_->stop() != -EACCES)
- return TestFail;
-
/* Test operations which should pass. */
if (camera_->release())
return TestFail;
+ if (camera_->stop())
+ return TestFail;
+
/* Test valid state transitions, end in Acquired state. */
if (camera_->acquire())
return TestFail;
@@ -69,7 +72,8 @@ protected:
if (camera_->queueRequest(&request) != -EACCES)
return TestFail;
- if (camera_->stop() != -EACCES)
+ /* Test operations which should pass. */
+ if (camera_->stop())
return TestFail;
/* Test valid state transitions, end in Configured state. */
@@ -95,16 +99,13 @@ protected:
if (camera_->queueRequest(&request1) != -EACCES)
return TestFail;
- if (camera_->stop() != -EACCES)
- return TestFail;
-
/* Test operations which should pass. */
- Request *request2 = camera_->createRequest();
+ std::unique_ptr<Request> request2 = camera_->createRequest();
if (!request2)
return TestFail;
- /* Never handed to hardware so need to manually delete it. */
- delete request2;
+ if (camera_->stop())
+ return TestFail;
/* Test valid state transitions, end in Running state. */
if (camera_->release())
@@ -144,7 +145,7 @@ protected:
return TestFail;
/* Test operations which should pass. */
- Request *request = camera_->createRequest();
+ std::unique_ptr<Request> request = camera_->createRequest();
if (!request)
return TestFail;
@@ -152,7 +153,7 @@ protected:
if (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))
return TestFail;
- if (camera_->queueRequest(request))
+ if (camera_->queueRequest(request.get()))
return TestFail;
/* Test valid state transitions, end in Available state. */
@@ -212,4 +213,4 @@ protected:
} /* namespace */
-TEST_REGISTER(Statemachine);
+TEST_REGISTER(Statemachine)
diff --git a/test/color-space.cpp b/test/color-space.cpp
new file mode 100644
index 00000000..7d45b217
--- /dev/null
+++ b/test/color-space.cpp
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * libcamera ColorSpace test
+ */
+
+#include <array>
+#include <iostream>
+
+#include <libcamera/color_space.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class ColorSpaceTest : public Test
+{
+protected:
+ int run()
+ {
+ if (ColorSpace::toString(std::nullopt) != "Unset") {
+ std::cerr << "Conversion from nullopt to string failed" << std::endl;
+ return TestFail;
+ }
+
+ const std::array<std::pair<ColorSpace, std::string>, 10> colorSpaces = { {
+ { ColorSpace::Raw, "RAW" },
+ { ColorSpace::Srgb, "sRGB" },
+ { ColorSpace::Sycc, "sYCC" },
+ { ColorSpace::Smpte170m, "SMPTE170M" },
+ { ColorSpace::Rec709, "Rec709" },
+ { ColorSpace::Rec2020, "Rec2020" },
+ {
+ ColorSpace{
+ ColorSpace::Primaries::Raw,
+ ColorSpace::TransferFunction::Linear,
+ ColorSpace::YcbcrEncoding::None,
+ ColorSpace::Range::Limited
+ },
+ "RAW/Linear/None/Limited"
+ }, {
+ ColorSpace{
+ ColorSpace::Primaries::Smpte170m,
+ ColorSpace::TransferFunction::Srgb,
+ ColorSpace::YcbcrEncoding::Rec601,
+ ColorSpace::Range::Full
+ },
+ "SMPTE170M/sRGB/Rec601/Full"
+ }, {
+ ColorSpace{
+ ColorSpace::Primaries::Rec709,
+ ColorSpace::TransferFunction::Rec709,
+ ColorSpace::YcbcrEncoding::Rec709,
+ ColorSpace::Range::Full
+ },
+ "Rec709/Rec709/Rec709/Full"
+ }, {
+ ColorSpace{
+ ColorSpace::Primaries::Rec2020,
+ ColorSpace::TransferFunction::Linear,
+ ColorSpace::YcbcrEncoding::Rec2020,
+ ColorSpace::Range::Limited
+ },
+ "Rec2020/Linear/Rec2020/Limited"
+ },
+ } };
+
+ for (const auto &[colorSpace, name] : colorSpaces) {
+ if (colorSpace.toString() != name) {
+ std::cerr
+ << "Conversion from ColorSpace to string failed: "
+ << "expected " << name
+ << ", got " << colorSpace.toString()
+ << std::endl;
+ return TestFail;
+ }
+
+ if (ColorSpace::fromString(name) != colorSpace) {
+ std::cerr
+ << "Conversion from string "
+ << name << " to ColorSpace failed"
+ << std::endl;
+ return TestFail;
+ }
+ }
+
+ if (ColorSpace::fromString("Invalid")) {
+ std::cerr << "Conversion from invalid name string to color space succeeded"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (ColorSpace::fromString("Rec709/Rec709/Rec710/Limited")) {
+ std::cerr << "Conversion from invalid component string to color space succeeded"
+ << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(ColorSpaceTest)
diff --git a/test/controls/control_info.cpp b/test/controls/control_info.cpp
index 1e05e131..e1bb43f0 100644
--- a/test/controls/control_info.cpp
+++ b/test/controls/control_info.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * control_info.cpp - ControlInfo tests
+ * ControlInfo tests
*/
#include <iostream>
@@ -26,24 +26,59 @@ protected:
*/
ControlInfo brightness;
- if (brightness.min().get<int32_t>() != 0 ||
- brightness.max().get<int32_t>() != 0) {
+ if (brightness.min().type() != ControlType::ControlTypeNone ||
+ brightness.max().type() != ControlType::ControlTypeNone ||
+ brightness.def().type() != ControlType::ControlTypeNone) {
cout << "Invalid control range for Brightness" << endl;
return TestFail;
}
/*
* Test information retrieval from a control with a minimum and
- * a maximum value.
+ * a maximum value, and an implicit default value.
*/
ControlInfo contrast(10, 200);
if (contrast.min().get<int32_t>() != 10 ||
- contrast.max().get<int32_t>() != 200) {
+ contrast.max().get<int32_t>() != 200 ||
+ !contrast.def().isNone()) {
cout << "Invalid control range for Contrast" << endl;
return TestFail;
}
+ /*
+ * Test information retrieval from a control with boolean
+ * values.
+ */
+ ControlInfo aeEnable({ false, true }, false);
+
+ if (aeEnable.min().get<bool>() != false ||
+ aeEnable.def().get<bool>() != false ||
+ aeEnable.max().get<bool>() != true) {
+ cout << "Invalid control range for AeEnable" << endl;
+ return TestFail;
+ }
+
+ if (aeEnable.values()[0].get<bool>() != false ||
+ aeEnable.values()[1].get<bool>() != true) {
+ cout << "Invalid control values for AeEnable" << endl;
+ return TestFail;
+ }
+
+ ControlInfo awbEnable(true);
+
+ if (awbEnable.min().get<bool>() != true ||
+ awbEnable.def().get<bool>() != true ||
+ awbEnable.max().get<bool>() != true) {
+ cout << "Invalid control range for AwbEnable" << endl;
+ return TestFail;
+ }
+
+ if (awbEnable.values()[0].get<bool>() != true) {
+ cout << "Invalid control values for AwbEnable" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
};
diff --git a/test/controls/control_info_map.cpp b/test/controls/control_info_map.cpp
index eeb702db..b0be14b5 100644
--- a/test/controls/control_info_map.cpp
+++ b/test/controls/control_info_map.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * control_info.cpp - ControlInfoMap tests
+ * ControlInfoMap tests
*/
#include <iostream>
@@ -12,7 +12,7 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
-#include "camera_controls.h"
+#include "libcamera/internal/camera_controls.h"
#include "camera_test.h"
#include "test.h"
@@ -24,7 +24,7 @@ class ControlInfoMapTest : public CameraTest, public Test
{
public:
ControlInfoMapTest()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
@@ -75,6 +75,13 @@ protected:
return TestFail;
}
+ /* Test looking up a control on a default-constructed infoMap */
+ const ControlInfoMap emptyInfoMap;
+ if (emptyInfoMap.find(12345) != emptyInfoMap.end()) {
+ cerr << "find() on empty ControlInfoMap failed" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
};
diff --git a/test/controls/control_list.cpp b/test/controls/control_list.cpp
index 5374c6f9..e27325c3 100644
--- a/test/controls/control_list.cpp
+++ b/test/controls/control_list.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * control_list.cpp - ControlList tests
+ * ControlList tests
*/
#include <iostream>
@@ -12,7 +12,7 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
-#include "camera_controls.h"
+#include "libcamera/internal/camera_controls.h"
#include "camera_test.h"
#include "test.h"
@@ -24,7 +24,7 @@ class ControlListTest : public CameraTest, public Test
{
public:
ControlListTest()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
@@ -50,7 +50,7 @@ protected:
return TestFail;
}
- if (list.contains(controls::Brightness)) {
+ if (list.get(controls::Brightness)) {
cout << "List should not contain Brightness control" << endl;
return TestFail;
}
@@ -68,7 +68,7 @@ protected:
* Set a control, and verify that the list now contains it, and
* nothing else.
*/
- list.set(controls::Brightness, 255);
+ list.set(controls::Brightness, -0.5f);
if (list.empty()) {
cout << "List should not be empty" << endl;
@@ -80,7 +80,7 @@ protected:
return TestFail;
}
- if (!list.contains(controls::Brightness)) {
+ if (!list.get(controls::Brightness)) {
cout << "List should contain Brightness control" << endl;
return TestFail;
}
@@ -94,28 +94,29 @@ protected:
return TestFail;
}
- if (list.get(controls::Brightness) != 255) {
+ if (list.get(controls::Brightness) != -0.5f) {
cout << "Incorrest Brightness control value" << endl;
return TestFail;
}
- if (list.contains(controls::Contrast)) {
+ if (list.get(controls::Contrast)) {
cout << "List should not contain Contract control" << endl;
return TestFail;
}
/* Update the first control and set a second one. */
- list.set(controls::Brightness, 64);
- list.set(controls::Contrast, 128);
+ list.set(controls::Brightness, 0.0f);
+ list.set(controls::Contrast, 1.5f);
- if (!list.contains(controls::Contrast) ||
- !list.contains(controls::Contrast)) {
- cout << "List should contain Contrast control" << endl;
+ if (!list.get(controls::Brightness) ||
+ !list.get(controls::Contrast)) {
+ cout << "List should contain Brightness and Contrast controls"
+ << endl;
return TestFail;
}
- if (list.get(controls::Brightness) != 64 ||
- list.get(controls::Contrast) != 128) {
+ if (list.get(controls::Brightness) != 0.0f ||
+ list.get(controls::Contrast) != 1.5f) {
cout << "Failed to retrieve control value" << endl;
return TestFail;
}
@@ -124,11 +125,11 @@ protected:
* Update both controls and verify that the container doesn't
* grow.
*/
- list.set(controls::Brightness, 10);
- list.set(controls::Contrast, 20);
+ list.set(controls::Brightness, 0.5f);
+ list.set(controls::Contrast, 1.1f);
- if (list.get(controls::Brightness) != 10 ||
- list.get(controls::Contrast) != 20) {
+ if (list.get(controls::Brightness) != 0.5f ||
+ list.get(controls::Contrast) != 1.1f) {
cout << "Failed to update control value" << endl;
return TestFail;
}
@@ -144,11 +145,107 @@ protected:
*/
list.set(controls::AwbEnable, true);
- if (list.contains(controls::AwbEnable)) {
+ if (list.get(controls::AwbEnable)) {
cout << "List shouldn't contain AwbEnable control" << endl;
return TestFail;
}
+ /*
+ * Create a new list with a new control and merge it with the
+ * existing one, verifying that the existing controls
+ * values don't get overwritten.
+ */
+ ControlList mergeList(controls::controls, &validator);
+ mergeList.set(controls::Brightness, 0.7f);
+ mergeList.set(controls::Saturation, 0.4f);
+
+ mergeList.merge(list);
+ if (mergeList.size() != 3) {
+ cout << "Merged list should contain three elements" << endl;
+ return TestFail;
+ }
+
+ if (list.size() != 2) {
+ cout << "The list to merge should contain two elements"
+ << endl;
+ return TestFail;
+ }
+
+ if (!mergeList.get(controls::Brightness) ||
+ !mergeList.get(controls::Contrast) ||
+ !mergeList.get(controls::Saturation)) {
+ cout << "Merged list does not contain all controls" << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Brightness) != 0.7f) {
+ cout << "Brightness control value changed after merging lists"
+ << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Contrast) != 1.1f) {
+ cout << "Contrast control value changed after merging lists"
+ << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Saturation) != 0.4f) {
+ cout << "Saturation control value changed after merging lists"
+ << endl;
+ return TestFail;
+ }
+
+ /*
+ * Create two lists with overlapping controls. Merge them with
+ * overwriteExisting = true, verifying that the existing control
+ * values *get* overwritten.
+ */
+ mergeList.clear();
+ mergeList.set(controls::Brightness, 0.7f);
+ mergeList.set(controls::Saturation, 0.4f);
+
+ list.clear();
+ list.set(controls::Brightness, 0.5f);
+ list.set(controls::Contrast, 1.1f);
+
+ mergeList.merge(list, ControlList::MergePolicy::OverwriteExisting);
+ if (mergeList.size() != 3) {
+ cout << "Merged list should contain three elements" << endl;
+ return TestFail;
+ }
+
+ if (list.size() != 2) {
+ cout << "The list to merge should contain two elements"
+ << endl;
+ return TestFail;
+ }
+
+ if (!mergeList.get(controls::Brightness) ||
+ !mergeList.get(controls::Contrast) ||
+ !mergeList.get(controls::Saturation)) {
+ cout << "Merged list does not contain all controls" << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Brightness) != 0.5f) {
+ cout << "Brightness control value did not change after merging lists"
+ << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Contrast) != 1.1f) {
+ cout << "Contrast control value changed after merging lists"
+ << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Saturation) != 0.4f) {
+ cout << "Saturation control value changed after merging lists"
+ << endl;
+ return TestFail;
+ }
+
return TestPass;
}
};
diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp
index ad8e05d0..5084fd0c 100644
--- a/test/controls/control_value.cpp
+++ b/test/controls/control_value.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * control_value.cpp - ControlValue tests
+ * ControlValue tests
*/
#include <algorithm>
@@ -110,6 +110,86 @@ protected:
}
/*
+ * Unsigned Integer16 type.
+ */
+ value.set(static_cast<uint16_t>(42));
+ if (value.isNone() || value.isArray() ||
+ value.type() != ControlTypeUnsigned16) {
+ cerr << "Control type mismatch after setting to uint16_t" << endl;
+ return TestFail;
+ }
+
+ if (value.get<uint16_t>() != 42) {
+ cerr << "Control value mismatch after setting to uint16_t" << endl;
+ return TestFail;
+ }
+
+ if (value.toString() != "42") {
+ cerr << "Control string mismatch after setting to uint16_t" << endl;
+ return TestFail;
+ }
+
+ std::array<uint16_t, 4> uint16s{ 3, 14, 15, 9 };
+ value.set(Span<uint16_t>(uint16s));
+ if (value.isNone() || !value.isArray() ||
+ value.type() != ControlTypeUnsigned16) {
+ cerr << "Control type mismatch after setting to uint16_t array" << endl;
+ return TestFail;
+ }
+
+ Span<const uint16_t> uint16sResult = value.get<Span<const uint16_t>>();
+ if (uint16s.size() != uint16sResult.size() ||
+ !std::equal(uint16s.begin(), uint16s.end(), uint16sResult.begin())) {
+ cerr << "Control value mismatch after setting to uint16_t array" << endl;
+ return TestFail;
+ }
+
+ if (value.toString() != "[ 3, 14, 15, 9 ]") {
+ cerr << "Control string mismatch after setting to uint16_t array" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Unsigned Integer32 type.
+ */
+ value.set(static_cast<uint32_t>(42));
+ if (value.isNone() || value.isArray() ||
+ value.type() != ControlTypeUnsigned32) {
+ cerr << "Control type mismatch after setting to uint32_t" << endl;
+ return TestFail;
+ }
+
+ if (value.get<uint32_t>() != 42) {
+ cerr << "Control value mismatch after setting to uint32_t" << endl;
+ return TestFail;
+ }
+
+ if (value.toString() != "42") {
+ cerr << "Control string mismatch after setting to uint32_t" << endl;
+ return TestFail;
+ }
+
+ std::array<uint32_t, 4> uint32s{ 3, 14, 15, 9 };
+ value.set(Span<uint32_t>(uint32s));
+ if (value.isNone() || !value.isArray() ||
+ value.type() != ControlTypeUnsigned32) {
+ cerr << "Control type mismatch after setting to uint32_t array" << endl;
+ return TestFail;
+ }
+
+ Span<const uint32_t> uint32sResult = value.get<Span<const uint32_t>>();
+ if (uint32s.size() != uint32sResult.size() ||
+ !std::equal(uint32s.begin(), uint32s.end(), uint32sResult.begin())) {
+ cerr << "Control value mismatch after setting to uint32_t array" << endl;
+ return TestFail;
+ }
+
+ if (value.toString() != "[ 3, 14, 15, 9 ]") {
+ cerr << "Control string mismatch after setting to uint32_t array" << endl;
+ return TestFail;
+ }
+
+ /*
* Integer32 type.
*/
value.set(0x42000000);
diff --git a/test/controls/meson.build b/test/controls/meson.build
index 7fff2413..763f8905 100644
--- a/test/controls/meson.build
+++ b/test/controls/meson.build
@@ -1,14 +1,16 @@
+# SPDX-License-Identifier: CC0-1.0
+
control_tests = [
- [ 'control_info', 'control_info.cpp' ],
- [ 'control_info_map', 'control_info_map.cpp' ],
- [ 'control_list', 'control_list.cpp' ],
- [ 'control_value', 'control_value.cpp' ],
+ {'name': 'control_info', 'sources': ['control_info.cpp']},
+ {'name': 'control_info_map', 'sources': ['control_info_map.cpp']},
+ {'name': 'control_list', 'sources': ['control_list.cpp']},
+ {'name': 'control_value', 'sources': ['control_value.cpp']},
]
-foreach t : control_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : control_tests
+ exe = executable(test['name'], test['sources'],
+ dependencies : libcamera_public,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'controls', is_parallel : false)
+ test(test['name'], exe, suite : 'controls', is_parallel : false)
endforeach
diff --git a/test/delayed_controls.cpp b/test/delayed_controls.cpp
new file mode 100644
index 00000000..7bd30e7a
--- /dev/null
+++ b/test/delayed_controls.cpp
@@ -0,0 +1,303 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * libcamera delayed controls test
+ */
+
+#include <iostream>
+
+#include "libcamera/internal/delayed_controls.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class DelayedControlsTest : public Test
+{
+public:
+ DelayedControlsTest()
+ {
+ }
+
+protected:
+ int init() override
+ {
+ enumerator_ = DeviceEnumerator::create();
+ if (!enumerator_) {
+ cerr << "Failed to create device enumerator" << endl;
+ return TestFail;
+ }
+
+ if (enumerator_->enumerate()) {
+ cerr << "Failed to enumerate media devices" << endl;
+ return TestFail;
+ }
+
+ DeviceMatch dm("vivid");
+ dm.add("vivid-000-vid-cap");
+
+ media_ = enumerator_->search(dm);
+ if (!media_) {
+ cerr << "vivid video device found" << endl;
+ return TestSkip;
+ }
+
+ dev_ = V4L2VideoDevice::fromEntityName(media_.get(), "vivid-000-vid-cap");
+ if (dev_->open()) {
+ cerr << "Failed to open video device" << endl;
+ return TestFail;
+ }
+
+ const ControlInfoMap &infoMap = dev_->controls();
+
+ /* Make sure the controls we require are present. */
+ if (infoMap.empty()) {
+ cerr << "Failed to enumerate controls" << endl;
+ return TestFail;
+ }
+
+ if (infoMap.find(V4L2_CID_BRIGHTNESS) == infoMap.end() ||
+ infoMap.find(V4L2_CID_CONTRAST) == infoMap.end()) {
+ cerr << "Missing controls" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ int singleControlNoDelay()
+ {
+ std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
+ { V4L2_CID_BRIGHTNESS, { 0, false } },
+ };
+ std::unique_ptr<DelayedControls> delayed =
+ std::make_unique<DelayedControls>(dev_.get(), delays);
+ ControlList ctrls;
+
+ /* Reset control to value not used in test. */
+ ctrls.set(V4L2_CID_BRIGHTNESS, 1);
+ dev_->setControls(&ctrls);
+ delayed->reset();
+
+ /* Trigger the first frame start event */
+ delayed->applyControls(0);
+
+ /* Test control without delay are set at once. */
+ for (unsigned int i = 1; i < 100; i++) {
+ int32_t value = 100 + i;
+
+ ctrls.set(V4L2_CID_BRIGHTNESS, value);
+ delayed->push(ctrls);
+
+ delayed->applyControls(i);
+
+ ControlList result = delayed->get(i);
+ int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
+ if (brightness != value) {
+ cerr << "Failed single control without delay"
+ << " frame " << i
+ << " expected " << value
+ << " got " << brightness
+ << endl;
+ return TestFail;
+ }
+ }
+
+ return TestPass;
+ }
+
+ int singleControlWithDelay()
+ {
+ std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
+ { V4L2_CID_BRIGHTNESS, { 1, false } },
+ };
+ std::unique_ptr<DelayedControls> delayed =
+ std::make_unique<DelayedControls>(dev_.get(), delays);
+ ControlList ctrls;
+
+ /* Reset control to value that will be first in test. */
+ int32_t expected = 4;
+ ctrls.set(V4L2_CID_BRIGHTNESS, expected);
+ dev_->setControls(&ctrls);
+ delayed->reset();
+
+ /* Trigger the first frame start event */
+ delayed->applyControls(0);
+
+ /* Test single control with delay. */
+ for (unsigned int i = 1; i < 100; i++) {
+ int32_t value = 10 + i;
+
+ ctrls.set(V4L2_CID_BRIGHTNESS, value);
+ delayed->push(ctrls);
+
+ delayed->applyControls(i);
+
+ ControlList result = delayed->get(i);
+ int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
+ if (brightness != expected) {
+ cerr << "Failed single control with delay"
+ << " frame " << i
+ << " expected " << expected
+ << " got " << brightness
+ << endl;
+ return TestFail;
+ }
+
+ expected = value;
+ }
+
+ return TestPass;
+ }
+
+ int dualControlsWithDelay()
+ {
+ static const unsigned int maxDelay = 2;
+
+ std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
+ { V4L2_CID_BRIGHTNESS, { 1, false } },
+ { V4L2_CID_CONTRAST, { maxDelay, false } },
+ };
+ std::unique_ptr<DelayedControls> delayed =
+ std::make_unique<DelayedControls>(dev_.get(), delays);
+ ControlList ctrls;
+
+ /* Reset control to value that will be first two frames in test. */
+ int32_t expected = 200;
+ ctrls.set(V4L2_CID_BRIGHTNESS, expected);
+ ctrls.set(V4L2_CID_CONTRAST, expected + 1);
+ dev_->setControls(&ctrls);
+ delayed->reset();
+
+ /* Trigger the first frame start event */
+ delayed->applyControls(0);
+
+ /* Test dual control with delay. */
+ for (unsigned int i = 1; i < 100; i++) {
+ int32_t value = 10 + i;
+
+ ctrls.set(V4L2_CID_BRIGHTNESS, value);
+ ctrls.set(V4L2_CID_CONTRAST, value + 1);
+ delayed->push(ctrls);
+
+ delayed->applyControls(i);
+
+ ControlList result = delayed->get(i);
+ int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
+ int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
+ if (brightness != expected || contrast != expected + 1) {
+ cerr << "Failed dual controls"
+ << " frame " << i
+ << " brightness " << brightness
+ << " contrast " << contrast
+ << " expected " << expected
+ << endl;
+ return TestFail;
+ }
+
+ expected = i < maxDelay ? expected : value - 1;
+ }
+
+ return TestPass;
+ }
+
+ int dualControlsMultiQueue()
+ {
+ static const unsigned int maxDelay = 2;
+
+ std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
+ { V4L2_CID_BRIGHTNESS, { 1, false } },
+ { V4L2_CID_CONTRAST, { maxDelay, false } }
+ };
+ std::unique_ptr<DelayedControls> delayed =
+ std::make_unique<DelayedControls>(dev_.get(), delays);
+ ControlList ctrls;
+
+ /* Reset control to value that will be first two frames in test. */
+ int32_t expected = 100;
+ ctrls.set(V4L2_CID_BRIGHTNESS, expected);
+ ctrls.set(V4L2_CID_CONTRAST, expected);
+ dev_->setControls(&ctrls);
+ delayed->reset();
+
+ /* Trigger the first frame start event */
+ delayed->applyControls(0);
+
+ /*
+ * Queue all controls before any fake frame start. Note we
+ * can't queue up more then the delayed controls history size
+ * which is 16. Where one spot is used by the reset control.
+ */
+ for (unsigned int i = 0; i < 15; i++) {
+ int32_t value = 10 + i;
+
+ ctrls.set(V4L2_CID_BRIGHTNESS, value);
+ ctrls.set(V4L2_CID_CONTRAST, value);
+ delayed->push(ctrls);
+ }
+
+ /* Process all queued controls. */
+ for (unsigned int i = 1; i < 16; i++) {
+ int32_t value = 10 + i - 1;
+
+ delayed->applyControls(i);
+
+ ControlList result = delayed->get(i);
+
+ int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
+ int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
+ if (brightness != expected || contrast != expected) {
+ cerr << "Failed multi queue"
+ << " frame " << i
+ << " brightness " << brightness
+ << " contrast " << contrast
+ << " expected " << expected
+ << endl;
+ return TestFail;
+ }
+
+ expected = i < maxDelay ? expected : value - 1;
+ }
+
+ return TestPass;
+ }
+
+ int run() override
+ {
+ int ret;
+
+ /* Test single control without delay. */
+ ret = singleControlNoDelay();
+ if (ret)
+ return ret;
+
+ /* Test single control with delay. */
+ ret = singleControlWithDelay();
+ if (ret)
+ return ret;
+
+ /* Test dual controls with different delays. */
+ ret = dualControlsWithDelay();
+ if (ret)
+ return ret;
+
+ /* Test control values produced faster than consumed. */
+ ret = dualControlsMultiQueue();
+ if (ret)
+ return ret;
+
+ return TestPass;
+ }
+
+private:
+ std::unique_ptr<DeviceEnumerator> enumerator_;
+ std::shared_ptr<MediaDevice> media_;
+ std::unique_ptr<V4L2VideoDevice> dev_;
+};
+
+TEST_REGISTER(DelayedControlsTest)
diff --git a/test/event-dispatcher.cpp b/test/event-dispatcher.cpp
index 9f9cf178..f71c1c0d 100644
--- a/test/event-dispatcher.cpp
+++ b/test/event-dispatcher.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * event-dispatcher.cpp - Event dispatcher test
+ * Event dispatcher test
*/
#include <chrono>
@@ -10,14 +10,15 @@
#include <signal.h>
#include <sys/time.h>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
#include "test.h"
-#include "thread.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
static EventDispatcher *dispatcher;
static bool interrupt;
@@ -50,7 +51,7 @@ protected:
/* Event processing interruption by signal. */
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
- timer.start(1000);
+ timer.start(1000ms);
struct itimerval itimer = {};
itimer.it_value.tv_usec = 500000;
@@ -69,7 +70,7 @@ protected:
}
/* Event processing interruption. */
- timer.start(1000);
+ timer.start(1000ms);
dispatcher->interrupt();
dispatcher->processEvents();
@@ -79,7 +80,7 @@ protected:
return TestFail;
}
- timer.start(1000);
+ timer.start(1000ms);
itimer.it_value.tv_usec = 500000;
interrupt = true;
setitimer(ITIMER_REAL, &itimer, nullptr);
diff --git a/test/event-thread.cpp b/test/event-thread.cpp
index 01120733..5499bbf8 100644
--- a/test/event-thread.cpp
+++ b/test/event-thread.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * event-thread.cpp - Threaded event test
+ * Threaded event test
*/
#include <chrono>
@@ -10,11 +10,12 @@
#include <string.h>
#include <unistd.h>
-#include <libcamera/event_notifier.h>
-#include <libcamera/timer.h>
+#include <libcamera/base/event_notifier.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
#include "test.h"
-#include "thread.h"
using namespace std;
using namespace libcamera;
@@ -66,9 +67,9 @@ public:
}
private:
- void readReady(EventNotifier *notifier)
+ void readReady()
{
- size_ = read(notifier->fd(), data_, sizeof(data_));
+ size_ = read(notifier_->fd(), data_, sizeof(data_));
notified_ = true;
}
@@ -84,10 +85,17 @@ private:
class EventThreadTest : public Test
{
protected:
+ int init()
+ {
+ thread_.start();
+
+ handler_ = new EventHandler();
+
+ return TestPass;
+ }
+
int run()
{
- Thread thread;
- thread.start();
/*
* Fire the event notifier and then move the notifier to a
@@ -97,23 +105,33 @@ protected:
* different thread will correctly process already pending
* events in the new thread.
*/
- EventHandler handler;
- handler.notify();
- handler.moveToThread(&thread);
+ handler_->notify();
+ handler_->moveToThread(&thread_);
this_thread::sleep_for(chrono::milliseconds(100));
- /* Must stop thread before destroying the handler. */
- thread.exit(0);
- thread.wait();
-
- if (!handler.notified()) {
+ if (!handler_->notified()) {
cout << "Thread event handling test failed" << endl;
return TestFail;
}
return TestPass;
}
+
+ void cleanup()
+ {
+ /*
+ * Object class instances must be destroyed from the thread
+ * they live in.
+ */
+ handler_->deleteLater();
+ thread_.exit(0);
+ thread_.wait();
+ }
+
+private:
+ EventHandler *handler_;
+ Thread thread_;
};
TEST_REGISTER(EventThreadTest)
diff --git a/test/event.cpp b/test/event.cpp
index 816060cc..9f7b1ed4 100644
--- a/test/event.cpp
+++ b/test/event.cpp
@@ -2,34 +2,37 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * event.cpp - Event test
+ * Event test
*/
#include <iostream>
#include <string.h>
#include <unistd.h>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/event_notifier.h>
-#include <libcamera/timer.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/event_notifier.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
#include "test.h"
-#include "thread.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
class EventTest : public Test
{
protected:
- void readReady(EventNotifier *notifier)
+ void readReady()
{
- size_ = read(notifier->fd(), data_, sizeof(data_));
+ size_ = read(notifier_->fd(), data_, sizeof(data_));
notified_ = true;
}
int init()
{
+ notifier_ = nullptr;
+
return pipe(pipefd_);
}
@@ -40,8 +43,8 @@ protected:
Timer timeout;
ssize_t ret;
- EventNotifier readNotifier(pipefd_[0], EventNotifier::Read);
- readNotifier.activated.connect(this, &EventTest::readReady);
+ notifier_ = new EventNotifier(pipefd_[0], EventNotifier::Read);
+ notifier_->activated.connect(this, &EventTest::readReady);
/* Test read notification with data. */
memset(data_, 0, sizeof(data_));
@@ -53,7 +56,7 @@ protected:
return TestFail;
}
- timeout.start(100);
+ timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
@@ -65,7 +68,7 @@ protected:
/* Test read notification without data. */
notified_ = false;
- timeout.start(100);
+ timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
@@ -76,7 +79,7 @@ protected:
/* Test read notifier disabling. */
notified_ = false;
- readNotifier.setEnabled(false);
+ notifier_->setEnabled(false);
ret = write(pipefd_[1], data.data(), data.size());
if (ret < 0) {
@@ -84,7 +87,7 @@ protected:
return TestFail;
}
- timeout.start(100);
+ timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
@@ -95,9 +98,9 @@ protected:
/* Test read notifier enabling. */
notified_ = false;
- readNotifier.setEnabled(true);
+ notifier_->setEnabled(true);
- timeout.start(100);
+ timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
@@ -111,6 +114,8 @@ protected:
void cleanup()
{
+ delete notifier_;
+
close(pipefd_[0]);
close(pipefd_[1]);
}
@@ -118,6 +123,7 @@ protected:
private:
int pipefd_[2];
+ EventNotifier *notifier_;
bool notified_;
char data_[16];
ssize_t size_;
diff --git a/test/fence.cpp b/test/fence.cpp
new file mode 100644
index 00000000..8095b228
--- /dev/null
+++ b/test/fence.cpp
@@ -0,0 +1,361 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * Fence test
+ */
+
+#include <iostream>
+#include <memory>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+#include <libcamera/base/unique_fd.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/fence.h>
+#include <libcamera/framebuffer_allocator.h>
+
+#include "camera_test.h"
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
+
+class FenceTest : public CameraTest, public Test
+{
+public:
+ FenceTest();
+
+protected:
+ int init() override;
+ int run() override;
+
+private:
+ int validateExpiredRequest(Request *request);
+ int validateRequest(Request *request);
+ void requestComplete(Request *request);
+ void requestRequeue(Request *request);
+
+ void signalFence();
+
+ EventDispatcher *dispatcher_;
+ UniqueFD eventFd_;
+ UniqueFD eventFd2_;
+ Timer fenceTimer_;
+
+ std::vector<std::unique_ptr<Request>> requests_;
+ std::unique_ptr<CameraConfiguration> config_;
+ std::unique_ptr<FrameBufferAllocator> allocator_;
+
+ Stream *stream_;
+
+ bool expectedCompletionResult_ = true;
+ bool setFence_ = true;
+
+ /*
+ * Request IDs track the number of requests that have completed. They
+ * are one-based, and don't wrap.
+ */
+ unsigned int completedRequestId_;
+ unsigned int signalledRequestId_;
+ unsigned int expiredRequestId_;
+ unsigned int nbuffers_;
+
+ int efd2_;
+ int efd_;
+};
+
+FenceTest::FenceTest()
+ : CameraTest("platform/vimc.0 Sensor B")
+{
+}
+
+int FenceTest::init()
+{
+ /* Make sure the CameraTest constructor succeeded. */
+ if (status_ != TestPass)
+ return status_;
+
+ dispatcher_ = Thread::current()->eventDispatcher();
+
+ /*
+ * Create two eventfds to model the fences. This is enough to support the
+ * needs of libcamera which only needs to wait for read events through
+ * poll(). Once native support for fences will be available in the
+ * backend kernel APIs this will need to be replaced by a sw_sync fence,
+ * but that requires debugfs.
+ */
+ eventFd_ = UniqueFD(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
+ eventFd2_ = UniqueFD(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
+ if (!eventFd_.isValid() || !eventFd2_.isValid()) {
+ cerr << "Unable to create eventfd" << endl;
+ return TestFail;
+ }
+
+ efd_ = eventFd_.get();
+ efd2_ = eventFd2_.get();
+
+ config_ = camera_->generateConfiguration({ StreamRole::Viewfinder });
+ if (!config_ || config_->size() != 1) {
+ cerr << "Failed to generate default configuration" << endl;
+ return TestFail;
+ }
+
+ if (camera_->acquire()) {
+ cerr << "Failed to acquire the camera" << endl;
+ return TestFail;
+ }
+
+ if (camera_->configure(config_.get())) {
+ cerr << "Failed to set default configuration" << endl;
+ return TestFail;
+ }
+
+ StreamConfiguration &cfg = config_->at(0);
+ stream_ = cfg.stream();
+
+ allocator_ = std::make_unique<FrameBufferAllocator>(camera_);
+ if (allocator_->allocate(stream_) < 0)
+ return TestFail;
+
+ nbuffers_ = allocator_->buffers(stream_).size();
+ if (nbuffers_ < 2) {
+ cerr << "Not enough buffers available" << endl;
+ return TestFail;
+ }
+
+ completedRequestId_ = 0;
+
+ /*
+ * All but two requests are queued without a fence. Request
+ * expiredRequestId_ will be queued with a fence that we won't signal
+ * (which is then expected to expire), and request signalledRequestId_
+ * will be queued with a fence that gets signalled. Select nbuffers_
+ * and nbuffers_ * 2 for those two requests, to space them by a few
+ * frames while still not requiring a long time for the test to
+ * complete.
+ */
+ expiredRequestId_ = nbuffers_;
+ signalledRequestId_ = nbuffers_ * 2;
+
+ return TestPass;
+}
+
+int FenceTest::validateExpiredRequest(Request *request)
+{
+ /* The last request is expected to fail. */
+ if (request->status() != Request::RequestCancelled) {
+ cerr << "The last request should have failed: " << endl;
+ return TestFail;
+ }
+
+ FrameBuffer *buffer = request->buffers().begin()->second;
+ std::unique_ptr<Fence> fence = buffer->releaseFence();
+ if (!fence) {
+ cerr << "The expired fence should be present" << endl;
+ return TestFail;
+ }
+
+ if (!fence->isValid()) {
+ cerr << "The expired fence should be valid" << endl;
+ return TestFail;
+ }
+
+ UniqueFD fd = fence->release();
+ if (fd.get() != efd_) {
+ cerr << "The expired fence file descriptor should not change" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+}
+
+int FenceTest::validateRequest(Request *request)
+{
+ uint64_t cookie = request->cookie();
+
+ /* All requests but the last are expected to succeed. */
+ if (request->status() != Request::RequestComplete) {
+ cerr << "Unexpected request failure: " << cookie << endl;
+ return TestFail;
+ }
+
+ /* A successfully completed request should have the Fence closed. */
+ const Request::BufferMap &buffers = request->buffers();
+ FrameBuffer *buffer = buffers.begin()->second;
+
+ std::unique_ptr<Fence> bufferFence = buffer->releaseFence();
+ if (bufferFence) {
+ cerr << "Unexpected valid fence in completed request" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+}
+
+void FenceTest::requestRequeue(Request *request)
+{
+ const Request::BufferMap &buffers = request->buffers();
+ const Stream *stream = buffers.begin()->first;
+ FrameBuffer *buffer = buffers.begin()->second;
+
+ request->reuse();
+
+ if (completedRequestId_ == signalledRequestId_ - nbuffers_ && setFence_) {
+ /*
+ * This is the request that will be used to test fence
+ * signalling when it completes next time. Add a fence to it,
+ * using efd2_. The main loop will signal the fence by using a
+ * timer to write to the efd2_ file descriptor before the fence
+ * expires.
+ */
+ std::unique_ptr<Fence> fence =
+ std::make_unique<Fence>(std::move(eventFd2_));
+ request->addBuffer(stream, buffer, std::move(fence));
+ } else {
+ /* All the other requests continue to operate without fences. */
+ request->addBuffer(stream, buffer);
+ }
+
+ camera_->queueRequest(request);
+}
+
+void FenceTest::requestComplete(Request *request)
+{
+ completedRequestId_++;
+
+ /*
+ * Request expiredRequestId_ is expected to fail as its fence has not
+ * been signalled.
+ *
+ * Validate the fence status but do not re-queue it.
+ */
+ if (completedRequestId_ == expiredRequestId_) {
+ if (validateExpiredRequest(request) != TestPass)
+ expectedCompletionResult_ = false;
+
+ dispatcher_->interrupt();
+ return;
+ }
+
+ /* Validate all other requests. */
+ if (validateRequest(request) != TestPass) {
+ expectedCompletionResult_ = false;
+
+ dispatcher_->interrupt();
+ return;
+ }
+
+ requestRequeue(request);
+
+ /*
+ * Interrupt the dispatcher to return control to the main loop and
+ * activate the fenceTimer.
+ */
+ dispatcher_->interrupt();
+}
+
+/* Callback to signal a fence waiting on the eventfd file descriptor. */
+void FenceTest::signalFence()
+{
+ uint64_t value = 1;
+ int ret;
+
+ ret = write(efd2_, &value, sizeof(value));
+ if (ret != sizeof(value))
+ cerr << "Failed to signal fence" << endl;
+
+ setFence_ = false;
+ dispatcher_->processEvents();
+}
+
+int FenceTest::run()
+{
+ for (const auto &[i, buffer] : utils::enumerate(allocator_->buffers(stream_))) {
+ std::unique_ptr<Request> request = camera_->createRequest(i);
+ if (!request) {
+ cerr << "Failed to create request" << endl;
+ return TestFail;
+ }
+
+ int ret;
+ if (i == expiredRequestId_ - 1) {
+ /* This request will have a fence, and it will expire. */
+ std::unique_ptr<Fence> fence =
+ std::make_unique<Fence>(std::move(eventFd_));
+ if (!fence->isValid()) {
+ cerr << "Fence should be valid" << endl;
+ return TestFail;
+ }
+
+ ret = request->addBuffer(stream_, buffer.get(), std::move(fence));
+ } else {
+ /* All other requests will have no Fence. */
+ ret = request->addBuffer(stream_, buffer.get());
+ }
+
+ if (ret) {
+ cerr << "Failed to associate buffer with request" << endl;
+ return TestFail;
+ }
+
+ requests_.push_back(std::move(request));
+ }
+
+ camera_->requestCompleted.connect(this, &FenceTest::requestComplete);
+
+ if (camera_->start()) {
+ cerr << "Failed to start camera" << endl;
+ return TestFail;
+ }
+
+ for (std::unique_ptr<Request> &request : requests_) {
+ if (camera_->queueRequest(request.get())) {
+ cerr << "Failed to queue request" << endl;
+ return TestFail;
+ }
+ }
+
+ expectedCompletionResult_ = true;
+
+ /* This timer serves to signal fences associated with "signalledRequestId_" */
+ Timer fenceTimer;
+ fenceTimer.timeout.connect(this, &FenceTest::signalFence);
+
+ /*
+ * Loop long enough for all requests to complete, allowing 500ms per
+ * request.
+ */
+ Timer timer;
+ timer.start(500ms * (signalledRequestId_ + 1));
+ while (timer.isRunning() && expectedCompletionResult_ &&
+ completedRequestId_ <= signalledRequestId_ + 1) {
+ if (completedRequestId_ == signalledRequestId_ - 1 && setFence_)
+ /*
+ * The request just before signalledRequestId_ has just
+ * completed. Request signalledRequestId_ has been
+ * queued with a fence, and libcamera is likely already
+ * waiting on the fence, or will soon. Start the timer
+ * to signal the fence in 10 msec.
+ */
+ fenceTimer.start(10ms);
+
+ dispatcher_->processEvents();
+ }
+
+ camera_->requestCompleted.disconnect();
+
+ if (camera_->stop()) {
+ cerr << "Failed to stop camera" << endl;
+ return TestFail;
+ }
+
+ return expectedCompletionResult_ ? TestPass : TestFail;
+}
+
+TEST_REGISTER(FenceTest)
diff --git a/test/file.cpp b/test/file.cpp
new file mode 100644
index 00000000..170e6ccd
--- /dev/null
+++ b/test/file.cpp
@@ -0,0 +1,383 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * File I/O operations tests
+ */
+
+#include <fstream>
+#include <iostream>
+#include <stdlib.h>
+#include <string>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libcamera/base/file.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class FileTest : public Test
+{
+protected:
+ int init()
+ {
+ fileName_ = "/tmp/libcamera.test.XXXXXX";
+ int fd = mkstemp(&fileName_.front());
+ if (fd == -1)
+ return TestFail;
+
+ close(fd);
+ unlink(fileName_.c_str());
+
+ return TestPass;
+ }
+
+ int run()
+ {
+ /* Test static functions. */
+ if (!File::exists("/dev/null")) {
+ cerr << "Valid file not found" << endl;
+ return TestFail;
+ }
+
+ if (File::exists("/dev/null/invalid")) {
+ cerr << "Invalid file should not exist" << endl;
+ return TestFail;
+ }
+
+ if (File::exists("/dev")) {
+ cerr << "Directories should not be treated as files" << endl;
+ return TestFail;
+ }
+
+ /* Test unnamed file. */
+ File file;
+
+ if (!file.fileName().empty()) {
+ cerr << "Unnamed file has non-empty file name" << endl;
+ return TestFail;
+ }
+
+ if (file.exists()) {
+ cerr << "Unnamed file exists" << endl;
+ return TestFail;
+ }
+
+ if (file.isOpen()) {
+ cerr << "File is open after construction" << endl;
+ return TestFail;
+ }
+
+ if (file.openMode() != File::OpenModeFlag::NotOpen) {
+ cerr << "File has invalid open mode after construction"
+ << endl;
+ return TestFail;
+ }
+
+ if (file.size() >= 0) {
+ cerr << "Unnamed file has a size" << endl;
+ return TestFail;
+ }
+
+ if (file.open(File::OpenModeFlag::ReadWrite)) {
+ cerr << "Opening unnamed file succeeded" << endl;
+ return TestFail;
+ }
+
+ if (file.error() == 0) {
+ cerr << "Open failure didn't set error" << endl;
+ return TestFail;
+ }
+
+ /* Test named file referring to an invalid file. */
+ file.setFileName("/dev/null/invalid");
+
+ if (file.fileName() != "/dev/null/invalid") {
+ cerr << "File reports incorrect file name" << endl;
+ return TestFail;
+ }
+
+ if (file.exists()) {
+ cerr << "Invalid file exists" << endl;
+ return TestFail;
+ }
+
+ if (file.isOpen()) {
+ cerr << "Invalid file is open after construction" << endl;
+ return TestFail;
+ }
+
+ if (file.openMode() != File::OpenModeFlag::NotOpen) {
+ cerr << "Invalid file has invalid open mode after construction"
+ << endl;
+ return TestFail;
+ }
+
+ if (file.size() >= 0) {
+ cerr << "Invalid file has a size" << endl;
+ return TestFail;
+ }
+
+ if (file.open(File::OpenModeFlag::ReadWrite)) {
+ cerr << "Opening invalid file succeeded" << endl;
+ return TestFail;
+ }
+
+ /* Test named file referring to a valid file. */
+ file.setFileName("/dev/null");
+
+ if (!file.exists()) {
+ cerr << "Valid file does not exist" << endl;
+ return TestFail;
+ }
+
+ if (file.isOpen()) {
+ cerr << "Valid file is open after construction" << endl;
+ return TestFail;
+ }
+
+ if (file.openMode() != File::OpenModeFlag::NotOpen) {
+ cerr << "Valid file has invalid open mode after construction"
+ << endl;
+ return TestFail;
+ }
+
+ if (file.size() >= 0) {
+ cerr << "Invalid file has a size" << endl;
+ return TestFail;
+ }
+
+ /* Test open and close. */
+ if (!file.open(File::OpenModeFlag::ReadWrite)) {
+ cerr << "Opening file failed" << endl;
+ return TestFail;
+ }
+
+ if (!file.isOpen()) {
+ cerr << "Open file reported as closed" << endl;
+ return TestFail;
+ }
+
+ if (file.openMode() != File::OpenModeFlag::ReadWrite) {
+ cerr << "Open file has invalid open mode" << endl;
+ return TestFail;
+ }
+
+ file.close();
+
+ if (file.isOpen()) {
+ cerr << "Closed file reported as open" << endl;
+ return TestFail;
+ }
+
+ if (file.openMode() != File::OpenModeFlag::NotOpen) {
+ cerr << "Closed file has invalid open mode" << endl;
+ return TestFail;
+ }
+
+ /* Test size(). */
+ file.setFileName(self());
+
+ if (file.size() >= 0) {
+ cerr << "File has valid size before open" << endl;
+ return TestFail;
+ }
+
+ file.open(File::OpenModeFlag::ReadOnly);
+
+ ssize_t size = file.size();
+ if (size <= 0) {
+ cerr << "File has invalid size after open" << endl;
+ return TestFail;
+ }
+
+ file.close();
+
+ /* Test file creation. */
+ file.setFileName(fileName_);
+
+ if (file.exists()) {
+ cerr << "Temporary file already exists" << endl;
+ return TestFail;
+ }
+
+ if (file.open(File::OpenModeFlag::ReadOnly)) {
+ cerr << "Read-only open succeeded on nonexistent file" << endl;
+ return TestFail;
+ }
+
+ if (!file.open(File::OpenModeFlag::WriteOnly)) {
+ cerr << "Write-only open failed on nonexistent file" << endl;
+ return TestFail;
+ }
+
+ if (!file.exists()) {
+ cerr << "Write-only open failed to create file" << endl;
+ return TestFail;
+ }
+
+ file.close();
+
+ /* Test read and write. */
+ std::array<uint8_t, 256> buffer = { 0 };
+
+ strncpy(reinterpret_cast<char *>(buffer.data()), "libcamera",
+ buffer.size());
+
+ if (file.read(buffer) >= 0) {
+ cerr << "Read succeeded on closed file" << endl;
+ return TestFail;
+ }
+
+ if (file.write(buffer) >= 0) {
+ cerr << "Write succeeded on closed file" << endl;
+ return TestFail;
+ }
+
+ file.open(File::OpenModeFlag::ReadOnly);
+
+ if (file.write(buffer) >= 0) {
+ cerr << "Write succeeded on read-only file" << endl;
+ return TestFail;
+ }
+
+ file.close();
+
+ file.open(File::OpenModeFlag::ReadWrite);
+
+ if (file.write({ buffer.data(), 9 }) != 9) {
+ cerr << "Write test failed" << endl;
+ return TestFail;
+ }
+
+ if (file.read(buffer) != 0) {
+ cerr << "Read at end of file test failed" << endl;
+ return TestFail;
+ }
+
+ if (file.seek(0) != 0) {
+ cerr << "Seek test failed" << endl;
+ return TestFail;
+ }
+
+ if (file.read(buffer) != 9) {
+ cerr << "Read test failed" << endl;
+ return TestFail;
+ }
+
+ if (file.pos() != 9) {
+ cerr << "Position test failed" << endl;
+ return TestFail;
+ }
+
+ file.close();
+
+ /* Test mapping and unmapping. */
+ file.setFileName(self());
+ file.open(File::OpenModeFlag::ReadOnly);
+
+ Span<uint8_t> data = file.map();
+ if (data.empty()) {
+ cerr << "Mapping of complete file failed" << endl;
+ return TestFail;
+ }
+
+ if (data.size() != static_cast<size_t>(size)) {
+ cerr << "Mapping of complete file has invalid size" << endl;
+ return TestFail;
+ }
+
+ if (!file.unmap(data.data())) {
+ cerr << "Unmapping of complete file failed" << endl;
+ return TestFail;
+ }
+
+ data = file.map(4096, 8192);
+ if (data.empty()) {
+ cerr << "Mapping of file region failed" << endl;
+ return TestFail;
+ }
+
+ if (data.size() != 8192) {
+ cerr << "Mapping of file region has invalid size" << endl;
+ return TestFail;
+ }
+
+ if (!file.unmap(data.data())) {
+ cerr << "Unmapping of file region failed" << endl;
+ return TestFail;
+ }
+
+ file.close();
+
+ /* Test private mapping. */
+ file.setFileName(fileName_);
+ file.open(File::OpenModeFlag::ReadWrite);
+
+ data = file.map(0, -1, File::MapFlag::Private);
+ if (data.empty()) {
+ cerr << "Private mapping failed" << endl;
+ return TestFail;
+ }
+
+ std::string str{ reinterpret_cast<char *>(data.data()), data.size() };
+ if (str != "libcamera") {
+ cerr << "Invalid contents of private mapping" << endl;
+ return TestFail;
+ }
+
+ memcpy(data.data(), "LIBCAMERA", 9);
+
+ if (!file.unmap(data.data())) {
+ cerr << "Private unmapping failed" << endl;
+ return TestFail;
+ }
+
+ data = file.map();
+
+ str = { reinterpret_cast<char *>(data.data()), data.size() };
+ if (str != "libcamera") {
+ cerr << "Private mapping changed file contents" << endl;
+ return TestFail;
+ }
+
+ /* Test shared mapping. */
+ data = file.map();
+ if (data.empty()) {
+ cerr << "Shared mapping failed" << endl;
+ return TestFail;
+ }
+
+ memcpy(data.data(), "LIBCAMERA", 9);
+
+ if (!file.unmap(data.data())) {
+ cerr << "Shared unmapping failed" << endl;
+ return TestFail;
+ }
+
+ data = file.map();
+
+ str = { reinterpret_cast<char *>(data.data()), data.size() };
+ if (str != "LIBCAMERA") {
+ cerr << "Shared mapping failed to change file contents"
+ << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ void cleanup()
+ {
+ unlink(fileName_.c_str());
+ }
+
+private:
+ std::string fileName_;
+};
+
+TEST_REGISTER(FileTest)
diff --git a/test/flags.cpp b/test/flags.cpp
new file mode 100644
index 00000000..85c34788
--- /dev/null
+++ b/test/flags.cpp
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * Flags tests
+ */
+
+#include <iostream>
+
+#include <libcamera/base/flags.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class FlagsTest : public Test
+{
+protected:
+ enum class Option {
+ First = (1 << 0),
+ Second = (1 << 1),
+ Third = (1 << 2),
+ };
+
+ using Options = Flags<Option>;
+
+ enum class Mode {
+ Alpha = (1 << 0),
+ Beta = (1 << 1),
+ Gamma = (1 << 2),
+ };
+
+ using Modes = Flags<Mode>;
+
+ int run() override;
+};
+
+namespace libcamera {
+
+LIBCAMERA_FLAGS_ENABLE_OPERATORS(FlagsTest::Option)
+
+} /* namespace libcamera */
+
+int FlagsTest::run()
+{
+ /* Commented-out constructs are expected not to compile. */
+
+ /* Flags<int> f; */
+
+ /*
+ * Unary operators with enum argument.
+ */
+
+ Options options;
+
+ if (options) {
+ cerr << "Default-constructed Flags<> is not zero" << endl;
+ return TestFail;
+ }
+
+ options |= Option::First;
+ if (!options || options != Option::First) {
+ cerr << "Unary bitwise OR with enum failed" << endl;
+ return TestFail;
+ }
+
+ /* options &= Mode::Alpha; */
+ /* options |= Mode::Beta; */
+ /* options ^= Mode::Gamma; */
+
+ options &= ~Option::First;
+ if (options) {
+ cerr << "Unary bitwise AND with enum failed" << endl;
+ return TestFail;
+ }
+
+ options ^= Option::Second;
+ if (options != Option::Second) {
+ cerr << "Unary bitwise XOR with enum failed" << endl;
+ return TestFail;
+ }
+
+ options = Options();
+
+ /*
+ * Unary operators with Flags argument.
+ */
+
+ options |= Options(Option::First);
+ if (options != Option::First) {
+ cerr << "Unary bitwise OR with Flags<> failed" << endl;
+ return TestFail;
+ }
+
+ /* options &= Options(Mode::Alpha); */
+ /* options |= Options(Mode::Beta); */
+ /* options ^= Options(Mode::Gamma); */
+
+ options &= ~Options(Option::First);
+ if (options) {
+ cerr << "Unary bitwise AND with Flags<> failed" << endl;
+ return TestFail;
+ }
+
+ options ^= Options(Option::Second);
+ if (options != Option::Second) {
+ cerr << "Unary bitwise XOR with Flags<> failed" << endl;
+ return TestFail;
+ }
+
+ options = Options();
+
+ /*
+ * Binary operators with enum argument.
+ */
+
+ options = options | Option::First;
+ if (!(options & Option::First)) {
+ cerr << "Binary bitwise OR with enum failed" << endl;
+ return TestFail;
+ }
+
+ /* options = options & Mode::Alpha; */
+ /* options = options | Mode::Beta; */
+ /* options = options ^ Mode::Gamma; */
+
+ options = options & ~Option::First;
+ if (options != (Option::First & Option::Second)) {
+ cerr << "Binary bitwise AND with enum failed" << endl;
+ return TestFail;
+ }
+
+ options = options ^ (Option::First ^ Option::Second);
+ if (options != (Option::First | Option::Second)) {
+ cerr << "Binary bitwise XOR with enum failed" << endl;
+ return TestFail;
+ }
+
+ options = Options();
+
+ /*
+ * Binary operators with Flags argument.
+ */
+
+ options |= Options(Option::First);
+ if (!(options & Option::First)) {
+ cerr << "Binary bitwise OR with Flags<> failed" << endl;
+ return TestFail;
+ }
+
+ /* options = options & Options(Mode::Alpha); */
+ /* options = options | Options(Mode::Beta); */
+ /* options = options ^ Options(Mode::Gamma); */
+
+ options = options & ~Options(Option::First);
+ if (options) {
+ cerr << "Binary bitwise AND with Flags<> failed" << endl;
+ return TestFail;
+ }
+
+ options = options ^ Options(Option::Second);
+ if (options != Option::Second) {
+ cerr << "Binary bitwise XOR with Flags<> failed" << endl;
+ return TestFail;
+ }
+
+ options = Options();
+
+ /*
+ * Conversion operators.
+ */
+
+ options |= Option::First | Option::Second | Option::Third;
+ if (static_cast<Options::Type>(options) != 7) {
+ cerr << "Cast to underlying type failed" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Conversion of the result of ninary operators on the underlying enum.
+ */
+
+ /* unsigned int val1 = Option::First; */
+ /* unsigned int val2 = ~Option::First; */
+ /* unsigned int val3 = Option::First | Option::Second; */
+ /* Option val4 = ~Option::First; */
+ /* Option val5 = Option::First | Option::Second; */
+
+ return TestPass;
+}
+
+TEST_REGISTER(FlagsTest)
diff --git a/test/geometry.cpp b/test/geometry.cpp
index 27e65565..11df043b 100644
--- a/test/geometry.cpp
+++ b/test/geometry.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * geometry.cpp - Geometry classes tests
+ * Geometry classes tests
*/
#include <iostream>
@@ -17,16 +17,15 @@ using namespace libcamera;
class GeometryTest : public Test
{
protected:
- bool compare(const Size &lhs, const Size &rhs,
- bool (*op)(const Size &lhs, const Size &rhs),
+ template<typename T>
+ bool compare(const T &lhs, const T &rhs,
+ bool (*op)(const T &lhs, const T &rhs),
const char *opName, bool expect)
{
bool result = op(lhs, rhs);
if (result != expect) {
- cout << "Size(" << lhs.width << ", " << lhs.height << ") "
- << opName << " "
- << "Size(" << rhs.width << ", " << rhs.height << ") "
+ cout << lhs << opName << " " << rhs
<< "test failed" << std::endl;
return false;
}
@@ -36,6 +35,243 @@ protected:
int run()
{
+ /*
+ * Point tests
+ */
+
+ /* Equality */
+ if (!compare(Point(50, 100), Point(50, 100), &operator==, "==", true))
+ return TestFail;
+
+ if (!compare(Point(-50, 100), Point(-50, 100), &operator==, "==", true))
+ return TestFail;
+
+ if (!compare(Point(50, -100), Point(50, -100), &operator==, "==", true))
+ return TestFail;
+
+ if (!compare(Point(-50, -100), Point(-50, -100), &operator==, "==", true))
+ return TestFail;
+
+ /* Inequality */
+ if (!compare(Point(50, 100), Point(50, 100), &operator!=, "!=", false))
+ return TestFail;
+
+ if (!compare(Point(-50, 100), Point(-50, 100), &operator!=, "!=", false))
+ return TestFail;
+
+ if (!compare(Point(50, -100), Point(50, -100), &operator!=, "!=", false))
+ return TestFail;
+
+ if (!compare(Point(-50, -100), Point(-50, -100), &operator!=, "!=", false))
+ return TestFail;
+
+ if (!compare(Point(-50, 100), Point(50, 100), &operator!=, "!=", true))
+ return TestFail;
+
+ if (!compare(Point(50, -100), Point(50, 100), &operator!=, "!=", true))
+ return TestFail;
+
+ if (!compare(Point(-50, -100), Point(50, 100), &operator!=, "!=", true))
+ return TestFail;
+
+ /* Negation */
+ if (Point(50, 100) != -Point(-50, -100) ||
+ Point(50, 100) == -Point(50, -100) ||
+ Point(50, 100) == -Point(-50, 100)) {
+ cout << "Point negation test failed" << endl;
+ return TestFail;
+ }
+
+ /* Default constructor */
+ if (Point() != Point(0, 0)) {
+ cout << "Default constructor test failed" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Size tests
+ */
+
+ if (!Size().isNull() || !Size(0, 0).isNull()) {
+ cout << "Null size incorrectly reported as not null" << endl;
+ return TestFail;
+ }
+
+ if (Size(0, 100).isNull() || Size(100, 0).isNull() || Size(100, 100).isNull()) {
+ cout << "Non-null size incorrectly reported as null" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Test alignDownTo(), alignUpTo(), boundTo(), expandTo(),
+ * growBy() and shrinkBy()
+ */
+ Size s(50, 50);
+
+ s.alignDownTo(16, 16);
+ if (s != Size(48, 48)) {
+ cout << "Size::alignDownTo() test failed" << endl;
+ return TestFail;
+ }
+
+ s.alignUpTo(32, 32);
+ if (s != Size(64, 64)) {
+ cout << "Size::alignUpTo() test failed" << endl;
+ return TestFail;
+ }
+
+ s.boundTo({ 40, 40 });
+ if (s != Size(40, 40)) {
+ cout << "Size::boundTo() test failed" << endl;
+ return TestFail;
+ }
+
+ s.expandTo({ 50, 50 });
+ if (s != Size(50, 50)) {
+ cout << "Size::expandTo() test failed" << endl;
+ return TestFail;
+ }
+
+ s.growBy({ 10, 20 });
+ if (s != Size(60, 70)) {
+ cout << "Size::growBy() test failed" << endl;
+ return TestFail;
+ }
+
+ s.shrinkBy({ 20, 10 });
+ if (s != Size(40, 60)) {
+ cout << "Size::shrinkBy() test failed" << endl;
+ return TestFail;
+ }
+
+ s.shrinkBy({ 100, 100 });
+ if (s != Size(0, 0)) {
+ cout << "Size::shrinkBy() clamp test failed" << endl;
+ return TestFail;
+ }
+
+ s = Size(50,50).alignDownTo(16, 16).alignUpTo(32, 32)
+ .boundTo({ 40, 80 }).expandTo({ 16, 80 })
+ .growBy({ 4, 4 }).shrinkBy({ 10, 20 });
+ if (s != Size(34, 64)) {
+ cout << "Size chained in-place modifiers test failed" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Test alignedDownTo(), alignedUpTo(), boundedTo(),
+ * expandedTo(), grownBy() and shrunkBy()
+ */
+ if (Size(0, 0).alignedDownTo(16, 8) != Size(0, 0) ||
+ Size(1, 1).alignedDownTo(16, 8) != Size(0, 0) ||
+ Size(16, 8).alignedDownTo(16, 8) != Size(16, 8)) {
+ cout << "Size::alignedDownTo() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Size(0, 0).alignedUpTo(16, 8) != Size(0, 0) ||
+ Size(1, 1).alignedUpTo(16, 8) != Size(16, 8) ||
+ Size(16, 8).alignedUpTo(16, 8) != Size(16, 8)) {
+ cout << "Size::alignedUpTo() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Size(0, 0).boundedTo({ 100, 100 }) != Size(0, 0) ||
+ Size(200, 50).boundedTo({ 100, 100 }) != Size(100, 50) ||
+ Size(50, 200).boundedTo({ 100, 100 }) != Size(50, 100)) {
+ cout << "Size::boundedTo() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Size(0, 0).expandedTo({ 100, 100 }) != Size(100, 100) ||
+ Size(200, 50).expandedTo({ 100, 100 }) != Size(200, 100) ||
+ Size(50, 200).expandedTo({ 100, 100 }) != Size(100, 200)) {
+ cout << "Size::expandedTo() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Size(0, 0).grownBy({ 10, 20 }) != Size(10, 20) ||
+ Size(200, 50).grownBy({ 10, 20 }) != Size(210, 70)) {
+ cout << "Size::grownBy() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Size(200, 50).shrunkBy({ 10, 20 }) != Size(190, 30) ||
+ Size(200, 50).shrunkBy({ 10, 100 }) != Size(190, 0) ||
+ Size(200, 50).shrunkBy({ 300, 20 }) != Size(0, 30)) {
+ cout << "Size::shrunkBy() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Aspect ratio tests */
+ if (Size(0, 0).boundedToAspectRatio(Size(4, 3)) != Size(0, 0) ||
+ Size(1920, 1440).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) ||
+ Size(1920, 1440).boundedToAspectRatio(Size(65536, 36864)) != Size(1920, 1080) ||
+ Size(1440, 1920).boundedToAspectRatio(Size(9, 16)) != Size(1080, 1920) ||
+ Size(1920, 1080).boundedToAspectRatio(Size(4, 3)) != Size(1440, 1080) ||
+ Size(1920, 1080).boundedToAspectRatio(Size(65536, 49152)) != Size(1440, 1080) ||
+ Size(1024, 1024).boundedToAspectRatio(Size(1, 1)) != Size(1024, 1024) ||
+ Size(1920, 1080).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) ||
+ Size(200, 100).boundedToAspectRatio(Size(16, 9)) != Size(177, 100) ||
+ Size(300, 200).boundedToAspectRatio(Size(16, 9)) != Size(300, 168)) {
+ cout << "Size::boundedToAspectRatio() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Size(0, 0).expandedToAspectRatio(Size(4, 3)) != Size(0, 0) ||
+ Size(1920, 1440).expandedToAspectRatio(Size(16, 9)) != Size(2560, 1440) ||
+ Size(1920, 1440).expandedToAspectRatio(Size(65536, 36864)) != Size(2560, 1440) ||
+ Size(1440, 1920).expandedToAspectRatio(Size(9, 16)) != Size(1440, 2560) ||
+ Size(1920, 1080).expandedToAspectRatio(Size(4, 3)) != Size(1920, 1440) ||
+ Size(1920, 1080).expandedToAspectRatio(Size(65536, 49152)) != Size(1920, 1440) ||
+ Size(1024, 1024).expandedToAspectRatio(Size(1, 1)) != Size(1024, 1024) ||
+ Size(1920, 1080).expandedToAspectRatio(Size(16, 9)) != Size(1920, 1080) ||
+ Size(200, 100).expandedToAspectRatio(Size(16, 9)) != Size(200, 112) ||
+ Size(300, 200).expandedToAspectRatio(Size(16, 9)) != Size(355, 200)) {
+ cout << "Size::expandedToAspectRatio() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Size::centeredTo() tests */
+ if (Size(0, 0).centeredTo(Point(50, 100)) != Rectangle(50, 100, 0, 0) ||
+ Size(0, 0).centeredTo(Point(-50, -100)) != Rectangle(-50, -100, 0, 0) ||
+ Size(100, 200).centeredTo(Point(50, 100)) != Rectangle(0, 0, 100, 200) ||
+ Size(100, 200).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 100, 200) ||
+ Size(101, 201).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 101, 201) ||
+ Size(101, 201).centeredTo(Point(-51, -101)) != Rectangle(-101, -201, 101, 201)) {
+ cout << "Size::centeredTo() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Scale a size by a float */
+ if (Size(1000, 2000) * 2.0 != Size(2000, 4000) ||
+ Size(300, 100) * 0.5 != Size(150, 50) ||
+ Size(1, 2) * 1.6 != Size(1, 3)) {
+ cout << "Size::operator*() failed" << endl;
+ return TestFail;
+ }
+
+ if (Size(1000, 2000) / 2.0 != Size(500, 1000) ||
+ Size(300, 100) / 0.5 != Size(600, 200) ||
+ Size(1000, 2000) / 3.0 != Size(333, 666)) {
+ cout << "Size::operator*() failed" << endl;
+ return TestFail;
+ }
+
+ s = Size(300, 100);
+ s *= 0.3333;
+ if (s != Size(99, 33)) {
+ cout << "Size::operator*() test failed" << endl;
+ return TestFail;
+ }
+
+ s = Size(300, 100);
+ s /= 3;
+ if (s != Size(100, 33)) {
+ cout << "Size::operator*() test failed" << endl;
+ return TestFail;
+ }
+
/* Test Size equality and inequality. */
if (!compare(Size(100, 100), Size(100, 100), &operator==, "==", true))
return TestFail;
@@ -109,6 +345,167 @@ protected:
if (!compare(Size(200, 100), Size(100, 200), &operator>=, ">=", true))
return TestFail;
+ /*
+ * Rectangle tests
+ */
+
+ /* Test Rectangle::isNull(). */
+ if (!Rectangle(0, 0, 0, 0).isNull() ||
+ !Rectangle(1, 1, 0, 0).isNull()) {
+ cout << "Null rectangle incorrectly reported as not null" << endl;
+ return TestFail;
+ }
+
+ if (Rectangle(0, 0, 0, 1).isNull() ||
+ Rectangle(0, 0, 1, 0).isNull() ||
+ Rectangle(0, 0, 1, 1).isNull()) {
+ cout << "Non-null rectangle incorrectly reported as null" << endl;
+ return TestFail;
+ }
+
+ /* Rectangle::size(), Rectangle::topLeft() and Rectangle::center() tests */
+ if (Rectangle(-1, -2, 3, 4).size() != Size(3, 4) ||
+ Rectangle(0, 0, 100000, 200000).size() != Size(100000, 200000)) {
+ cout << "Rectangle::size() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Rectangle(1, 2, 3, 4).topLeft() != Point(1, 2) ||
+ Rectangle(-1, -2, 3, 4).topLeft() != Point(-1, -2)) {
+ cout << "Rectangle::topLeft() test failed" << endl;
+ return TestFail;
+ }
+
+ if (Rectangle(0, 0, 300, 400).center() != Point(150, 200) ||
+ Rectangle(-1000, -2000, 300, 400).center() != Point(-850, -1800) ||
+ Rectangle(10, 20, 301, 401).center() != Point(160, 220) ||
+ Rectangle(11, 21, 301, 401).center() != Point(161, 221) ||
+ Rectangle(-1011, -2021, 301, 401).center() != Point(-861, -1821)) {
+ cout << "Rectangle::center() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Rectangle::boundedTo() (intersection function) */
+ if (Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
+ Rectangle(0, 0, 1000, 2000) ||
+ Rectangle(-500, -1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
+ Rectangle(0, 0, 500, 1000) ||
+ Rectangle(500, 1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
+ Rectangle(500, 1000, 500, 1000) ||
+ Rectangle(300, 400, 50, 100).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
+ Rectangle(300, 400, 50, 100) ||
+ Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(300, 400, 50, 100)) !=
+ Rectangle(300, 400, 50, 100) ||
+ Rectangle(0, 0, 100, 100).boundedTo(Rectangle(50, 100, 100, 100)) !=
+ Rectangle(50, 100, 50, 0) ||
+ Rectangle(0, 0, 100, 100).boundedTo(Rectangle(100, 50, 100, 100)) !=
+ Rectangle(100, 50, 0, 50) ||
+ Rectangle(-10, -20, 10, 20).boundedTo(Rectangle(10, 20, 100, 100)) !=
+ Rectangle(10, 20, 0, 0)) {
+ cout << "Rectangle::boundedTo() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Rectangle::enclosedIn() tests */
+ if (Rectangle(10, 20, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
+ Rectangle(10, 20, 300, 400) ||
+ Rectangle(-100, -200, 3000, 4000).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
+ Rectangle(-10, -20, 1300, 1400) ||
+ Rectangle(-100, -200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
+ Rectangle(-10, -20, 300, 400) ||
+ Rectangle(5100, 6200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
+ Rectangle(990, 980, 300, 400) ||
+ Rectangle(100, -300, 150, 200).enclosedIn(Rectangle(50, 0, 200, 300)) !=
+ Rectangle(100, 0, 150, 200) ||
+ Rectangle(100, -300, 150, 1200).enclosedIn(Rectangle(50, 0, 200, 300)) !=
+ Rectangle(100, 0, 150, 300) ||
+ Rectangle(-300, 100, 200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) !=
+ Rectangle(0, 100, 200, 150) ||
+ Rectangle(-300, 100, 1200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) !=
+ Rectangle(0, 100, 300, 150)) {
+ cout << "Rectangle::enclosedIn() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Rectange::scaledBy() tests */
+ if (Rectangle(10, 20, 300, 400).scaledBy(Size(0, 0), Size(1, 1)) !=
+ Rectangle(0, 0, 0, 0) ||
+ Rectangle(10, -20, 300, 400).scaledBy(Size(32768, 65536), Size(32768, 32768)) !=
+ Rectangle(10, -40, 300, 800) ||
+ Rectangle(-30000, 10000, 20000, 20000).scaledBy(Size(7, 7), Size(7, 7)) !=
+ Rectangle(-30000, 10000, 20000, 20000) ||
+ Rectangle(-20, -30, 320, 240).scaledBy(Size(1280, 960), Size(640, 480)) !=
+ Rectangle(-40, -60, 640, 480) ||
+ Rectangle(1, 1, 2026, 1510).scaledBy(Size(4056, 3024), Size(2028, 1512)) !=
+ Rectangle(2, 2, 4052, 3020)) {
+ cout << "Rectangle::scaledBy() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Rectangle::translatedBy() tests */
+ if (Rectangle(10, -20, 300, 400).translatedBy(Point(-30, 40)) !=
+ Rectangle(-20, 20, 300, 400) ||
+ Rectangle(-10, 20, 400, 300).translatedBy(Point(50, -60)) !=
+ Rectangle(40, -40, 400, 300)) {
+ cout << "Rectangle::translatedBy() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Rectangle::scaleBy() tests */
+ Rectangle r(-20, -30, 320, 240);
+ r.scaleBy(Size(1280, 960), Size(640, 480));
+ if (r != Rectangle(-40, -60, 640, 480)) {
+ cout << "Rectangle::scaleBy() test failed" << endl;
+ return TestFail;
+ }
+
+ r = Rectangle(1, 1, 2026, 1510);
+ r.scaleBy(Size(4056, 3024), Size(2028, 1512));
+ if (r != Rectangle(2, 2, 4052, 3020)) {
+ cout << "Rectangle::scaleBy() test failed" << endl;
+ return TestFail;
+ }
+
+ /* Rectangle::translateBy() tests */
+ r = Rectangle(10, -20, 300, 400);
+ r.translateBy(Point(-30, 40));
+ if (r != Rectangle(-20, 20, 300, 400)) {
+ cout << "Rectangle::translateBy() test failed" << endl;
+ return TestFail;
+ }
+
+ r = Rectangle(-10, 20, 400, 300);
+ r.translateBy(Point(50, -60));
+ if (r != Rectangle(40, -40, 400, 300)) {
+ cout << "Rectangle::translateBy() test failed" << endl;
+ return TestFail;
+ }
+
+ Point topLeft(3, 3);
+ Point bottomRight(30, 30);
+ Point topRight(30, 3);
+ Point bottomLeft(3, 30);
+ Rectangle rect1(topLeft, bottomRight);
+ Rectangle rect2(topRight, bottomLeft);
+ Rectangle rect3(bottomRight, topLeft);
+ Rectangle rect4(bottomLeft, topRight);
+
+ if (rect1 != rect2 || rect1 != rect3 || rect1 != rect4) {
+ cout << "Point-to-point construction failed" << endl;
+ return TestFail;
+ }
+
+ Rectangle f1 = Rectangle(100, 200, 3000, 2000);
+ Rectangle f2 = Rectangle(200, 300, 1500, 1000);
+ /* Bottom right quarter of the corresponding frames. */
+ Rectangle r1 = Rectangle(100 + 1500, 200 + 1000, 1500, 1000);
+ Rectangle r2 = Rectangle(200 + 750, 300 + 500, 750, 500);
+ if (r1.transformedBetween(f1, f2) != r2 ||
+ r2.transformedBetween(f2, f1) != r1) {
+ cout << "Rectangle::transformedBetween() test failed" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
};
diff --git a/test/gstreamer/gstreamer_device_provider_test.cpp b/test/gstreamer/gstreamer_device_provider_test.cpp
new file mode 100644
index 00000000..521c60b8
--- /dev/null
+++ b/test/gstreamer/gstreamer_device_provider_test.cpp
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023, Umang Jain <umang.jain@ideasonboard.com>
+ *
+ * GStreamer single stream capture test
+ */
+
+#include <vector>
+
+#include <libcamera/libcamera.h>
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+using namespace std;
+
+class GstreamerDeviceProviderTest : public GstreamerTest, public Test
+{
+public:
+ GstreamerDeviceProviderTest()
+ : GstreamerTest()
+ {
+ }
+
+protected:
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ return TestPass;
+ }
+
+ int run() override
+ {
+ g_autoptr(GstDeviceProvider) provider = NULL;
+ GList *devices, *l;
+ std::vector<std::string> cameraNames;
+ std::unique_ptr<libcamera::CameraManager> cm;
+
+ cm = std::make_unique<libcamera::CameraManager>();
+ cm->start();
+ for (auto &camera : cm->cameras())
+ cameraNames.push_back(camera->id());
+ cm->stop();
+ cm.reset();
+
+ provider = gst_device_provider_factory_get_by_name("libcameraprovider");
+ devices = gst_device_provider_get_devices(provider);
+
+ for (l = devices; l != NULL; l = g_list_next(l)) {
+ GstDevice *device = GST_DEVICE(l->data);
+ g_autofree gchar *gst_name;
+
+ g_autoptr(GstElement) element = gst_device_create_element(device, NULL);
+ g_object_get(element, "camera-name", &gst_name, NULL);
+
+ if (std::find(cameraNames.begin(), cameraNames.end(), gst_name) ==
+ cameraNames.end())
+ return TestFail;
+ }
+
+ g_list_free_full(devices, (GDestroyNotify)gst_object_unref);
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(GstreamerDeviceProviderTest)
diff --git a/test/gstreamer/gstreamer_memory_lifetime_test.cpp b/test/gstreamer/gstreamer_memory_lifetime_test.cpp
new file mode 100644
index 00000000..1738cf56
--- /dev/null
+++ b/test/gstreamer/gstreamer_memory_lifetime_test.cpp
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Nicolas Dufresne
+ *
+ * gstreamer_memory_lifetime_test.cpp - GStreamer memory lifetime test
+ */
+
+#include <iostream>
+#include <unistd.h>
+
+#include <gst/app/app.h>
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+using namespace std;
+
+class GstreamerMemoryLifetimeTest : public GstreamerTest, public Test
+{
+public:
+ GstreamerMemoryLifetimeTest()
+ : GstreamerTest()
+ {
+ }
+
+protected:
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ appsink_ = gst_element_factory_make("appsink", nullptr);
+ if (!appsink_) {
+ g_printerr("Your installation is missing 'appsink'\n");
+ return TestFail;
+ }
+ g_object_ref_sink(appsink_);
+
+ return createPipeline();
+ }
+
+ int run() override
+ {
+ /* Build the pipeline */
+ gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, appsink_, nullptr);
+ if (gst_element_link(libcameraSrc_, appsink_) != TRUE) {
+ g_printerr("Elements could not be linked.\n");
+ return TestFail;
+ }
+
+ if (startPipeline() != TestPass)
+ return TestFail;
+
+ sample_ = gst_app_sink_try_pull_sample(GST_APP_SINK(appsink_), GST_SECOND * 5);
+ if (!sample_) {
+ /* Failed to obtain a sample. Abort the test */
+ gst_element_set_state(pipeline_, GST_STATE_NULL);
+ return TestFail;
+ }
+
+ /*
+ * Keep the sample referenced and set the pipeline state to
+ * NULL. This causes the libcamerasrc element to synchronously
+ * release resources it holds. The sample will be released
+ * later in cleanup().
+ *
+ * The test case verifies that libcamerasrc keeps alive long
+ * enough all the resources that are needed until memory
+ * allocated for frames gets freed. We return TestPass at this
+ * stage, and any use-after-free will be caught by the test
+ * crashing in cleanup().
+ */
+ gst_element_set_state(pipeline_, GST_STATE_NULL);
+
+ return TestPass;
+ }
+
+ void cleanup() override
+ {
+ g_clear_pointer(&sample_, gst_sample_unref);
+ g_clear_object(&appsink_);
+ }
+
+private:
+ GstElement *appsink_;
+ GstSample *sample_;
+};
+
+TEST_REGISTER(GstreamerMemoryLifetimeTest)
diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp b/test/gstreamer/gstreamer_multi_stream_test.cpp
new file mode 100644
index 00000000..263d1e86
--- /dev/null
+++ b/test/gstreamer/gstreamer_multi_stream_test.cpp
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Vedant Paranjape
+ *
+ * GStreamer multi stream capture test
+ */
+
+#include <iostream>
+#include <unistd.h>
+
+#include <libcamera/libcamera.h>
+
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+#if !GST_CHECK_VERSION(1, 19, 1)
+static inline GstPad *gst_element_request_pad_simple(GstElement *element,
+ const gchar *name)
+{
+ return gst_element_get_request_pad(element, name);
+}
+#endif
+
+using namespace std;
+
+class GstreamerMultiStreamTest : public GstreamerTest, public Test
+{
+public:
+ GstreamerMultiStreamTest()
+ : GstreamerTest(2)
+ {
+ }
+
+protected:
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ const gchar *streamDescription = "queue ! fakesink";
+ g_autoptr(GError) error = NULL;
+
+ stream0_ = gst_parse_bin_from_description_full(streamDescription, TRUE,
+ NULL,
+ GST_PARSE_FLAG_FATAL_ERRORS,
+ &error);
+ if (!stream0_) {
+ g_printerr("Stream0 could not be created (%s)\n", error->message);
+ return TestFail;
+ }
+ g_object_ref_sink(stream0_);
+
+ stream1_ = gst_parse_bin_from_description_full(streamDescription, TRUE,
+ NULL,
+ GST_PARSE_FLAG_FATAL_ERRORS,
+ &error);
+ if (!stream1_) {
+ g_printerr("Stream1 could not be created (%s)\n", error->message);
+ return TestFail;
+ }
+ g_object_ref_sink(stream1_);
+
+ if (createPipeline() != TestPass)
+ return TestFail;
+
+ return TestPass;
+ }
+
+ int run() override
+ {
+ /* Build the pipeline */
+ gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
+ stream0_, stream1_, NULL);
+
+ g_autoptr(GstPad) src_pad = gst_element_get_static_pad(libcameraSrc_, "src");
+ g_autoptr(GstPad) request_pad = gst_element_request_pad_simple(libcameraSrc_, "src_%u");
+
+ {
+ g_autoptr(GstPad) queue0_sink_pad = gst_element_get_static_pad(stream0_, "sink");
+ g_autoptr(GstPad) queue1_sink_pad = gst_element_get_static_pad(stream1_, "sink");
+
+ if (gst_pad_link(src_pad, queue0_sink_pad) != GST_PAD_LINK_OK ||
+ gst_pad_link(request_pad, queue1_sink_pad) != GST_PAD_LINK_OK) {
+ g_printerr("Pads could not be linked.\n");
+ return TestFail;
+ }
+ }
+
+ if (startPipeline() != TestPass)
+ return TestFail;
+
+ if (processEvent() != TestPass)
+ return TestFail;
+
+ return TestPass;
+ }
+
+ void cleanup() override
+ {
+ g_clear_object(&stream0_);
+ g_clear_object(&stream1_);
+ }
+
+private:
+ GstElement *stream0_;
+ GstElement *stream1_;
+};
+
+TEST_REGISTER(GstreamerMultiStreamTest)
diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
new file mode 100644
index 00000000..3ef2d323
--- /dev/null
+++ b/test/gstreamer/gstreamer_single_stream_test.cpp
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Vedant Paranjape
+ *
+ * GStreamer single stream capture test
+ */
+
+#include <iostream>
+#include <unistd.h>
+
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+using namespace std;
+
+class GstreamerSingleStreamTest : public GstreamerTest, public Test
+{
+public:
+ GstreamerSingleStreamTest()
+ : GstreamerTest()
+ {
+ }
+
+protected:
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ fakesink_ = gst_element_factory_make("fakesink", nullptr);
+ if (!fakesink_) {
+ g_printerr("Your installation is missing 'fakesink'\n");
+ return TestFail;
+ }
+ g_object_ref_sink(fakesink_);
+
+ return createPipeline();
+ }
+
+ int run() override
+ {
+ /* Build the pipeline */
+ gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, fakesink_, nullptr);
+ if (!gst_element_link(libcameraSrc_, fakesink_)) {
+ g_printerr("Elements could not be linked.\n");
+ return TestFail;
+ }
+
+ if (startPipeline() != TestPass)
+ return TestFail;
+
+ if (processEvent() != TestPass)
+ return TestFail;
+
+ return TestPass;
+ }
+
+ void cleanup() override
+ {
+ g_clear_object(&fakesink_);
+ }
+
+private:
+ GstElement *fakesink_;
+};
+
+TEST_REGISTER(GstreamerSingleStreamTest)
diff --git a/test/gstreamer/gstreamer_test.cpp b/test/gstreamer/gstreamer_test.cpp
new file mode 100644
index 00000000..a15fef0e
--- /dev/null
+++ b/test/gstreamer/gstreamer_test.cpp
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Vedant Paranjape
+ *
+ * libcamera Gstreamer element API tests
+ */
+
+#include <libcamera/libcamera.h>
+
+#include <libcamera/base/utils.h>
+
+#if HAVE_ASAN
+#include <sanitizer/asan_interface.h>
+#endif
+
+#include "gstreamer_test.h"
+
+#include "test.h"
+
+using namespace std;
+
+#if HAVE_ASAN
+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";
+}
+}
+#endif
+
+GstreamerTest::GstreamerTest(unsigned int numStreams)
+ : pipeline_(nullptr), libcameraSrc_(nullptr)
+{
+ /*
+ * GStreamer by default 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 causes 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. Fix this issue by disabling
+ * spawning of a child helper process when scanning the build directory
+ * for plugins.
+ */
+ gst_registry_fork_set_enabled(false);
+
+ /* Initialize GStreamer */
+ 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");
+
+ status_ = TestFail;
+ return;
+ }
+
+ /*
+ * Atleast one camera should be available with numStreams streams,
+ * otherwise skip the test entirely.
+ */
+ if (!checkMinCameraStreamsAndSetCameraName(numStreams)) {
+ status_ = TestSkip;
+ return;
+ }
+
+ status_ = TestPass;
+}
+
+bool GstreamerTest::checkMinCameraStreamsAndSetCameraName(unsigned int numStreams)
+{
+ libcamera::CameraManager cm;
+ bool cameraFound = false;
+
+ cm.start();
+
+ for (auto &camera : cm.cameras()) {
+ if (camera->streams().size() < numStreams)
+ continue;
+
+ cameraFound = true;
+ cameraName_ = camera->id();
+ break;
+ }
+
+ cm.stop();
+
+ return cameraFound;
+}
+
+GstreamerTest::~GstreamerTest()
+{
+ g_clear_object(&pipeline_);
+ g_clear_object(&libcameraSrc_);
+
+ gst_deinit();
+}
+
+int GstreamerTest::createPipeline()
+{
+ libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
+ pipeline_ = gst_pipeline_new("test-pipeline");
+
+ if (!libcameraSrc_ || !pipeline_) {
+ g_printerr("Unable to create pipeline %p.%p\n",
+ libcameraSrc_, pipeline_);
+
+ return TestFail;
+ }
+
+ g_object_set(libcameraSrc_, "camera-name", cameraName_.c_str(), NULL);
+ g_object_ref_sink(libcameraSrc_);
+
+ return TestPass;
+}
+
+int GstreamerTest::startPipeline()
+{
+ GstStateChangeReturn ret;
+
+ /* 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;
+ }
+
+ return TestPass;
+}
+
+int GstreamerTest::processEvent()
+{
+ /* 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 = 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;
+
+ switch (GST_MESSAGE_TYPE(msg)) {
+ case GST_MESSAGE_ERROR:
+ printError(msg);
+ break;
+ case GST_MESSAGE_EOS:
+ g_print("End-Of-Stream reached.\n");
+ break;
+ default:
+ g_printerr("Unexpected message received.\n");
+ break;
+ }
+
+ return TestFail;
+}
+
+void GstreamerTest::printError(GstMessage *msg)
+{
+ g_autoptr(GError) err = NULL;
+ g_autofree gchar *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");
+}
diff --git a/test/gstreamer/gstreamer_test.h b/test/gstreamer/gstreamer_test.h
new file mode 100644
index 00000000..abb37c1b
--- /dev/null
+++ b/test/gstreamer/gstreamer_test.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Vedant Paranjape
+ *
+ * GStreamer test base class
+ */
+
+#pragma once
+
+#include <iostream>
+#include <unistd.h>
+
+#include <gst/gst.h>
+
+class GstreamerTest
+{
+public:
+ GstreamerTest(unsigned int numStreams = 1);
+ virtual ~GstreamerTest();
+
+protected:
+ virtual int createPipeline();
+ int startPipeline();
+ int processEvent();
+ void printError(GstMessage *msg);
+
+ std::string cameraName_;
+ GstElement *pipeline_;
+ GstElement *libcameraSrc_;
+ int status_;
+
+private:
+ bool checkMinCameraStreamsAndSetCameraName(unsigned int numStreams);
+};
diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
new file mode 100644
index 00000000..e066c582
--- /dev/null
+++ b/test/gstreamer/meson.build
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: CC0-1.0
+
+if not gst_enabled
+ subdir_done()
+endif
+
+gstreamer_tests = [
+ {'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']},
+ {'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']},
+ {'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']},
+ {'name': 'memory_lifetime_test', 'sources': ['gstreamer_memory_lifetime_test.cpp']},
+]
+gstreamer_dep = dependency('gstreamer-1.0', required : true)
+gstapp_dep = dependency('gstreamer-app-1.0', required : true)
+
+gstreamer_test_args = []
+
+if asan_enabled
+ gstreamer_test_args += ['-D', 'HAVE_ASAN=1']
+endif
+
+foreach test : gstreamer_tests
+ exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp',
+ cpp_args : gstreamer_test_args,
+ dependencies : [libcamera_private, gstreamer_dep, gstapp_dep],
+ link_with : test_libraries,
+ include_directories : test_includes_internal)
+
+ test(test['name'], exe, suite : 'gstreamer', is_parallel : false,
+ env : gst_env, should_fail : test.get('should_fail', false))
+endforeach
diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp
new file mode 100644
index 00000000..530e9a31
--- /dev/null
+++ b/test/hotplug-cameras.cpp
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Umang Jain <email@uajain.com>
+ *
+ * Test cameraAdded/cameraRemoved signals in CameraManager
+ */
+
+#include <dirent.h>
+#include <fstream>
+#include <iostream>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/file.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std::chrono_literals;
+
+class HotplugTest : public Test
+{
+protected:
+ void cameraAddedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
+ {
+ cameraAdded_ = true;
+ }
+
+ void cameraRemovedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
+ {
+ cameraRemoved_ = 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;
+ }
+
+ cameraAdded_ = false;
+ cameraRemoved_ = false;
+
+ cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
+ cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);
+
+ return 0;
+ }
+
+ int run()
+ {
+ DIR *dir;
+ struct dirent *dirent;
+ std::string uvcDeviceDir;
+
+ dir = opendir(uvcDriverDir_.c_str());
+ /* Find a UVC device directory, which we can bind/unbind. */
+ while ((dirent = readdir(dir)) != nullptr) {
+ if (!File::exists(uvcDriverDir_ + dirent->d_name + "/video4linux"))
+ continue;
+
+ uvcDeviceDir = dirent->d_name;
+ break;
+ }
+ closedir(dir);
+
+ /* If no UVC device found, skip the test. */
+ if (uvcDeviceDir.empty())
+ return TestSkip;
+
+ /* Unbind a camera and process events. */
+ std::ofstream(uvcDriverDir_ + "unbind", std::ios::binary)
+ << uvcDeviceDir;
+ Timer timer;
+ timer.start(1000ms);
+ while (timer.isRunning() && !cameraRemoved_)
+ Thread::current()->eventDispatcher()->processEvents();
+ if (!cameraRemoved_) {
+ std::cout << "Camera unplug not detected" << std::endl;
+ return TestFail;
+ }
+
+ /* Bind the camera again and process events. */
+ std::ofstream(uvcDriverDir_ + "bind", std::ios::binary)
+ << uvcDeviceDir;
+ timer.start(1000ms);
+ while (timer.isRunning() && !cameraAdded_)
+ Thread::current()->eventDispatcher()->processEvents();
+ if (!cameraAdded_) {
+ std::cout << "Camera plug not detected" << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ void cleanup()
+ {
+ cm_->stop();
+ delete cm_;
+ }
+
+private:
+ CameraManager *cm_;
+ static const std::string uvcDriverDir_;
+ bool cameraRemoved_;
+ bool cameraAdded_;
+};
+
+const std::string HotplugTest::uvcDriverDir_ = "/sys/bus/usb/drivers/uvcvideo/";
+
+TEST_REGISTER(HotplugTest)
diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp
index cafc249b..b8178366 100644
--- a/test/ipa/ipa_interface_test.cpp
+++ b/test/ipa/ipa_interface_test.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * ipa_interface_test.cpp - Test the IPA interface
+ * Test the IPA interface
*/
#include <fcntl.h>
@@ -12,44 +12,52 @@
#include <sys/types.h>
#include <unistd.h>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/event_notifier.h>
-#include <libcamera/timer.h>
+#include <libcamera/ipa/vimc_ipa_proxy.h>
-#include <ipa/ipa_vimc.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/event_notifier.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "libcamera/internal/camera_manager.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/ipa_module.h"
+#include "libcamera/internal/pipeline_handler.h"
-#include "device_enumerator.h"
-#include "ipa_manager.h"
-#include "ipa_module.h"
-#include "pipeline_handler.h"
#include "test.h"
-#include "thread.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
class IPAInterfaceTest : public Test, public Object
{
public:
IPAInterfaceTest()
- : trace_(IPAOperationNone), notifier_(nullptr), fd_(-1)
+ : trace_(ipa::vimc::IPAOperationNone), notifier_(nullptr), fd_(-1)
{
}
~IPAInterfaceTest()
{
delete notifier_;
+ ipa_.reset();
+ cameraManager_.reset();
}
protected:
int init() override
{
+ cameraManager_ = make_unique<CameraManager>();
+
/* Create a pipeline handler for vimc. */
- std::vector<PipelineHandlerFactory *> &factories =
- PipelineHandlerFactory::factories();
- for (PipelineHandlerFactory *factory : factories) {
- if (factory->name() == "PipelineHandlerVimc") {
- pipe_ = factory->create(nullptr);
+ const std::vector<PipelineHandlerFactoryBase *> &factories =
+ PipelineHandlerFactoryBase::factories();
+ for (const PipelineHandlerFactoryBase *factory : factories) {
+ if (factory->name() == "vimc") {
+ pipe_ = factory->create(cameraManager_.get());
break;
}
}
@@ -60,22 +68,22 @@ protected:
}
/* Create and open the communication FIFO. */
- int ret = mkfifo(VIMC_IPA_FIFO_PATH, S_IRUSR | S_IWUSR);
+ int ret = mkfifo(ipa::vimc::VimcIPAFIFOPath.c_str(), S_IRUSR | S_IWUSR);
if (ret) {
ret = errno;
cerr << "Failed to create IPA test FIFO at '"
- << VIMC_IPA_FIFO_PATH << "': " << strerror(ret)
+ << ipa::vimc::VimcIPAFIFOPath << "': " << strerror(ret)
<< endl;
return TestFail;
}
- ret = open(VIMC_IPA_FIFO_PATH, O_RDONLY | O_NONBLOCK);
+ ret = open(ipa::vimc::VimcIPAFIFOPath.c_str(), O_RDONLY | O_NONBLOCK);
if (ret < 0) {
ret = errno;
cerr << "Failed to open IPA test FIFO at '"
- << VIMC_IPA_FIFO_PATH << "': " << strerror(ret)
+ << ipa::vimc::VimcIPAFIFOPath << "': " << strerror(ret)
<< endl;
- unlink(VIMC_IPA_FIFO_PATH);
+ unlink(ipa::vimc::VimcIPAFIFOPath.c_str());
return TestFail;
}
fd_ = ret;
@@ -91,49 +99,82 @@ protected:
EventDispatcher *dispatcher = thread()->eventDispatcher();
Timer timer;
- ipa_ = IPAManager::instance()->createIPA(pipe_.get(), 0, 0);
+ ipa_ = IPAManager::createIPA<ipa::vimc::IPAProxyVimc>(pipe_.get(), 0, 0);
if (!ipa_) {
cerr << "Failed to create VIMC IPA interface" << endl;
return TestFail;
}
/* Test initialization of IPA module. */
- ipa_->init();
- timer.start(1000);
- while (timer.isRunning() && trace_ != IPAOperationInit)
+ std::string conf = ipa_->configurationFile("vimc.conf");
+ Flags<ipa::vimc::TestFlag> inFlags;
+ Flags<ipa::vimc::TestFlag> outFlags;
+ int ret = ipa_->init(IPASettings{ conf, "vimc" },
+ ipa::vimc::IPAOperationInit,
+ inFlags, &outFlags);
+ if (ret < 0) {
+ cerr << "IPA interface init() failed" << endl;
+ return TestFail;
+ }
+
+ timer.start(1000ms);
+ while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationInit)
dispatcher->processEvents();
- if (trace_ != IPAOperationInit) {
+ if (trace_ != ipa::vimc::IPAOperationInit) {
cerr << "Failed to test IPA initialization sequence"
<< endl;
return TestFail;
}
+ /* Test start of IPA module. */
+ ipa_->start();
+ timer.start(1000ms);
+ while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationStart)
+ dispatcher->processEvents();
+
+ if (trace_ != ipa::vimc::IPAOperationStart) {
+ cerr << "Failed to test IPA start sequence" << endl;
+ return TestFail;
+ }
+
+ /* Test stop of IPA module. */
+ ipa_->stop();
+ timer.start(1000ms);
+ while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationStop)
+ dispatcher->processEvents();
+
+ if (trace_ != ipa::vimc::IPAOperationStop) {
+ cerr << "Failed to test IPA stop sequence" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
void cleanup() override
{
close(fd_);
- unlink(VIMC_IPA_FIFO_PATH);
+ unlink(ipa::vimc::VimcIPAFIFOPath.c_str());
}
private:
- void readTrace(EventNotifier *notifier)
+ void readTrace()
{
- ssize_t s = read(notifier->fd(), &trace_, sizeof(trace_));
+ ssize_t s = read(notifier_->fd(), &trace_, sizeof(trace_));
if (s < 0) {
int ret = errno;
cerr << "Failed to read from IPA test FIFO at '"
- << VIMC_IPA_FIFO_PATH << "': " << strerror(ret)
+ << ipa::vimc::VimcIPAFIFOPath << "': " << strerror(ret)
<< endl;
- trace_ = IPAOperationNone;
+ trace_ = ipa::vimc::IPAOperationNone;
}
}
std::shared_ptr<PipelineHandler> pipe_;
- std::unique_ptr<IPAInterface> ipa_;
- enum IPAOperationCode trace_;
+ std::unique_ptr<ipa::vimc::IPAProxyVimc> ipa_;
+ std::unique_ptr<CameraManager> cameraManager_;
+ enum ipa::vimc::IPAOperationCode trace_;
EventNotifier *notifier_;
int fd_;
};
diff --git a/test/ipa/ipa_module_test.cpp b/test/ipa/ipa_module_test.cpp
index 287e53fd..1c97da32 100644
--- a/test/ipa/ipa_module_test.cpp
+++ b/test/ipa/ipa_module_test.cpp
@@ -2,13 +2,13 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * ipa_module_test.cpp - Test loading of the VIMC IPA module and verify its info
+ * Test loading of the VIMC IPA module and verify its info
*/
#include <iostream>
#include <string.h>
-#include "ipa_module.h"
+#include "libcamera/internal/ipa_module.h"
#include "test.h"
@@ -57,9 +57,8 @@ protected:
const struct IPAModuleInfo testInfo = {
IPA_MODULE_API_VERSION,
0,
- "PipelineHandlerVimc",
- "Dummy IPA for Vimc",
- "GPL-2.0-or-later",
+ "vimc",
+ "vimc",
};
count += runTest("src/ipa/vimc/ipa_vimc.so", testInfo);
diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp
deleted file mode 100644
index 1ae17811..00000000
--- a/test/ipa/ipa_wrappers_test.cpp
+++ /dev/null
@@ -1,394 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers
- */
-
-#include <fcntl.h>
-#include <iostream>
-#include <memory>
-#include <linux/videodev2.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <libcamera/controls.h>
-#include <libipa/ipa_interface_wrapper.h>
-
-#include "device_enumerator.h"
-#include "ipa_context_wrapper.h"
-#include "media_device.h"
-#include "v4l2_subdevice.h"
-
-#include "test.h"
-
-using namespace libcamera;
-using namespace std;
-
-enum Operation {
- Op_init,
- Op_configure,
- Op_mapBuffers,
- Op_unmapBuffers,
- Op_processEvent,
-};
-
-class TestIPAInterface : public IPAInterface
-{
-public:
- TestIPAInterface()
- : sequence_(0)
- {
- }
-
- int init() override
- {
- report(Op_init, TestPass);
- return 0;
- }
-
- void configure(const std::map<unsigned int, IPAStream> &streamConfig,
- const std::map<unsigned int, const ControlInfoMap &> &entityControls) override
- {
- /* Verify streamConfig. */
- if (streamConfig.size() != 2) {
- cerr << "configure(): Invalid number of streams "
- << streamConfig.size() << endl;
- return report(Op_configure, TestFail);
- }
-
- auto iter = streamConfig.find(1);
- if (iter == streamConfig.end()) {
- cerr << "configure(): No configuration for stream 1" << endl;
- return report(Op_configure, TestFail);
- }
- const IPAStream *stream = &iter->second;
- if (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||
- stream->size != Size{ 1024, 768 }) {
- cerr << "configure(): Invalid configuration for stream 1" << endl;
- return report(Op_configure, TestFail);
- }
-
- iter = streamConfig.find(2);
- if (iter == streamConfig.end()) {
- cerr << "configure(): No configuration for stream 2" << endl;
- return report(Op_configure, TestFail);
- }
- stream = &iter->second;
- if (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||
- stream->size != Size{ 800, 600 }) {
- cerr << "configure(): Invalid configuration for stream 2" << endl;
- return report(Op_configure, TestFail);
- }
-
- /* Verify entityControls. */
- auto ctrlIter = entityControls.find(42);
- if (ctrlIter == entityControls.end()) {
- cerr << "configure(): Controls not found" << endl;
- return report(Op_configure, TestFail);
- }
-
- const ControlInfoMap &infoMap = ctrlIter->second;
-
- if (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||
- infoMap.count(V4L2_CID_CONTRAST) != 1 ||
- infoMap.count(V4L2_CID_SATURATION) != 1) {
- cerr << "configure(): Invalid control IDs" << endl;
- return report(Op_configure, TestFail);
- }
-
- report(Op_configure, TestPass);
- }
-
- void mapBuffers(const std::vector<IPABuffer> &buffers) override
- {
- if (buffers.size() != 2) {
- cerr << "mapBuffers(): Invalid number of buffers "
- << buffers.size() << endl;
- return report(Op_mapBuffers, TestFail);
- }
-
- if (buffers[0].id != 10 ||
- buffers[1].id != 11) {
- cerr << "mapBuffers(): Invalid buffer IDs" << endl;
- return report(Op_mapBuffers, TestFail);
- }
-
- if (buffers[0].planes.size() != 3 ||
- buffers[1].planes.size() != 3) {
- cerr << "mapBuffers(): Invalid number of planes" << endl;
- return report(Op_mapBuffers, TestFail);
- }
-
- if (buffers[0].planes[0].length != 4096 ||
- buffers[0].planes[1].length != 0 ||
- buffers[0].planes[2].length != 0 ||
- buffers[0].planes[0].length != 4096 ||
- buffers[1].planes[1].length != 4096 ||
- buffers[1].planes[2].length != 0) {
- cerr << "mapBuffers(): Invalid length" << endl;
- return report(Op_mapBuffers, TestFail);
- }
-
- if (buffers[0].planes[0].fd.fd() == -1 ||
- buffers[0].planes[1].fd.fd() != -1 ||
- buffers[0].planes[2].fd.fd() != -1 ||
- buffers[0].planes[0].fd.fd() == -1 ||
- buffers[1].planes[1].fd.fd() == -1 ||
- buffers[1].planes[2].fd.fd() != -1) {
- cerr << "mapBuffers(): Invalid dmabuf" << endl;
- return report(Op_mapBuffers, TestFail);
- }
-
- report(Op_mapBuffers, TestPass);
- }
-
- void unmapBuffers(const std::vector<unsigned int> &ids) override
- {
- if (ids.size() != 2) {
- cerr << "unmapBuffers(): Invalid number of ids "
- << ids.size() << endl;
- return report(Op_unmapBuffers, TestFail);
- }
-
- if (ids[0] != 10 || ids[1] != 11) {
- cerr << "unmapBuffers(): Invalid buffer IDs" << endl;
- return report(Op_unmapBuffers, TestFail);
- }
-
- report(Op_unmapBuffers, TestPass);
- }
-
- void processEvent(const IPAOperationData &data) override
- {
- /* Verify operation and data. */
- if (data.operation != Op_processEvent) {
- cerr << "processEvent(): Invalid operation "
- << data.operation << endl;
- return report(Op_processEvent, TestFail);
- }
-
- if (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {
- cerr << "processEvent(): Invalid data" << endl;
- return report(Op_processEvent, TestFail);
- }
-
- /* Verify controls. */
- if (data.controls.size() != 1) {
- cerr << "processEvent(): Controls not found" << endl;
- return report(Op_processEvent, TestFail);
- }
-
- const ControlList &controls = data.controls[0];
- if (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||
- controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||
- controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {
- cerr << "processEvent(): Invalid controls" << endl;
- return report(Op_processEvent, TestFail);
- }
-
- report(Op_processEvent, TestPass);
- }
-
-private:
- void report(Operation op, int status)
- {
- IPAOperationData data;
- data.operation = op;
- data.data.resize(1);
- data.data[0] = status;
- queueFrameAction.emit(sequence_++, data);
- }
-
- unsigned int sequence_;
-};
-
-#define INVOKE(method, ...) \
- invoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)
-
-class IPAWrappersTest : public Test
-{
-public:
- IPAWrappersTest()
- : subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)
- {
- }
-
-protected:
- int init() override
- {
- /* Locate the VIMC Sensor B subdevice. */
- enumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());
- if (!enumerator_) {
- cerr << "Failed to create device enumerator" << endl;
- return TestFail;
- }
-
- if (enumerator_->enumerate()) {
- cerr << "Failed to enumerate media devices" << endl;
- return TestFail;
- }
-
- DeviceMatch dm("vimc");
- media_ = enumerator_->search(dm);
- if (!media_) {
- cerr << "No VIMC media device found: skip test" << endl;
- return TestSkip;
- }
-
- MediaEntity *entity = media_->getEntityByName("Sensor A");
- if (!entity) {
- cerr << "Unable to find media entity 'Sensor A'" << endl;
- return TestFail;
- }
-
- subdev_ = new V4L2Subdevice(entity);
- if (subdev_->open() < 0) {
- cerr << "Unable to open 'Sensor A' subdevice" << endl;
- return TestFail;
- }
-
- /* Force usage of the C API as that's what we want to test. */
- int ret = setenv("LIBCAMERA_IPA_FORCE_C_API", "", 1);
- if (ret)
- return TestFail;
-
- std::unique_ptr<IPAInterface> intf = std::make_unique<TestIPAInterface>();
- wrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));
- wrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);
-
- /* Create a file descriptor for the buffer-related operations. */
- fd_ = open("/tmp", O_TMPFILE | O_RDWR, 0600);
- if (fd_ == -1)
- return TestFail;
-
- ret = ftruncate(fd_, 4096);
- if (ret < 0)
- return TestFail;
-
- return TestPass;
- }
-
- int run() override
- {
- int ret;
-
- /* Test configure(). */
- std::map<unsigned int, IPAStream> config{
- { 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },
- { 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },
- };
- std::map<unsigned int, const ControlInfoMap &> controlInfo;
- controlInfo.emplace(42, subdev_->controls());
- ret = INVOKE(configure, config, controlInfo);
- if (ret == TestFail)
- return TestFail;
-
- /* Test mapBuffers(). */
- std::vector<IPABuffer> buffers(2);
- buffers[0].planes.resize(3);
- buffers[0].id = 10;
- buffers[0].planes[0].fd = FileDescriptor(fd_);
- buffers[0].planes[0].length = 4096;
- buffers[1].id = 11;
- buffers[1].planes.resize(3);
- buffers[1].planes[0].fd = FileDescriptor(fd_);
- buffers[1].planes[0].length = 4096;
- buffers[1].planes[1].fd = FileDescriptor(fd_);
- buffers[1].planes[1].length = 4096;
-
- ret = INVOKE(mapBuffers, buffers);
- if (ret == TestFail)
- return TestFail;
-
- /* Test unmapBuffers(). */
- std::vector<unsigned int> bufferIds = { 10, 11 };
- ret = INVOKE(unmapBuffers, bufferIds);
- if (ret == TestFail)
- return TestFail;
-
- /* Test processEvent(). */
- IPAOperationData data;
- data.operation = Op_processEvent;
- data.data = { 1, 2, 3, 4 };
- data.controls.emplace_back(subdev_->controls());
-
- ControlList &controls = data.controls.back();
- controls.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));
- controls.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));
- controls.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));
-
- ret = INVOKE(processEvent, data);
- if (ret == TestFail)
- return TestFail;
-
- /*
- * Test init() last to ensure nothing in the wrappers or
- * serializer depends on init() being called first.
- */
- ret = INVOKE(init);
- if (ret == TestFail)
- return TestFail;
-
- return TestPass;
- }
-
- void cleanup() override
- {
- delete wrapper_;
- delete subdev_;
-
- if (fd_ != -1)
- close(fd_);
- }
-
-private:
- template<typename T, typename... Args1, typename... Args2>
- int invoke(T (IPAInterface::*func)(Args1...), Operation op,
- const char *name, Args2... args)
- {
- data_ = IPAOperationData();
- (wrapper_->*func)(args...);
-
- if (frame_ != sequence_) {
- cerr << "IPAInterface::" << name
- << "(): invalid frame number " << frame_
- << ", expected " << sequence_;
- return TestFail;
- }
-
- sequence_++;
-
- if (data_.operation != op) {
- cerr << "IPAInterface::" << name
- << "(): failed to propagate" << endl;
- return TestFail;
- }
-
- if (data_.data[0] != TestPass) {
- cerr << "IPAInterface::" << name
- << "(): reported an error" << endl;
- return TestFail;
- }
-
- return TestPass;
- }
-
- void queueFrameAction(unsigned int frame, const IPAOperationData &data)
- {
- frame_ = frame;
- data_ = data;
- }
-
- std::shared_ptr<MediaDevice> media_;
- std::unique_ptr<DeviceEnumerator> enumerator_;
- V4L2Subdevice *subdev_;
-
- IPAContextWrapper *wrapper_;
- IPAOperationData data_;
- unsigned int sequence_;
- unsigned int frame_;
- int fd_;
-};
-
-TEST_REGISTER(IPAWrappersTest)
diff --git a/test/ipa/libipa/fixedpoint.cpp b/test/ipa/libipa/fixedpoint.cpp
new file mode 100644
index 00000000..99eb662d
--- /dev/null
+++ b/test/ipa/libipa/fixedpoint.cpp
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Fixed / Floating point utility tests
+ */
+
+#include <cmath>
+#include <iostream>
+#include <map>
+#include <stdint.h>
+
+#include "../src/ipa/libipa/fixedpoint.h"
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+using namespace ipa;
+
+class FixedPointUtilsTest : public Test
+{
+protected:
+ /* R for real, I for integer */
+ template<unsigned int IntPrec, unsigned int FracPrec, typename I, typename R>
+ int testFixedToFloat(I input, R expected)
+ {
+ R out = fixedToFloatingPoint<IntPrec, FracPrec, R>(input);
+ R prec = 1.0 / (1 << FracPrec);
+ if (std::abs(out - expected) > prec) {
+ cerr << "Reverse conversion expected " << input
+ << " to convert to " << expected
+ << ", got " << out << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ template<unsigned int IntPrec, unsigned int FracPrec, typename T>
+ int testSingleFixedPoint(double input, T expected)
+ {
+ T ret = floatingToFixedPoint<IntPrec, FracPrec, T>(input);
+ if (ret != expected) {
+ cerr << "Expected " << input << " to convert to "
+ << expected << ", got " << ret << std::endl;
+ return TestFail;
+ }
+
+ /*
+ * The precision check is fairly arbitrary but is based on what
+ * the rkisp1 is capable of in the crosstalk module.
+ */
+ double f = fixedToFloatingPoint<IntPrec, FracPrec, double>(ret);
+ if (std::abs(f - input) > 0.005) {
+ cerr << "Reverse conversion expected " << ret
+ << " to convert to " << input
+ << ", got " << f << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ int testFixedPoint()
+ {
+ /*
+ * The second 7.992 test is to test that unused bits don't
+ * affect the result.
+ */
+ std::map<double, uint16_t> testCases = {
+ { 7.992, 0x3ff },
+ { 0.2, 0x01a },
+ { -0.2, 0x7e6 },
+ { -0.8, 0x79a },
+ { -0.4, 0x7cd },
+ { -1.4, 0x74d },
+ { -8, 0x400 },
+ { 0, 0 },
+ };
+
+ int ret;
+ for (const auto &testCase : testCases) {
+ ret = testSingleFixedPoint<4, 7, uint16_t>(testCase.first,
+ testCase.second);
+ if (ret != TestPass)
+ return ret;
+ }
+
+ /* Special case with a superfluous one in the unused bits */
+ ret = testFixedToFloat<4, 7, uint16_t, double>(0xbff, 7.992);
+ if (ret != TestPass)
+ return ret;
+
+ return TestPass;
+ }
+
+ int run()
+ {
+ /* fixed point conversion test */
+ if (testFixedPoint() != TestPass)
+ return TestFail;
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(FixedPointUtilsTest)
diff --git a/test/ipa/libipa/interpolator.cpp b/test/ipa/libipa/interpolator.cpp
new file mode 100644
index 00000000..6abb7760
--- /dev/null
+++ b/test/ipa/libipa/interpolator.cpp
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Interpolator tests
+ */
+
+#include "../src/ipa/libipa/interpolator.h"
+
+#include <cmath>
+#include <iostream>
+#include <map>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+using namespace ipa;
+
+#define ASSERT_EQ(a, b) \
+ if ((a) != (b)) { \
+ printf(#a " != " #b "\n"); \
+ return TestFail; \
+ }
+
+class InterpolatorTest : public Test
+{
+protected:
+ int run()
+ {
+ Interpolator<int> interpolator;
+ interpolator.setData({ { 10, 100 }, { 20, 200 }, { 30, 300 } });
+
+ ASSERT_EQ(interpolator.getInterpolated(0), 100);
+ ASSERT_EQ(interpolator.getInterpolated(10), 100);
+ ASSERT_EQ(interpolator.getInterpolated(20), 200);
+ ASSERT_EQ(interpolator.getInterpolated(25), 250);
+ ASSERT_EQ(interpolator.getInterpolated(30), 300);
+ ASSERT_EQ(interpolator.getInterpolated(40), 300);
+
+ interpolator.setQuantization(10);
+ unsigned int q = 0;
+ ASSERT_EQ(interpolator.getInterpolated(25, &q), 300);
+ ASSERT_EQ(q, 30);
+ ASSERT_EQ(interpolator.getInterpolated(24, &q), 200);
+ ASSERT_EQ(q, 20);
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(InterpolatorTest)
diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
new file mode 100644
index 00000000..0f4155d2
--- /dev/null
+++ b/test/ipa/libipa/meson.build
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libipa_test = [
+ {'name': 'fixedpoint', 'sources': ['fixedpoint.cpp']},
+ {'name': 'interpolator', 'sources': ['interpolator.cpp']},
+ {'name': 'vector', 'sources': ['vector.cpp']},
+]
+
+foreach test : libipa_test
+ exe = executable(test['name'], test['sources'],
+ dependencies : [libcamera_private, libipa_dep],
+ implicit_include_directories : false,
+ link_with : [test_libraries],
+ include_directories : [test_includes_internal,
+ '../../../src/ipa/libipa/'])
+
+ test(test['name'], exe, suite : 'ipa')
+endforeach
diff --git a/test/ipa/libipa/vector.cpp b/test/ipa/libipa/vector.cpp
new file mode 100644
index 00000000..8e4ec77d
--- /dev/null
+++ b/test/ipa/libipa/vector.cpp
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Vector tests
+ */
+
+#include "../src/ipa/libipa/vector.h"
+
+#include <cmath>
+#include <iostream>
+
+#include "test.h"
+
+using namespace libcamera::ipa;
+
+#define ASSERT_EQ(a, b) \
+if ((a) != (b)) { \
+ std::cout << #a " != " #b << " (line " << __LINE__ << ")" \
+ << std::endl; \
+ return TestFail; \
+}
+
+class VectorTest : public Test
+{
+protected:
+ int run()
+ {
+ Vector<double, 3> v1{ 0.0 };
+
+ ASSERT_EQ(v1[0], 0.0);
+ ASSERT_EQ(v1[1], 0.0);
+ ASSERT_EQ(v1[2], 0.0);
+
+ ASSERT_EQ(v1.length(), 0.0);
+ ASSERT_EQ(v1.length2(), 0.0);
+
+ Vector<double, 3> v2{{ 1.0, 4.0, 8.0 }};
+
+ ASSERT_EQ(v2[0], 1.0);
+ ASSERT_EQ(v2[1], 4.0);
+ ASSERT_EQ(v2[2], 8.0);
+
+ ASSERT_EQ(v2.x(), 1.0);
+ ASSERT_EQ(v2.y(), 4.0);
+ ASSERT_EQ(v2.z(), 8.0);
+
+ ASSERT_EQ(v2.r(), 1.0);
+ ASSERT_EQ(v2.g(), 4.0);
+ ASSERT_EQ(v2.b(), 8.0);
+
+ ASSERT_EQ(v2.length2(), 81.0);
+ ASSERT_EQ(v2.length(), 9.0);
+ ASSERT_EQ(v2.sum(), 13.0);
+
+ Vector<double, 3> v3{ v2 };
+
+ ASSERT_EQ(v2, v3);
+
+ v3 = Vector<double, 3>{{ 4.0, 4.0, 4.0 }};
+
+ ASSERT_EQ(v2 + v3, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+ ASSERT_EQ(v2 + 4.0, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+ ASSERT_EQ(v2 - v3, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
+ ASSERT_EQ(v2 - 4.0, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
+ ASSERT_EQ(v2 * v3, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+ ASSERT_EQ(v2 * 4.0, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+ ASSERT_EQ(v2 / v3, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
+ ASSERT_EQ(v2 / 4.0, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
+
+ ASSERT_EQ(v2.min(v3), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
+ ASSERT_EQ(v2.min(4.0), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
+ ASSERT_EQ(v2.max(v3), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
+ ASSERT_EQ(v2.max(4.0), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
+
+ ASSERT_EQ(v2.dot(v3), 52.0);
+
+ v2 += v3;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+ v2 -= v3;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+ v2 *= v3;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+ v2 /= v3;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+
+ v2 += 4.0;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+ v2 -= 4.0;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+ v2 *= 4.0;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+ v2 /= 4.0;
+ ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(VectorTest)
diff --git a/test/ipa/meson.build b/test/ipa/meson.build
index f925c50a..ceed15ba 100644
--- a/test/ipa/meson.build
+++ b/test/ipa/meson.build
@@ -1,14 +1,17 @@
+# SPDX-License-Identifier: CC0-1.0
+
+subdir('libipa')
+
ipa_test = [
- ['ipa_module_test', 'ipa_module_test.cpp'],
- ['ipa_interface_test', 'ipa_interface_test.cpp'],
- ['ipa_wrappers_test', 'ipa_wrappers_test.cpp'],
+ {'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']},
+ {'name': 'ipa_interface_test', 'sources': ['ipa_interface_test.cpp']},
]
-foreach t : ipa_test
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
- link_with : [libipa, test_libraries],
- include_directories : [libipa_includes, test_includes_internal])
+foreach test : ipa_test
+ exe = executable(test['name'], test['sources'],
+ dependencies : [libcamera_private, libipa_dep],
+ link_with : [test_libraries],
+ include_directories : [test_includes_internal])
- test(t[0], exe, suite : 'ipa')
+ test(test['name'], exe, suite : 'ipa')
endforeach
diff --git a/test/ipc/meson.build b/test/ipc/meson.build
index cc46b41c..8e447d22 100644
--- a/test/ipc/meson.build
+++ b/test/ipc/meson.build
@@ -1,12 +1,15 @@
+# SPDX-License-Identifier: CC0-1.0
+
ipc_tests = [
- [ 'unixsocket', 'unixsocket.cpp' ],
+ {'name': 'unixsocket_ipc', 'sources': ['unixsocket_ipc.cpp']},
+ {'name': 'unixsocket', 'sources': ['unixsocket.cpp']},
]
-foreach t : ipc_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : ipc_tests
+ exe = executable(test['name'], test['sources'],
+ dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'ipc')
+ test(test['name'], exe, suite : 'ipc')
endforeach
diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp
index f53042b8..f39bd986 100644
--- a/test/ipc/unixsocket.cpp
+++ b/test/ipc/unixsocket.cpp
@@ -2,10 +2,11 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * unixsocket.cpp - Unix socket IPC test
+ * Unix socket IPC test
*/
#include <algorithm>
+#include <array>
#include <fcntl.h>
#include <iostream>
#include <stdlib.h>
@@ -14,14 +15,15 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <vector>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "libcamera/internal/ipc_unixsocket.h"
-#include "ipc_unixsocket.h"
#include "test.h"
-#include "thread.h"
-#include "utils.h"
#define CMD_CLOSE 0
#define CMD_REVERSE 1
@@ -29,8 +31,11 @@
#define CMD_LEN_CMP 3
#define CMD_JOIN 4
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
+
+namespace {
int calculateLength(int fd)
{
@@ -41,6 +46,8 @@ int calculateLength(int fd)
return size;
}
+} /* namespace */
+
class UnixSocketTestSlave
{
public:
@@ -51,9 +58,9 @@ public:
ipc_.readyRead.connect(this, &UnixSocketTestSlave::readyRead);
}
- int run(int fd)
+ int run(UniqueFD fd)
{
- if (ipc_.bind(fd)) {
+ if (ipc_.bind(std::move(fd))) {
cerr << "Failed to connect to IPC channel" << endl;
return EXIT_FAILURE;
}
@@ -67,12 +74,12 @@ public:
}
private:
- void readyRead(IPCUnixSocket *ipc)
+ void readyRead()
{
IPCUnixSocket::Payload message, response;
int ret;
- ret = ipc->receive(&message);
+ ret = ipc_.receive(&message);
if (ret) {
cerr << "Receive message failed: " << ret << endl;
return;
@@ -145,6 +152,7 @@ private:
if (num < 0) {
cerr << "Read failed" << endl;
+ close(outfd);
stop(-EIO);
return;
} else if (!num)
@@ -152,6 +160,7 @@ private:
if (write(outfd, buf, num) < 0) {
cerr << "Write failed" << endl;
+ close(outfd);
stop(-EIO);
return;
}
@@ -206,8 +215,7 @@ protected:
if (!pid_) {
std::string arg = std::to_string(fd);
- execl("/proc/self/exe", "/proc/self/exe",
- arg.c_str(), nullptr);
+ execl(self().c_str(), self().c_str(), arg.c_str(), nullptr);
/* Only get here if exec fails. */
exit(TestFail);
@@ -309,7 +317,7 @@ protected:
};
int fds[2];
- for (unsigned int i = 0; i < ARRAY_SIZE(strings); i++) {
+ for (unsigned int i = 0; i < std::size(strings); i++) {
unsigned int len = strlen(strings[i]);
fds[i] = open("/tmp", O_TMPFILE | O_RDWR,
@@ -331,16 +339,16 @@ protected:
if (ret)
return ret;
- for (unsigned int i = 0; i < ARRAY_SIZE(strings); i++) {
+ for (unsigned int i = 0; i < std::size(strings); i++) {
unsigned int len = strlen(strings[i]);
- char buf[len];
+ std::vector<char> buf(len);
close(fds[i]);
- if (read(response.fds[0], &buf, len) <= 0)
+ if (read(response.fds[0], buf.data(), len) <= 0)
return TestFail;
- if (memcmp(buf, strings[i], len))
+ if (memcmp(buf.data(), strings[i], len))
return TestFail;
}
@@ -357,11 +365,11 @@ protected:
int run()
{
- int slavefd = ipc_.create();
- if (slavefd < 0)
+ UniqueFD slavefd = ipc_.create();
+ if (!slavefd.isValid())
return TestFail;
- if (slaveStart(slavefd)) {
+ if (slaveStart(slavefd.release())) {
cerr << "Failed to start slave" << endl;
return TestFail;
}
@@ -428,7 +436,7 @@ private:
if (ret)
return ret;
- timeout.start(200);
+ timeout.start(2s);
while (!callDone_) {
if (!timeout.isRunning()) {
cerr << "Call timeout!" << endl;
@@ -444,14 +452,14 @@ private:
return 0;
}
- void readyRead(IPCUnixSocket *ipc)
+ void readyRead()
{
if (!callResponse_) {
cerr << "Read ready without expecting data, fail." << endl;
return;
}
- if (ipc->receive(callResponse_)) {
+ if (ipc_.receive(callResponse_)) {
cerr << "Receive message failed" << endl;
return;
}
@@ -461,7 +469,7 @@ private:
int prepareFDs(IPCUnixSocket::Payload *message, unsigned int num)
{
- int fd = open("/proc/self/exe", O_RDONLY);
+ int fd = open(self().c_str(), O_RDONLY);
if (fd < 0)
return fd;
@@ -493,10 +501,12 @@ private:
int main(int argc, char **argv)
{
if (argc == 2) {
- int ipcfd = std::stoi(argv[1]);
+ UniqueFD ipcfd = UniqueFD(std::stoi(argv[1]));
UnixSocketTestSlave slave;
- return slave.run(ipcfd);
+ return slave.run(std::move(ipcfd));
}
- return UnixSocketTest().execute();
+ UnixSocketTest test;
+ test.setArgs(argc, argv);
+ return test.execute();
}
diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp
new file mode 100644
index 00000000..df7d9c2b
--- /dev/null
+++ b/test/ipc/unixsocket_ipc.cpp
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * Unix socket IPC test
+ */
+
+#include <algorithm>
+#include <fcntl.h>
+#include <iostream>
+#include <limits.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/ipa_data_serializer.h"
+#include "libcamera/internal/ipc_pipe.h"
+#include "libcamera/internal/ipc_pipe_unixsocket.h"
+#include "libcamera/internal/process.h"
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+enum {
+ CmdExit = 0,
+ CmdGetSync = 1,
+ CmdSetAsync = 2,
+};
+
+const int32_t kInitialValue = 1337;
+const int32_t kChangedValue = 9001;
+
+class UnixSocketTestIPCSlave
+{
+public:
+ UnixSocketTestIPCSlave()
+ : value_(kInitialValue), exitCode_(EXIT_FAILURE), exit_(false)
+ {
+ dispatcher_ = Thread::current()->eventDispatcher();
+ ipc_.readyRead.connect(this, &UnixSocketTestIPCSlave::readyRead);
+ }
+
+ int run(UniqueFD fd)
+ {
+ if (ipc_.bind(std::move(fd))) {
+ cerr << "Failed to connect to IPC channel" << endl;
+ return EXIT_FAILURE;
+ }
+
+ while (!exit_)
+ dispatcher_->processEvents();
+
+ ipc_.close();
+
+ return exitCode_;
+ }
+
+private:
+ void readyRead()
+ {
+ IPCUnixSocket::Payload message;
+ int ret;
+
+ ret = ipc_.receive(&message);
+ if (ret) {
+ cerr << "Receive message failed: " << ret << endl;
+ return;
+ }
+
+ IPCMessage ipcMessage(message);
+ uint32_t cmd = ipcMessage.header().cmd;
+
+ switch (cmd) {
+ case CmdExit: {
+ exit_ = true;
+ break;
+ }
+
+ case CmdGetSync: {
+ IPCMessage::Header header = { cmd, ipcMessage.header().cookie };
+ IPCMessage response(header);
+
+ vector<uint8_t> buf;
+ tie(buf, ignore) = IPADataSerializer<int32_t>::serialize(value_);
+ response.data().insert(response.data().end(), buf.begin(), buf.end());
+
+ ret = ipc_.send(response.payload());
+ if (ret < 0) {
+ cerr << "Reply failed" << endl;
+ stop(ret);
+ }
+ break;
+ }
+
+ case CmdSetAsync: {
+ value_ = IPADataSerializer<int32_t>::deserialize(ipcMessage.data());
+ break;
+ }
+ }
+ }
+
+ void stop(int code)
+ {
+ exitCode_ = code;
+ exit_ = true;
+ }
+
+ int32_t value_;
+
+ IPCUnixSocket ipc_;
+ EventDispatcher *dispatcher_;
+ int exitCode_;
+ bool exit_;
+};
+
+class UnixSocketTestIPC : public Test
+{
+protected:
+ int init()
+ {
+ return 0;
+ }
+
+ int setValue(int32_t val)
+ {
+ IPCMessage msg(CmdSetAsync);
+ tie(msg.data(), ignore) = IPADataSerializer<int32_t>::serialize(val);
+
+ int ret = ipc_->sendAsync(msg);
+ if (ret < 0) {
+ cerr << "Failed to call set value" << endl;
+ return ret;
+ }
+
+ return 0;
+ }
+
+ int getValue()
+ {
+ IPCMessage msg(CmdGetSync);
+ IPCMessage buf;
+
+ int ret = ipc_->sendSync(msg, &buf);
+ if (ret < 0) {
+ cerr << "Failed to call get value" << endl;
+ return ret;
+ }
+
+ return IPADataSerializer<int32_t>::deserialize(buf.data());
+ }
+
+ int exit()
+ {
+ IPCMessage msg(CmdExit);
+
+ int ret = ipc_->sendAsync(msg);
+ if (ret < 0) {
+ cerr << "Failed to call exit" << endl;
+ return ret;
+ }
+
+ return 0;
+ }
+
+ int run()
+ {
+ ipc_ = std::make_unique<IPCPipeUnixSocket>("", self().c_str());
+ if (!ipc_->isConnected()) {
+ cerr << "Failed to create IPCPipe" << endl;
+ return TestFail;
+ }
+
+ int ret = getValue();
+ if (ret != kInitialValue) {
+ cerr << "Wrong initial value, expected "
+ << kInitialValue << ", got " << ret << endl;
+ return TestFail;
+ }
+
+ ret = setValue(kChangedValue);
+ if (ret < 0) {
+ cerr << "Failed to set value: " << strerror(-ret) << endl;
+ return TestFail;
+ }
+
+ ret = getValue();
+ if (ret != kChangedValue) {
+ cerr << "Wrong set value, expected " << kChangedValue
+ << ", got " << ret << endl;
+ return TestFail;
+ }
+
+ ret = exit();
+ if (ret < 0) {
+ cerr << "Failed to exit: " << strerror(-ret) << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+private:
+ ProcessManager processManager_;
+
+ unique_ptr<IPCPipeUnixSocket> ipc_;
+};
+
+/*
+ * Can't use TEST_REGISTER() as single binary needs to act as both client and
+ * server
+ */
+int main(int argc, char **argv)
+{
+ /* IPCPipeUnixSocket passes IPA module path in argv[1] */
+ if (argc == 3) {
+ UniqueFD ipcfd = UniqueFD(std::stoi(argv[2]));
+ UnixSocketTestIPCSlave slave;
+ return slave.run(std::move(ipcfd));
+ }
+
+ UnixSocketTestIPC test;
+ test.setArgs(argc, argv);
+ return test.execute();
+}
diff --git a/test/libtest/buffer_source.cpp b/test/libtest/buffer_source.cpp
index dae3cb9f..dde11f36 100644
--- a/test/libtest/buffer_source.cpp
+++ b/test/libtest/buffer_source.cpp
@@ -10,10 +10,12 @@
#include <iostream>
#include <memory>
-#include "device_enumerator.h"
+#include "libcamera/internal/device_enumerator.h"
#include "test.h"
+using namespace libcamera;
+
BufferSource::BufferSource()
{
}
@@ -50,7 +52,7 @@ int BufferSource::allocate(const StreamConfiguration &config)
return TestSkip;
}
- std::unique_ptr<V4L2VideoDevice> video{ V4L2VideoDevice::fromEntityName(media_.get(), videoDeviceName) };
+ std::unique_ptr<V4L2VideoDevice> video = V4L2VideoDevice::fromEntityName(media_.get(), videoDeviceName);
if (!video) {
std::cout << "Failed to get video device from entity "
<< videoDeviceName << std::endl;
@@ -70,8 +72,7 @@ int BufferSource::allocate(const StreamConfiguration &config)
}
format.size = config.size;
- format.fourcc = V4L2VideoDevice::toV4L2PixelFormat(config.pixelFormat,
- false);
+ format.fourcc = video->toV4L2PixelFormat(config.pixelFormat);
if (video->setFormat(&format)) {
std::cout << "Failed to set format on output device" << std::endl;
return TestFail;
diff --git a/test/libtest/buffer_source.h b/test/libtest/buffer_source.h
index ae0879c9..495da8a9 100644
--- a/test/libtest/buffer_source.h
+++ b/test/libtest/buffer_source.h
@@ -2,17 +2,15 @@
/*
* Copyright (C) 2020, Google Inc.
*
- * buffer_source.h - libcamera camera test helper to create FrameBuffers
+ * libcamera camera test helper to create FrameBuffers
*/
-#ifndef __LIBCAMERA_BUFFER_SOURCE_TEST_H__
-#define __LIBCAMERA_BUFFER_SOURCE_TEST_H__
-#include <libcamera/libcamera.h>
+#pragma once
-#include "media_device.h"
-#include "v4l2_videodevice.h"
+#include <libcamera/stream.h>
-using namespace libcamera;
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_videodevice.h"
class BufferSource
{
@@ -20,12 +18,10 @@ public:
BufferSource();
~BufferSource();
- int allocate(const StreamConfiguration &config);
- const std::vector<std::unique_ptr<FrameBuffer>> &buffers();
+ int allocate(const libcamera::StreamConfiguration &config);
+ const std::vector<std::unique_ptr<libcamera::FrameBuffer>> &buffers();
private:
- std::shared_ptr<MediaDevice> media_;
- std::vector<std::unique_ptr<FrameBuffer>> buffers_;
+ std::shared_ptr<libcamera::MediaDevice> media_;
+ std::vector<std::unique_ptr<libcamera::FrameBuffer>> buffers_;
};
-
-#endif /* __LIBCAMERA_BUFFER_SOURCE_TEST_H__ */
diff --git a/test/libtest/camera_test.cpp b/test/libtest/camera_test.cpp
index 2ae4d677..fe13d6ac 100644
--- a/test/libtest/camera_test.cpp
+++ b/test/libtest/camera_test.cpp
@@ -13,10 +13,13 @@
using namespace libcamera;
using namespace std;
-CameraTest::CameraTest(const char *name)
+CameraTest::CameraTest(const char *name, bool isolate)
{
cm_ = new CameraManager();
+ if (isolate)
+ setenv("LIBCAMERA_IPA_FORCE_ISOLATION", "1", 1);
+
if (cm_->start()) {
cerr << "Failed to start camera manager" << endl;
status_ = TestFail;
diff --git a/test/libtest/camera_test.h b/test/libtest/camera_test.h
index 0b6bad05..713b503f 100644
--- a/test/libtest/camera_test.h
+++ b/test/libtest/camera_test.h
@@ -2,25 +2,24 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * camera_test.h - libcamera camera test base class
+ * libcamera camera test base class
*/
-#ifndef __LIBCAMERA_CAMERA_TEST_H__
-#define __LIBCAMERA_CAMERA_TEST_H__
-#include <libcamera/libcamera.h>
+#pragma once
-using namespace libcamera;
+#include <memory>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
class CameraTest
{
public:
- CameraTest(const char *name);
+ CameraTest(const char *name, bool isolate = false);
~CameraTest();
protected:
- CameraManager *cm_;
- std::shared_ptr<Camera> camera_;
+ libcamera::CameraManager *cm_;
+ std::shared_ptr<libcamera::Camera> camera_;
int status_;
};
-
-#endif /* __LIBCAMERA_CAMERA_TEST_H__ */
diff --git a/test/libtest/meson.build b/test/libtest/meson.build
index 33565e0e..351629f3 100644
--- a/test/libtest/meson.build
+++ b/test/libtest/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
libtest_sources = files([
'buffer_source.cpp',
'camera_test.cpp',
@@ -13,11 +15,10 @@ test_includes_public = [
test_includes_internal = [
test_includes_public,
- libcamera_internal_includes,
]
libtest = static_library('libtest', libtest_sources,
- dependencies : libcamera_dep,
+ dependencies : libcamera_private,
include_directories : test_includes_internal)
test_libraries = [libtest]
diff --git a/test/libtest/test.cpp b/test/libtest/test.cpp
index fd9f3d74..4e03def9 100644
--- a/test/libtest/test.cpp
+++ b/test/libtest/test.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2018, Google Inc.
*
- * test.cpp - libcamera test base class
+ * libcamera test base class
*/
#include <stdlib.h>
@@ -17,6 +17,11 @@ Test::~Test()
{
}
+void Test::setArgs([[maybe_unused]] int argc, char *argv[])
+{
+ self_ = argv[0];
+}
+
int Test::execute()
{
int ret;
diff --git a/test/libtest/test.h b/test/libtest/test.h
index 26d4b94b..3a90885d 100644
--- a/test/libtest/test.h
+++ b/test/libtest/test.h
@@ -2,12 +2,13 @@
/*
* Copyright (C) 2018, Google Inc.
*
- * test.h - libcamera test base class
+ * libcamera test base class
*/
-#ifndef __TEST_TEST_H__
-#define __TEST_TEST_H__
+
+#pragma once
#include <sstream>
+#include <string>
enum TestStatus {
TestPass = 0,
@@ -21,18 +22,24 @@ public:
Test();
virtual ~Test();
+ void setArgs(int argc, char *argv[]);
int execute();
+ const std::string &self() const { return self_; }
+
protected:
virtual int init() { return 0; }
virtual int run() = 0;
virtual void cleanup() {}
+
+private:
+ std::string self_;
};
-#define TEST_REGISTER(klass) \
+#define TEST_REGISTER(Klass) \
int main(int argc, char *argv[]) \
{ \
- return klass().execute(); \
+ Klass klass; \
+ klass.setArgs(argc, argv); \
+ return klass.execute(); \
}
-
-#endif /* __TEST_TEST_H__ */
diff --git a/test/list-cameras.cpp b/test/list-cameras.cpp
deleted file mode 100644
index 55551d7e..00000000
--- a/test/list-cameras.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2018, Google Inc.
- *
- * list.cpp - camera list tests
- */
-
-#include <iostream>
-
-#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
-
-#include "test.h"
-
-using namespace std;
-using namespace libcamera;
-
-class ListTest : public Test
-{
-protected:
- int init()
- {
- cm_ = new CameraManager();
- cm_->start();
-
- return 0;
- }
-
- int run()
- {
- unsigned int count = 0;
-
- for (const std::shared_ptr<Camera> &camera : cm_->cameras()) {
- cout << "- " << camera->name() << endl;
- count++;
- }
-
- return count ? 0 : -ENODEV;
- }
-
- void cleanup()
- {
- cm_->stop();
- delete cm_;
- }
-
-private:
- CameraManager *cm_;
-};
-
-TEST_REGISTER(ListTest)
diff --git a/test/log/log_api.cpp b/test/log/log_api.cpp
index 33622f84..0b999738 100644
--- a/test/log/log_api.cpp
+++ b/test/log/log_api.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * log.cpp - log API test
+ * log API test
*/
#include <algorithm>
@@ -16,9 +16,10 @@
#include <sys/types.h>
#include <unistd.h>
+#include <libcamera/base/log.h>
+
#include <libcamera/logging.h>
-#include "log.h"
#include "test.h"
using namespace std;
@@ -86,6 +87,7 @@ protected:
if (logSetFile(path) < 0) {
cerr << "Failed to set log file" << endl;
+ close(fd);
return TestFail;
}
@@ -96,6 +98,7 @@ protected:
lseek(fd, 0, SEEK_SET);
if (read(fd, buf, sizeof(buf)) < 0) {
cerr << "Failed to read tmp log file" << endl;
+ close(fd);
return TestFail;
}
close(fd);
diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp
index 2df4aa43..9609e23d 100644
--- a/test/log/log_process.cpp
+++ b/test/log/log_process.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * log_process.cpp - Logging in isolated child process test
+ * Logging in isolated child process test
*/
#include <fcntl.h>
@@ -14,18 +14,21 @@
#include <unistd.h>
#include <vector>
-#include <libcamera/event_dispatcher.h>
#include <libcamera/logging.h>
-#include <libcamera/timer.h>
-#include "log.h"
-#include "process.h"
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/process.h"
+
#include "test.h"
-#include "thread.h"
-#include "utils.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
static const string message("hello from the child");
@@ -72,18 +75,19 @@ protected:
vector<std::string> args;
args.push_back(to_string(exitCode));
args.push_back(to_string(num_));
- int ret = proc_.start("/proc/self/exe", args);
+ int ret = proc_.start(self(), args);
if (ret) {
cerr << "failed to start process" << endl;
return TestFail;
}
- timeout.start(200);
+ timeout.start(2s);
while (timeout.isRunning())
dispatcher->processEvents();
if (exitStatus_ != Process::NormalExit) {
- cerr << "process did not exit normally" << endl;
+ cerr << "process did not exit normally: " << exitStatus_
+ << endl;
return TestFail;
}
@@ -106,13 +110,17 @@ protected:
memset(buf, 0, sizeof(buf));
if (read(fd, buf, sizeof(buf)) < 0) {
cerr << "Failed to read tmp log file" << endl;
+ close(fd);
return TestFail;
}
close(fd);
string str(buf);
- if (str.find(message) == string::npos)
+ if (str.find(message) == string::npos) {
+ cerr << "Received message is not correct (received "
+ << str.length() << " bytes)" << endl;
return TestFail;
+ }
return TestPass;
}
@@ -123,14 +131,16 @@ protected:
}
private:
- void procFinished(Process *proc, enum Process::ExitStatus exitStatus, int exitCode)
+ void procFinished(enum Process::ExitStatus exitStatus, int exitCode)
{
exitStatus_ = exitStatus;
exitCode_ = exitCode;
}
+ ProcessManager processManager_;
+
Process proc_;
- Process::ExitStatus exitStatus_;
+ Process::ExitStatus exitStatus_ = Process::NotExited;
string logPath_;
int exitCode_;
int num_;
@@ -149,5 +159,7 @@ int main(int argc, char **argv)
return child.run(status, num);
}
- return LogProcessTest().execute();
+ LogProcessTest test;
+ test.setArgs(argc, argv);
+ return test.execute();
}
diff --git a/test/log/meson.build b/test/log/meson.build
index 95f6c1a2..2298ff84 100644
--- a/test/log/meson.build
+++ b/test/log/meson.build
@@ -1,13 +1,15 @@
+# SPDX-License-Identifier: CC0-1.0
+
log_test = [
- ['log_api', 'log_api.cpp'],
- ['log_process', 'log_process.cpp'],
+ {'name': 'log_api', 'sources': ['log_api.cpp']},
+ {'name': 'log_process', 'sources': ['log_process.cpp']},
]
-foreach t : log_test
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : log_test
+ exe = executable(test['name'], test['sources'],
+ dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'log')
+ test(test['name'], exe, suite : 'log')
endforeach
diff --git a/test/mapped-buffer.cpp b/test/mapped-buffer.cpp
new file mode 100644
index 00000000..b4422f7d
--- /dev/null
+++ b/test/mapped-buffer.cpp
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * libcamera internal MappedBuffer tests
+ */
+
+#include <iostream>
+
+#include <libcamera/framebuffer_allocator.h>
+
+#include "libcamera/internal/mapped_framebuffer.h"
+
+#include "camera_test.h"
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+namespace {
+
+class MappedBufferTest : public CameraTest, public Test
+{
+public:
+ MappedBufferTest()
+ : CameraTest("platform/vimc.0 Sensor B")
+ {
+ }
+
+protected:
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
+ if (!config_ || config_->size() != 1) {
+ cout << "Failed to generate default configuration" << endl;
+ return TestFail;
+ }
+
+ allocator_ = new FrameBufferAllocator(camera_);
+
+ StreamConfiguration &cfg = config_->at(0);
+
+ if (camera_->acquire()) {
+ cout << "Failed to acquire the camera" << endl;
+ return TestFail;
+ }
+
+ if (camera_->configure(config_.get())) {
+ cout << "Failed to set default configuration" << endl;
+ return TestFail;
+ }
+
+ stream_ = cfg.stream();
+
+ int ret = allocator_->allocate(stream_);
+ if (ret < 0)
+ return TestFail;
+
+ return TestPass;
+ }
+
+ void cleanup() override
+ {
+ delete allocator_;
+ }
+
+ int run() override
+ {
+ const std::unique_ptr<FrameBuffer> &buffer = allocator_->buffers(stream_).front();
+ std::vector<MappedBuffer> maps;
+
+ MappedFrameBuffer map(buffer.get(), MappedFrameBuffer::MapFlag::Read);
+ if (!map.isValid()) {
+ cout << "Failed to successfully map buffer" << endl;
+ return TestFail;
+ }
+
+ /* Make sure we can move it. */
+ maps.emplace_back(std::move(map));
+
+ /* But copying is prevented, it would cause double-unmap. */
+ // MappedFrameBuffer map_copy = map;
+
+ /* Local map should be invalid (after move). */
+ if (map.isValid()) {
+ cout << "Post-move map should not be valid" << endl;
+ return TestFail;
+ }
+
+ /* Test for multiple successful maps on the same buffer. */
+ MappedFrameBuffer write_map(buffer.get(), MappedFrameBuffer::MapFlag::Write);
+ if (!write_map.isValid()) {
+ cout << "Failed to map write buffer" << endl;
+ return TestFail;
+ }
+
+ MappedFrameBuffer rw_map(buffer.get(), MappedFrameBuffer::MapFlag::ReadWrite);
+ if (!rw_map.isValid()) {
+ cout << "Failed to map RW buffer" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+private:
+ std::unique_ptr<CameraConfiguration> config_;
+ FrameBufferAllocator *allocator_;
+ Stream *stream_;
+};
+
+} /* namespace */
+
+TEST_REGISTER(MappedBufferTest)
diff --git a/test/media_device/media_device_acquire.cpp b/test/media_device/media_device_acquire.cpp
index d1e3d744..371e30e9 100644
--- a/test/media_device/media_device_acquire.cpp
+++ b/test/media_device/media_device_acquire.cpp
@@ -30,4 +30,4 @@ class MediaDeviceAcquire : public MediaDeviceTest
}
};
-TEST_REGISTER(MediaDeviceAcquire);
+TEST_REGISTER(MediaDeviceAcquire)
diff --git a/test/media_device/media_device_link_test.cpp b/test/media_device/media_device_link_test.cpp
index fe7542bb..31528000 100644
--- a/test/media_device/media_device_link_test.cpp
+++ b/test/media_device/media_device_link_test.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * media_device_link_test.cpp - Tests link handling on VIMC media device
+ * Tests link handling on VIMC media device
*/
#include <iostream>
@@ -56,7 +56,7 @@ class MediaDeviceLinkTest : public MediaDeviceTest
/*
* Test if link can be consistently retrieved through the
- * different methods the media device offers.
+ * different functions the media device offers.
*/
string linkName("'Debayer A':[1] -> 'Scaler':[0]'");
MediaLink *link = media_->link("Debayer A", 1, "Scaler", 0);
@@ -219,4 +219,4 @@ class MediaDeviceLinkTest : public MediaDeviceTest
}
};
-TEST_REGISTER(MediaDeviceLinkTest);
+TEST_REGISTER(MediaDeviceLinkTest)
diff --git a/test/media_device/media_device_print_test.cpp b/test/media_device/media_device_print_test.cpp
index 5018906c..63aeed48 100644
--- a/test/media_device/media_device_print_test.cpp
+++ b/test/media_device/media_device_print_test.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2018-2019, Google Inc.
*
- * media_device_print_test.cpp - Print out media devices
+ * Print out media devices
*/
#include <iostream>
@@ -10,7 +10,7 @@
#include <sys/stat.h>
#include <unistd.h>
-#include "media_device.h"
+#include "libcamera/internal/media_device.h"
#include "test.h"
@@ -25,10 +25,6 @@ using namespace std;
*/
class MediaDevicePrintTest : public Test
{
-public:
- MediaDevicePrintTest() {}
- ~MediaDevicePrintTest() {}
-
protected:
int init() { return 0; }
int run();
@@ -151,4 +147,4 @@ int MediaDevicePrintTest::run()
return ret;
}
-TEST_REGISTER(MediaDevicePrintTest);
+TEST_REGISTER(MediaDevicePrintTest)
diff --git a/test/media_device/media_device_test.cpp b/test/media_device/media_device_test.cpp
index 1397d123..3e41d0f0 100644
--- a/test/media_device/media_device_test.cpp
+++ b/test/media_device/media_device_test.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * media_device_test.cpp - libcamera media device test base class
+ * libcamera media device test base class
*/
#include <iostream>
diff --git a/test/media_device/media_device_test.h b/test/media_device/media_device_test.h
index cdbd1484..5223b760 100644
--- a/test/media_device/media_device_test.h
+++ b/test/media_device/media_device_test.h
@@ -2,20 +2,18 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * media_device_test.h - libcamera media device test base class
+ * libcamera media device test base class
*/
-#ifndef __LIBCAMERA_MEDIADEVICE_TEST_H__
-#define __LIBCAMERA_MEDIADEVICE_TEST_H__
+
+#pragma once
#include <memory>
-#include "device_enumerator.h"
-#include "media_device.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
#include "test.h"
-using namespace libcamera;
-
class MediaDeviceTest : public Test
{
public:
@@ -25,10 +23,8 @@ public:
protected:
int init();
- std::shared_ptr<MediaDevice> media_;
+ std::shared_ptr<libcamera::MediaDevice> media_;
private:
- std::unique_ptr<DeviceEnumerator> enumerator_;
+ std::unique_ptr<libcamera::DeviceEnumerator> enumerator_;
};
-
-#endif /* __LIBCAMERA_MEDIADEVICE_TEST_H__ */
diff --git a/test/media_device/meson.build b/test/media_device/meson.build
index 6a0e4684..84966c97 100644
--- a/test/media_device/meson.build
+++ b/test/media_device/meson.build
@@ -1,22 +1,24 @@
+# SPDX-License-Identifier: CC0-1.0
+
lib_mdev_test_sources = files([
'media_device_test.cpp',
])
media_device_tests = [
- ['media_device_acquire', 'media_device_acquire.cpp'],
- ['media_device_print_test', 'media_device_print_test.cpp'],
- ['media_device_link_test', 'media_device_link_test.cpp'],
+ {'name': 'media_device_acquire', 'sources': ['media_device_acquire.cpp']},
+ {'name': 'media_device_print_test', 'sources': ['media_device_print_test.cpp']},
+ {'name': 'media_device_link_test', 'sources': ['media_device_link_test.cpp']},
]
lib_mdev_test = static_library('lib_mdev_test', lib_mdev_test_sources,
- dependencies : libcamera_dep,
+ dependencies : libcamera_private,
include_directories : test_includes_internal)
-foreach t : media_device_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : media_device_tests
+ exe = executable(test['name'], test['sources'],
+ dependencies : libcamera_private,
link_with : [test_libraries, lib_mdev_test],
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'media_device', is_parallel : false)
+ test(test['name'], exe, suite : 'media_device', is_parallel : false)
endforeach
diff --git a/test/meson.build b/test/meson.build
index 8ab58ac1..5ed052ed 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,56 +1,129 @@
+# SPDX-License-Identifier: CC0-1.0
+
+if not get_option('test')
+ test_enabled = false
+ subdir_done()
+endif
+
+test_enabled = true
+
+# When ASan is enabled, find the path to the ASan runtime needed by multiple
+# tests. This currently works with gcc only, as clang uses different file names
+# depending on the compiler version and target architecture.
+asan_enabled = false
+asan_runtime_missing = false
+
+if get_option('b_sanitize').contains('address')
+ asan_enabled = true
+
+ if cc.get_id() == 'gcc'
+ asan_runtime = run_command(cc, '-print-file-name=libasan.so', check : true).stdout().strip()
+ else
+ asan_runtime_missing = true
+ endif
+endif
+
subdir('libtest')
subdir('camera')
subdir('controls')
+subdir('gstreamer')
subdir('ipa')
subdir('ipc')
subdir('log')
subdir('media_device')
-subdir('pipeline')
subdir('process')
+subdir('py')
subdir('serialization')
subdir('stream')
+subdir('v4l2_compat')
subdir('v4l2_subdevice')
subdir('v4l2_videodevice')
public_tests = [
- ['geometry', 'geometry.cpp'],
- ['list-cameras', 'list-cameras.cpp'],
- ['signal', 'signal.cpp'],
- ['span', 'span.cpp'],
+ {'name': 'color-space', 'sources': ['color-space.cpp']},
+ {'name': 'geometry', 'sources': ['geometry.cpp']},
+ {'name': 'public-api', 'sources': ['public-api.cpp']},
+ {'name': 'signal', 'sources': ['signal.cpp']},
+ {'name': 'span', 'sources': ['span.cpp']},
+ {'name': 'transform', 'sources': ['transform.cpp']},
]
internal_tests = [
- ['byte-stream-buffer', 'byte-stream-buffer.cpp'],
- ['camera-sensor', 'camera-sensor.cpp'],
- ['event', 'event.cpp'],
- ['event-dispatcher', 'event-dispatcher.cpp'],
- ['event-thread', 'event-thread.cpp'],
- ['file-descriptor', 'file-descriptor.cpp'],
- ['message', 'message.cpp'],
- ['object', 'object.cpp'],
- ['object-invoke', 'object-invoke.cpp'],
- ['signal-threads', 'signal-threads.cpp'],
- ['threads', 'threads.cpp'],
- ['timer', 'timer.cpp'],
- ['timer-thread', 'timer-thread.cpp'],
- ['utils', 'utils.cpp'],
+ {'name': 'bayer-format', 'sources': ['bayer-format.cpp']},
+ {'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']},
+ {'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']},
+ {'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']},
+ {'name': 'event', 'sources': ['event.cpp']},
+ {'name': 'event-dispatcher', 'sources': ['event-dispatcher.cpp']},
+ {'name': 'event-thread', 'sources': ['event-thread.cpp']},
+ {'name': 'file', 'sources': ['file.cpp']},
+ {'name': 'flags', 'sources': ['flags.cpp']},
+ {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']},
+ {'name': 'message', 'sources': ['message.cpp']},
+ {'name': 'object', 'sources': ['object.cpp']},
+ {'name': 'object-delete', 'sources': ['object-delete.cpp']},
+ {'name': 'object-invoke', 'sources': ['object-invoke.cpp']},
+ {'name': 'pixel-format', 'sources': ['pixel-format.cpp']},
+ {'name': 'shared-fd', 'sources': ['shared-fd.cpp']},
+ {'name': 'signal-threads', 'sources': ['signal-threads.cpp']},
+ {'name': 'threads', 'sources': 'threads.cpp', 'dependencies': [libthreads]},
+ {'name': 'timer', 'sources': ['timer.cpp']},
+ {'name': 'timer-fail', 'sources': ['timer-fail.cpp'], 'should_fail': true},
+ {'name': 'timer-thread', 'sources': ['timer-thread.cpp']},
+ {'name': 'unique-fd', 'sources': ['unique-fd.cpp']},
+ {'name': 'utils', 'sources': ['utils.cpp']},
+ {'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']},
+]
+
+internal_non_parallel_tests = [
+ {'name': 'fence', 'sources': ['fence.cpp']},
+ {'name': 'mapped-buffer', 'sources': ['mapped-buffer.cpp']},
]
-foreach t : public_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : public_tests
+ deps = [libcamera_public]
+ if 'dependencies' in test
+ deps += test['dependencies']
+ endif
+
+ exe = executable(test['name'], test['sources'],
+ dependencies : deps,
+ implicit_include_directories : false,
link_with : test_libraries,
include_directories : test_includes_public)
- test(t[0], exe)
+ test(test['name'], exe, should_fail : test.get('should_fail', false))
+endforeach
+
+foreach test : internal_tests
+ deps = [libcamera_private]
+ if 'dependencies' in test
+ deps += test['dependencies']
+ endif
+
+ exe = executable(test['name'], test['sources'],
+ dependencies : deps,
+ implicit_include_directories : false,
+ link_with : test_libraries,
+ include_directories : test_includes_internal)
+
+ test(test['name'], exe, should_fail : test.get('should_fail', false))
endforeach
-foreach t : internal_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : internal_non_parallel_tests
+ deps = [libcamera_private]
+ if 'dependencies' in test
+ deps += test['dependencies']
+ endif
+
+ exe = executable(test['name'], test['sources'],
+ dependencies : deps,
+ implicit_include_directories : false,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe)
+ test(test['name'], exe,
+ is_parallel : false,
+ should_fail : test.get('should_fail', false))
endforeach
diff --git a/test/message.cpp b/test/message.cpp
index 478bc79d..19e6646d 100644
--- a/test/message.cpp
+++ b/test/message.cpp
@@ -2,15 +2,18 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * message.cpp - Messages test
+ * Messages test
*/
#include <chrono>
#include <iostream>
+#include <memory>
#include <thread>
-#include "message.h"
-#include "thread.h"
+#include <libcamera/base/message.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+
#include "test.h"
using namespace std;
@@ -25,8 +28,8 @@ public:
MessageReceived,
};
- MessageReceiver()
- : status_(NoMessage)
+ MessageReceiver(Object *parent = nullptr)
+ : Object(parent), status_(NoMessage)
{
}
@@ -51,23 +54,43 @@ private:
Status status_;
};
-class SlowMessageReceiver : public Object
+class RecursiveMessageReceiver : public Object
{
+public:
+ RecursiveMessageReceiver()
+ : child_(this), success_(false)
+ {
+ }
+
+ bool success() const { return success_; }
+
protected:
- void message(Message *msg)
+ void message([[maybe_unused]] Message *msg)
{
if (msg->type() != Message::None) {
Object::message(msg);
return;
}
+ child_.postMessage(std::make_unique<Message>(Message::None));
+
/*
- * Don't access any member of the object here (including the
- * vtable) as the object will be deleted by the main thread
- * while we're sleeping.
+ * If the child has already received the message, something is
+ * wrong.
*/
- this_thread::sleep_for(chrono::milliseconds(100));
+ if (child_.status() != MessageReceiver::NoMessage)
+ return;
+
+ Thread::current()->dispatchMessages(Message::None);
+
+ /* The child should now have received the message. */
+ if (child_.status() == MessageReceiver::MessageReceived)
+ success_ = true;
}
+
+private:
+ MessageReceiver child_;
+ bool success_;
};
class MessageTest : public Test
@@ -86,16 +109,19 @@ protected:
return TestFail;
}
- MessageReceiver receiver;
- receiver.moveToThread(&thread_);
+ MessageReceiver *receiver = new MessageReceiver();
+ receiver->moveToThread(&thread_);
thread_.start();
- receiver.postMessage(std::make_unique<Message>(Message::None));
+ receiver->postMessage(std::make_unique<Message>(Message::None));
this_thread::sleep_for(chrono::milliseconds(100));
- switch (receiver.status()) {
+ MessageReceiver::Status status = receiver->status();
+ receiver->deleteLater();
+
+ switch (status) {
case MessageReceiver::NoMessage:
cout << "No message received" << endl;
return TestFail;
@@ -107,17 +133,26 @@ protected:
}
/*
- * Test for races between message delivery and object deletion.
- * Failures result in assertion errors, there is no need for
- * explicit checks.
+ * Test recursive calls to Thread::dispatchMessages(). Messages
+ * should be delivered correctly, without crashes or memory
+ * leaks. Two messages need to be posted to ensure we don't only
+ * test the simple case of a queue containing a single message.
*/
- SlowMessageReceiver *slowReceiver = new SlowMessageReceiver();
- slowReceiver->moveToThread(&thread_);
- slowReceiver->postMessage(std::make_unique<Message>(Message::None));
+ RecursiveMessageReceiver *recursiveReceiver = new RecursiveMessageReceiver();
+ recursiveReceiver->moveToThread(&thread_);
+
+ recursiveReceiver->postMessage(std::make_unique<Message>(Message::None));
+ recursiveReceiver->postMessage(std::make_unique<Message>(Message::UserMessage));
this_thread::sleep_for(chrono::milliseconds(10));
- delete slowReceiver;
+ bool success = recursiveReceiver->success();
+ recursiveReceiver->deleteLater();
+
+ if (!success) {
+ cout << "Recursive message delivery failed" << endl;
+ return TestFail;
+ }
return TestPass;
}
diff --git a/test/object-delete.cpp b/test/object-delete.cpp
new file mode 100644
index 00000000..676c3970
--- /dev/null
+++ b/test/object-delete.cpp
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * Object deletion tests
+ */
+
+#include <iostream>
+
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class TestObject : public Object
+{
+public:
+ TestObject(unsigned int *count)
+ : deleteCount_(count)
+ {
+ }
+
+ ~TestObject()
+ {
+ /* Count the deletions from the correct thread. */
+ if (thread() == Thread::current())
+ (*deleteCount_)++;
+ }
+
+ unsigned int *deleteCount_;
+};
+
+class DeleterThread : public Thread
+{
+public:
+ DeleterThread(Object *obj)
+ : object_(obj)
+ {
+ }
+
+protected:
+ void run()
+ {
+ object_->deleteLater();
+ }
+
+private:
+ Object *object_;
+};
+
+class ObjectDeleteTest : public Test
+{
+protected:
+ int run()
+ {
+ /*
+ * Test that deferred deletion is executed from the object's
+ * thread, not the caller's thread.
+ */
+ unsigned int count = 0;
+ TestObject *obj = new TestObject(&count);
+
+ DeleterThread delThread(obj);
+ delThread.start();
+ delThread.wait();
+
+ Thread::current()->dispatchMessages(Message::Type::DeferredDelete);
+
+ if (count != 1) {
+ cout << "Failed to dispatch DeferredDelete (" << count << ")" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Test that multiple calls to deleteLater() delete the object
+ * once only.
+ */
+ count = 0;
+ obj = new TestObject(&count);
+ obj->deleteLater();
+ obj->deleteLater();
+
+ Thread::current()->dispatchMessages(Message::Type::DeferredDelete);
+ if (count != 1) {
+ cout << "Multiple deleteLater() failed (" << count << ")" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Test that deleteLater() works properly when called just
+ * before the object's thread exits.
+ */
+ Thread boundThread;
+ boundThread.start();
+
+ count = 0;
+ obj = new TestObject(&count);
+ obj->moveToThread(&boundThread);
+
+ obj->deleteLater();
+ boundThread.exit();
+ boundThread.wait();
+
+ if (count != 1) {
+ cout << "Object deletion right before thread exit failed (" << count << ")" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(ObjectDeleteTest)
diff --git a/test/object-invoke.cpp b/test/object-invoke.cpp
index fa162c83..def1e61e 100644
--- a/test/object-invoke.cpp
+++ b/test/object-invoke.cpp
@@ -2,17 +2,17 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * object-invoke.cpp - Cross-thread Object method invocation test
+ * Cross-thread Object method invocation test
*/
#include <iostream>
#include <thread>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/object.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
#include "test.h"
-#include "thread.h"
using namespace std;
using namespace libcamera;
@@ -49,7 +49,7 @@ public:
value_ = value;
}
- void methodWithReference(const int &value)
+ void methodWithReference([[maybe_unused]] const int &value)
{
}
diff --git a/test/object.cpp b/test/object.cpp
index 16118971..95dc1ef3 100644
--- a/test/object.cpp
+++ b/test/object.cpp
@@ -2,15 +2,14 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * object.cpp - Object tests
+ * Object tests
*/
#include <iostream>
-#include <libcamera/object.h>
-
-#include "message.h"
-#include "thread.h"
+#include <libcamera/base/message.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
#include "test.h"
diff --git a/test/pipeline/ipu3/ipu3_pipeline_test.cpp b/test/pipeline/ipu3/ipu3_pipeline_test.cpp
deleted file mode 100644
index a5c6be09..00000000
--- a/test/pipeline/ipu3/ipu3_pipeline_test.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * ipu3_pipeline_test.cpp - Intel IPU3 pipeline test
- */
-
-#include <iostream>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
-
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "media_object.h"
-#include "test.h"
-
-using namespace std;
-using namespace libcamera;
-
-/*
- * Verify that the Intel IPU3 pipeline handler gets matched and cameras
- * are enumerated correctly.
- *
- * The test is supposed to be run on an IPU3 platform, otherwise it gets
- * skipped.
- *
- * The test lists all cameras registered in the system, if any camera is
- * available at all.
- */
-class IPU3PipelineTest : public Test
-{
-protected:
- int init();
- int run();
- void cleanup();
-
-private:
- CameraManager *cameraManager_;
- unsigned int sensors_;
-};
-
-int IPU3PipelineTest::init()
-{
- unique_ptr<DeviceEnumerator> enumerator = DeviceEnumerator::create();
- if (!enumerator) {
- cerr << "Failed to create device enumerator" << endl;
- return TestFail;
- }
-
- if (enumerator->enumerate()) {
- cerr << "Failed to enumerate media devices" << endl;
- return TestFail;
- }
-
- DeviceMatch imgu_dm("ipu3-imgu");
- DeviceMatch cio2_dm("ipu3-cio2");
-
- if (!enumerator->search(imgu_dm)) {
- cerr << "Failed to find IPU3 IMGU: test skip" << endl;
- return TestSkip;
- }
-
- std::shared_ptr<MediaDevice> cio2 = enumerator->search(cio2_dm);
- if (!cio2) {
- cerr << "Failed to find IPU3 CIO2: test skip" << endl;
- return TestSkip;
- }
-
- /*
- * Camera sensor are connected to the CIO2 unit.
- * Count how many sensors are connected in the system
- * and later verify this matches the number of registered
- * cameras.
- */
- int ret = cio2->populate();
- if (ret) {
- cerr << "Failed to populate media device " << cio2->deviceNode() << endl;
- return TestFail;
- }
-
- sensors_ = 0;
- const vector<MediaEntity *> &entities = cio2->entities();
- for (MediaEntity *entity : entities) {
- if (entity->function() == MEDIA_ENT_F_CAM_SENSOR)
- sensors_++;
- }
-
- enumerator.reset(nullptr);
-
- cameraManager_ = new CameraManager();
- ret = cameraManager_->start();
- if (ret) {
- cerr << "Failed to start the CameraManager" << endl;
- return TestFail;
- }
-
- return 0;
-}
-
-int IPU3PipelineTest::run()
-{
- auto cameras = cameraManager_->cameras();
- for (const std::shared_ptr<Camera> &cam : cameras)
- cout << "Found camera '" << cam->name() << "'" << endl;
-
- if (cameras.size() != sensors_) {
- cerr << cameras.size() << " cameras registered, but " << sensors_
- << " were expected" << endl;
- return TestFail;
- }
-
- return TestPass;
-}
-
-void IPU3PipelineTest::cleanup()
-{
- cameraManager_->stop();
- delete cameraManager_;
-}
-
-TEST_REGISTER(IPU3PipelineTest)
diff --git a/test/pipeline/ipu3/meson.build b/test/pipeline/ipu3/meson.build
deleted file mode 100644
index d02927c9..00000000
--- a/test/pipeline/ipu3/meson.build
+++ /dev/null
@@ -1,12 +0,0 @@
-ipu3_test = [
- ['ipu3_pipeline_test', 'ipu3_pipeline_test.cpp'],
-]
-
-foreach t : ipu3_test
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
- link_with : test_libraries,
- include_directories : test_includes_internal)
-
- test(t[0], exe, suite : 'ipu3', is_parallel : false)
-endforeach
diff --git a/test/pipeline/meson.build b/test/pipeline/meson.build
deleted file mode 100644
index 157f789c..00000000
--- a/test/pipeline/meson.build
+++ /dev/null
@@ -1,2 +0,0 @@
-subdir('ipu3')
-subdir('rkisp1')
diff --git a/test/pipeline/rkisp1/meson.build b/test/pipeline/rkisp1/meson.build
deleted file mode 100644
index d3f97496..00000000
--- a/test/pipeline/rkisp1/meson.build
+++ /dev/null
@@ -1,12 +0,0 @@
-rkisp1_test = [
- ['rkisp1_pipeline_test', 'rkisp1_pipeline_test.cpp'],
-]
-
-foreach t : rkisp1_test
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
- link_with : test_libraries,
- include_directories : test_includes_internal)
-
- test(t[0], exe, suite : 'rkisp1', is_parallel : false)
-endforeach
diff --git a/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp b/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp
deleted file mode 100644
index d46c928f..00000000
--- a/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2020, Linaro
- *
- * Based on test/pipeline/ipu3/ipu3_pipeline_test.cpp
- *
- * rkisp1_pipeline_test.cpp - Rockchip RK3399 rkisp1 pipeline test
- */
-
-#include <iostream>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
-
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "media_object.h"
-#include "test.h"
-
-using namespace std;
-using namespace libcamera;
-
-/*
- * Verify that the RK3399 pipeline handler gets matched and cameras
- * are enumerated correctly.
- *
- * The test is supposed to be run on rockchip platform.
- *
- * The test lists all cameras registered in the system, if any camera is
- * available at all.
- */
-class RKISP1PipelineTest : public Test
-{
-protected:
- int init();
- int run();
- void cleanup();
-
-private:
- CameraManager *cameraManager_;
- unsigned int sensors_;
-};
-
-int RKISP1PipelineTest::init()
-{
- unique_ptr<DeviceEnumerator> enumerator = DeviceEnumerator::create();
- if (!enumerator) {
- cerr << "Failed to create device enumerator" << endl;
- return TestFail;
- }
-
- if (enumerator->enumerate()) {
- cerr << "Failed to enumerate media devices" << endl;
- return TestFail;
- }
-
- DeviceMatch dm("rkisp1");
-
- std::shared_ptr<MediaDevice> rkisp1 = enumerator->search(dm);
- if (!rkisp1) {
- cerr << "Failed to find rkisp1: test skip" << endl;
- return TestSkip;
- }
-
- int ret = rkisp1->populate();
- if (ret) {
- cerr << "Failed to populate media device "
- << rkisp1->deviceNode() << endl;
- return TestFail;
- }
-
- sensors_ = 0;
- const vector<MediaEntity *> &entities = rkisp1->entities();
- for (MediaEntity *entity : entities) {
- if (entity->function() == MEDIA_ENT_F_CAM_SENSOR)
- sensors_++;
- }
-
- cameraManager_ = new CameraManager();
- ret = cameraManager_->start();
- if (ret) {
- cerr << "Failed to start the CameraManager" << endl;
- return TestFail;
- }
-
- return 0;
-}
-
-int RKISP1PipelineTest::run()
-{
- auto cameras = cameraManager_->cameras();
- for (const std::shared_ptr<Camera> &cam : cameras)
- cout << "Found camera '" << cam->name() << "'" << endl;
-
- if (cameras.size() != sensors_) {
- cerr << cameras.size() << " cameras registered, but " << sensors_
- << " were expected" << endl;
- return TestFail;
- }
-
- return TestPass;
-}
-
-void RKISP1PipelineTest::cleanup()
-{
- cameraManager_->stop();
- delete cameraManager_;
-}
-
-TEST_REGISTER(RKISP1PipelineTest)
diff --git a/test/pixel-format.cpp b/test/pixel-format.cpp
new file mode 100644
index 00000000..0f364f83
--- /dev/null
+++ b/test/pixel-format.cpp
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Kaaira Gupta
+ * libcamera pixel format handling test
+ */
+
+#include <iostream>
+#include <vector>
+
+#include <libcamera/formats.h>
+#include <libcamera/pixel_format.h>
+
+#include <libcamera/base/utils.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class PixelFormatTest : public Test
+{
+protected:
+ int run()
+ {
+ std::vector<std::pair<PixelFormat, const char *>> formatsMap{
+ { formats::R8, "R8" },
+ { formats::SRGGB10_CSI2P, "SRGGB10_CSI2P" },
+ { PixelFormat(0, 0), "<INVALID>" },
+ { PixelFormat(0x20203843), "<C8 >" }
+ };
+
+ for (const auto &format : formatsMap) {
+ if ((format.first).toString() != format.second) {
+ cerr << "Failed to convert PixelFormat "
+ << utils::hex(format.first.fourcc()) << " to string"
+ << endl;
+ return TestFail;
+ }
+ }
+
+ if (PixelFormat().toString() != "<INVALID>") {
+ cerr << "Failed to convert default PixelFormat to string"
+ << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(PixelFormatTest)
diff --git a/test/process/meson.build b/test/process/meson.build
index c4d83d6c..a80dc2d9 100644
--- a/test/process/meson.build
+++ b/test/process/meson.build
@@ -1,12 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+
process_tests = [
- [ 'process_test', 'process_test.cpp' ],
+ {'name': 'process_test', 'sources': ['process_test.cpp']},
]
-foreach t : process_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
+foreach test : process_tests
+ exe = executable(test['name'], test['sources'],
+ dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'process', is_parallel : false)
+ test(test['name'], exe, suite : 'process', is_parallel : false)
endforeach
diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
index 7e7b3c2c..e9f5e7e9 100644
--- a/test/process/process_test.cpp
+++ b/test/process/process_test.cpp
@@ -2,23 +2,25 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * process_test.cpp - Process test
+ * Process test
*/
#include <iostream>
#include <unistd.h>
#include <vector>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/process.h"
-#include "process.h"
#include "test.h"
-#include "thread.h"
-#include "utils.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
class ProcessTestChild
{
@@ -50,13 +52,17 @@ protected:
args.push_back(to_string(exitCode));
proc_.finished.connect(this, &ProcessTest::procFinished);
- int ret = proc_.start("/proc/self/exe", args);
+ /* Test that kill() on an unstarted process is safe. */
+ proc_.kill();
+
+ /* Test starting the process and retrieving the exit code. */
+ int ret = proc_.start(self(), args);
if (ret) {
cerr << "failed to start process" << endl;
return TestFail;
}
- timeout.start(2000);
+ timeout.start(2000ms);
while (timeout.isRunning() && exitStatus_ == Process::NotExited)
dispatcher->processEvents();
@@ -75,12 +81,14 @@ protected:
}
private:
- void procFinished(Process *proc, enum Process::ExitStatus exitStatus, int exitCode)
+ void procFinished(enum Process::ExitStatus exitStatus, int exitCode)
{
exitStatus_ = exitStatus;
exitCode_ = exitCode;
}
+ ProcessManager processManager_;
+
Process proc_;
enum Process::ExitStatus exitStatus_;
int exitCode_;
@@ -98,5 +106,7 @@ int main(int argc, char **argv)
return child.run(status);
}
- return ProcessTest().execute();
+ ProcessTest test;
+ test.setArgs(argc, argv);
+ return test.execute();
}
diff --git a/test/public-api.cpp b/test/public-api.cpp
new file mode 100644
index 00000000..b1336f75
--- /dev/null
+++ b/test/public-api.cpp
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * Public API validation
+ */
+
+#include <libcamera/libcamera.h>
+
+#include "test.h"
+
+class PublicAPITest : public Test
+{
+ int run()
+ {
+#ifdef LIBCAMERA_BASE_PRIVATE
+#error "Public interfaces should not be exposed to LIBCAMERA_BASE_PRIVATE"
+ return TestFail;
+#else
+ return TestPass;
+#endif
+ }
+};
+
+TEST_REGISTER(PublicAPITest)
diff --git a/test/py/meson.build b/test/py/meson.build
new file mode 100644
index 00000000..b922e857
--- /dev/null
+++ b/test/py/meson.build
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: CC0-1.0
+
+if not pycamera_enabled
+ subdir_done()
+endif
+
+# If ASan is enabled, the link order runtime check will fail as Python is not
+# linked to ASan. LD_PRELOAD the ASan runtime if available, or skip the test
+# otherwise.
+
+if asan_runtime_missing
+ warning('Unable to get path to ASan runtime, Python test disabled')
+ subdir_done()
+endif
+
+py_env = environment()
+
+pymod = import('python')
+py3 = pymod.find_installation('python3')
+
+pypathdir = meson.project_build_root() / 'src' / 'py'
+py_env.append('PYTHONPATH', pypathdir)
+
+if asan_enabled
+ py_env.append('LD_PRELOAD', asan_runtime)
+
+ # Preload the C++ standard library to work around a bug in ASan when
+ # dynamically loading C++ .so modules.
+ stdlib = run_command(cxx, '-print-file-name=' + cxx_stdlib + '.so',
+ check : true).stdout().strip()
+ py_env.append('LD_PRELOAD', stdlib)
+
+ # Disable leak detection as the Python interpreter is full of leaks.
+ py_env.append('ASAN_OPTIONS', 'detect_leaks=0')
+endif
+
+test('pyunittests',
+ py3,
+ args : files('unittests.py'),
+ env : py_env,
+ suite : 'pybindings',
+ is_parallel : false)
diff --git a/test/py/unittests.py b/test/py/unittests.py
new file mode 100755
index 00000000..8cb850d4
--- /dev/null
+++ b/test/py/unittests.py
@@ -0,0 +1,365 @@
+#!/usr/bin/env python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+
+from collections import defaultdict
+import gc
+import libcamera as libcam
+import selectors
+import typing
+import unittest
+import weakref
+
+
+class BaseTestCase(unittest.TestCase):
+ def assertZero(self, a, msg=None):
+ self.assertEqual(a, 0, msg)
+
+ def assertIsAlive(self, wr, msg='object not alive'):
+ self.assertIsNotNone(wr(), msg)
+
+ def assertIsDead(self, wr, msg='object not dead'):
+ self.assertIsNone(wr(), msg)
+
+ def assertIsAllAlive(self, wr_list, msg='object not alive'):
+ self.assertTrue(all([wr() for wr in wr_list]), msg)
+
+ def assertIsAllDead(self, wr_list, msg='object not dead'):
+ self.assertTrue(all([not wr() for wr in wr_list]), msg)
+
+
+class SimpleTestMethods(BaseTestCase):
+ def test_get_ref(self):
+ cm = libcam.CameraManager.singleton()
+ wr_cm = weakref.ref(cm)
+
+ cam = cm.get('platform/vimc.0 Sensor B')
+ self.assertIsNotNone(cam)
+ wr_cam = weakref.ref(cam)
+
+ del cm
+ gc.collect()
+ self.assertIsAlive(wr_cm)
+
+ del cam
+ gc.collect()
+ self.assertIsDead(wr_cm)
+ self.assertIsDead(wr_cam)
+
+ def test_acquire_release(self):
+ cm = libcam.CameraManager.singleton()
+ cam = cm.get('platform/vimc.0 Sensor B')
+ self.assertIsNotNone(cam)
+
+ cam.acquire()
+
+ cam.release()
+
+ def test_double_acquire(self):
+ cm = libcam.CameraManager.singleton()
+ cam = cm.get('platform/vimc.0 Sensor B')
+ self.assertIsNotNone(cam)
+
+ cam.acquire()
+
+ libcam.log_set_level('Camera', 'FATAL')
+ with self.assertRaises(RuntimeError):
+ cam.acquire()
+ libcam.log_set_level('Camera', 'INFO')
+
+ cam.release()
+
+ # I expected exception here, but looks like double release works fine
+ cam.release()
+
+ def test_version(self):
+ cm = libcam.CameraManager.singleton()
+ self.assertIsInstance(cm.version, str)
+
+
+class CameraTesterBase(BaseTestCase):
+ cm: typing.Any
+ cam: typing.Any
+
+ def setUp(self):
+ self.cm = libcam.CameraManager.singleton()
+ self.cam = next((cam for cam in self.cm.cameras if 'platform/vimc' in cam.id), None)
+ if self.cam is None:
+ self.cm = None
+ self.skipTest('No vimc found')
+
+ self.cam.acquire()
+
+ self.wr_cam = weakref.ref(self.cam)
+ self.wr_cm = weakref.ref(self.cm)
+
+ def tearDown(self):
+ # If a test fails, the camera may be in running state. So always stop.
+ self.cam.stop()
+
+ self.cam.release()
+
+ self.cam = None
+ self.cm = None
+
+ self.assertIsDead(self.wr_cm)
+ self.assertIsDead(self.wr_cam)
+
+
+class AllocatorTestMethods(CameraTesterBase):
+ def test_allocator(self):
+ cam = self.cam
+
+ camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture])
+ self.assertTrue(camconfig.size == 1)
+ wr_camconfig = weakref.ref(camconfig)
+
+ streamconfig = camconfig.at(0)
+ wr_streamconfig = weakref.ref(streamconfig)
+
+ cam.configure(camconfig)
+
+ stream = streamconfig.stream
+ wr_stream = weakref.ref(stream)
+
+ # stream should keep streamconfig and camconfig alive
+ del streamconfig
+ del camconfig
+ gc.collect()
+ self.assertIsAlive(wr_camconfig)
+ self.assertIsAlive(wr_streamconfig)
+
+ allocator = libcam.FrameBufferAllocator(cam)
+ num_bufs = allocator.allocate(stream)
+ self.assertTrue(num_bufs > 0)
+ wr_allocator = weakref.ref(allocator)
+
+ buffers = allocator.buffers(stream)
+ self.assertIsNotNone(buffers)
+ del buffers
+
+ buffer = allocator.buffers(stream)[0]
+ self.assertIsNotNone(buffer)
+ wr_buffer = weakref.ref(buffer)
+
+ del allocator
+ gc.collect()
+ self.assertIsAlive(wr_buffer)
+ self.assertIsAlive(wr_allocator)
+ self.assertIsAlive(wr_stream)
+
+ del buffer
+ gc.collect()
+ self.assertIsDead(wr_buffer)
+ self.assertIsDead(wr_allocator)
+ self.assertIsAlive(wr_stream)
+
+ del stream
+ gc.collect()
+ self.assertIsDead(wr_stream)
+ self.assertIsDead(wr_camconfig)
+ self.assertIsDead(wr_streamconfig)
+
+
+class SimpleCaptureMethods(CameraTesterBase):
+ def test_blocking(self):
+ cm = self.cm
+ cam = self.cam
+
+ camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture])
+ self.assertTrue(camconfig.size == 1)
+
+ streamconfig = camconfig.at(0)
+ fmts = streamconfig.formats
+ self.assertIsNotNone(fmts)
+ fmts = None
+
+ cam.configure(camconfig)
+
+ stream = streamconfig.stream
+
+ allocator = libcam.FrameBufferAllocator(cam)
+ num_bufs = allocator.allocate(stream)
+ self.assertTrue(num_bufs > 0)
+
+ num_bufs = len(allocator.buffers(stream))
+
+ reqs = []
+ for i in range(num_bufs):
+ req = cam.create_request(i)
+ self.assertIsNotNone(req)
+
+ buffer = allocator.buffers(stream)[i]
+ req.add_buffer(stream, buffer)
+
+ reqs.append(req)
+
+ buffer = None
+
+ cam.start()
+
+ for req in reqs:
+ cam.queue_request(req)
+
+ reqs = None
+ gc.collect()
+
+ sel = selectors.DefaultSelector()
+ sel.register(cm.event_fd, selectors.EVENT_READ)
+
+ reqs = []
+
+ while True:
+ events = sel.select()
+ if not events:
+ continue
+
+ ready_reqs = cm.get_ready_requests()
+
+ reqs += ready_reqs
+
+ if len(reqs) == num_bufs:
+ break
+
+ for i, req in enumerate(reqs):
+ self.assertTrue(i == req.cookie)
+
+ reqs = None
+ gc.collect()
+
+ cam.stop()
+
+ def test_select(self):
+ cm = self.cm
+ cam = self.cam
+
+ camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture])
+ self.assertTrue(camconfig.size == 1)
+
+ streamconfig = camconfig.at(0)
+ fmts = streamconfig.formats
+ self.assertIsNotNone(fmts)
+ fmts = None
+
+ cam.configure(camconfig)
+
+ stream = streamconfig.stream
+
+ allocator = libcam.FrameBufferAllocator(cam)
+ num_bufs = allocator.allocate(stream)
+ self.assertTrue(num_bufs > 0)
+
+ num_bufs = len(allocator.buffers(stream))
+
+ reqs = []
+ for i in range(num_bufs):
+ req = cam.create_request(i)
+ self.assertIsNotNone(req)
+
+ buffer = allocator.buffers(stream)[i]
+ req.add_buffer(stream, buffer)
+
+ reqs.append(req)
+
+ buffer = None
+
+ cam.start()
+
+ for req in reqs:
+ cam.queue_request(req)
+
+ reqs = None
+ gc.collect()
+
+ sel = selectors.DefaultSelector()
+ sel.register(cm.event_fd, selectors.EVENT_READ)
+
+ reqs = []
+
+ running = True
+ while running:
+ events = sel.select()
+ for _ in events:
+ ready_reqs = cm.get_ready_requests()
+
+ reqs += ready_reqs
+
+ if len(reqs) == num_bufs:
+ running = False
+
+ self.assertTrue(len(reqs) == num_bufs)
+
+ for i, req in enumerate(reqs):
+ self.assertTrue(i == req.cookie)
+
+ reqs = None
+ gc.collect()
+
+ cam.stop()
+
+
+# Recursively expand slist's objects into olist, using seen to track already
+# processed objects.
+def _getr(slist, olist, seen):
+ for e in slist:
+ if id(e) in seen:
+ continue
+ seen.add(id(e))
+ olist.append(e)
+ tl = gc.get_referents(e)
+ if tl:
+ _getr(tl, olist, seen)
+
+
+def get_all_objects(ignored=[]):
+ gcl = gc.get_objects()
+ olist = []
+ seen = set()
+
+ seen.add(id(gcl))
+ seen.add(id(olist))
+ seen.add(id(seen))
+ seen.update(set([id(o) for o in ignored]))
+
+ _getr(gcl, olist, seen)
+
+ return olist
+
+
+def create_type_count_map(olist):
+ map = defaultdict(int)
+ for o in olist:
+ map[type(o)] += 1
+ return map
+
+
+def diff_type_count_maps(before, after):
+ return [(k, after[k] - before[k]) for k in after if after[k] != before[k]]
+
+
+if __name__ == '__main__':
+ # \todo This is an attempt to see the Python objects that are not collected,
+ # but this doesn't work very well, as things always leak a bit.
+ test_leaks = False
+
+ if test_leaks:
+ gc.unfreeze()
+ gc.collect()
+
+ obs_before = get_all_objects()
+
+ unittest.main(exit=False)
+
+ if test_leaks:
+ gc.unfreeze()
+ gc.collect()
+
+ obs_after = get_all_objects([obs_before]) # type: ignore
+
+ before = create_type_count_map(obs_before) # type: ignore
+ after = create_type_count_map(obs_after)
+
+ leaks = diff_type_count_maps(before, after)
+ if len(leaks) > 0:
+ print(leaks)
diff --git a/test/serialization/control_serialization.cpp b/test/serialization/control_serialization.cpp
index 2989b527..06c572b7 100644
--- a/test/serialization/control_serialization.cpp
+++ b/test/serialization/control_serialization.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * control_serialization.cpp - Serialize and deserialize controls
+ * Serialize and deserialize controls
*/
#include <iostream>
@@ -11,8 +11,9 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
-#include "byte_stream_buffer.h"
-#include "control_serializer.h"
+#include "libcamera/internal/byte_stream_buffer.h"
+#include "libcamera/internal/control_serializer.h"
+
#include "serialization_test.h"
#include "test.h"
@@ -29,8 +30,8 @@ protected:
int run() override
{
- ControlSerializer serializer;
- ControlSerializer deserializer;
+ ControlSerializer serializer(ControlSerializer::Role::Proxy);
+ ControlSerializer deserializer(ControlSerializer::Role::Worker);
std::vector<uint8_t> infoData;
std::vector<uint8_t> listData;
@@ -42,9 +43,9 @@ protected:
const ControlInfoMap &infoMap = camera_->controls();
ControlList list(infoMap);
- list.set(controls::Brightness, 255);
- list.set(controls::Contrast, 128);
- list.set(controls::Saturation, 50);
+ list.set(controls::Brightness, 0.5f);
+ list.set(controls::Contrast, 1.2f);
+ list.set(controls::Saturation, 0.2f);
/*
* Serialize the control list, this should fail as the control
@@ -139,6 +140,15 @@ protected:
return TestFail;
}
+ /* Make sure control limits looked up by id are not changed. */
+ const ControlInfo &newLimits = newInfoMap.at(&controls::Brightness);
+ const ControlInfo &initialLimits = infoMap.at(&controls::Brightness);
+ if (newLimits.min() != initialLimits.min() ||
+ newLimits.max() != initialLimits.max()) {
+ cerr << "The brightness control limits have changed" << endl;
+ return TestFail;
+ }
+
/* Deserialize the control list and verify the contents. */
buffer = ByteStreamBuffer(const_cast<const uint8_t *>(listData.data()),
listData.size());
diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp
new file mode 100644
index 00000000..dd696885
--- /dev/null
+++ b/test/serialization/generated_serializer/generated_serializer_test.cpp
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * Test generated serializer
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <tuple>
+#include <vector>
+
+#include "test.h"
+
+#include "test_ipa_interface.h"
+#include "test_ipa_serializer.h"
+
+using namespace std;
+using namespace libcamera;
+
+class IPAGeneratedSerializerTest : public Test
+{
+protected:
+ int init() override
+ {
+ return TestPass;
+ }
+
+ int run() override
+ {
+
+#define TEST_FIELD_EQUALITY(struct1, struct2, field) \
+if (struct1.field != struct2.field) { \
+ cerr << #field << " field incorrect: expected \"" \
+ << t.field << "\", got \"" << u.field << "\"" << endl;\
+ return TestFail; \
+}
+
+#define TEST_SCOPED_ENUM_EQUALITY(struct1, struct2, field) \
+if (struct1.field != struct2.field) { \
+ cerr << #field << " field incorrect" << endl; \
+ return TestFail; \
+}
+
+
+ ipa::test::TestStruct t, u;
+
+ t.m = {
+ { "a", "z" },
+ { "b", "z" },
+ { "c", "z" },
+ { "d", "z" },
+ { "e", "z" },
+ };
+
+ t.a = { "a", "b", "c", "d", "e" };
+
+ t.s1 = "hello world";
+ t.s2 = "goodbye";
+ t.s3 = "lorem ipsum";
+ t.i = 58527;
+ t.c = ipa::test::IPAOperationInit;
+ t.e = ipa::test::ErrorFlags::Error1;
+
+ Flags<ipa::test::ErrorFlags> flags;
+ flags |= ipa::test::ErrorFlags::Error1;
+ flags |= ipa::test::ErrorFlags::Error2;
+ t.f = flags;
+
+ std::vector<uint8_t> serialized;
+
+ std::tie(serialized, ignore) =
+ IPADataSerializer<ipa::test::TestStruct>::serialize(t);
+
+ u = IPADataSerializer<ipa::test::TestStruct>::deserialize(serialized);
+
+ if (!equals(t.m, u.m))
+ return TestFail;
+
+ if (!equals(t.a, u.a))
+ return TestFail;
+
+ TEST_FIELD_EQUALITY(t, u, s1);
+ TEST_FIELD_EQUALITY(t, u, s2);
+ TEST_FIELD_EQUALITY(t, u, s3);
+ TEST_FIELD_EQUALITY(t, u, i);
+ TEST_FIELD_EQUALITY(t, u, c);
+
+ TEST_SCOPED_ENUM_EQUALITY(t, u, e);
+ TEST_SCOPED_ENUM_EQUALITY(t, u, f);
+
+ /* Test vector of generated structs */
+ std::vector<ipa::test::TestStruct> v = { t, u };
+ std::vector<ipa::test::TestStruct> w;
+
+ std::tie(serialized, ignore) =
+ IPADataSerializer<vector<ipa::test::TestStruct>>::serialize(v);
+
+ w = IPADataSerializer<vector<ipa::test::TestStruct>>::deserialize(serialized);
+
+ if (!equals(v[0].m, w[0].m) ||
+ !equals(v[1].m, w[1].m))
+ return TestFail;
+
+ if (!equals(v[0].a, w[0].a) ||
+ !equals(v[1].a, w[1].a))
+ return TestFail;
+
+ TEST_FIELD_EQUALITY(v[0], w[0], s1);
+ TEST_FIELD_EQUALITY(v[0], w[0], s2);
+ TEST_FIELD_EQUALITY(v[0], w[0], s3);
+ TEST_FIELD_EQUALITY(v[0], w[0], i);
+ TEST_FIELD_EQUALITY(v[0], w[0], c);
+
+ TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], e);
+ TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], f);
+
+ TEST_FIELD_EQUALITY(v[1], w[1], s1);
+ TEST_FIELD_EQUALITY(v[1], w[1], s2);
+ TEST_FIELD_EQUALITY(v[1], w[1], s3);
+ TEST_FIELD_EQUALITY(v[1], w[1], i);
+ TEST_FIELD_EQUALITY(v[1], w[1], c);
+
+ TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], e);
+ TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], f);
+
+ return TestPass;
+ }
+
+private:
+ bool equals(const map<string, string> &lhs, const map<string, string> &rhs)
+ {
+ bool eq = lhs.size() == rhs.size() &&
+ equal(lhs.begin(), lhs.end(), rhs.begin(),
+ [](auto &a, auto &b) { return a.first == b.first &&
+ a.second == b.second; });
+
+ if (eq)
+ return true;
+
+ cerr << "lhs:" << endl;
+ for (const auto &pair : lhs)
+ cerr << "- " << pair.first << ": "
+ << pair.second << endl;
+
+ cerr << "rhs:" << endl;
+ for (const auto &pair : rhs)
+ cerr << "- " << pair.first << ": "
+ << pair.second << endl;
+
+ return false;
+ }
+
+ bool equals(const vector<string> &lhs, const vector<string> &rhs)
+ {
+ bool eq = lhs.size() == rhs.size();
+
+ if (!eq) {
+ cerr << "sizes not equal" << endl;
+ return false;
+ }
+
+ for (unsigned int i = 0; i < lhs.size(); i++)
+ if (lhs[i] != rhs[i])
+ eq = false;
+
+ if (eq)
+ return true;
+
+ cerr << "lhs:" << endl;
+ for (const auto &str : lhs)
+ cerr << "- " << str << endl;
+
+ cerr << "rhs:" << endl;
+ for (const auto &str : rhs)
+ cerr << "- " << str << endl;
+
+ return false;
+ }
+};
+
+TEST_REGISTER(IPAGeneratedSerializerTest)
diff --git a/test/serialization/generated_serializer/include/libcamera/ipa/meson.build b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build
new file mode 100644
index 00000000..ae08e9be
--- /dev/null
+++ b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: CC0-1.0
+
+# test.mojom-module
+mojom = custom_target('test_mojom_module',
+ input : 'test.mojom',
+ output : 'test.mojom-module',
+ command : [
+ mojom_parser,
+ '--output-root', meson.project_build_root(),
+ '--input-root', meson.project_source_root(),
+ '--mojoms', '@INPUT@'
+ ],
+ env : py_build_env)
+
+# test_ipa_interface.h
+generated_test_header = custom_target('test_ipa_interface_h',
+ input : mojom,
+ output : 'test_ipa_interface.h',
+ depends : mojom_templates,
+ command : [
+ mojom_generator, 'generate',
+ '-g', 'libcamera',
+ '--bytecode_path', mojom_templates_dir,
+ '--libcamera_generate_header',
+ '--libcamera_output_path=@OUTPUT@',
+ './' +'@INPUT@'
+ ],
+ env : py_build_env)
+
+# test_ipa_serializer.h
+generated_test_serializer = custom_target('test_ipa_serializer_h',
+ input : mojom,
+ output : 'test_ipa_serializer.h',
+ depends : mojom_templates,
+ command : [
+ mojom_generator, 'generate',
+ '-g', 'libcamera',
+ '--bytecode_path', mojom_templates_dir,
+ '--libcamera_generate_serializer',
+ '--libcamera_output_path=@OUTPUT@',
+ './' +'@INPUT@'
+ ],
+ env : py_build_env)
diff --git a/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom b/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom
new file mode 100644
index 00000000..91c31642
--- /dev/null
+++ b/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+module ipa.test;
+
+enum IPAOperationCode {
+ IPAOperationNone,
+ IPAOperationInit,
+ IPAOperationStart,
+ IPAOperationStop,
+};
+
+[scopedEnum] enum ErrorFlags {
+ Error1 = 0x1,
+ Error2 = 0x2,
+ Error3 = 0x4,
+ Error4 = 0x8,
+};
+
+struct IPASettings {};
+
+struct TestStruct {
+ map<string, string> m;
+ array<string> a;
+ string s1;
+ string s2;
+ int32 i;
+ string s3;
+ IPAOperationCode c;
+ ErrorFlags e;
+ [flags] ErrorFlags f;
+};
+
+interface IPATestInterface {
+ init(IPASettings settings) => (int32 ret);
+ start() => (int32 ret);
+ stop();
+
+ test(TestStruct s);
+};
+
+interface IPATestEventInterface {
+ dummyEvent(uint32 val);
+};
diff --git a/test/serialization/generated_serializer/meson.build b/test/serialization/generated_serializer/meson.build
new file mode 100644
index 00000000..9fb9cd1d
--- /dev/null
+++ b/test/serialization/generated_serializer/meson.build
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: CC0-1.0
+
+subdir('include/libcamera/ipa')
+
+exe = executable('generated_serializer_test',
+ [
+ 'generated_serializer_test.cpp',
+ generated_test_header,
+ generated_test_serializer,
+ ],
+ dependencies : libcamera_private,
+ link_with : test_libraries,
+ include_directories : [
+ test_includes_internal,
+ './include',
+ ])
+
+test('generated_serializer_test', exe,
+ suite : 'generated_serializer', is_parallel : false)
diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp
new file mode 100644
index 00000000..aea63c73
--- /dev/null
+++ b/test/serialization/ipa_data_serializer_test.cpp
@@ -0,0 +1,436 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * Test serializing/deserializing with IPADataSerializer
+ */
+
+#include <algorithm>
+#include <cxxabi.h>
+#include <fcntl.h>
+#include <iostream>
+#include <limits>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <tuple>
+#include <unistd.h>
+#include <vector>
+
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "libcamera/internal/ipa_data_serializer.h"
+
+#include "serialization_test.h"
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+static const ControlInfoMap Controls = ControlInfoMap({
+ { &controls::AeEnable, ControlInfo(false, true) },
+ { &controls::ExposureTime, ControlInfo(0, 999999) },
+ { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },
+ { &controls::ColourGains, ControlInfo(0.0f, 32.0f) },
+ { &controls::Brightness, ControlInfo(-1.0f, 1.0f) },
+ }, controls::controls);
+
+namespace libcamera {
+
+static bool operator==(const ControlInfoMap &lhs, const ControlInfoMap &rhs)
+{
+ return SerializationTest::equals(lhs, rhs);
+}
+
+} /* namespace libcamera */
+
+template<typename T>
+int testPodSerdes(T in)
+{
+ std::vector<uint8_t> buf;
+ std::vector<SharedFD> fds;
+
+ std::tie(buf, fds) = IPADataSerializer<T>::serialize(in);
+ T out = IPADataSerializer<T>::deserialize(buf, fds);
+ if (in == out)
+ return TestPass;
+
+ char *name = abi::__cxa_demangle(typeid(T).name(), nullptr,
+ nullptr, nullptr);
+ cerr << "Deserialized " << name << " doesn't match original" << endl;
+ free(name);
+ return TestFail;
+}
+
+template<typename T>
+int testVectorSerdes(const std::vector<T> &in,
+ ControlSerializer *cs = nullptr)
+{
+ std::vector<uint8_t> buf;
+ std::vector<SharedFD> fds;
+
+ std::tie(buf, fds) = IPADataSerializer<std::vector<T>>::serialize(in, cs);
+ std::vector<T> out = IPADataSerializer<std::vector<T>>::deserialize(buf, fds, cs);
+ if (in == out)
+ return TestPass;
+
+ char *name = abi::__cxa_demangle(typeid(T).name(), nullptr,
+ nullptr, nullptr);
+ cerr << "Deserialized std::vector<" << name
+ << "> doesn't match original" << endl;
+ free(name);
+ return TestFail;
+}
+
+template<typename K, typename V>
+int testMapSerdes(const std::map<K, V> &in,
+ ControlSerializer *cs = nullptr)
+{
+ std::vector<uint8_t> buf;
+ std::vector<SharedFD> fds;
+
+ std::tie(buf, fds) = IPADataSerializer<std::map<K, V>>::serialize(in, cs);
+ std::map<K, V> out = IPADataSerializer<std::map<K, V>>::deserialize(buf, fds, cs);
+ if (in == out)
+ return TestPass;
+
+ char *nameK = abi::__cxa_demangle(typeid(K).name(), nullptr,
+ nullptr, nullptr);
+ char *nameV = abi::__cxa_demangle(typeid(V).name(), nullptr,
+ nullptr, nullptr);
+ cerr << "Deserialized std::map<" << nameK << ", " << nameV
+ << "> doesn't match original" << endl;
+ free(nameK);
+ free(nameV);
+ return TestFail;
+}
+
+class IPADataSerializerTest : public CameraTest, public Test
+{
+public:
+ IPADataSerializerTest()
+ : CameraTest("platform/vimc.0 Sensor B")
+ {
+ }
+
+protected:
+ int init() override
+ {
+ return status_;
+ }
+
+ int run() override
+ {
+ int ret;
+
+ ret = testControls();
+ if (ret != TestPass)
+ return ret;
+
+ ret = testVector();
+ if (ret != TestPass)
+ return ret;
+
+ ret = testMap();
+ if (ret != TestPass)
+ return ret;
+
+ ret = testPod();
+ if (ret != TestPass)
+ return ret;
+
+ return TestPass;
+ }
+
+private:
+ ControlList generateControlList(const ControlInfoMap &infoMap)
+ {
+ /* Create a control list with three controls. */
+ ControlList list(infoMap);
+
+ list.set(controls::Brightness, 0.5f);
+ list.set(controls::Contrast, 1.2f);
+ list.set(controls::Saturation, 0.2f);
+
+ return list;
+ }
+
+ int testControls()
+ {
+ ControlSerializer cs(ControlSerializer::Role::Proxy);
+
+ const ControlInfoMap &infoMap = camera_->controls();
+ ControlList list = generateControlList(infoMap);
+
+ std::vector<uint8_t> infoMapBuf;
+ std::tie(infoMapBuf, std::ignore) =
+ IPADataSerializer<ControlInfoMap>::serialize(infoMap, &cs);
+
+ std::vector<uint8_t> listBuf;
+ std::tie(listBuf, std::ignore) =
+ IPADataSerializer<ControlList>::serialize(list, &cs);
+
+ const ControlInfoMap infoMapOut =
+ IPADataSerializer<ControlInfoMap>::deserialize(infoMapBuf, &cs);
+
+ ControlList listOut = IPADataSerializer<ControlList>::deserialize(listBuf, &cs);
+
+ if (!SerializationTest::equals(infoMap, infoMapOut)) {
+ cerr << "Deserialized map doesn't match original" << endl;
+ return TestFail;
+ }
+
+ if (!SerializationTest::equals(list, listOut)) {
+ cerr << "Deserialized list doesn't match original" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ int testVector()
+ {
+ ControlSerializer cs(ControlSerializer::Role::Proxy);
+
+ /*
+ * We don't test SharedFD serdes because it dup()s, so we
+ * can't check for equality.
+ */
+ std::vector<uint8_t> vecUint8 = { 1, 2, 3, 4, 5, 6 };
+ std::vector<uint16_t> vecUint16 = { 1, 2, 3, 4, 5, 6 };
+ std::vector<uint32_t> vecUint32 = { 1, 2, 3, 4, 5, 6 };
+ std::vector<uint64_t> vecUint64 = { 1, 2, 3, 4, 5, 6 };
+ std::vector<int8_t> vecInt8 = { 1, 2, 3, -4, 5, -6 };
+ std::vector<int16_t> vecInt16 = { 1, 2, 3, -4, 5, -6 };
+ std::vector<int32_t> vecInt32 = { 1, 2, 3, -4, 5, -6 };
+ std::vector<int64_t> vecInt64 = { 1, 2, 3, -4, 5, -6 };
+ std::vector<float> vecFloat = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
+ std::vector<double> vecDouble = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
+ std::vector<bool> vecBool = { true, true, false, false, true, false };
+ std::vector<std::string> vecString = { "foo", "bar", "baz" };
+ std::vector<ControlInfoMap> vecControlInfoMap = {
+ camera_->controls(),
+ Controls,
+ };
+
+ std::vector<uint8_t> buf;
+ std::vector<SharedFD> fds;
+
+ if (testVectorSerdes(vecUint8) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecUint16) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecUint32) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecUint64) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecInt8) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecInt16) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecInt32) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecInt64) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecFloat) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecDouble) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecBool) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecString) != TestPass)
+ return TestFail;
+
+ if (testVectorSerdes(vecControlInfoMap, &cs) != TestPass)
+ return TestFail;
+
+ return TestPass;
+ }
+
+ int testMap()
+ {
+ ControlSerializer cs(ControlSerializer::Role::Proxy);
+
+ /*
+ * Realistically, only string and integral keys.
+ * Test simple, complex, and nested compound value.
+ */
+ std::map<uint64_t, std::string> mapUintStr =
+ { { 101, "foo" }, { 102, "bar" }, { 103, "baz" } };
+ std::map<int64_t, std::string> mapIntStr =
+ { { 101, "foo" }, { -102, "bar" }, { -103, "baz" } };
+ std::map<std::string, std::string> mapStrStr =
+ { { "a", "foo" }, { "b", "bar" }, { "c", "baz" } };
+ std::map<uint64_t, ControlInfoMap> mapUintCIM =
+ { { 201, camera_->controls() }, { 202, Controls } };
+ std::map<int64_t, ControlInfoMap> mapIntCIM =
+ { { 201, camera_->controls() }, { -202, Controls } };
+ std::map<std::string, ControlInfoMap> mapStrCIM =
+ { { "a", camera_->controls() }, { "b", Controls } };
+ std::map<uint64_t, std::vector<uint8_t>> mapUintBVec =
+ { { 301, { 1, 2, 3 } }, { 302, { 4, 5, 6 } }, { 303, { 7, 8, 9 } } };
+ std::map<int64_t, std::vector<uint8_t>> mapIntBVec =
+ { { 301, { 1, 2, 3 } }, { -302, { 4, 5, 6} }, { -303, { 7, 8, 9 } } };
+ std::map<std::string, std::vector<uint8_t>> mapStrBVec =
+ { { "a", { 1, 2, 3 } }, { "b", { 4, 5, 6 } }, { "c", { 7, 8, 9 } } };
+
+ std::vector<uint8_t> buf;
+ std::vector<SharedFD> fds;
+
+ if (testMapSerdes(mapUintStr) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapIntStr) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapStrStr) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapUintCIM, &cs) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapIntCIM, &cs) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapStrCIM, &cs) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapUintBVec) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapIntBVec) != TestPass)
+ return TestFail;
+
+ if (testMapSerdes(mapStrBVec) != TestPass)
+ return TestFail;
+
+ return TestPass;
+ }
+
+ int testPod()
+ {
+ uint32_t u32min = std::numeric_limits<uint32_t>::min();
+ uint32_t u32max = std::numeric_limits<uint32_t>::max();
+ uint32_t u32one = 1;
+ int32_t i32min = std::numeric_limits<int32_t>::min();
+ int32_t i32max = std::numeric_limits<int32_t>::max();
+ int32_t i32one = 1;
+
+ uint64_t u64min = std::numeric_limits<uint64_t>::min();
+ uint64_t u64max = std::numeric_limits<uint64_t>::max();
+ uint64_t u64one = 1;
+ int64_t i64min = std::numeric_limits<int64_t>::min();
+ int64_t i64max = std::numeric_limits<int64_t>::max();
+ int64_t i64one = 1;
+
+ float flow = std::numeric_limits<float>::lowest();
+ float fmin = std::numeric_limits<float>::min();
+ float fmax = std::numeric_limits<float>::max();
+ float falmostOne = 1 + 1.0e-37;
+ double dlow = std::numeric_limits<double>::lowest();
+ double dmin = std::numeric_limits<double>::min();
+ double dmax = std::numeric_limits<double>::max();
+ double dalmostOne = 1 + 1.0e-307;
+
+ bool t = true;
+ bool f = false;
+
+ std::stringstream ss;
+ for (unsigned int i = 0; i < (1 << 11); i++)
+ ss << "0123456789";
+
+ std::string strLong = ss.str();
+ std::string strEmpty = "";
+
+ std::vector<uint8_t> buf;
+ std::vector<SharedFD> fds;
+
+ if (testPodSerdes(u32min) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(u32max) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(u32one) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(i32min) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(i32max) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(i32one) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(u64min) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(u64max) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(u64one) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(i64min) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(i64max) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(i64one) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(flow) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(fmin) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(fmax) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(falmostOne) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(dlow) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(dmin) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(dmax) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(dalmostOne) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(t) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(f) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(strLong) != TestPass)
+ return TestFail;
+
+ if (testPodSerdes(strEmpty) != TestPass)
+ return TestFail;
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(IPADataSerializerTest)
diff --git a/test/serialization/meson.build b/test/serialization/meson.build
index d78d92e6..a6e8d793 100644
--- a/test/serialization/meson.build
+++ b/test/serialization/meson.build
@@ -1,11 +1,16 @@
+# SPDX-License-Identifier: CC0-1.0
+
+subdir('generated_serializer')
+
serialization_tests = [
- [ 'control_serialization', 'control_serialization.cpp' ],
+ {'name': 'control_serialization', 'sources': ['control_serialization.cpp']},
+ {'name': 'ipa_data_serializer_test', 'sources': ['ipa_data_serializer_test.cpp']},
]
-foreach t : serialization_tests
- exe = executable(t[0], [t[1], 'serialization_test.cpp'],
- dependencies : libcamera_dep,
+foreach test : serialization_tests
+ exe = executable(test['name'], test['sources'], 'serialization_test.cpp',
+ dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'serialization', is_parallel : true)
+ test(test['name'], exe, suite : 'serialization', is_parallel : false)
endforeach
diff --git a/test/serialization/serialization_test.cpp b/test/serialization/serialization_test.cpp
index 11d0f0f3..af9969fd 100644
--- a/test/serialization/serialization_test.cpp
+++ b/test/serialization/serialization_test.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * serialization_test.cpp - Base class for serialization tests
+ * Base class for serialization tests
*/
#include "serialization_test.h"
diff --git a/test/serialization/serialization_test.h b/test/serialization/serialization_test.h
index fe77221e..760e3721 100644
--- a/test/serialization/serialization_test.h
+++ b/test/serialization/serialization_test.h
@@ -2,10 +2,10 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * serialization_test.h - Base class for serialization tests
+ * Base class for serialization tests
*/
-#ifndef __LIBCAMERA_SERIALIZATION_TEST_H__
-#define __LIBCAMERA_SERIALIZATION_TEST_H__
+
+#pragma once
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
@@ -14,20 +14,16 @@
#include "camera_test.h"
#include "test.h"
-using namespace libcamera;
-
class SerializationTest : public CameraTest, public Test
{
public:
SerializationTest()
- : CameraTest("VIMC Sensor B")
+ : CameraTest("platform/vimc.0 Sensor B")
{
}
- static bool equals(const ControlInfoMap &lhs,
- const ControlInfoMap &rhs);
- static bool equals(const ControlList &lhs,
- const ControlList &rhs);
+ static bool equals(const libcamera::ControlInfoMap &lhs,
+ const libcamera::ControlInfoMap &rhs);
+ static bool equals(const libcamera::ControlList &lhs,
+ const libcamera::ControlList &rhs);
};
-
-#endif /* __LIBCAMERA_SERIALIZATION_TEST_H__ */
diff --git a/test/file-descriptor.cpp b/test/shared-fd.cpp
index e467f3a7..57199dfe 100644
--- a/test/file-descriptor.cpp
+++ b/test/shared-fd.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * file_descriptor.cpp - FileDescriptor test
+ * SharedFD test
*/
#include <fcntl.h>
@@ -11,15 +11,15 @@
#include <sys/types.h>
#include <unistd.h>
-#include <libcamera/file_descriptor.h>
+#include <libcamera/base/shared_fd.h>
+#include <libcamera/base/utils.h>
#include "test.h"
-#include "utils.h"
using namespace libcamera;
using namespace std;
-class FileDescriptorTest : public Test
+class SharedFDTest : public Test
{
protected:
int init()
@@ -43,10 +43,10 @@ protected:
int run()
{
- /* Test creating empty FileDescriptor. */
- desc1_ = new FileDescriptor();
+ /* Test creating empty SharedFD. */
+ desc1_ = new SharedFD();
- if (desc1_->fd() != -1) {
+ if (desc1_->get() != -1) {
std::cout << "Failed fd numerical check (default constructor)"
<< std::endl;
return TestFail;
@@ -55,42 +55,77 @@ protected:
delete desc1_;
desc1_ = nullptr;
- /* Test creating FileDescriptor from numerical file descriptor. */
- desc1_ = new FileDescriptor(fd_);
- if (desc1_->fd() == fd_) {
- std::cout << "Failed fd numerical check (int constructor)"
+ /*
+ * Test creating SharedFD by copying numerical file
+ * descriptor.
+ */
+ desc1_ = new SharedFD(fd_);
+ if (desc1_->get() == fd_) {
+ std::cout << "Failed fd numerical check (lvalue ref constructor)"
<< std::endl;
return TestFail;
}
- if (!isValidFd(fd_) || !isValidFd(desc1_->fd())) {
- std::cout << "Failed fd validity after construction (int constructor)"
+ if (!isValidFd(fd_) || !isValidFd(desc1_->get())) {
+ std::cout << "Failed fd validity after construction (lvalue ref constructor)"
<< std::endl;
return TestFail;
}
- int fd = desc1_->fd();
+ int fd = desc1_->get();
delete desc1_;
desc1_ = nullptr;
if (!isValidFd(fd_) || isValidFd(fd)) {
- std::cout << "Failed fd validity after destruction (int constructor)"
+ std::cout << "Failed fd validity after destruction (lvalue ref constructor)"
<< std::endl;
return TestFail;
}
- /* Test creating FileDescriptor from other FileDescriptor. */
- desc1_ = new FileDescriptor(fd_);
- desc2_ = new FileDescriptor(*desc1_);
+ /*
+ * Test creating SharedFD by taking ownership of
+ * numerical file descriptor.
+ */
+ int dupFd = dup(fd_);
+ int dupFdCopy = dupFd;
- if (desc1_->fd() == fd_ || desc2_->fd() == fd_ || desc1_->fd() != desc2_->fd()) {
+ desc1_ = new SharedFD(std::move(dupFd));
+ if (desc1_->get() != dupFdCopy) {
+ std::cout << "Failed fd numerical check (rvalue ref constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (dupFd != -1 || !isValidFd(fd_) || !isValidFd(desc1_->get())) {
+ std::cout << "Failed fd validity after construction (rvalue ref constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ fd = desc1_->get();
+
+ delete desc1_;
+ desc1_ = nullptr;
+
+ if (!isValidFd(fd_) || isValidFd(fd)) {
+ std::cout << "Failed fd validity after destruction (rvalue ref constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test creating SharedFD from other SharedFD. */
+ desc1_ = new SharedFD(fd_);
+ desc2_ = new SharedFD(*desc1_);
+
+ if (desc1_->get() == fd_ || desc2_->get() == fd_ ||
+ desc1_->get() != desc2_->get()) {
std::cout << "Failed fd numerical check (copy constructor)"
<< std::endl;
return TestFail;
}
- if (!isValidFd(desc1_->fd()) || !isValidFd(desc2_->fd())) {
+ if (!isValidFd(desc1_->get()) || !isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after construction (copy constructor)"
<< std::endl;
return TestFail;
@@ -99,7 +134,7 @@ protected:
delete desc1_;
desc1_ = nullptr;
- if (!isValidFd(desc2_->fd())) {
+ if (!isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after destruction (copy constructor)"
<< std::endl;
return TestFail;
@@ -108,18 +143,18 @@ protected:
delete desc2_;
desc2_ = nullptr;
- /* Test creating FileDescriptor by taking over other FileDescriptor. */
- desc1_ = new FileDescriptor(fd_);
- fd = desc1_->fd();
- desc2_ = new FileDescriptor(std::move(*desc1_));
+ /* Test creating SharedFD by taking over other SharedFD. */
+ desc1_ = new SharedFD(fd_);
+ fd = desc1_->get();
+ desc2_ = new SharedFD(std::move(*desc1_));
- if (desc1_->fd() != -1 || desc2_->fd() != fd) {
+ if (desc1_->get() != -1 || desc2_->get() != fd) {
std::cout << "Failed fd numerical check (move constructor)"
<< std::endl;
return TestFail;
}
- if (!isValidFd(desc2_->fd())) {
+ if (!isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after construction (move constructor)"
<< std::endl;
return TestFail;
@@ -130,20 +165,20 @@ protected:
delete desc2_;
desc2_ = nullptr;
- /* Test creating FileDescriptor by copy assignment. */
- desc1_ = new FileDescriptor();
- desc2_ = new FileDescriptor(fd_);
+ /* Test creating SharedFD by copy assignment. */
+ desc1_ = new SharedFD();
+ desc2_ = new SharedFD(fd_);
- fd = desc2_->fd();
+ fd = desc2_->get();
*desc1_ = *desc2_;
- if (desc1_->fd() != fd || desc2_->fd() != fd) {
+ if (desc1_->get() != fd || desc2_->get() != fd) {
std::cout << "Failed fd numerical check (copy assignment)"
<< std::endl;
return TestFail;
}
- if (!isValidFd(desc1_->fd()) || !isValidFd(desc2_->fd())) {
+ if (!isValidFd(desc1_->get()) || !isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after construction (copy assignment)"
<< std::endl;
return TestFail;
@@ -154,20 +189,20 @@ protected:
delete desc2_;
desc2_ = nullptr;
- /* Test creating FileDescriptor by move assignment. */
- desc1_ = new FileDescriptor();
- desc2_ = new FileDescriptor(fd_);
+ /* Test creating SharedFD by move assignment. */
+ desc1_ = new SharedFD();
+ desc2_ = new SharedFD(fd_);
- fd = desc2_->fd();
+ fd = desc2_->get();
*desc1_ = std::move(*desc2_);
- if (desc1_->fd() != fd || desc2_->fd() != -1) {
+ if (desc1_->get() != fd || desc2_->get() != -1) {
std::cout << "Failed fd numerical check (move assignment)"
<< std::endl;
return TestFail;
}
- if (!isValidFd(desc1_->fd())) {
+ if (!isValidFd(desc1_->get())) {
std::cout << "Failed fd validity after construction (move assignment)"
<< std::endl;
return TestFail;
@@ -203,7 +238,7 @@ private:
int fd_;
ino_t inodeNr_;
- FileDescriptor *desc1_, *desc2_;
+ SharedFD *desc1_, *desc2_;
};
-TEST_REGISTER(FileDescriptorTest)
+TEST_REGISTER(SharedFDTest)
diff --git a/test/signal-threads.cpp b/test/signal-threads.cpp
index f77733eb..c4789c83 100644
--- a/test/signal-threads.cpp
+++ b/test/signal-threads.cpp
@@ -2,17 +2,19 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * signal-threads.cpp - Cross-thread signal delivery test
+ * Cross-thread signal delivery test
*/
#include <chrono>
#include <iostream>
#include <thread>
-#include "message.h"
-#include "thread.h"
+#include <libcamera/base/message.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/utils.h>
+
#include "test.h"
-#include "utils.h"
using namespace std;
using namespace libcamera;
@@ -57,15 +59,20 @@ private:
class SignalThreadsTest : public Test
{
protected:
- int run()
+ int init()
{
- SignalReceiver receiver;
- signal_.connect(&receiver, &SignalReceiver::slot);
+ receiver_ = new SignalReceiver();
+ signal_.connect(receiver_, &SignalReceiver::slot);
+ return TestPass;
+ }
+
+ int run()
+ {
/* Test that a signal is received in the main thread. */
signal_.emit(0);
- switch (receiver.status()) {
+ switch (receiver_->status()) {
case SignalReceiver::NoSignal:
cout << "No signal received for direct connection" << endl;
return TestFail;
@@ -81,8 +88,8 @@ protected:
* Move the object to a thread and verify that the signal is
* correctly delivered, with the correct data.
*/
- receiver.reset();
- receiver.moveToThread(&thread_);
+ receiver_->reset();
+ receiver_->moveToThread(&thread_);
thread_.start();
@@ -90,7 +97,7 @@ protected:
this_thread::sleep_for(chrono::milliseconds(100));
- switch (receiver.status()) {
+ switch (receiver_->status()) {
case SignalReceiver::NoSignal:
cout << "No signal received for message connection" << endl;
return TestFail;
@@ -102,7 +109,7 @@ protected:
break;
}
- if (receiver.value() != 42) {
+ if (receiver_->value() != 42) {
cout << "Signal received with incorrect value" << endl;
return TestFail;
}
@@ -112,11 +119,13 @@ protected:
void cleanup()
{
+ receiver_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
+ SignalReceiver *receiver_;
Thread thread_;
Signal<int> signal_;
diff --git a/test/signal.cpp b/test/signal.cpp
index f83ceb05..3f596b22 100644
--- a/test/signal.cpp
+++ b/test/signal.cpp
@@ -2,14 +2,14 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * signal.cpp - Signal test
+ * Signal test
*/
#include <iostream>
#include <string.h>
-#include <libcamera/object.h>
-#include <libcamera/signal.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/signal.h>
#include "test.h"
@@ -41,8 +41,8 @@ class BaseClass
{
public:
/*
- * A virtual method is required in the base class, otherwise the compiler
- * will always store Object before BaseClass in memory.
+ * A virtual function is required in the base class, otherwise the
+ * compiler will always store Object before BaseClass in memory.
*/
virtual ~BaseClass()
{
@@ -191,20 +191,40 @@ protected:
signalVoid_.connect(slotStaticReturn);
signalVoid_.connect(this, &SignalTest::slotReturn);
+ /* Test signal connection to a lambda. */
+ int value = 0;
+ signalInt_.connect(this, [&](int v) { value = v; });
+ signalInt_.emit(42);
+
+ if (value != 42) {
+ cout << "Signal connection to lambda failed" << endl;
+ return TestFail;
+ }
+
+ signalInt_.disconnect(this);
+ signalInt_.emit(0);
+
+ if (value != 42) {
+ cout << "Signal disconnection from lambda failed" << endl;
+ return TestFail;
+ }
+
/* ----------------- Signal -> Object tests ----------------- */
/*
- * Test automatic disconnection on object deletion. Connect the
- * slot twice to ensure all instances are disconnected.
+ * Test automatic disconnection on object deletion. Connect two
+ * signals to ensure all instances are disconnected.
*/
signalVoid_.disconnect();
+ signalVoid2_.disconnect();
SlotObject *slotObject = new SlotObject();
signalVoid_.connect(slotObject, &SlotObject::slot);
- signalVoid_.connect(slotObject, &SlotObject::slot);
+ signalVoid2_.connect(slotObject, &SlotObject::slot);
delete slotObject;
valueStatic_ = 0;
signalVoid_.emit();
+ signalVoid2_.emit();
if (valueStatic_ != 0) {
cout << "Signal disconnection on object deletion test failed" << endl;
return TestFail;
@@ -256,20 +276,43 @@ protected:
delete slotObject;
+ /* Test signal connection to a lambda. */
+ slotObject = new SlotObject();
+ value = 0;
+ signalInt_.connect(slotObject, [&](int v) { value = v; });
+ signalInt_.emit(42);
+
+ if (value != 42) {
+ cout << "Signal connection to Object lambda failed" << endl;
+ return TestFail;
+ }
+
+ signalInt_.disconnect(slotObject);
+ signalInt_.emit(0);
+
+ if (value != 42) {
+ cout << "Signal disconnection from Object lambda failed" << endl;
+ return TestFail;
+ }
+
+ delete slotObject;
+
/* --------- Signal -> Object (multiple inheritance) -------- */
/*
- * Test automatic disconnection on object deletion. Connect the
- * slot twice to ensure all instances are disconnected.
+ * Test automatic disconnection on object deletion. Connect two
+ * signals to ensure all instances are disconnected.
*/
signalVoid_.disconnect();
+ signalVoid2_.disconnect();
SlotMulti *slotMulti = new SlotMulti();
signalVoid_.connect(slotMulti, &SlotMulti::slot);
- signalVoid_.connect(slotMulti, &SlotMulti::slot);
+ signalVoid2_.connect(slotMulti, &SlotMulti::slot);
delete slotMulti;
valueStatic_ = 0;
signalVoid_.emit();
+ signalVoid2_.emit();
if (valueStatic_ != 0) {
cout << "Signal disconnection on object deletion test failed" << endl;
return TestFail;
@@ -306,6 +349,7 @@ protected:
private:
Signal<> signalVoid_;
+ Signal<> signalVoid2_;
Signal<int> signalInt_;
Signal<int, const std::string &> signalMultiArgs_;
diff --git a/test/span.cpp b/test/span.cpp
index 69f0732e..4b9f3279 100644
--- a/test/span.cpp
+++ b/test/span.cpp
@@ -2,14 +2,14 @@
/*
* Copyright (C) 2020, Google Inc.
*
- * span.cpp - Span tests
+ * Span tests
*/
/*
* Include first to ensure the header is self-contained, as there's no span.cpp
* in libcamera.
*/
-#include <libcamera/span.h>
+#include <libcamera/base/span.h>
#include <array>
#include <iostream>
@@ -72,12 +72,24 @@ protected:
staticSpan = Span<int, 4>{ v };
- staticSpan.begin();
- staticSpan.cbegin();
+ if (*staticSpan.begin() != 1) {
+ std::cout << "Span<static_extent>::begin() failed" << std::endl;
+ return TestFail;
+ }
+ if (*staticSpan.cbegin() != 1) {
+ std::cout << "Span<static_extent>::cbegin() failed" << std::endl;
+ return TestFail;
+ }
staticSpan.end();
staticSpan.cend();
- staticSpan.rbegin();
- staticSpan.crbegin();
+ if (*staticSpan.rbegin() != 4) {
+ std::cout << "Span<static_extent>::rbegin() failed" << std::endl;
+ return TestFail;
+ }
+ if (*staticSpan.crbegin() != 4) {
+ std::cout << "Span<static_extent>::crbegin() failed" << std::endl;
+ return TestFail;
+ }
staticSpan.rend();
staticSpan.crend();
@@ -106,7 +118,7 @@ protected:
/* staticSpan.subspan(2, 4); */
/*
- * Compile-test construction and usage of spans with static
+ * Compile-test construction and usage of spans with dynamic
* extent. Commented-out tests are expected not to compile, or
* to generate undefined behaviour.
*/
@@ -131,9 +143,9 @@ protected:
Span<const int>{ v };
/* Span<float>{ v }; */
- Span<const int>{ v };
- /* Span<int>{ v }; */
- /* Span<const float>{ v }; */
+ Span<const int>{ cv };
+ /* Span<int>{ cv }; */
+ /* Span<const float>{ cv }; */
Span<int> dynamicSpan{ i };
Span<int>{ dynamicSpan };
@@ -141,12 +153,24 @@ protected:
dynamicSpan = Span<int>{ a };
- dynamicSpan.begin();
- dynamicSpan.cbegin();
+ if (*dynamicSpan.begin() != 1) {
+ std::cout << "Span<dynamic_extent>::begin() failed" << std::endl;
+ return TestFail;
+ }
+ if (*dynamicSpan.cbegin() != 1) {
+ std::cout << "Span<dynamic_extent>::cbegin() failed" << std::endl;
+ return TestFail;
+ }
dynamicSpan.end();
dynamicSpan.cend();
- dynamicSpan.rbegin();
- dynamicSpan.crbegin();
+ if (*dynamicSpan.rbegin() != 4) {
+ std::cout << "Span<dynamic_extent>::rbegin() failed" << std::endl;
+ return TestFail;
+ }
+ if (*dynamicSpan.crbegin() != 4) {
+ std::cout << "Span<dynamic_extent>::crbegin() failed" << std::endl;
+ return TestFail;
+ }
dynamicSpan.rend();
dynamicSpan.crend();
diff --git a/test/stream/meson.build b/test/stream/meson.build
index 005f4aa4..dd77f2f7 100644
--- a/test/stream/meson.build
+++ b/test/stream/meson.build
@@ -1,11 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+
stream_tests = [
- [ 'stream_formats', 'stream_formats.cpp' ],
+ {'name': 'stream_colorspace', 'sources': ['stream_colorspace.cpp']},
+ {'name': 'stream_formats', 'sources': ['stream_formats.cpp']},
]
-foreach t : stream_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_dep,
- link_with : test_libraries,
- include_directories : test_includes_internal)
- test(t[0], exe, suite: 'stream')
+foreach test : stream_tests
+ exe = executable(test['name'], test['sources'],
+ dependencies : libcamera_public,
+ link_with : test_libraries,
+ include_directories : test_includes_internal)
+ test(test['name'], exe, suite : 'stream')
endforeach
diff --git a/test/stream/stream_colorspace.cpp b/test/stream/stream_colorspace.cpp
new file mode 100644
index 00000000..4c904c4c
--- /dev/null
+++ b/test/stream/stream_colorspace.cpp
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy.
+ *
+ * Stream colorspace adjustment test
+ */
+
+#include <iostream>
+
+#include <libcamera/camera.h>
+#include <libcamera/formats.h>
+#include <libcamera/stream.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class TestCameraConfiguration : public CameraConfiguration
+{
+public:
+ TestCameraConfiguration()
+ : CameraConfiguration()
+ {
+ }
+
+ Status validate() override
+ {
+ return validateColorSpaces();
+ }
+};
+
+class StreamColorSpaceTest : public Test
+{
+protected:
+ int run()
+ {
+ TestCameraConfiguration config;
+
+ StreamConfiguration cfg;
+ cfg.size = { 640, 320 };
+ cfg.pixelFormat = formats::YUV422;
+ cfg.colorSpace = ColorSpace::Srgb;
+ config.addConfiguration(cfg);
+
+ StreamConfiguration &streamCfg = config.at(0);
+
+ /*
+ * YUV pixelformat with sRGB colorspace should have Y'CbCr encoding
+ * adjusted.
+ */
+ config.validate();
+ if (streamCfg.colorSpace->ycbcrEncoding == ColorSpace::YcbcrEncoding::None) {
+ cerr << "YUV format must have YCbCr encoding" << endl;
+ return TestFail;
+ }
+
+ /*
+ * For YUV pixelFormat, encoding should be picked up according
+ * to primaries and transfer function, if 'None' is specified.
+ */
+ streamCfg.pixelFormat = formats::YUV422;
+ streamCfg.colorSpace = ColorSpace(ColorSpace::Primaries::Rec2020,
+ ColorSpace::TransferFunction::Rec709,
+ ColorSpace::YcbcrEncoding::None,
+ ColorSpace::Range::Limited);
+ config.validate();
+ if (streamCfg.colorSpace->ycbcrEncoding != ColorSpace::YcbcrEncoding::Rec2020) {
+ cerr << "Failed to adjust colorspace Y'CbCr encoding according"
+ << " to primaries and transfer function" << endl;
+ return TestFail;
+ }
+
+ /* For RGB pixelFormat, Sycc colorspace should get adjusted to sRGB. */
+ streamCfg.pixelFormat = formats::RGB888;
+ streamCfg.colorSpace = ColorSpace::Sycc;
+ config.validate();
+ if (streamCfg.colorSpace != ColorSpace::Srgb) {
+ cerr << "RGB format's colorspace should be set to Srgb" << endl;
+ return TestFail;
+ }
+
+ /* Raw formats should always set colorspace to ColorSpace::Raw. */
+ streamCfg.pixelFormat = formats::SBGGR8;
+ streamCfg.colorSpace = ColorSpace::Rec709;
+ config.validate();
+ if (streamCfg.colorSpace != ColorSpace::Raw) {
+ cerr << "Raw format must always have Raw colorspace" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(StreamColorSpaceTest)
diff --git a/test/stream/stream_formats.cpp b/test/stream/stream_formats.cpp
index 9353d008..553b59aa 100644
--- a/test/stream/stream_formats.cpp
+++ b/test/stream/stream_formats.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * stream_formats.cpp - StreamFormats test
+ * StreamFormats test
*/
#include <iostream>
@@ -40,10 +40,10 @@ protected:
cout << "Failed " << name << endl;
cout << "Sizes to test:" << endl;
for (Size &size : test)
- cout << size.toString() << endl;
+ cout << size << endl;
cout << "Valid sizes:" << endl;
for (Size &size : valid)
- cout << size.toString() << endl;
+ cout << size << endl;
return TestFail;
}
diff --git a/test/threads.cpp b/test/threads.cpp
index 0454761d..8d6ee151 100644
--- a/test/threads.cpp
+++ b/test/threads.cpp
@@ -2,14 +2,20 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * threads.cpp - Threads test
+ * Threads test
*/
#include <chrono>
#include <iostream>
+#include <memory>
+#include <pthread.h>
+#include <sched.h>
#include <thread>
+#include <time.h>
+
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
-#include "thread.h"
#include "test.h"
using namespace std;
@@ -33,6 +39,56 @@ private:
chrono::steady_clock::duration duration_;
};
+class CancelThread : public Thread
+{
+public:
+ CancelThread(bool &cancelled)
+ : cancelled_(cancelled)
+ {
+ }
+
+protected:
+ void run()
+ {
+ cancelled_ = true;
+
+ /*
+ * Cancel the thread and call a guaranteed cancellation point
+ * (nanosleep).
+ */
+ pthread_cancel(pthread_self());
+
+ struct timespec req{ 0, 100*000*000 };
+ nanosleep(&req, nullptr);
+
+ cancelled_ = false;
+ }
+
+private:
+ bool &cancelled_;
+};
+
+class CpuSetTester : public Object
+{
+public:
+ CpuSetTester(unsigned int cpuset)
+ : cpuset_(cpuset) {}
+
+ bool testCpuSet()
+ {
+ int ret = sched_getcpu();
+ if (static_cast<int>(cpuset_) != ret) {
+ cout << "Invalid cpuset: " << ret << ", expecting: " << cpuset_ << endl;
+ return false;
+ }
+
+ return true;
+ }
+
+private:
+ const unsigned int cpuset_;
+};
+
class ThreadTest : public Test
{
protected:
@@ -44,23 +100,23 @@ protected:
int run()
{
/* Test Thread() retrieval for the main thread. */
- Thread *thread = Thread::current();
- if (!thread) {
+ Thread *mainThread = Thread::current();
+ if (!mainThread) {
cout << "Thread::current() failed to main thread"
<< endl;
return TestFail;
}
- if (!thread->isRunning()) {
+ if (!mainThread->isRunning()) {
cout << "Main thread is not running" << endl;
return TestFail;
}
/* Test starting the main thread, the test shall not crash. */
- thread->start();
+ mainThread->start();
/* Test the running state of a custom thread. */
- thread = new Thread();
+ std::unique_ptr<Thread> thread = std::make_unique<Thread>();
thread->start();
if (!thread->isRunning()) {
@@ -78,10 +134,8 @@ protected:
return TestFail;
}
- delete thread;
-
/* Test waiting for completion with a timeout. */
- thread = new DelayThread(chrono::milliseconds(500));
+ thread = std::make_unique<DelayThread>(chrono::milliseconds(500));
thread->start();
thread->exit(0);
@@ -99,10 +153,8 @@ protected:
return TestFail;
}
- delete thread;
-
/* Test waiting on a thread that isn't running. */
- thread = new Thread();
+ thread = std::make_unique<Thread>();
timeout = !thread->wait();
if (timeout) {
@@ -120,6 +172,39 @@ protected:
return TestFail;
}
+ /* Test thread cleanup upon abnormal termination. */
+ bool cancelled = false;
+ bool finished = false;
+
+ thread = std::make_unique<CancelThread>(cancelled);
+ thread->finished.connect(this, [&finished]() { finished = true; });
+
+ thread->start();
+ thread->exit(0);
+ thread->wait(chrono::milliseconds(1000));
+
+ if (!cancelled || !finished) {
+ cout << "Cleanup failed upon abnormal termination" << endl;
+ return TestFail;
+ }
+
+ const unsigned int numCpus = std::thread::hardware_concurrency();
+ for (unsigned int i = 0; i < numCpus; ++i) {
+ thread = std::make_unique<Thread>();
+ const std::array<const unsigned int, 1> cpus{ i };
+ thread->setThreadAffinity(cpus);
+ thread->start();
+
+ CpuSetTester tester(i);
+ tester.moveToThread(thread.get());
+
+ if (!tester.invokeMethod(&CpuSetTester::testCpuSet, ConnectionTypeBlocking))
+ return TestFail;
+
+ thread->exit(0);
+ thread->wait();
+ }
+
return TestPass;
}
diff --git a/test/timer-fail.cpp b/test/timer-fail.cpp
new file mode 100644
index 00000000..0ced6441
--- /dev/null
+++ b/test/timer-fail.cpp
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Threaded timer failure test
+ */
+
+#include <chrono>
+#include <iostream>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
+
+class TimeoutHandler : public Object
+{
+public:
+ TimeoutHandler()
+ : timer_(this), timeout_(false)
+ {
+ timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler);
+ }
+
+ void start()
+ {
+ timer_.start(100ms);
+ }
+
+ bool timeout() const
+ {
+ return timeout_;
+ }
+
+private:
+ void timeoutHandler()
+ {
+ timeout_ = true;
+ }
+
+ Timer timer_;
+ bool timeout_;
+};
+
+class TimerFailTest : public Test
+{
+protected:
+ int init()
+ {
+ thread_.start();
+
+ timeout_ = new TimeoutHandler();
+ timeout_->moveToThread(&thread_);
+
+ return TestPass;
+ }
+
+ int run()
+ {
+ /*
+ * Test that the forbidden operation of starting the timer from
+ * another thread results in a failure. We need to interrupt the
+ * event dispatcher to make sure we don't succeed simply because
+ * the event dispatcher hasn't noticed the timer restart.
+ */
+ timeout_->start();
+ thread_.eventDispatcher()->interrupt();
+
+ this_thread::sleep_for(chrono::milliseconds(200));
+
+ /*
+ * The wrong start() call should result in an assertion in debug
+ * builds, and a timeout in release builds. The test is
+ * therefore marked in meson.build as expected to fail. We need
+ * to return TestPass in the unexpected (usually known as
+ * "fail") case, and TestFail otherwise.
+ */
+ if (timeout_->timeout()) {
+ cout << "Timer start from wrong thread succeeded unexpectedly"
+ << endl;
+ return TestPass;
+ }
+
+ return TestFail;
+ }
+
+ void cleanup()
+ {
+ /*
+ * Object class instances must be destroyed from the thread
+ * they live in.
+ */
+ timeout_->deleteLater();
+ thread_.exit(0);
+ thread_.wait();
+ }
+
+private:
+ TimeoutHandler *timeout_;
+ Thread thread_;
+};
+
+TEST_REGISTER(TimerFailTest)
diff --git a/test/timer-thread.cpp b/test/timer-thread.cpp
index 32853b4e..55e5cfdf 100644
--- a/test/timer-thread.cpp
+++ b/test/timer-thread.cpp
@@ -2,20 +2,22 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * timer-thread.cpp - Threaded timer test
+ * Threaded timer test
*/
#include <chrono>
#include <iostream>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
#include "test.h"
-#include "thread.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
class TimeoutHandler : public Object
{
@@ -24,13 +26,7 @@ public:
: timer_(this), timeout_(false)
{
timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler);
- timer_.start(100);
- }
-
- void restart()
- {
- timeout_ = false;
- timer_.start(100);
+ timer_.start(100ms);
}
bool timeout() const
@@ -39,7 +35,7 @@ public:
}
private:
- void timeoutHandler(Timer *timer)
+ void timeoutHandler()
{
timeout_ = true;
}
@@ -54,7 +50,9 @@ protected:
int init()
{
thread_.start();
- timeout_.moveToThread(&thread_);
+
+ timeout_ = new TimeoutHandler();
+ timeout_->moveToThread(&thread_);
return TestPass;
}
@@ -67,39 +65,27 @@ protected:
*/
this_thread::sleep_for(chrono::milliseconds(200));
- if (!timeout_.timeout()) {
+ if (!timeout_->timeout()) {
cout << "Timer expiration test failed" << endl;
return TestFail;
}
- /*
- * Test that starting the timer from another thread fails. We
- * need to interrupt the event dispatcher to make sure we don't
- * succeed simply because the event dispatcher hasn't noticed
- * the timer restart.
- */
- timeout_.restart();
- thread_.eventDispatcher()->interrupt();
-
- this_thread::sleep_for(chrono::milliseconds(200));
-
- if (timeout_.timeout()) {
- cout << "Timer restart test failed" << endl;
- return TestFail;
- }
-
return TestPass;
}
void cleanup()
{
- /* Must stop thread before destroying timeout. */
+ /*
+ * Object class instances must be destroyed from the thread
+ * they live in.
+ */
+ timeout_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
- TimeoutHandler timeout_;
+ TimeoutHandler *timeout_;
Thread thread_;
};
diff --git a/test/timer.cpp b/test/timer.cpp
index 2bdb006e..2eacc059 100644
--- a/test/timer.cpp
+++ b/test/timer.cpp
@@ -2,20 +2,21 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * timer.cpp - Timer test
+ * Timer test
*/
#include <chrono>
#include <iostream>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
#include "test.h"
-#include "thread.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
class ManagedTimer : public Timer
{
@@ -26,7 +27,7 @@ public:
timeout.connect(this, &ManagedTimer::timeoutHandler);
}
- void start(int msec)
+ void start(std::chrono::milliseconds msec)
{
count_ = 0;
start_ = std::chrono::steady_clock::now();
@@ -56,7 +57,7 @@ public:
}
private:
- void timeoutHandler(Timer *timer)
+ void timeoutHandler()
{
expiration_ = std::chrono::steady_clock::now();
count_++;
@@ -82,7 +83,7 @@ protected:
ManagedTimer timer2;
/* Timer expiration. */
- timer.start(1000);
+ timer.start(1000ms);
if (!timer.isRunning()) {
cout << "Timer expiration test failed" << endl;
@@ -101,7 +102,7 @@ protected:
* Nanosecond resolution in a 32 bit value wraps at 4.294967
* seconds (0xFFFFFFFF / 1000000)
*/
- timer.start(4295);
+ timer.start(4295ms);
dispatcher->processEvents();
if (timer.hasFailed()) {
@@ -110,7 +111,7 @@ protected:
}
/* Timer restart. */
- timer.start(500);
+ timer.start(500ms);
if (!timer.isRunning()) {
cout << "Timer restart test failed" << endl;
@@ -125,9 +126,9 @@ protected:
}
/* Timer restart before expiration. */
- timer.start(50);
- timer.start(100);
- timer.start(150);
+ timer.start(50ms);
+ timer.start(100ms);
+ timer.start(150ms);
dispatcher->processEvents();
@@ -147,8 +148,8 @@ protected:
}
/* Two timers. */
- timer.start(1000);
- timer2.start(300);
+ timer.start(1000ms);
+ timer2.start(300ms);
dispatcher->processEvents();
@@ -170,8 +171,8 @@ protected:
}
/* Restart timer before expiration. */
- timer.start(1000);
- timer2.start(300);
+ timer.start(1000ms);
+ timer2.start(300ms);
dispatcher->processEvents();
@@ -180,7 +181,7 @@ protected:
return TestFail;
}
- timer.start(1000);
+ timer.start(1000ms);
dispatcher->processEvents();
@@ -194,10 +195,10 @@ protected:
* deleted. This will result in a crash on failure.
*/
ManagedTimer *dyntimer = new ManagedTimer();
- dyntimer->start(100);
+ dyntimer->start(100ms);
delete dyntimer;
- timer.start(200);
+ timer.start(200ms);
dispatcher->processEvents();
return TestPass;
diff --git a/test/transform.cpp b/test/transform.cpp
new file mode 100644
index 00000000..4ec7a1eb
--- /dev/null
+++ b/test/transform.cpp
@@ -0,0 +1,329 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023, Ideas On Board Oy
+ *
+ * Transform and Orientation tests
+ */
+
+#include <iostream>
+
+#include <libcamera/orientation.h>
+#include <libcamera/transform.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class TransformTest : public Test
+{
+protected:
+ int run();
+};
+
+int TransformTest::run()
+{
+ /*
+ * RotationTestEntry collects two Orientation and one Transform that
+ * gets combined to validate that (o1 / o2 = T) and (o1 = o2 * T)
+ *
+ * o1 / o2 = t computes the Transform to apply to o2 to obtain o1
+ * o2 * t = o1 combines o2 with t by applying o2 first then t
+ *
+ * The comments on the (most complex) transform show how applying to
+ * an image with orientation o2 the Transform t allows to obtain o1.
+ *
+ * The image with basic rotation0 is assumed to be:
+ *
+ * AB
+ * CD
+ *
+ * And the Transform operators are:
+ *
+ * V = vertical flip
+ * H = horizontal flip
+ * T = transpose
+ *
+ * the operator '* (T|V)' applies V first then T.
+ */
+ static const struct RotationTestEntry {
+ Orientation o1;
+ Orientation o2;
+ Transform t;
+ } testEntries[] = {
+ /* Test identities transforms first. */
+ {
+ Orientation::Rotate0, Orientation::Rotate0,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate0Mirror, Orientation::Rotate0Mirror,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate180, Orientation::Rotate180,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate180Mirror, Orientation::Rotate180Mirror,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate90, Orientation::Rotate90,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate90Mirror, Orientation::Rotate90Mirror,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate270, Orientation::Rotate270,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate270Mirror, Orientation::Rotate270Mirror,
+ Transform::Identity,
+ },
+ /*
+ * Combine 0 and 180 degrees rotation as they're the most common
+ * ones.
+ */
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CD * (H|V) = BA AB
+ * BA CD CD
+ */
+ Orientation::Rotate0, Orientation::Rotate180,
+ Transform::Rot180,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * AB * (H|V) = CD DC
+ * CD AB BA
+ */
+ Orientation::Rotate180, Orientation::Rotate0,
+ Transform::Rot180
+ },
+ /* Test that transpositions are handled correctly. */
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * AB * (T|V) = CD CA
+ * CD AB DB
+ */
+ Orientation::Rotate90, Orientation::Rotate0,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CA * (T|H) = AC AB
+ * DB BD CD
+ */
+ Orientation::Rotate0, Orientation::Rotate90,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * AB * (T|H) = BA BD
+ * CD DC AC
+ */
+ Orientation::Rotate270, Orientation::Rotate0,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * BD * (T|V) = AC AB
+ * AC BD CD
+ */
+ Orientation::Rotate0, Orientation::Rotate270,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CD * (T|H) = DC DA
+ * BA AB CB
+ */
+ Orientation::Rotate90, Orientation::Rotate180,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * DA * (T|V) = CB CD
+ * CB DA BA
+ */
+ Orientation::Rotate180, Orientation::Rotate90,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CD * (T|V) = BA BC
+ * BA CD AD
+ */
+ Orientation::Rotate270, Orientation::Rotate180,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * BC * (T|H) = CB CD
+ * AD DA BA
+ */
+ Orientation::Rotate180, Orientation::Rotate270,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * DA * (V|H) = AD BC
+ * CB BC AD
+ */
+ Orientation::Rotate270, Orientation::Rotate90,
+ Transform::Rot180,
+ },
+ /* Test that mirroring is handled correctly. */
+ {
+ Orientation::Rotate0, Orientation::Rotate0Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate0Mirror, Orientation::Rotate0,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate180, Orientation::Rotate180Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate180Mirror, Orientation::Rotate180,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate90, Orientation::Rotate90Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate90Mirror, Orientation::Rotate90,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate270, Orientation::Rotate270Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate270Mirror, Orientation::Rotate270,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate0, Orientation::Rotate0Mirror,
+ Transform::HFlip
+ },
+ /*
+ * More exotic transforms which include Transpositions and
+ * mirroring.
+ */
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * BC * (V) = AD
+ * AD BC
+ */
+ Orientation::Rotate90Mirror, Orientation::Rotate270,
+ Transform::VFlip,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * CB * (T) = CD
+ * DA BA
+ */
+ Orientation::Rotate180, Orientation::Rotate270Mirror,
+ Transform::Transpose,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * AD * (T) = AB
+ * BC DC
+ */
+ Orientation::Rotate0, Orientation::Rotate90Mirror,
+ Transform::Transpose,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * AD * (V) = BC
+ * BC AD
+ */
+ Orientation::Rotate270, Orientation::Rotate90Mirror,
+ Transform::VFlip,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * DA * (V) = CB
+ * CB DA
+ */
+ Orientation::Rotate270Mirror, Orientation::Rotate90,
+ Transform::VFlip,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CB * (V|H) = BC AD
+ * DA AD BC
+ */
+ Orientation::Rotate90Mirror, Orientation::Rotate270Mirror,
+ Transform::Rot180,
+ },
+ };
+
+ for (const auto &entry : testEntries) {
+ Transform transform = entry.o1 / entry.o2;
+ if (transform != entry.t) {
+ cerr << "Failed to validate: " << entry.o1
+ << " / " << entry.o2
+ << " = " << transformToString(entry.t) << endl;
+ cerr << "Got back: "
+ << transformToString(transform) << endl;
+ return TestFail;
+ }
+
+ Orientation adjusted = entry.o2 * entry.t;
+ if (adjusted != entry.o1) {
+ cerr << "Failed to validate: " << entry.o2
+ << " * " << transformToString(entry.t)
+ << " = " << entry.o1 << endl;
+ cerr << "Got back: " << adjusted << endl;
+ return TestFail;
+ }
+ }
+
+ return TestPass;
+}
+
+TEST_REGISTER(TransformTest)
diff --git a/test/unique-fd.cpp b/test/unique-fd.cpp
new file mode 100644
index 00000000..e556439e
--- /dev/null
+++ b/test/unique-fd.cpp
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * UniqueFD test
+ */
+
+#include <fcntl.h>
+#include <iostream>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libcamera/base/unique_fd.h>
+#include <libcamera/base/utils.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class UniqueFDTest : public Test
+{
+protected:
+ int init() override
+ {
+ return createFd();
+ }
+
+ int run() override
+ {
+ /* Test creating empty UniqueFD. */
+ UniqueFD fd;
+
+ if (fd.isValid() || fd.get() != -1) {
+ std::cout << "Failed fd check (default constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test creating UniqueFD from numerical file descriptor. */
+ UniqueFD fd2(fd_);
+ if (!fd2.isValid() || fd2.get() != fd_) {
+ std::cout << "Failed fd check (fd constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (!isValidFd(fd_)) {
+ std::cout << "Failed fd validity (fd constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test move constructor. */
+ UniqueFD fd3(std::move(fd2));
+ if (!fd3.isValid() || fd3.get() != fd_) {
+ std::cout << "Failed fd check (move constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (fd2.isValid() || fd2.get() != -1) {
+ std::cout << "Failed moved fd check (move constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (!isValidFd(fd_)) {
+ std::cout << "Failed fd validity (move constructor)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test move assignment operator. */
+ fd = std::move(fd3);
+ if (!fd.isValid() || fd.get() != fd_) {
+ std::cout << "Failed fd check (move assignment)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (fd3.isValid() || fd3.get() != -1) {
+ std::cout << "Failed moved fd check (move assignment)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (!isValidFd(fd_)) {
+ std::cout << "Failed fd validity (move assignment)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test swapping. */
+ fd2.swap(fd);
+ if (!fd2.isValid() || fd2.get() != fd_) {
+ std::cout << "Failed fd check (swap)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (fd.isValid() || fd.get() != -1) {
+ std::cout << "Failed swapped fd check (swap)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (!isValidFd(fd_)) {
+ std::cout << "Failed fd validity (swap)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test release. */
+ int numFd = fd2.release();
+ if (fd2.isValid() || fd2.get() != -1) {
+ std::cout << "Failed fd check (release)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (numFd != fd_) {
+ std::cout << "Failed released fd check (release)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (!isValidFd(fd_)) {
+ std::cout << "Failed fd validity (release)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test reset assignment. */
+ fd.reset(numFd);
+ if (!fd.isValid() || fd.get() != fd_) {
+ std::cout << "Failed fd check (reset assignment)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (!isValidFd(fd_)) {
+ std::cout << "Failed fd validity (reset assignment)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test reset destruction. */
+ fd.reset();
+ if (fd.isValid() || fd.get() != -1) {
+ std::cout << "Failed fd check (reset destruction)"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (isValidFd(fd_)) {
+ std::cout << "Failed fd validity (reset destruction)"
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Test destruction. */
+ if (createFd() == TestFail) {
+ std::cout << "Failed to recreate test fd"
+ << std::endl;
+ return TestFail;
+ }
+
+ {
+ UniqueFD fd4(fd_);
+ }
+
+ if (isValidFd(fd_)) {
+ std::cout << "Failed fd validity (destruction)"
+ << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ void cleanup() override
+ {
+ if (fd_ > 0)
+ close(fd_);
+ }
+
+private:
+ int createFd()
+ {
+ fd_ = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+ if (fd_ < 0)
+ return TestFail;
+
+ /* Cache inode number of temp file. */
+ struct stat s;
+ if (fstat(fd_, &s))
+ return TestFail;
+
+ inodeNr_ = s.st_ino;
+
+ return 0;
+ }
+
+ bool isValidFd(int fd)
+ {
+ struct stat s;
+ if (fstat(fd, &s))
+ return false;
+
+ /* Check that inode number matches cached temp file. */
+ return s.st_ino == inodeNr_;
+ }
+
+ int fd_;
+ ino_t inodeNr_;
+};
+
+TEST_REGISTER(UniqueFDTest)
diff --git a/test/utils.cpp b/test/utils.cpp
index 58816f15..d25475cb 100644
--- a/test/utils.cpp
+++ b/test/utils.cpp
@@ -2,19 +2,26 @@
/*
* Copyright (C) 2018, Google Inc.
*
- * utils.cpp - Miscellaneous utility tests
+ * Miscellaneous utility tests
*/
#include <iostream>
+#include <map>
+#include <optional>
#include <sstream>
#include <string>
#include <vector>
+#include <libcamera/base/span.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/geometry.h>
+
#include "test.h"
-#include "utils.h"
using namespace std;
using namespace libcamera;
+using namespace std::literals::chrono_literals;
class UtilsTest : public Test
{
@@ -69,12 +76,114 @@ protected:
return TestPass;
}
+ int testEnumerate()
+ {
+ std::vector<unsigned int> integers{ 1, 2, 3, 4, 5 };
+ unsigned int i = 0;
+
+ for (auto [index, value] : utils::enumerate(integers)) {
+ if (index != i || value != i + 1) {
+ cerr << "utils::enumerate(<vector>) test failed: i=" << i
+ << ", index=" << index << ", value=" << value
+ << std::endl;
+ return TestFail;
+ }
+
+ /* Verify that we can modify the value. */
+ --value;
+ ++i;
+ }
+
+ if (integers != std::vector<unsigned int>{ 0, 1, 2, 3, 4 }) {
+ cerr << "Failed to modify container in enumerated range loop" << endl;
+ return TestFail;
+ }
+
+ Span<const unsigned int> span{ integers };
+ i = 0;
+
+ for (auto [index, value] : utils::enumerate(span)) {
+ if (index != i || value != i) {
+ cerr << "utils::enumerate(<span>) test failed: i=" << i
+ << ", index=" << index << ", value=" << value
+ << std::endl;
+ return TestFail;
+ }
+
+ ++i;
+ }
+
+ const unsigned int array[] = { 0, 2, 4, 6, 8 };
+ i = 0;
+
+ for (auto [index, value] : utils::enumerate(array)) {
+ if (index != i || value != i * 2) {
+ cerr << "utils::enumerate(<array>) test failed: i=" << i
+ << ", index=" << index << ", value=" << value
+ << std::endl;
+ return TestFail;
+ }
+
+ ++i;
+ }
+
+ return TestPass;
+ }
+
+ int testDuration()
+ {
+ std::ostringstream os;
+ utils::Duration exposure;
+ double ratio;
+
+ exposure = 25ms + 25ms;
+ if (exposure.get<std::micro>() != 50000.0) {
+ cerr << "utils::Duration failed to return microsecond count";
+ return TestFail;
+ }
+
+ exposure = 1.0s / 4;
+ if (exposure != 250ms) {
+ cerr << "utils::Duration failed scalar divide test";
+ return TestFail;
+ }
+
+ exposure = 5000.5us;
+ if (!exposure) {
+ cerr << "utils::Duration failed boolean test";
+ return TestFail;
+ }
+
+ os << exposure;
+ if (os.str() != "5000.50us") {
+ cerr << "utils::Duration operator << failed";
+ return TestFail;
+ }
+
+ exposure = 100ms;
+ ratio = exposure / 25ms;
+ if (ratio != 4.0) {
+ cerr << "utils::Duration failed ratio test";
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
int run()
{
/* utils::hex() test. */
std::ostringstream os;
std::string ref;
+ os << utils::hex(static_cast<int8_t>(0x42)) << " ";
+ ref += "0x42 ";
+ os << utils::hex(static_cast<uint8_t>(0x42)) << " ";
+ ref += "0x42 ";
+ os << utils::hex(static_cast<int16_t>(0x42)) << " ";
+ ref += "0x0042 ";
+ os << utils::hex(static_cast<uint16_t>(0x42)) << " ";
+ ref += "0x0042 ";
os << utils::hex(static_cast<int32_t>(0x42)) << " ";
ref += "0x00000042 ";
os << utils::hex(static_cast<uint32_t>(0x42)) << " ";
@@ -83,6 +192,15 @@ protected:
ref += "0x0000000000000042 ";
os << utils::hex(static_cast<uint64_t>(0x42)) << " ";
ref += "0x0000000000000042 ";
+
+ os << utils::hex(static_cast<int8_t>(0x42), 6) << " ";
+ ref += "0x000042 ";
+ os << utils::hex(static_cast<uint8_t>(0x42), 1) << " ";
+ ref += "0x42 ";
+ os << utils::hex(static_cast<int16_t>(0x42), 6) << " ";
+ ref += "0x000042 ";
+ os << utils::hex(static_cast<uint16_t>(0x42), 1) << " ";
+ ref += "0x42 ";
os << utils::hex(static_cast<int32_t>(0x42), 4) << " ";
ref += "0x0042 ";
os << utils::hex(static_cast<uint32_t>(0x42), 1) << " ";
@@ -99,7 +217,7 @@ protected:
return TestFail;
}
- /* utils::split() test. */
+ /* utils::join() and utils::split() test. */
std::vector<std::string> elements = {
"/bin",
"/usr/bin",
@@ -111,6 +229,11 @@ protected:
for (const auto &element : elements)
path += (path.empty() ? "" : ":") + element;
+ if (path != utils::join(elements, ":")) {
+ cerr << "utils::join() test failed" << endl;
+ return TestFail;
+ }
+
std::vector<std::string> dirs;
for (const auto &dir : utils::split(path, ":"))
@@ -121,10 +244,69 @@ protected:
return TestFail;
}
+ const auto &split = utils::split(path, ":");
+ dirs = std::vector<std::string>{ split.begin(), split.end() };
+
+ if (dirs != elements) {
+ cerr << "utils::split() LegacyInputIterator test failed" << endl;
+ return TestFail;
+ }
+
+ /* utils::join() with conversion function test. */
+ std::vector<Size> sizes = { { 0, 0 }, { 100, 100 } };
+ s = utils::join(sizes, "/", [](const Size &size) {
+ return size.toString();
+ });
+
+ if (s != "0x0/100x100") {
+ cerr << "utils::join() with conversion test failed" << endl;
+ return TestFail;
+ }
+
/* utils::dirname() tests. */
if (TestPass != testDirname())
return TestFail;
+
+ /* utils::map_keys() test. */
+ const std::map<std::string, unsigned int> map{
+ { "zero", 0 },
+ { "one", 1 },
+ { "two", 2 },
+ };
+ std::vector<std::string> expectedKeys{
+ "zero",
+ "one",
+ "two",
+ };
+
+ std::sort(expectedKeys.begin(), expectedKeys.end());
+
+ const std::vector<std::string> keys = utils::map_keys(map);
+ if (keys != expectedKeys) {
+ cerr << "utils::map_keys() test failed" << endl;
+ return TestFail;
+ }
+
+ /* utils::alignUp() and utils::alignDown() tests. */
+ if (utils::alignDown(6, 3) != 6 || utils::alignDown(7, 3) != 6) {
+ cerr << "utils::alignDown test failed" << endl;
+ return TestFail;
+ }
+
+ if (utils::alignUp(6, 3) != 6 || utils::alignUp(7, 3) != 9) {
+ cerr << "utils::alignUp test failed" << endl;
+ return TestFail;
+ }
+
+ /* utils::enumerate() test. */
+ if (testEnumerate() != TestPass)
+ return TestFail;
+
+ /* utils::Duration test. */
+ if (testDuration() != TestPass)
+ return TestFail;
+
return TestPass;
}
};
diff --git a/test/v4l2_compat/meson.build b/test/v4l2_compat/meson.build
new file mode 100644
index 00000000..2691eacf
--- /dev/null
+++ b/test/v4l2_compat/meson.build
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: CC0-1.0
+
+if not is_variable('v4l2_compat')
+ subdir_done()
+endif
+
+# If ASan is enabled but the ASan runtime shared library is missing,
+# v4l2_compat_test.py won't be able to LD_PRELOAD it, resulting in a link order
+# runtime check failure as v4l2-ctl and v4l2-compliance are not linked to ASan.
+# Skip the test in that case.
+
+if asan_runtime_missing
+ warning('Unable to get path to ASan runtime, v4l2_compat test disabled')
+ subdir_done()
+endif
+
+v4l2_compat_test = files('v4l2_compat_test.py')
+v4l2_compat_args = []
+
+if asan_enabled
+ v4l2_compat_args += ['-s', asan_runtime]
+endif
+
+v4l2_compat_args += [v4l2_compat]
+
+test('v4l2_compat_test', v4l2_compat_test,
+ args : v4l2_compat_args,
+ suite : 'v4l2_compat',
+ timeout : 60)
diff --git a/test/v4l2_compat/v4l2_compat_test.py b/test/v4l2_compat/v4l2_compat_test.py
new file mode 100755
index 00000000..443babc2
--- /dev/null
+++ b/test/v4l2_compat/v4l2_compat_test.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2020, Google Inc.
+#
+# Author: Paul Elder <paul.elder@ideasonboard.com>
+#
+# Test the V4L2 compatibility layer
+
+import argparse
+import glob
+import os
+from packaging import version
+import re
+import shutil
+import signal
+import subprocess
+import sys
+
+MIN_V4L_UTILS_VERSION = version.parse("1.21.0")
+
+TestPass = 0
+TestFail = -1
+TestSkip = 77
+
+
+supported_pipelines = [
+ 'bcm2835-isp',
+ 'uvcvideo',
+ 'vimc',
+]
+
+
+def grep(exp, arr):
+ return [s for s in arr if re.search(exp, s)]
+
+
+def run_with_stdout(*args, env={}):
+ try:
+ with open(os.devnull, 'w') as devnull:
+ output = subprocess.check_output(args, env=env, stderr=devnull)
+ ret = 0
+ except subprocess.CalledProcessError as err:
+ output = err.output
+ ret = err.returncode
+ return ret, output.decode('utf-8').split('\n')
+
+
+def extract_result(result):
+ res = result.split(', ')
+ ret = {}
+ ret['total'] = int(res[0].split(': ')[-1])
+ ret['succeeded'] = int(res[1].split(': ')[-1])
+ ret['failed'] = int(res[2].split(': ')[-1])
+ ret['warnings'] = int(res[3].split(': ')[-1])
+ ret['device'] = res[0].split()[4].strip(':')
+ ret['driver'] = res[0].split()[2]
+ return ret
+
+
+def test_v4l2_compliance(v4l2_compliance, ld_preload, device, base_driver):
+ ret, output = run_with_stdout(v4l2_compliance, '-s', '-d', device, env={'LD_PRELOAD': ld_preload})
+ if ret < 0:
+ output.append(f'Test for {device} terminated due to signal {signal.Signals(-ret).name}')
+ return TestFail, output
+
+ result = extract_result(output[-2])
+ if result['failed'] == 0:
+ return TestPass, output
+
+ # vimc will fail s_fmt because it only supports framesizes that are
+ # multiples of 3
+ if base_driver == 'vimc' and result['failed'] == 1:
+ failures = grep('fail', output)
+ if re.search('S_FMT cannot handle an invalid format', failures[0]) is None:
+ return TestFail, output
+ return TestPass, output
+
+ return TestFail, output
+
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-a', '--all', action='store_true',
+ help='Test all available cameras')
+ parser.add_argument('-s', '--sanitizer', type=str,
+ help='Path to the address sanitizer (ASan) runtime')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='Make the output verbose')
+ parser.add_argument('v4l2_compat', type=str,
+ help='Path to v4l2-compat.so')
+ args = parser.parse_args(argv[1:])
+
+ # Compute the LD_PRELOAD value by first loading ASan (if specified) and
+ # then the V4L2 compat layer.
+ ld_preload = []
+ if args.sanitizer:
+ ld_preload.append(args.sanitizer)
+ ld_preload.append(args.v4l2_compat)
+ ld_preload = ':'.join(ld_preload)
+
+ v4l2_compliance = shutil.which('v4l2-compliance')
+ if v4l2_compliance is None:
+ print('v4l2-compliance is not available')
+ return TestSkip
+
+ ret, out = run_with_stdout(v4l2_compliance, '--version')
+ if ret != 0 or version.parse(out[0].split()[1].replace(',', '')) < MIN_V4L_UTILS_VERSION:
+ print('v4l2-compliance version >= 1.21.0 required')
+ return TestSkip
+
+ v4l2_ctl = shutil.which('v4l2-ctl')
+ if v4l2_ctl is None:
+ print('v4l2-ctl is not available')
+ return TestSkip
+
+ ret, out = run_with_stdout(v4l2_ctl, '--version')
+ if ret != 0 or version.parse(out[0].split()[-1]) < MIN_V4L_UTILS_VERSION:
+ print('v4l2-ctl version >= 1.21.0 required')
+ return TestSkip
+
+ dev_nodes = glob.glob('/dev/video*')
+ if len(dev_nodes) == 0:
+ print('no video nodes available to test with')
+ return TestSkip
+
+ failed = []
+ drivers_tested = {}
+ for device in dev_nodes:
+ ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device, env={'LD_PRELOAD': ld_preload})
+ if ret < 0:
+ failed.append(device)
+ print(f'v4l2-ctl failed on {device} with v4l2-compat')
+ continue
+ driver = grep('Driver name', out)[0].split(':')[-1].strip()
+ if driver != "libcamera":
+ continue
+
+ ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device)
+ if ret < 0:
+ failed.append(device)
+ print(f'v4l2-ctl failed on {device} without v4l2-compat')
+ continue
+ driver = grep('Driver name', out)[0].split(':')[-1].strip()
+ if driver not in supported_pipelines:
+ continue
+
+ # TODO: Add kernel version check when vimc supports scaling
+ if driver == "vimc":
+ continue
+
+ if not args.all and driver in drivers_tested:
+ continue
+
+ print(f'Testing {device} with {driver} driver... ', end='')
+ ret, msg = test_v4l2_compliance(v4l2_compliance, ld_preload, device, driver)
+ if ret == TestFail:
+ failed.append(device)
+ print('failed')
+ else:
+ print('success')
+
+ if ret == TestFail or args.verbose:
+ print('\n'.join(msg))
+
+ drivers_tested[driver] = True
+
+ if len(drivers_tested) == 0:
+ print(f'No compatible drivers found')
+ return TestSkip
+
+ if len(failed) > 0:
+ print(f'Failed {len(failed)} tests:')
+ for device in failed:
+ print(f'- {device}')
+
+ return TestPass if not failed else TestFail
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/test/v4l2_subdevice/list_formats.cpp b/test/v4l2_subdevice/list_formats.cpp
index 067dc5ed..9cbd7b94 100644
--- a/test/v4l2_subdevice/list_formats.cpp
+++ b/test/v4l2_subdevice/list_formats.cpp
@@ -5,13 +5,15 @@
* libcamera V4L2 Subdevice format handling test
*/
-#include <iomanip>
#include <iostream>
#include <vector>
#include <libcamera/geometry.h>
-#include "v4l2_subdevice.h"
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/v4l2_subdevice.h"
+
#include "v4l2_subdevice_test.h"
using namespace std;
@@ -35,8 +37,7 @@ void ListFormatsTest::printFormats(unsigned int pad,
{
cout << "Enumerate formats on pad " << pad << endl;
for (const SizeRange &size : sizes) {
- cout << " mbus code: 0x" << setfill('0') << setw(4)
- << hex << code << endl;
+ cout << " mbus code: " << utils::hex(code, 4) << endl;
cout << " min width: " << dec << size.min.width << endl;
cout << " min height: " << dec << size.min.height << endl;
cout << " max width: " << dec << size.max.width << endl;
@@ -47,29 +48,29 @@ void ListFormatsTest::printFormats(unsigned int pad,
int ListFormatsTest::run()
{
/* List all formats available on existing "Scaler" pads. */
- ImageFormats formats;
+ V4L2Subdevice::Formats formats;
formats = scaler_->formats(0);
- if (formats.isEmpty()) {
+ if (formats.empty()) {
cerr << "Failed to list formats on pad 0 of subdevice "
<< scaler_->entity()->name() << endl;
return TestFail;
}
- for (unsigned int code : formats.formats())
- printFormats(0, code, formats.sizes(code));
+ for (unsigned int code : utils::map_keys(formats))
+ printFormats(0, code, formats[code]);
formats = scaler_->formats(1);
- if (formats.isEmpty()) {
+ if (formats.empty()) {
cerr << "Failed to list formats on pad 1 of subdevice "
<< scaler_->entity()->name() << endl;
return TestFail;
}
- for (unsigned int code : formats.formats())
- printFormats(1, code, formats.sizes(code));
+ for (unsigned int code : utils::map_keys(formats))
+ printFormats(1, code, formats[code]);
/* List format on a non-existing pad, format vector shall be empty. */
formats = scaler_->formats(2);
- if (!formats.isEmpty()) {
+ if (!formats.empty()) {
cerr << "Listing formats on non-existing pad 2 of subdevice "
<< scaler_->entity()->name()
<< " should return an empty format list" << endl;
@@ -79,4 +80,4 @@ int ListFormatsTest::run()
return TestPass;
}
-TEST_REGISTER(ListFormatsTest);
+TEST_REGISTER(ListFormatsTest)
diff --git a/test/v4l2_subdevice/meson.build b/test/v4l2_subdevice/meson.build
index 0521984b..277f29bb 100644
--- a/test/v4l2_subdevice/meson.build
+++ b/test/v4l2_subdevice/meson.build
@@ -1,12 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+
v4l2_subdevice_tests = [
- [ 'list_formats', 'list_formats.cpp'],
- [ 'test_formats', 'test_formats.cpp'],
+ {'name': 'list_formats', 'sources': ['list_formats.cpp']},
+ {'name': 'test_formats', 'sources': ['test_formats.cpp']},
]
-foreach t : v4l2_subdevice_tests
- exe = executable(t[0], [t[1], 'v4l2_subdevice_test.cpp'],
- dependencies : libcamera_dep,
+foreach test : v4l2_subdevice_tests
+ exe = executable(test['name'], test['sources'], 'v4l2_subdevice_test.cpp',
+ dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'v4l2_subdevice', is_parallel : false)
+ test(test['name'], exe, suite : 'v4l2_subdevice', is_parallel : false)
endforeach
diff --git a/test/v4l2_subdevice/test_formats.cpp b/test/v4l2_subdevice/test_formats.cpp
index 5cf5d566..47f979e2 100644
--- a/test/v4l2_subdevice/test_formats.cpp
+++ b/test/v4l2_subdevice/test_formats.cpp
@@ -8,7 +8,8 @@
#include <iostream>
#include <limits.h>
-#include "v4l2_subdevice.h"
+#include "libcamera/internal/v4l2_subdevice.h"
+
#include "v4l2_subdevice_test.h"
using namespace std;
@@ -66,7 +67,7 @@ int FormatHandlingTest::run()
return TestFail;
}
- if (format.size.width == 0 || format.size.height == 0) {
+ if (format.size.isNull()) {
cerr << "Failed to update image format" << endl;
return TestFail;
}
@@ -74,4 +75,4 @@ int FormatHandlingTest::run()
return TestPass;
}
-TEST_REGISTER(FormatHandlingTest);
+TEST_REGISTER(FormatHandlingTest)
diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.cpp b/test/v4l2_subdevice/v4l2_subdevice_test.cpp
index 562a638c..c349c9e3 100644
--- a/test/v4l2_subdevice/v4l2_subdevice_test.cpp
+++ b/test/v4l2_subdevice/v4l2_subdevice_test.cpp
@@ -2,16 +2,17 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_subdevice_test.cpp - VIMC-based V4L2 subdevice test
+ * VIMC-based V4L2 subdevice test
*/
#include <iostream>
#include <string.h>
#include <sys/stat.h>
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "v4l2_subdevice.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_subdevice.h"
+
#include "v4l2_subdevice_test.h"
using namespace std;
diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.h b/test/v4l2_subdevice/v4l2_subdevice_test.h
index 3bce6691..89b78302 100644
--- a/test/v4l2_subdevice/v4l2_subdevice_test.h
+++ b/test/v4l2_subdevice/v4l2_subdevice_test.h
@@ -2,20 +2,18 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_subdevice_test.h - VIMC-based V4L2 subdevice test
+ * VIMC-based V4L2 subdevice test
*/
-#ifndef __LIBCAMERA_V4L2_SUBDEVICE_TEST_H__
-#define __LIBCAMERA_V4L2_SUBDEVICE_TEST_H__
+#pragma once
-#include <libcamera/buffer.h>
+#include <libcamera/framebuffer.h>
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "test.h"
-#include "v4l2_subdevice.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_subdevice.h"
-using namespace libcamera;
+#include "test.h"
class V4L2SubdeviceTest : public Test
{
@@ -29,9 +27,7 @@ protected:
int init() override;
void cleanup() override;
- std::unique_ptr<DeviceEnumerator> enumerator_;
- std::shared_ptr<MediaDevice> media_;
- V4L2Subdevice *scaler_;
+ std::unique_ptr<libcamera::DeviceEnumerator> enumerator_;
+ std::shared_ptr<libcamera::MediaDevice> media_;
+ libcamera::V4L2Subdevice *scaler_;
};
-
-#endif /* __LIBCAMERA_V4L2_SUBDEVICE_TEST_H__ */
diff --git a/test/v4l2_videodevice/buffer_cache.cpp b/test/v4l2_videodevice/buffer_cache.cpp
index d730e755..5a9aa219 100644
--- a/test/v4l2_videodevice/buffer_cache.cpp
+++ b/test/v4l2_videodevice/buffer_cache.cpp
@@ -9,6 +9,7 @@
#include <random>
#include <vector>
+#include <libcamera/formats.h>
#include <libcamera/stream.h>
#include "buffer_source.h"
@@ -125,6 +126,35 @@ public:
return TestPass;
}
+ int testIsEmpty(const std::vector<std::unique_ptr<FrameBuffer>> &buffers)
+ {
+ V4L2BufferCache cache(buffers.size());
+
+ if (!cache.isEmpty())
+ return TestFail;
+
+ for (auto const &buffer : buffers) {
+ FrameBuffer &b = *buffer.get();
+ cache.get(b);
+ }
+
+ if (cache.isEmpty())
+ return TestFail;
+
+ unsigned int i;
+ for (i = 0; i < buffers.size() - 1; i++)
+ cache.put(i);
+
+ if (cache.isEmpty())
+ return TestFail;
+
+ cache.put(i);
+ if (!cache.isEmpty())
+ return TestFail;
+
+ return TestPass;
+ }
+
int init() override
{
std::random_device rd;
@@ -142,7 +172,7 @@ public:
const unsigned int numBuffers = 8;
StreamConfiguration cfg;
- cfg.pixelFormat = PixelFormat(DRM_FORMAT_YUYV);
+ cfg.pixelFormat = formats::YUYV;
cfg.size = Size(600, 800);
cfg.bufferCount = numBuffers;
@@ -203,6 +233,13 @@ public:
if (testHot(&cacheHalf, buffers, numBuffers / 2) != TestPass)
return TestFail;
+ /*
+ * Test that the isEmpty function reports the correct result at
+ * various levels of cache fullness.
+ */
+ if (testIsEmpty(buffers) != TestPass)
+ return TestFail;
+
return TestPass;
}
@@ -212,4 +249,4 @@ private:
} /* namespace */
-TEST_REGISTER(BufferCacheTest);
+TEST_REGISTER(BufferCacheTest)
diff --git a/test/v4l2_videodevice/buffer_sharing.cpp b/test/v4l2_videodevice/buffer_sharing.cpp
index 14d3055a..fa856ab6 100644
--- a/test/v4l2_videodevice/buffer_sharing.cpp
+++ b/test/v4l2_videodevice/buffer_sharing.cpp
@@ -12,13 +12,17 @@
#include <iostream>
-#include <libcamera/buffer.h>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/framebuffer.h>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
-#include "thread.h"
#include "v4l2_videodevice_test.h"
+using namespace libcamera;
+using namespace std::chrono_literals;
+
class BufferSharingTest : public V4L2VideoDeviceTest
{
public:
@@ -142,7 +146,7 @@ protected:
return TestFail;
}
- timeout.start(10000);
+ timeout.start(10000ms);
while (timeout.isRunning()) {
dispatcher->processEvents();
if (framesCaptured_ > 30 && framesOutput_ > 30)
@@ -199,4 +203,4 @@ private:
unsigned int framesOutput_;
};
-TEST_REGISTER(BufferSharingTest);
+TEST_REGISTER(BufferSharingTest)
diff --git a/test/v4l2_videodevice/capture_async.cpp b/test/v4l2_videodevice/capture_async.cpp
index b38aabc6..67366461 100644
--- a/test/v4l2_videodevice/capture_async.cpp
+++ b/test/v4l2_videodevice/capture_async.cpp
@@ -7,13 +7,17 @@
#include <iostream>
-#include <libcamera/buffer.h>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/framebuffer.h>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
-#include "thread.h"
#include "v4l2_videodevice_test.h"
+using namespace libcamera;
+using namespace std::chrono_literals;
+
class CaptureAsyncTest : public V4L2VideoDeviceTest
{
public:
@@ -57,10 +61,12 @@ protected:
if (ret)
return TestFail;
- timeout.start(10000);
+ const unsigned int nFrames = 30;
+
+ timeout.start(500ms * nFrames);
while (timeout.isRunning()) {
dispatcher->processEvents();
- if (frames > 30)
+ if (frames > nFrames)
break;
}
@@ -69,8 +75,9 @@ protected:
return TestFail;
}
- if (frames < 30) {
- std::cout << "Failed to capture 30 frames within timeout." << std::endl;
+ if (frames < nFrames) {
+ std::cout << "Failed to capture " << nFrames
+ << " frames within timeout." << std::endl;
return TestFail;
}
@@ -87,4 +94,4 @@ private:
unsigned int frames;
};
-TEST_REGISTER(CaptureAsyncTest);
+TEST_REGISTER(CaptureAsyncTest)
diff --git a/test/v4l2_videodevice/controls.cpp b/test/v4l2_videodevice/controls.cpp
index da9e0111..b0130295 100644
--- a/test/v4l2_videodevice/controls.cpp
+++ b/test/v4l2_videodevice/controls.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * controls.cpp - V4L2 device controls handling test
+ * V4L2 device controls handling test
*/
#include <algorithm>
@@ -10,12 +10,13 @@
#include <iostream>
#include <limits.h>
-#include "v4l2_videodevice.h"
+#include "libcamera/internal/v4l2_videodevice.h"
#include "v4l2_videodevice_test.h"
/* These come from the vivid driver. */
#define VIVID_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000)
+#define VIVID_CID_INTEGER64 (VIVID_CID_CUSTOM_BASE + 3)
#define VIVID_CID_U8_4D_ARRAY (VIVID_CID_CUSTOM_BASE + 10)
/* Helper for VIVID_CID_U8_4D_ARRAY control array size: not from kernel. */
@@ -46,6 +47,7 @@ protected:
if (infoMap.find(V4L2_CID_BRIGHTNESS) == infoMap.end() ||
infoMap.find(V4L2_CID_CONTRAST) == infoMap.end() ||
infoMap.find(V4L2_CID_SATURATION) == infoMap.end() ||
+ infoMap.find(VIVID_CID_INTEGER64) == infoMap.end() ||
infoMap.find(VIVID_CID_U8_4D_ARRAY) == infoMap.end()) {
cerr << "Missing controls" << endl;
return TestFail;
@@ -54,21 +56,25 @@ protected:
const ControlInfo &brightness = infoMap.find(V4L2_CID_BRIGHTNESS)->second;
const ControlInfo &contrast = infoMap.find(V4L2_CID_CONTRAST)->second;
const ControlInfo &saturation = infoMap.find(V4L2_CID_SATURATION)->second;
+ const ControlInfo &int64 = infoMap.find(VIVID_CID_INTEGER64)->second;
const ControlInfo &u8 = infoMap.find(VIVID_CID_U8_4D_ARRAY)->second;
/* Test getting controls. */
- ControlList ctrls(infoMap);
- ctrls.set(V4L2_CID_BRIGHTNESS, -1);
- ctrls.set(V4L2_CID_CONTRAST, -1);
- ctrls.set(V4L2_CID_SATURATION, -1);
- ctrls.set(VIVID_CID_U8_4D_ARRAY, 0);
-
- int ret = capture_->getControls(&ctrls);
- if (ret) {
+ ControlList ctrls = capture_->getControls({ V4L2_CID_BRIGHTNESS,
+ V4L2_CID_CONTRAST,
+ V4L2_CID_SATURATION,
+ VIVID_CID_INTEGER64,
+ VIVID_CID_U8_4D_ARRAY });
+ if (ctrls.empty()) {
cerr << "Failed to get controls" << endl;
return TestFail;
}
+ if (ctrls.infoMap() != &infoMap) {
+ cerr << "Incorrect infoMap for retrieved controls" << endl;
+ return TestFail;
+ }
+
if (ctrls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() == -1 ||
ctrls.get(V4L2_CID_CONTRAST).get<int32_t>() == -1 ||
ctrls.get(V4L2_CID_SATURATION).get<int32_t>() == -1) {
@@ -76,6 +82,12 @@ protected:
return TestFail;
}
+ /*
+ * The VIVID_CID_INTEGER64 control can take any value, just test
+ * that its value can be retrieved and has the right type.
+ */
+ ctrls.get(VIVID_CID_INTEGER64).get<int64_t>();
+
uint8_t u8Min = u8.min().get<uint8_t>();
uint8_t u8Max = u8.max().get<uint8_t>();
@@ -92,12 +104,13 @@ protected:
ctrls.set(V4L2_CID_BRIGHTNESS, brightness.min());
ctrls.set(V4L2_CID_CONTRAST, contrast.max());
ctrls.set(V4L2_CID_SATURATION, saturation.min());
+ ctrls.set(VIVID_CID_INTEGER64, int64.min());
std::array<uint8_t, VIVID_CID_U8_ARRAY_SIZE> u8Values;
std::fill(u8Values.begin(), u8Values.end(), u8.min().get<uint8_t>());
ctrls.set(VIVID_CID_U8_4D_ARRAY, Span<const uint8_t>(u8Values));
- ret = capture_->setControls(&ctrls);
+ int ret = capture_->setControls(&ctrls);
if (ret) {
cerr << "Failed to set controls" << endl;
return TestFail;
@@ -125,4 +138,4 @@ protected:
}
};
-TEST_REGISTER(V4L2ControlTest);
+TEST_REGISTER(V4L2ControlTest)
diff --git a/test/v4l2_videodevice/dequeue_watchdog.cpp b/test/v4l2_videodevice/dequeue_watchdog.cpp
new file mode 100644
index 00000000..320d14c8
--- /dev/null
+++ b/test/v4l2_videodevice/dequeue_watchdog.cpp
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy.
+ *
+ * libcamera V4L2 dequeue watchdog test
+ */
+
+#include <iostream>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include <libcamera/framebuffer.h>
+
+#include "v4l2_videodevice_test.h"
+
+using namespace libcamera;
+using namespace std::chrono_literals;
+
+class DequeueWatchdogTest : public V4L2VideoDeviceTest
+{
+public:
+ DequeueWatchdogTest()
+ : V4L2VideoDeviceTest("vimc", "Raw Capture 0"), frames_(0), barks_(0) {}
+
+protected:
+ int run()
+ {
+ constexpr unsigned int bufferCount = 8;
+
+ EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
+ Timer timeout;
+
+ int ret = capture_->allocateBuffers(bufferCount, &buffers_);
+ if (ret < 0) {
+ std::cout << "Failed to allocate buffers" << std::endl;
+ return TestFail;
+ }
+
+ capture_->dequeueTimeout.connect(this, &DequeueWatchdogTest::barkCounter);
+ capture_->setDequeueTimeout(5ms);
+
+ capture_->bufferReady.connect(this, &DequeueWatchdogTest::receiveBuffer);
+
+ for (const std::unique_ptr<FrameBuffer> &buffer : buffers_) {
+ if (capture_->queueBuffer(buffer.get())) {
+ std::cout << "Failed to queue buffer" << std::endl;
+ return TestFail;
+ }
+ }
+
+ ret = capture_->streamOn();
+ if (ret < 0) {
+ std::cout << "Failed to start streaming" << std::endl;
+ return TestFail;
+ }
+
+ timeout.start(5s);
+ while (timeout.isRunning()) {
+ dispatcher->processEvents();
+ if (frames_ > 5)
+ break;
+ }
+
+ std::cout << "Processed " << frames_ << " frames_ and heard "
+ << barks_ << " barks_" << std::endl;
+
+ if (!barks_) {
+ std::cout << "Failed to hear any barks_." << std::endl;
+ return TestFail;
+ }
+
+ capture_->streamOff();
+
+ return TestPass;
+ }
+
+private:
+ void receiveBuffer(FrameBuffer *buffer)
+ {
+ if (buffer->metadata().status == FrameMetadata::FrameCancelled)
+ return;
+
+ std::cout << "Buffer received" << std::endl;
+ frames_++;
+
+ /* Requeue the buffer for further use. */
+ capture_->queueBuffer(buffer);
+ }
+
+ void barkCounter()
+ {
+ std::cout << "Watchdog is barking" << std::endl;
+ barks_++;
+ }
+
+ unsigned int frames_;
+ unsigned int barks_;
+};
+
+TEST_REGISTER(DequeueWatchdogTest)
diff --git a/test/v4l2_videodevice/double_open.cpp b/test/v4l2_videodevice/double_open.cpp
index 5768d404..7d11f6fe 100644
--- a/test/v4l2_videodevice/double_open.cpp
+++ b/test/v4l2_videodevice/double_open.cpp
@@ -38,4 +38,4 @@ protected:
} /* namespace */
-TEST_REGISTER(DoubleOpen);
+TEST_REGISTER(DoubleOpen)
diff --git a/test/v4l2_videodevice/formats.cpp b/test/v4l2_videodevice/formats.cpp
index a7421421..6c052622 100644
--- a/test/v4l2_videodevice/formats.cpp
+++ b/test/v4l2_videodevice/formats.cpp
@@ -8,8 +8,9 @@
#include <iostream>
#include <limits.h>
-#include "utils.h"
-#include "v4l2_videodevice.h"
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/v4l2_videodevice.h"
#include "v4l2_videodevice_test.h"
@@ -55,10 +56,10 @@ protected:
{ V4L2_PIX_FMT_Y16_BE, "Y16 -BE" }
};
- for (const auto &format : formats) {
- if (V4L2PixelFormat(format.first).toString() != format.second) {
+ for (const auto &fmt : formats) {
+ if (V4L2PixelFormat(fmt.first).toString() != fmt.second) {
cerr << "Failed to convert V4L2PixelFormat"
- << utils::hex(format.first) << "to string"
+ << utils::hex(fmt.first) << "to string"
<< endl;
return TestFail;
}
@@ -74,4 +75,4 @@ protected:
}
};
-TEST_REGISTER(Format);
+TEST_REGISTER(Format)
diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build
index 685fcf6d..87ea4f96 100644
--- a/test/v4l2_videodevice/meson.build
+++ b/test/v4l2_videodevice/meson.build
@@ -1,21 +1,24 @@
+# SPDX-License-Identifier: CC0-1.0
+
# Tests are listed in order of complexity.
# They are not alphabetically sorted.
v4l2_videodevice_tests = [
- [ 'double_open', 'double_open.cpp' ],
- [ 'controls', 'controls.cpp' ],
- [ 'formats', 'formats.cpp' ],
- [ 'request_buffers', 'request_buffers.cpp' ],
- [ 'buffer_cache', 'buffer_cache.cpp' ],
- [ 'stream_on_off', 'stream_on_off.cpp' ],
- [ 'capture_async', 'capture_async.cpp' ],
- [ 'buffer_sharing', 'buffer_sharing.cpp' ],
- [ 'v4l2_m2mdevice', 'v4l2_m2mdevice.cpp' ],
+ {'name': 'double_open', 'sources': ['double_open.cpp']},
+ {'name': 'controls', 'sources': ['controls.cpp']},
+ {'name': 'formats', 'sources': ['formats.cpp']},
+ {'name': 'dequeue_watchdog', 'sources': ['dequeue_watchdog.cpp']},
+ {'name': 'request_buffers', 'sources': ['request_buffers.cpp']},
+ {'name': 'buffer_cache', 'sources': ['buffer_cache.cpp']},
+ {'name': 'stream_on_off', 'sources': ['stream_on_off.cpp']},
+ {'name': 'capture_async', 'sources': ['capture_async.cpp']},
+ {'name': 'buffer_sharing', 'sources': ['buffer_sharing.cpp']},
+ {'name': 'v4l2_m2mdevice', 'sources': ['v4l2_m2mdevice.cpp']},
]
-foreach t : v4l2_videodevice_tests
- exe = executable(t[0], [t[1], 'v4l2_videodevice_test.cpp'],
- dependencies : libcamera_dep,
+foreach test : v4l2_videodevice_tests
+ exe = executable(test['name'], [test['sources'], 'v4l2_videodevice_test.cpp'],
+ dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'v4l2_videodevice', is_parallel : false)
+ test(test['name'], exe, suite : 'v4l2_videodevice', is_parallel : false)
endforeach
diff --git a/test/v4l2_videodevice/request_buffers.cpp b/test/v4l2_videodevice/request_buffers.cpp
index 2f8dfe1c..fb577147 100644
--- a/test/v4l2_videodevice/request_buffers.cpp
+++ b/test/v4l2_videodevice/request_buffers.cpp
@@ -26,4 +26,4 @@ protected:
}
};
-TEST_REGISTER(RequestBuffersTest);
+TEST_REGISTER(RequestBuffersTest)
diff --git a/test/v4l2_videodevice/stream_on_off.cpp b/test/v4l2_videodevice/stream_on_off.cpp
index ce48310a..4ed99e93 100644
--- a/test/v4l2_videodevice/stream_on_off.cpp
+++ b/test/v4l2_videodevice/stream_on_off.cpp
@@ -33,4 +33,4 @@ protected:
}
};
-TEST_REGISTER(StreamOnStreamOffTest);
+TEST_REGISTER(StreamOnStreamOffTest)
diff --git a/test/v4l2_videodevice/v4l2_m2mdevice.cpp b/test/v4l2_videodevice/v4l2_m2mdevice.cpp
index d20e5dfc..c45f581a 100644
--- a/test/v4l2_videodevice/v4l2_m2mdevice.cpp
+++ b/test/v4l2_videodevice/v4l2_m2mdevice.cpp
@@ -7,19 +7,21 @@
#include <iostream>
-#include <libcamera/buffer.h>
-#include <libcamera/event_dispatcher.h>
-#include <libcamera/timer.h>
+#include <libcamera/framebuffer.h>
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "thread.h"
-#include "v4l2_videodevice.h"
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_videodevice.h"
#include "test.h"
-using namespace std;
using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
class V4L2M2MDeviceTest : public Test
{
@@ -93,6 +95,11 @@ protected:
V4L2VideoDevice *capture = vim2m_->capture();
V4L2VideoDevice *output = vim2m_->output();
+ if (capture->controls().empty() || output->controls().empty()) {
+ cerr << "VIM2M device has no control" << endl;
+ return TestFail;
+ }
+
V4L2DeviceFormat format = {};
if (capture->getFormat(&format)) {
cerr << "Failed to get capture format" << endl;
@@ -154,7 +161,7 @@ protected:
}
Timer timeout;
- timeout.start(5000);
+ timeout.start(5000ms);
while (timeout.isRunning()) {
dispatcher->processEvents();
if (captureFrames_ > 30)
@@ -187,7 +194,7 @@ protected:
void cleanup()
{
delete vim2m_;
- };
+ }
private:
std::unique_ptr<DeviceEnumerator> enumerator_;
@@ -201,4 +208,4 @@ private:
unsigned int captureFrames_;
};
-TEST_REGISTER(V4L2M2MDeviceTest);
+TEST_REGISTER(V4L2M2MDeviceTest)
diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.cpp b/test/v4l2_videodevice/v4l2_videodevice_test.cpp
index 93b9e72d..9fbd24cc 100644
--- a/test/v4l2_videodevice/v4l2_videodevice_test.cpp
+++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp
@@ -9,10 +9,10 @@
#include <linux/media-bus-format.h>
-#include "v4l2_videodevice_test.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
-#include "device_enumerator.h"
-#include "media_device.h"
+#include "v4l2_videodevice_test.h"
using namespace std;
using namespace libcamera;
@@ -60,9 +60,12 @@ int V4L2VideoDeviceTest::init()
if (capture_->getFormat(&format))
return TestFail;
+ format.size.width = 640;
+ format.size.height = 480;
+
if (driver_ == "vimc") {
- sensor_ = new CameraSensor(media_->getEntityByName("Sensor A"));
- if (sensor_->init())
+ sensor_ = CameraSensorFactoryBase::create(media_->getEntityByName("Sensor A"));
+ if (!sensor_)
return TestFail;
debayer_ = new V4L2Subdevice(media_->getEntityByName("Debayer A"));
@@ -72,7 +75,7 @@ int V4L2VideoDeviceTest::init()
format.fourcc = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8);
V4L2SubdeviceFormat subformat = {};
- subformat.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8;
+ subformat.code = MEDIA_BUS_FMT_SBGGR8_1X8;
subformat.size = format.size;
if (sensor_->setFormat(&subformat))
@@ -82,8 +85,6 @@ int V4L2VideoDeviceTest::init()
return TestFail;
}
- format.size.width = 640;
- format.size.height = 480;
if (capture_->setFormat(&format))
return TestFail;
@@ -97,6 +98,5 @@ void V4L2VideoDeviceTest::cleanup()
capture_->close();
delete debayer_;
- delete sensor_;
delete capture_;
}
diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.h b/test/v4l2_videodevice/v4l2_videodevice_test.h
index 9acaceb8..7c9003ec 100644
--- a/test/v4l2_videodevice/v4l2_videodevice_test.h
+++ b/test/v4l2_videodevice/v4l2_videodevice_test.h
@@ -2,24 +2,22 @@
/*
* Copyright (C) 2018, Google Inc.
*
- * vl42device_test.h - libcamera v4l2device test base class
+ * libcamera v4l2device test base class
*/
-#ifndef __LIBCAMERA_V4L2_DEVICE_TEST_H_
-#define __LIBCAMERA_V4L2_DEVICE_TEST_H_
-#include <memory>
+#pragma once
-#include <libcamera/buffer.h>
+#include <memory>
-#include "test.h"
+#include <libcamera/framebuffer.h>
-#include "camera_sensor.h"
-#include "device_enumerator.h"
-#include "media_device.h"
-#include "v4l2_subdevice.h"
-#include "v4l2_videodevice.h"
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_subdevice.h"
+#include "libcamera/internal/v4l2_videodevice.h"
-using namespace libcamera;
+#include "test.h"
class V4L2VideoDeviceTest : public Test
{
@@ -36,12 +34,10 @@ protected:
std::string driver_;
std::string entity_;
- std::unique_ptr<DeviceEnumerator> enumerator_;
- std::shared_ptr<MediaDevice> media_;
- CameraSensor *sensor_;
- V4L2Subdevice *debayer_;
- V4L2VideoDevice *capture_;
- std::vector<std::unique_ptr<FrameBuffer>> buffers_;
+ std::unique_ptr<libcamera::DeviceEnumerator> enumerator_;
+ std::shared_ptr<libcamera::MediaDevice> media_;
+ std::unique_ptr<libcamera::CameraSensor> sensor_;
+ libcamera::V4L2Subdevice *debayer_;
+ libcamera::V4L2VideoDevice *capture_;
+ std::vector<std::unique_ptr<libcamera::FrameBuffer>> buffers_;
};
-
-#endif /* __LIBCAMERA_V4L2_DEVICE_TEST_H_ */
diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
new file mode 100644
index 00000000..1b22c87b
--- /dev/null
+++ b/test/yaml-parser.cpp
@@ -0,0 +1,620 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * YAML parser operations tests
+ */
+
+#include <array>
+#include <iostream>
+#include <map>
+#include <string>
+#include <unistd.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+static const string testYaml =
+ "string: libcamera\n"
+ "double: 3.14159\n"
+ "int8_t: -100\n"
+ "uint8_t: 100\n"
+ "int16_t: -1000\n"
+ "uint16_t: 1000\n"
+ "int32_t: -100000\n"
+ "uint32_t: 100000\n"
+ "size: [1920, 1080]\n"
+ "list:\n"
+ " - James\n"
+ " - Mary\n"
+ " - \n"
+ "dictionary:\n"
+ " a: 1\n"
+ " c: 3\n"
+ " b: 2\n"
+ " empty:\n"
+ "level1:\n"
+ " level2:\n"
+ " - [1, 2]\n"
+ " - {one: 1, two: 2}\n";
+
+static const string invalidYaml =
+ "Invalid : - YAML : - Content";
+
+class YamlParserTest : public Test
+{
+protected:
+ bool createFile(const string &content, string &filename)
+ {
+ filename = "/tmp/libcamera.test.XXXXXX";
+ int fd = mkstemp(&filename.front());
+ if (fd == -1)
+ return false;
+
+ int ret = write(fd, content.c_str(), content.size());
+ close(fd);
+
+ if (ret != static_cast<int>(content.size()))
+ return false;
+
+ return true;
+ }
+
+ int init()
+ {
+ if (!createFile(testYaml, testYamlFile_))
+ return TestFail;
+
+ if (!createFile(invalidYaml, invalidYamlFile_))
+ return TestFail;
+
+ return TestPass;
+ }
+
+ enum class Type {
+ String,
+ Int8,
+ UInt8,
+ Int16,
+ UInt16,
+ Int32,
+ UInt32,
+ Double,
+ Size,
+ List,
+ Dictionary,
+ };
+
+ int testObjectType(const YamlObject &obj, const char *name, Type type)
+ {
+ bool isList = type == Type::List || type == Type::Size;
+ bool isScalar = !isList && type != Type::Dictionary;
+ bool isInteger8 = type == Type::Int8 || type == Type::UInt8;
+ bool isInteger16 = type == Type::Int16 || type == Type::UInt16;
+ bool isInteger32 = type == Type::Int32 || type == Type::UInt32;
+ bool isIntegerUpTo16 = isInteger8 || isInteger16;
+ bool isIntegerUpTo32 = isIntegerUpTo16 || isInteger32;
+ bool isSigned = type == Type::Int8 || type == Type::Int16 ||
+ type == Type::Int32;
+
+ if ((isScalar && !obj.isValue()) || (!isScalar && obj.isValue())) {
+ std::cerr
+ << "Object " << name << " type mismatch when compared to "
+ << "value" << std::endl;
+ return TestFail;
+ }
+
+ if ((isList && !obj.isList()) || (!isList && obj.isList())) {
+ std::cerr
+ << "Object " << name << " type mismatch when compared to "
+ << "list" << std::endl;
+ return TestFail;
+ }
+
+ if ((type == Type::Dictionary && !obj.isDictionary()) ||
+ (type != Type::Dictionary && obj.isDictionary())) {
+ std::cerr
+ << "Object " << name << " type mismatch when compared to "
+ << "dictionary" << std::endl;
+ return TestFail;
+ }
+
+ if (!isScalar && obj.get<std::string>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "string" << std::endl;
+ return TestFail;
+ }
+
+ if (!isInteger8 && obj.get<int8_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "int8_t" << std::endl;
+ return TestFail;
+ }
+
+ if ((!isInteger8 || isSigned) && obj.get<uint8_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "uint8_t" << std::endl;
+ return TestFail;
+ }
+
+ if (!isIntegerUpTo16 && obj.get<int16_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "int16_t" << std::endl;
+ return TestFail;
+ }
+
+ if ((!isIntegerUpTo16 || isSigned) && obj.get<uint16_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "uint16_t" << std::endl;
+ return TestFail;
+ }
+
+ if (!isIntegerUpTo32 && obj.get<int32_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "int32_t" << std::endl;
+ return TestFail;
+ }
+
+ if ((!isIntegerUpTo32 || isSigned) && obj.get<uint32_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "uint32_t" << std::endl;
+ return TestFail;
+ }
+
+ if (!isIntegerUpTo32 && type != Type::Double && obj.get<double>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "double" << std::endl;
+ return TestFail;
+ }
+
+ if (type != Type::Size && obj.get<Size>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "Size" << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ int testIntegerObject(const YamlObject &obj, const char *name, Type type,
+ int64_t value)
+ {
+ uint64_t unsignedValue = static_cast<uint64_t>(value);
+ std::string strValue = std::to_string(value);
+ bool isInteger8 = type == Type::Int8 || type == Type::UInt8;
+ bool isInteger16 = type == Type::Int16 || type == Type::UInt16;
+ bool isSigned = type == Type::Int8 || type == Type::Int16 ||
+ type == Type::Int32;
+
+ /* All integers can be parsed as strings or double. */
+
+ if (obj.get<string>().value_or("") != strValue ||
+ obj.get<string>("") != strValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "string" << std::endl;
+ return TestFail;
+ }
+
+ if (obj.get<double>().value_or(0.0) != value ||
+ obj.get<double>(0.0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "double" << std::endl;
+ return TestFail;
+ }
+
+ if (isInteger8) {
+ if (obj.get<int8_t>().value_or(0) != value ||
+ obj.get<int8_t>(0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "int8_t" << std::endl;
+ return TestFail;
+ }
+ }
+
+ if (isInteger8 && !isSigned) {
+ if (obj.get<uint8_t>().value_or(0) != unsignedValue ||
+ obj.get<uint8_t>(0) != unsignedValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "uint8_t" << std::endl;
+ return TestFail;
+ }
+ }
+
+ if (isInteger8 || isInteger16) {
+ if (obj.get<int16_t>().value_or(0) != value ||
+ obj.get<int16_t>(0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "int16_t" << std::endl;
+ return TestFail;
+ }
+ }
+
+ if ((isInteger8 || isInteger16) && !isSigned) {
+ if (obj.get<uint16_t>().value_or(0) != unsignedValue ||
+ obj.get<uint16_t>(0) != unsignedValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "uint16_t" << std::endl;
+ return TestFail;
+ }
+ }
+
+ if (obj.get<int32_t>().value_or(0) != value ||
+ obj.get<int32_t>(0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "int32_t" << std::endl;
+ return TestFail;
+ }
+
+ if (!isSigned) {
+ if (obj.get<uint32_t>().value_or(0) != unsignedValue ||
+ obj.get<uint32_t>(0) != unsignedValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "uint32_t" << std::endl;
+ return TestFail;
+ }
+ }
+
+ return TestPass;
+ }
+
+ int run()
+ {
+ /* Test invalid YAML file */
+ File file{ invalidYamlFile_ };
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ cerr << "Fail to open invalid YAML file" << std::endl;
+ return TestFail;
+ }
+
+ std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+ if (root) {
+ cerr << "Invalid YAML file parse successfully" << std::endl;
+ return TestFail;
+ }
+
+ /* Test YAML file */
+ file.close();
+ file.setFileName(testYamlFile_);
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ cerr << "Fail to open test YAML file" << std::endl;
+ return TestFail;
+ }
+
+ root = YamlParser::parse(file);
+
+ if (!root) {
+ cerr << "Fail to parse test YAML file: " << std::endl;
+ return TestFail;
+ }
+
+ if (!root->isDictionary()) {
+ cerr << "YAML root is not dictionary" << std::endl;
+ return TestFail;
+ }
+
+ std::vector<const char *> rootElemNames = {
+ "string", "double", "int8_t", "uint8_t", "int16_t",
+ "uint16_t", "int32_t", "uint32_t", "size", "list",
+ "dictionary", "level1",
+ };
+
+ for (const char *name : rootElemNames) {
+ if (!root->contains(name)) {
+ cerr << "Missing " << name << " object in YAML root"
+ << std::endl;
+ return TestFail;
+ }
+ }
+
+ /* Test string object */
+ auto &strObj = (*root)["string"];
+
+ if (testObjectType(strObj, "string", Type::String) != TestPass)
+ return TestFail;
+
+ if (strObj.get<string>().value_or("") != "libcamera" ||
+ strObj.get<string>("") != "libcamera") {
+ cerr << "String object parse as wrong content" << std::endl;
+ return TestFail;
+ }
+
+ /* Test int8_t object */
+ auto &int8Obj = (*root)["int8_t"];
+
+ if (testObjectType(int8Obj, "int8_t", Type::Int8) != TestPass)
+ return TestFail;
+
+ if (testIntegerObject(int8Obj, "int8_t", Type::Int8, -100) != TestPass)
+ return TestFail;
+
+ /* Test uint8_t object */
+ auto &uint8Obj = (*root)["uint8_t"];
+
+ if (testObjectType(uint8Obj, "uint8_t", Type::UInt8) != TestPass)
+ return TestFail;
+
+ if (testIntegerObject(uint8Obj, "uint8_t", Type::UInt8, 100) != TestPass)
+ return TestFail;
+
+ /* Test int16_t object */
+ auto &int16Obj = (*root)["int16_t"];
+
+ if (testObjectType(int16Obj, "int16_t", Type::Int16) != TestPass)
+ return TestFail;
+
+ if (testIntegerObject(int16Obj, "int16_t", Type::Int16, -1000) != TestPass)
+ return TestFail;
+
+ /* Test uint16_t object */
+ auto &uint16Obj = (*root)["uint16_t"];
+
+ if (testObjectType(uint16Obj, "uint16_t", Type::UInt16) != TestPass)
+ return TestFail;
+
+ if (testIntegerObject(uint16Obj, "uint16_t", Type::UInt16, 1000) != TestPass)
+ return TestFail;
+
+ /* Test int32_t object */
+ auto &int32Obj = (*root)["int32_t"];
+
+ if (testObjectType(int32Obj, "int32_t", Type::Int32) != TestPass)
+ return TestFail;
+
+ if (testIntegerObject(int32Obj, "int32_t", Type::Int32, -100000) != TestPass)
+ return TestFail;
+
+ /* Test uint32_t object */
+ auto &uint32Obj = (*root)["uint32_t"];
+
+ if (testObjectType(uint32Obj, "uint32_t", Type::UInt32) != TestPass)
+ return TestFail;
+
+ if (testIntegerObject(uint32Obj, "uint32_t", Type::UInt32, 100000) != TestPass)
+ return TestFail;
+
+ /* Test double value */
+ auto &doubleObj = (*root)["double"];
+
+ if (testObjectType(doubleObj, "double", Type::Double) != TestPass)
+ return TestFail;
+
+ if (doubleObj.get<string>().value_or("") != "3.14159" ||
+ doubleObj.get<string>("") != "3.14159") {
+ cerr << "Double object fail to parse as string" << std::endl;
+ return TestFail;
+ }
+
+ if (doubleObj.get<double>().value_or(0.0) != 3.14159 ||
+ doubleObj.get<double>(0.0) != 3.14159) {
+ cerr << "Double object parse as wrong value" << std::endl;
+ return TestFail;
+ }
+
+ /* Test Size value */
+ auto &sizeObj = (*root)["size"];
+
+ if (testObjectType(sizeObj, "size", Type::Size) != TestPass)
+ return TestFail;
+
+ if (sizeObj.get<Size>().value_or(Size(0, 0)) != Size(1920, 1080) ||
+ sizeObj.get<Size>(Size(0, 0)) != Size(1920, 1080)) {
+ cerr << "Size object parse as wrong value" << std::endl;
+ return TestFail;
+ }
+
+ /* Test list object */
+ auto &listObj = (*root)["list"];
+
+ if (testObjectType(listObj, "list", Type::List) != TestPass)
+ return TestFail;
+
+ static constexpr std::array<const char *, 3> listValues{
+ "James",
+ "Mary",
+ "",
+ };
+
+ if (listObj.size() != listValues.size()) {
+ cerr << "List object parse with wrong size" << std::endl;
+ return TestFail;
+ }
+
+ unsigned int i = 0;
+ for (auto &elem : listObj.asList()) {
+ if (i >= listValues.size()) {
+ std::cerr << "Too many elements in list during iteration"
+ << std::endl;
+ return TestFail;
+ }
+
+ std::string value = listValues[i];
+
+ if (&elem != &listObj[i]) {
+ std::cerr << "List element " << i << " has wrong address"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (elem.get<std::string>("") != value) {
+ std::cerr << "List element " << i << " has wrong value"
+ << std::endl;
+ return TestFail;
+ }
+
+ i++;
+ }
+
+ /* Ensure that empty objects get parsed as empty strings. */
+ if (!listObj[2].isValue()) {
+ cerr << "Empty object is not a value" << std::endl;
+ return TestFail;
+ }
+
+ /* Test dictionary object */
+ auto &dictObj = (*root)["dictionary"];
+
+ if (testObjectType(dictObj, "dictionary", Type::Dictionary) != TestPass)
+ return TestFail;
+
+ static constexpr std::array<std::pair<const char *, int>, 4> dictValues{ {
+ { "a", 1 },
+ { "c", 3 },
+ { "b", 2 },
+ { "empty", -100 },
+ } };
+
+ size_t dictSize = dictValues.size();
+
+ if (dictObj.size() != dictSize) {
+ cerr << "Dictionary object has wrong size" << std::endl;
+ return TestFail;
+ }
+
+ i = 0;
+ for (const auto &[key, elem] : dictObj.asDict()) {
+ if (i >= dictSize) {
+ std::cerr << "Too many elements in dictionary during iteration"
+ << std::endl;
+ return TestFail;
+ }
+
+ const auto &item = dictValues[i];
+ if (item.first != key) {
+ std::cerr << "Dictionary key " << i << " has wrong value"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (&elem != &dictObj[key]) {
+ std::cerr << "Dictionary element " << i << " has wrong address"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (elem.get<int32_t>(-100) != item.second) {
+ std::cerr << "Dictionary element " << i << " has wrong value"
+ << std::endl;
+ return TestFail;
+ }
+
+ i++;
+ }
+
+ /* Ensure that empty objects get parsed as empty strings. */
+ if (!dictObj["empty"].isValue()) {
+ cerr << "Empty object is not of type value" << std::endl;
+ return TestFail;
+ }
+
+ /* Ensure that keys without values are added to a dict. */
+ if (!dictObj.contains("empty")) {
+ cerr << "Empty element is missing in dict" << std::endl;
+ return TestFail;
+ }
+
+ /* Test access to nonexistent member. */
+ if (dictObj["nonexistent"].get<std::string>("default") != "default") {
+ cerr << "Accessing nonexistent dict entry fails to return default" << std::endl;
+ return TestFail;
+ }
+
+ /* Test nonexistent object has value type empty. */
+ if (!dictObj["nonexistent"].isEmpty()) {
+ cerr << "Accessing nonexistent object returns non-empty object" << std::endl;
+ return TestFail;
+ }
+
+ /* Test explicit cast to bool on an empty object returns true. */
+ if (!!dictObj["empty"] != true) {
+ cerr << "Casting empty entry to bool returns false" << std::endl;
+ return TestFail;
+ }
+
+ /* Test explicit cast to bool on nonexistent object returns false. */
+ if (!!dictObj["nonexistent"] != false) {
+ cerr << "Casting nonexistent dict entry to bool returns true" << std::endl;
+ return TestFail;
+ }
+
+ /* Make sure utils::map_keys() works on the adapter. */
+ (void)utils::map_keys(dictObj.asDict());
+
+ /* Test leveled objects */
+ auto &level1Obj = (*root)["level1"];
+
+ if (!level1Obj.isDictionary()) {
+ cerr << "level1 object fail to parse as Dictionary" << std::endl;
+ return TestFail;
+ }
+
+ auto &level2Obj = level1Obj["level2"];
+
+ if (!level2Obj.isList() || level2Obj.size() != 2) {
+ cerr << "level2 object should be 2 element list" << std::endl;
+ return TestFail;
+ }
+
+ auto &firstElement = level2Obj[0];
+ if (!firstElement.isList() ||
+ firstElement.size() != 2 ||
+ firstElement[0].get<int32_t>(0) != 1 ||
+ firstElement[1].get<int32_t>(0) != 2) {
+ cerr << "The first element of level2 object fail to parse as integer list" << std::endl;
+ return TestFail;
+ }
+
+ const auto &values = firstElement.getList<uint16_t>();
+ if (!values || values->size() != 2 || (*values)[0] != 1 || (*values)[1] != 2) {
+ cerr << "getList() failed to return correct vector" << std::endl;
+ return TestFail;
+ }
+
+ auto &secondElement = level2Obj[1];
+ if (!secondElement.isDictionary() ||
+ !secondElement.contains("one") ||
+ !secondElement.contains("two") ||
+ secondElement["one"].get<int32_t>(0) != 1 ||
+ secondElement["two"].get<int32_t>(0) != 2) {
+ cerr << "The second element of level2 object fail to parse as dictionary" << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+ void cleanup()
+ {
+ unlink(testYamlFile_.c_str());
+ unlink(invalidYamlFile_.c_str());
+ }
+
+private:
+ std::string testYamlFile_;
+ std::string invalidYamlFile_;
+};
+
+TEST_REGISTER(YamlParserTest)