diff options
Diffstat (limited to 'test')
82 files changed, 1211 insertions, 308 deletions
diff --git a/test/bayer-format.cpp b/test/bayer-format.cpp index 54f03487..f8d19804 100644 --- a/test/bayer-format.cpp +++ b/test/bayer-format.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Sebastian Fricke * - * bayer_format.cpp - BayerFormat class tests + * BayerFormat class tests */ #include <iostream> diff --git a/test/byte-stream-buffer.cpp b/test/byte-stream-buffer.cpp index 04ff0571..04aab3d2 100644 --- a/test/byte-stream-buffer.cpp +++ b/test/byte-stream-buffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * byte_stream_buffer.cpp - ByteStreamBuffer tests + * ByteStreamBuffer tests */ #include <array> diff --git a/test/camera-sensor.cpp b/test/camera-sensor.cpp index 2a17cc79..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> @@ -52,8 +52,8 @@ 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; } @@ -100,7 +100,7 @@ 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) @@ -118,13 +118,12 @@ 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_; }; diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp index 92884004..815d1cae 100644 --- a/test/camera/buffer_import.cpp +++ b/test/camera/buffer_import.cpp @@ -63,6 +63,8 @@ protected: request->reuse(); request->addBuffer(stream, buffer); camera_->queueRequest(request); + + dispatcher_->interrupt(); } int init() override @@ -76,6 +78,8 @@ protected: return TestFail; } + dispatcher_ = Thread::current()->eventDispatcher(); + return TestPass; } @@ -133,17 +137,20 @@ protected: } } - EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); + const unsigned int nFrames = cfg.bufferCount * 2; Timer timer; - timer.start(1000ms); - 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; } @@ -161,6 +168,8 @@ protected: } private: + EventDispatcher *dispatcher_; + std::vector<std::unique_ptr<Request>> requests_; unsigned int completeBuffersCount_; diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp index de824083..8766fb19 100644 --- a/test/camera/capture.cpp +++ b/test/camera/capture.cpp @@ -59,6 +59,8 @@ protected: request->reuse(); request->addBuffer(stream, buffer); camera_->queueRequest(request); + + dispatcher_->interrupt(); } int init() override @@ -73,6 +75,7 @@ protected: } allocator_ = new FrameBufferAllocator(camera_); + dispatcher_ = Thread::current()->eventDispatcher(); return TestPass; } @@ -135,19 +138,20 @@ protected: } } - EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); + unsigned int nFrames = allocator_->buffers(stream).size() * 2; Timer timer; - timer.start(1000ms); - 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; } @@ -164,6 +168,8 @@ protected: return TestPass; } + EventDispatcher *dispatcher_; + std::vector<std::unique_ptr<Request>> requests_; std::unique_ptr<CameraConfiguration> config_; diff --git a/test/controls/control_info.cpp b/test/controls/control_info.cpp index 1176a502..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> diff --git a/test/controls/control_info_map.cpp b/test/controls/control_info_map.cpp index 29b33515..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> diff --git a/test/controls/control_list.cpp b/test/controls/control_list.cpp index c03f230e..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> @@ -196,6 +196,56 @@ protected: 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/delayed_controls.cpp b/test/delayed_controls.cpp index a8ce9828..7bd30e7a 100644 --- a/test/delayed_controls.cpp +++ b/test/delayed_controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * delayed_controls.cpp - libcamera delayed controls test + * libcamera delayed controls test */ #include <iostream> diff --git a/test/event-dispatcher.cpp b/test/event-dispatcher.cpp index 9b07ab2b..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> diff --git a/test/event-thread.cpp b/test/event-thread.cpp index ef8a52c3..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> @@ -11,6 +11,7 @@ #include <unistd.h> #include <libcamera/base/event_notifier.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> @@ -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 19dceae1..9f7b1ed4 100644 --- a/test/event.cpp +++ b/test/event.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event.cpp - Event test + * Event test */ #include <iostream> diff --git a/test/fence.cpp b/test/fence.cpp index 1e38bc2f..8095b228 100644 --- a/test/fence.cpp +++ b/test/fence.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * fence.cpp - Fence test + * Fence test */ #include <iostream> @@ -43,7 +43,6 @@ private: void signalFence(); - std::unique_ptr<Fence> fence_; EventDispatcher *dispatcher_; UniqueFD eventFd_; UniqueFD eventFd2_; @@ -58,8 +57,11 @@ private: bool expectedCompletionResult_ = true; bool setFence_ = true; - unsigned int completedRequest_; - + /* + * 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_; @@ -127,8 +129,19 @@ int FenceTest::init() return TestFail; } - signalledRequestId_ = nbuffers_ - 2; - expiredRequestId_ = nbuffers_ - 1; + 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; } @@ -190,16 +203,16 @@ void FenceTest::requestRequeue(Request *request) const Request::BufferMap &buffers = request->buffers(); const Stream *stream = buffers.begin()->first; FrameBuffer *buffer = buffers.begin()->second; - uint64_t cookie = request->cookie(); request->reuse(); - if (cookie == signalledRequestId_ && setFence_) { + if (completedRequestId_ == signalledRequestId_ - nbuffers_ && setFence_) { /* - * The second time this request is queued add a fence to it. - * - * The main loop signals it by using a timer to write to the - * efd2_ file descriptor before the fence expires. + * 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_)); @@ -214,16 +227,15 @@ void FenceTest::requestRequeue(Request *request) void FenceTest::requestComplete(Request *request) { - uint64_t cookie = request->cookie(); - completedRequest_ = cookie; + completedRequestId_++; /* - * The last request is expected to fail as its fence has not been - * signaled. + * Request expiredRequestId_ is expected to fail as its fence has not + * been signalled. * * Validate the fence status but do not re-queue it. */ - if (cookie == expiredRequestId_) { + if (completedRequestId_ == expiredRequestId_) { if (validateExpiredRequest(request) != TestPass) expectedCompletionResult_ = false; @@ -231,7 +243,7 @@ void FenceTest::requestComplete(Request *request) return; } - /* Validate all requests but the last. */ + /* Validate all other requests. */ if (validateRequest(request) != TestPass) { expectedCompletionResult_ = false; @@ -272,15 +284,16 @@ int FenceTest::run() } int ret; - if (i == expiredRequestId_) { + if (i == expiredRequestId_ - 1) { /* This request will have a fence, and it will expire. */ - fence_ = std::make_unique<Fence>(std::move(eventFd_)); - if (!fence_->isValid()) { + 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_)); + ret = request->addBuffer(stream_, buffer.get(), std::move(fence)); } else { /* All other requests will have no Fence. */ ret = request->addBuffer(stream_, buffer.get()); @@ -314,15 +327,21 @@ int FenceTest::run() Timer fenceTimer; fenceTimer.timeout.connect(this, &FenceTest::signalFence); - /* Loop for one second. */ + /* + * Loop long enough for all requests to complete, allowing 500ms per + * request. + */ Timer timer; - timer.start(1000ms); - while (timer.isRunning() && expectedCompletionResult_) { - if (completedRequest_ == signalledRequestId_ && setFence_) + timer.start(500ms * (signalledRequestId_ + 1)); + while (timer.isRunning() && expectedCompletionResult_ && + completedRequestId_ <= signalledRequestId_ + 1) { + if (completedRequestId_ == signalledRequestId_ - 1 && setFence_) /* - * signalledRequestId_ has just completed and it has - * been re-queued with a fence. Start the timer to - * signal the fence in 10 msec. + * 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); diff --git a/test/file.cpp b/test/file.cpp index 5c978ebf..170e6ccd 100644 --- a/test/file.cpp +++ b/test/file.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * file.cpp - File I/O operations tests + * File I/O operations tests */ #include <fstream> diff --git a/test/flags.cpp b/test/flags.cpp index 2177e247..85c34788 100644 --- a/test/flags.cpp +++ b/test/flags.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * flags.cpp - Flags tests + * Flags tests */ #include <iostream> diff --git a/test/geometry.cpp b/test/geometry.cpp index 008d51ea..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> @@ -481,6 +481,31 @@ protected: 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 index 237af8cd..521c60b8 100644 --- a/test/gstreamer/gstreamer_device_provider_test.cpp +++ b/test/gstreamer/gstreamer_device_provider_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Umang Jain <umang.jain@ideasonboard.com> * - * gstreamer_single_stream_test.cpp - GStreamer single stream capture test + * GStreamer single stream capture test */ #include <vector> @@ -52,19 +52,12 @@ protected: for (l = devices; l != NULL; l = g_list_next(l)) { GstDevice *device = GST_DEVICE(l->data); g_autofree gchar *gst_name; - bool matched = false; g_autoptr(GstElement) element = gst_device_create_element(device, NULL); g_object_get(element, "camera-name", &gst_name, NULL); - for (auto name : cameraNames) { - if (strcmp(name.c_str(), gst_name) == 0) { - matched = true; - break; - } - } - - if (!matched) + if (std::find(cameraNames.begin(), cameraNames.end(), gst_name) == + cameraNames.end()) return TestFail; } 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 index cd669308..263d1e86 100644 --- a/test/gstreamer/gstreamer_multi_stream_test.cpp +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture test + * GStreamer multi stream capture test */ #include <iostream> diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp index a0dd12cf..3ef2d323 100644 --- a/test/gstreamer/gstreamer_single_stream_test.cpp +++ b/test/gstreamer/gstreamer_single_stream_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_single_stream_test.cpp - GStreamer single stream capture test + * GStreamer single stream capture test */ #include <iostream> @@ -29,30 +29,21 @@ protected: if (status_ != TestPass) return status_; - const gchar *streamDescription = "videoconvert ! fakesink"; - g_autoptr(GError) error0 = NULL; - stream0_ = gst_parse_bin_from_description_full(streamDescription, TRUE, - NULL, - GST_PARSE_FLAG_FATAL_ERRORS, - &error0); - - if (!stream0_) { - g_printerr("Bin could not be created (%s)\n", error0->message); + fakesink_ = gst_element_factory_make("fakesink", nullptr); + if (!fakesink_) { + g_printerr("Your installation is missing 'fakesink'\n"); return TestFail; } - g_object_ref_sink(stream0_); - - if (createPipeline() != TestPass) - return TestFail; + g_object_ref_sink(fakesink_); - return TestPass; + return createPipeline(); } int run() override { /* Build the pipeline */ - gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, stream0_, NULL); - if (gst_element_link(libcameraSrc_, stream0_) != TRUE) { + 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; } @@ -68,11 +59,11 @@ protected: void cleanup() override { - g_clear_object(&stream0_); + g_clear_object(&fakesink_); } private: - GstElement *stream0_; + GstElement *fakesink_; }; TEST_REGISTER(GstreamerSingleStreamTest) diff --git a/test/gstreamer/gstreamer_test.cpp b/test/gstreamer/gstreamer_test.cpp index 6ad0c15c..a15fef0e 100644 --- a/test/gstreamer/gstreamer_test.cpp +++ b/test/gstreamer/gstreamer_test.cpp @@ -9,12 +9,17 @@ #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() { @@ -26,20 +31,20 @@ const char *__asan_default_options() 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. - */ + * 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 */ @@ -53,23 +58,6 @@ GstreamerTest::GstreamerTest(unsigned int numStreams) } /* - * Remove the system libcamera plugin, if any, and add the - * plugin from the build directory. - */ - GstRegistry *registry = gst_registry_get(); - g_autoptr(GstPlugin) plugin = gst_registry_lookup(registry, "libgstlibcamera.so"); - if (plugin) - gst_registry_remove_plugin(registry, plugin); - - std::string path = libcamera::utils::libcameraBuildPath() + "src/gstreamer"; - if (!gst_registry_scan_path(registry, path.c_str())) { - g_printerr("Failed to add plugin to registry\n"); - - status_ = TestFail; - return; - } - - /* * Atleast one camera should be available with numStreams streams, * otherwise skip the test entirely. */ diff --git a/test/gstreamer/gstreamer_test.h b/test/gstreamer/gstreamer_test.h index aa2261e2..abb37c1b 100644 --- a/test/gstreamer/gstreamer_test.h +++ b/test/gstreamer/gstreamer_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_test.cpp - GStreamer test base class + * GStreamer test base class */ #pragma once diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build index a5c003b6..e066c582 100644 --- a/test/gstreamer/meson.build +++ b/test/gstreamer/meson.build @@ -8,14 +8,24 @@ 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', - dependencies : [libcamera_private, gstreamer_dep], + 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) + 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 index 5d9260a2..530e9a31 100644 --- a/test/hotplug-cameras.cpp +++ b/test/hotplug-cameras.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Umang Jain <email@uajain.com> * - * hotplug-cameras.cpp - Test cameraAdded/cameraRemoved signals in CameraManager + * Test cameraAdded/cameraRemoved signals in CameraManager */ #include <dirent.h> diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp index 051ef96e..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> @@ -16,14 +16,15 @@ #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 "libcamera/internal/process.h" #include "test.h" @@ -43,20 +44,20 @@ public: { delete notifier_; ipa_.reset(); - ipaManager_.reset(); + cameraManager_.reset(); } protected: int init() override { - ipaManager_ = make_unique<IPAManager>(); + cameraManager_ = make_unique<CameraManager>(); /* Create a pipeline handler for vimc. */ const std::vector<PipelineHandlerFactoryBase *> &factories = PipelineHandlerFactoryBase::factories(); for (const PipelineHandlerFactoryBase *factory : factories) { - if (factory->name() == "PipelineHandlerVimc") { - pipe_ = factory->create(nullptr); + if (factory->name() == "vimc") { + pipe_ = factory->create(cameraManager_.get()); break; } } @@ -170,11 +171,9 @@ private: } } - ProcessManager processManager_; - std::shared_ptr<PipelineHandler> pipe_; std::unique_ptr<ipa::vimc::IPAProxyVimc> ipa_; - std::unique_ptr<IPAManager> ipaManager_; + 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 bd5e0e4c..1c97da32 100644 --- a/test/ipa/ipa_module_test.cpp +++ b/test/ipa/ipa_module_test.cpp @@ -2,7 +2,7 @@ /* * 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> @@ -57,7 +57,7 @@ protected: const struct IPAModuleInfo testInfo = { IPA_MODULE_API_VERSION, 0, - "PipelineHandlerVimc", + "vimc", "vimc", }; 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/histogram.cpp b/test/ipa/libipa/histogram.cpp new file mode 100644 index 00000000..77ff31a6 --- /dev/null +++ b/test/ipa/libipa/histogram.cpp @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Histogram tests + */ + +#include "../src/ipa/libipa/histogram.h" + +#include <cmath> +#include <iostream> +#include <map> +#include <stdint.h> + +#include "test.h" + +using namespace std; +using namespace libcamera; +using namespace ipa; + +#define ASSERT_EQ(a, b) \ + if (!((a) == (b))) { \ + std::cout << #a " != " #b << std::endl; \ + return TestFail; \ + } + +class HistogramTest : public Test +{ +protected: + int run() + { + auto hist = Histogram({ { 50, 50 } }); + + ASSERT_EQ(hist.bins(), 2); + ASSERT_EQ(hist.total(), 100); + + ASSERT_EQ(hist.cumulativeFrequency(1.0), 50); + ASSERT_EQ(hist.cumulativeFrequency(1.5), 75); + ASSERT_EQ(hist.cumulativeFrequency(2.0), 100); + + ASSERT_EQ(hist.quantile(0.0), 0.0); + ASSERT_EQ(hist.quantile(1.0), 2.0); + ASSERT_EQ(hist.quantile(0.5), 1.0); + + /* Test quantile in the middle of a bin. */ + ASSERT_EQ(hist.quantile(0.75), 1.5); + + /* Test quantile smaller than the smallest histogram step. */ + ASSERT_EQ(hist.quantile(0.001), 0.002); + + ASSERT_EQ(hist.interQuantileMean(0.0, 1.0), 1.0); + ASSERT_EQ(hist.interQuantileMean(0.0, 0.5), 0.5); + ASSERT_EQ(hist.interQuantileMean(0.5, 1.0), 1.5); + + /* Test interquantile mean that starts and ends in the middle of a bin. */ + ASSERT_EQ(hist.interQuantileMean(0.25, 0.75), 1.0); + + /* Test small ranges at the borders of the histogram. */ + ASSERT_EQ(hist.interQuantileMean(0.0, 0.1), 0.1); + ASSERT_EQ(hist.interQuantileMean(0.9, 1.0), 1.9); + + return TestPass; + } +}; + +TEST_REGISTER(HistogramTest) 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..8c63ebd8 --- /dev/null +++ b/test/ipa/libipa/meson.build @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: CC0-1.0 + +libipa_test = [ + {'name': 'fixedpoint', 'sources': ['fixedpoint.cpp']}, + {'name': 'histogram', 'sources': ['histogram.cpp']}, + {'name': 'interpolator', 'sources': ['interpolator.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', + should_fail : test.get('should_fail', false)) +endforeach diff --git a/test/ipa/meson.build b/test/ipa/meson.build index 180b0da0..ceed15ba 100644 --- a/test/ipa/meson.build +++ b/test/ipa/meson.build @@ -1,15 +1,17 @@ # SPDX-License-Identifier: CC0-1.0 +subdir('libipa') + ipa_test = [ {'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']}, {'name': 'ipa_interface_test', 'sources': ['ipa_interface_test.cpp']}, ] foreach test : ipa_test - exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers, - dependencies : libcamera_private, - link_with : [libipa, test_libraries], - include_directories : [libipa_includes, test_includes_internal]) + exe = executable(test['name'], test['sources'], + dependencies : [libcamera_private, libipa_dep], + link_with : [test_libraries], + include_directories : [test_includes_internal]) test(test['name'], exe, suite : 'ipa') endforeach diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp index 304e613b..f39bd986 100644 --- a/test/ipc/unixsocket.cpp +++ b/test/ipc/unixsocket.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * unixsocket.cpp - Unix socket IPC test + * Unix socket IPC test */ #include <algorithm> @@ -15,6 +15,7 @@ #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#include <vector> #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/thread.h> @@ -34,6 +35,8 @@ using namespace libcamera; using namespace std; using namespace std::chrono_literals; +namespace { + int calculateLength(int fd) { lseek(fd, 0, 0); @@ -43,6 +46,8 @@ int calculateLength(int fd) return size; } +} /* namespace */ + class UnixSocketTestSlave { public: @@ -336,14 +341,14 @@ protected: 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; } @@ -431,7 +436,7 @@ private: if (ret) return ret; - timeout.start(200ms); + timeout.start(2s); while (!callDone_) { if (!timeout.isRunning()) { cerr << "Call timeout!" << endl; diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp index 3ee6017e..df7d9c2b 100644 --- a/test/ipc/unixsocket_ipc.cpp +++ b/test/ipc/unixsocket_ipc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * unixsocket_ipc.cpp - Unix socket IPC test + * Unix socket IPC test */ #include <algorithm> diff --git a/test/libtest/buffer_source.h b/test/libtest/buffer_source.h index 0cc71aa5..495da8a9 100644 --- a/test/libtest/buffer_source.h +++ b/test/libtest/buffer_source.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * buffer_source.h - libcamera camera test helper to create FrameBuffers + * libcamera camera test helper to create FrameBuffers */ #pragma once diff --git a/test/libtest/camera_test.h b/test/libtest/camera_test.h index 0b178bc2..713b503f 100644 --- a/test/libtest/camera_test.h +++ b/test/libtest/camera_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_test.h - libcamera camera test base class + * libcamera camera test base class */ #pragma once diff --git a/test/libtest/test.cpp b/test/libtest/test.cpp index af37b4dd..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> diff --git a/test/libtest/test.h b/test/libtest/test.h index 23b07743..3a90885d 100644 --- a/test/libtest/test.h +++ b/test/libtest/test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * test.h - libcamera test base class + * libcamera test base class */ #pragma once diff --git a/test/log/log_api.cpp b/test/log/log_api.cpp index 53118960..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> diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp index 966b80cf..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> @@ -81,12 +81,13 @@ protected: return TestFail; } - timeout.start(200ms); + 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; } @@ -115,8 +116,11 @@ protected: 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; } @@ -136,7 +140,7 @@ private: ProcessManager processManager_; Process proc_; - Process::ExitStatus exitStatus_; + Process::ExitStatus exitStatus_ = Process::NotExited; string logPath_; int exitCode_; int num_; diff --git a/test/matrix.cpp b/test/matrix.cpp new file mode 100644 index 00000000..4afae2da --- /dev/null +++ b/test/matrix.cpp @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Matrix tests + */ + +#include "libcamera/internal/matrix.h" + +#include <cmath> +#include <iostream> + +#include "test.h" + +using namespace libcamera; + +#define ASSERT_EQ(a, b) \ + if ((a) != (b)) { \ + std::cout << #a " != " #b << " (line " << __LINE__ << ")" \ + << std::endl; \ + return TestFail; \ + } + +class MatrixTest : public Test +{ +protected: + int run() + { + Matrix<double, 3, 3> m1; + + ASSERT_EQ(m1[0][0], 0.0); + ASSERT_EQ(m1[0][1], 0.0); + + constexpr Matrix<float, 2, 2> m2 = Matrix<float, 2, 2>().identity(); + ASSERT_EQ(m2[0][0], 1.0); + ASSERT_EQ(m2[0][1], 0.0); + ASSERT_EQ(m2[1][0], 0.0); + ASSERT_EQ(m2[1][1], 1.0); + + Matrix<float, 2, 2> m3{ { 2.0, 0.0, 0.0, 2.0 } }; + Matrix<float, 2, 2> m4 = m3.inverse(); + + Matrix<float, 2, 2> m5 = m3 * m4; + ASSERT_EQ(m5[0][0], 1.0); + ASSERT_EQ(m5[0][1], 0.0); + ASSERT_EQ(m5[1][0], 0.0); + ASSERT_EQ(m5[1][1], 1.0); + + return TestPass; + } +}; + +TEST_REGISTER(MatrixTest) diff --git a/test/media_device/media_device_link_test.cpp b/test/media_device/media_device_link_test.cpp index e11f6b78..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> diff --git a/test/media_device/media_device_print_test.cpp b/test/media_device/media_device_print_test.cpp index cdec5b8d..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> 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 9b226f1a..5223b760 100644 --- a/test/media_device/media_device_test.h +++ b/test/media_device/media_device_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * media_device_test.h - libcamera media device test base class + * libcamera media device test base class */ #pragma once diff --git a/test/meson.build b/test/meson.build index 189e1428..52f04364 100644 --- a/test/meson.build +++ b/test/meson.build @@ -60,6 +60,7 @@ internal_tests = [ {'name': 'file', 'sources': ['file.cpp']}, {'name': 'flags', 'sources': ['flags.cpp']}, {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']}, + {'name': 'matrix', 'sources': ['matrix.cpp']}, {'name': 'message', 'sources': ['message.cpp']}, {'name': 'object', 'sources': ['object.cpp']}, {'name': 'object-delete', 'sources': ['object-delete.cpp']}, @@ -69,9 +70,11 @@ internal_tests = [ {'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': 'vector', 'sources': ['vector.cpp']}, {'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']}, ] @@ -88,10 +91,11 @@ foreach test : public_tests exe = executable(test['name'], test['sources'], dependencies : deps, + implicit_include_directories : false, link_with : test_libraries, include_directories : test_includes_public) - test(test['name'], exe) + test(test['name'], exe, should_fail : test.get('should_fail', false)) endforeach foreach test : internal_tests @@ -102,10 +106,11 @@ foreach test : internal_tests 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) + test(test['name'], exe, should_fail : test.get('should_fail', false)) endforeach foreach test : internal_non_parallel_tests @@ -116,8 +121,11 @@ foreach test : internal_non_parallel_tests 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, is_parallel : false) + 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 d148a13d..19e6646d 100644 --- a/test/message.cpp +++ b/test/message.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * message.cpp - Messages test + * Messages test */ #include <chrono> @@ -11,6 +11,7 @@ #include <thread> #include <libcamera/base/message.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include "test.h" @@ -92,25 +93,6 @@ private: bool success_; }; -class SlowMessageReceiver : public Object -{ -protected: - void message(Message *msg) - { - if (msg->type() != Message::None) { - Object::message(msg); - return; - } - - /* - * 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. - */ - this_thread::sleep_for(chrono::milliseconds(100)); - } -}; - class MessageTest : public Test { protected: @@ -127,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; @@ -148,28 +133,12 @@ protected: } /* - * Test for races between message delivery and object deletion. - * Failures result in assertion errors, there is no need for - * explicit checks. - */ - SlowMessageReceiver *slowReceiver = new SlowMessageReceiver(); - slowReceiver->moveToThread(&thread_); - slowReceiver->postMessage(std::make_unique<Message>(Message::None)); - - this_thread::sleep_for(chrono::milliseconds(10)); - - delete slowReceiver; - - this_thread::sleep_for(chrono::milliseconds(100)); - - /* * 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. */ - std::unique_ptr<RecursiveMessageReceiver> recursiveReceiver = - std::make_unique<RecursiveMessageReceiver>(); + RecursiveMessageReceiver *recursiveReceiver = new RecursiveMessageReceiver(); recursiveReceiver->moveToThread(&thread_); recursiveReceiver->postMessage(std::make_unique<Message>(Message::None)); @@ -177,7 +146,10 @@ protected: this_thread::sleep_for(chrono::milliseconds(10)); - if (!recursiveReceiver->success()) { + bool success = recursiveReceiver->success(); + recursiveReceiver->deleteLater(); + + if (!success) { cout << "Recursive message delivery failed" << endl; return TestFail; } diff --git a/test/object-delete.cpp b/test/object-delete.cpp index eabefe93..676c3970 100644 --- a/test/object-delete.cpp +++ b/test/object-delete.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * object.cpp - Object deletion tests + * Object deletion tests */ #include <iostream> @@ -33,10 +33,10 @@ public: unsigned int *deleteCount_; }; -class NewThread : public Thread +class DeleterThread : public Thread { public: - NewThread(Object *obj) + DeleterThread(Object *obj) : object_(obj) { } @@ -63,9 +63,9 @@ protected: unsigned int count = 0; TestObject *obj = new TestObject(&count); - NewThread thread(obj); - thread.start(); - thread.wait(); + DeleterThread delThread(obj); + delThread.start(); + delThread.wait(); Thread::current()->dispatchMessages(Message::Type::DeferredDelete); @@ -89,6 +89,26 @@ protected: 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; } }; diff --git a/test/object-invoke.cpp b/test/object-invoke.cpp index b1c0f473..def1e61e 100644 --- a/test/object-invoke.cpp +++ b/test/object-invoke.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * object-invoke.cpp - Cross-thread Object method invocation test + * Cross-thread Object method invocation test */ #include <iostream> diff --git a/test/object.cpp b/test/object.cpp index cbd0d3ec..95dc1ef3 100644 --- a/test/object.cpp +++ b/test/object.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * object.cpp - Object tests + * Object tests */ #include <iostream> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp index cb6940c6..e9f5e7e9 100644 --- a/test/process/process_test.cpp +++ b/test/process/process_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * process_test.cpp - Process test + * Process test */ #include <iostream> diff --git a/test/public-api.cpp b/test/public-api.cpp index a1cebcf9..b1336f75 100644 --- a/test/public-api.cpp +++ b/test/public-api.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * public-api.cpp - Public API validation + * Public API validation */ #include <libcamera/libcamera.h> diff --git a/test/py/meson.build b/test/py/meson.build index 0b679d31..b922e857 100644 --- a/test/py/meson.build +++ b/test/py/meson.build @@ -13,15 +13,25 @@ if asan_runtime_missing subdir_done() endif +py_env = environment() + pymod = import('python') py3 = pymod.find_installation('python3') pypathdir = meson.project_build_root() / 'src' / 'py' -py_env = ['PYTHONPATH=' + pypathdir] +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 += ['LD_PRELOAD=' + asan_runtime, 'ASAN_OPTIONS=detect_leaks=0'] + py_env.append('ASAN_OPTIONS', 'detect_leaks=0') endif test('pyunittests', diff --git a/test/py/unittests.py b/test/py/unittests.py index 1caea98e..8cb850d4 100755 --- a/test/py/unittests.py +++ b/test/py/unittests.py @@ -66,7 +66,7 @@ class SimpleTestMethods(BaseTestCase): libcam.log_set_level('Camera', 'FATAL') with self.assertRaises(RuntimeError): cam.acquire() - libcam.log_set_level('Camera', 'ERROR') + libcam.log_set_level('Camera', 'INFO') cam.release() diff --git a/test/serialization/control_serialization.cpp b/test/serialization/control_serialization.cpp index a507d98a..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> diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp index 4670fe46..dd696885 100644 --- a/test/serialization/generated_serializer/generated_serializer_test.cpp +++ b/test/serialization/generated_serializer/generated_serializer_test.cpp @@ -2,10 +2,11 @@ /* * Copyright (C) 2020, Google Inc. * - * generated_serializer_test.cpp - Test generated serializer + * Test generated serializer */ #include <algorithm> +#include <iostream> #include <tuple> #include <vector> diff --git a/test/serialization/generated_serializer/include/libcamera/ipa/meson.build b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build index 6f8794c1..ae08e9be 100644 --- a/test/serialization/generated_serializer/include/libcamera/ipa/meson.build +++ b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build @@ -9,7 +9,8 @@ mojom = custom_target('test_mojom_module', '--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', @@ -23,7 +24,8 @@ generated_test_header = custom_target('test_ipa_interface_h', '--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', @@ -37,4 +39,5 @@ generated_test_serializer = custom_target('test_ipa_serializer_h', '--libcamera_generate_serializer', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp index 377ecdb0..afea93a6 100644 --- a/test/serialization/ipa_data_serializer_test.cpp +++ b/test/serialization/ipa_data_serializer_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * ipa_data_serializer_test.cpp - Test serializing/deserializing with IPADataSerializer + * Test serializing/deserializing with IPADataSerializer */ #include <algorithm> @@ -29,7 +29,7 @@ using namespace std; using namespace libcamera; static const ControlInfoMap Controls = ControlInfoMap({ - { &controls::AeEnable, ControlInfo(false, true) }, + { &controls::DebugMetadataEnable, ControlInfo(false, true) }, { &controls::ExposureTime, ControlInfo(0, 999999) }, { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) }, { &controls::ColourGains, ControlInfo(0.0f, 32.0f) }, 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 609f9fdf..760e3721 100644 --- a/test/serialization/serialization_test.h +++ b/test/serialization/serialization_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * serialization_test.h - Base class for serialization tests + * Base class for serialization tests */ #pragma once diff --git a/test/shared-fd.cpp b/test/shared-fd.cpp index 997d7be1..57199dfe 100644 --- a/test/shared-fd.cpp +++ b/test/shared-fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * shared_fd.cpp - SharedFD test + * SharedFD test */ #include <fcntl.h> diff --git a/test/signal-threads.cpp b/test/signal-threads.cpp index d5e2eb66..c4789c83 100644 --- a/test/signal-threads.cpp +++ b/test/signal-threads.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * signal-threads.cpp - Cross-thread signal delivery test + * Cross-thread signal delivery test */ #include <chrono> @@ -10,6 +10,7 @@ #include <thread> #include <libcamera/base/message.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/utils.h> @@ -58,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; @@ -82,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(); @@ -91,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; @@ -103,7 +109,7 @@ protected: break; } - if (receiver.value() != 42) { + if (receiver_->value() != 42) { cout << "Signal received with incorrect value" << endl; return TestFail; } @@ -113,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 5c6b304d..3f596b22 100644 --- a/test/signal.cpp +++ b/test/signal.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * signal.cpp - Signal test + * Signal test */ #include <iostream> diff --git a/test/span.cpp b/test/span.cpp index abf3a5d6..4b9f3279 100644 --- a/test/span.cpp +++ b/test/span.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * span.cpp - Span tests + * Span tests */ /* @@ -143,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 }; diff --git a/test/stream/stream_colorspace.cpp b/test/stream/stream_colorspace.cpp index 1b7afe65..4c904c4c 100644 --- a/test/stream/stream_colorspace.cpp +++ b/test/stream/stream_colorspace.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy. * - * stream_colorspace.cpp - Stream colorspace adjustment test + * Stream colorspace adjustment test */ #include <iostream> diff --git a/test/stream/stream_formats.cpp b/test/stream/stream_formats.cpp index 99fa0385..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> diff --git a/test/threads.cpp b/test/threads.cpp index 8f366c9d..c00d95a4 100644 --- a/test/threads.cpp +++ b/test/threads.cpp @@ -2,16 +2,18 @@ /* * 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 "test.h" @@ -50,14 +52,8 @@ protected: { 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); + pthread_testcancel(); cancelled_ = false; } @@ -66,6 +62,27 @@ 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: @@ -165,6 +182,23 @@ protected: 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 61821753..55e5cfdf 100644 --- a/test/timer-thread.cpp +++ b/test/timer-thread.cpp @@ -2,13 +2,14 @@ /* * Copyright (C) 2019, Google Inc. * - * timer-thread.cpp - Threaded timer test + * Threaded timer 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> @@ -28,12 +29,6 @@ public: timer_.start(100ms); } - void restart() - { - timeout_ = false; - timer_.start(100ms); - } - bool timeout() const { return timeout_; @@ -55,7 +50,9 @@ protected: int init() { thread_.start(); - timeout_.moveToThread(&thread_); + + timeout_ = new TimeoutHandler(); + timeout_->moveToThread(&thread_); return TestPass; } @@ -68,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 0f01c3cb..2eacc059 100644 --- a/test/timer.cpp +++ b/test/timer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * timer.cpp - Timer test + * Timer test */ #include <chrono> diff --git a/test/transform.cpp b/test/transform.cpp index fbc0308c..4ec7a1eb 100644 --- a/test/transform.cpp +++ b/test/transform.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Ideas On Board Oy * - * transform.cpp - Transform and Orientation tests + * Transform and Orientation tests */ #include <iostream> diff --git a/test/unique-fd.cpp b/test/unique-fd.cpp index eb3b591f..e556439e 100644 --- a/test/unique-fd.cpp +++ b/test/unique-fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * unique-fd.cpp - UniqueFD test + * UniqueFD test */ #include <fcntl.h> diff --git a/test/utils.cpp b/test/utils.cpp index fc56e14e..d25475cb 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * utils.cpp - Miscellaneous utility tests + * Miscellaneous utility tests */ #include <iostream> @@ -176,6 +176,14 @@ protected: 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)) << " "; @@ -184,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) << " "; diff --git a/test/v4l2_compat/v4l2_compat_test.py b/test/v4l2_compat/v4l2_compat_test.py index bd89d496..443babc2 100755 --- a/test/v4l2_compat/v4l2_compat_test.py +++ b/test/v4l2_compat/v4l2_compat_test.py @@ -4,7 +4,7 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# v4l2_compat_test.py - Test the V4L2 compatibility layer +# Test the V4L2 compatibility layer import argparse import glob diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.cpp b/test/v4l2_subdevice/v4l2_subdevice_test.cpp index d8fbfd9f..c349c9e3 100644 --- a/test/v4l2_subdevice/v4l2_subdevice_test.cpp +++ b/test/v4l2_subdevice/v4l2_subdevice_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice_test.cpp - VIMC-based V4L2 subdevice test + * VIMC-based V4L2 subdevice test */ #include <iostream> diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.h b/test/v4l2_subdevice/v4l2_subdevice_test.h index e73c583b..89b78302 100644 --- a/test/v4l2_subdevice/v4l2_subdevice_test.h +++ b/test/v4l2_subdevice/v4l2_subdevice_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice_test.h - VIMC-based V4L2 subdevice test + * VIMC-based V4L2 subdevice test */ #pragma once diff --git a/test/v4l2_videodevice/capture_async.cpp b/test/v4l2_videodevice/capture_async.cpp index 42e1e671..67366461 100644 --- a/test/v4l2_videodevice/capture_async.cpp +++ b/test/v4l2_videodevice/capture_async.cpp @@ -61,10 +61,12 @@ protected: if (ret) return TestFail; - timeout.start(10000ms); + const unsigned int nFrames = 30; + + timeout.start(500ms * nFrames); while (timeout.isRunning()) { dispatcher->processEvents(); - if (frames > 30) + if (frames > nFrames) break; } @@ -73,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; } diff --git a/test/v4l2_videodevice/controls.cpp b/test/v4l2_videodevice/controls.cpp index 0f603b85..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> diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.cpp b/test/v4l2_videodevice/v4l2_videodevice_test.cpp index 125aafd6..9fbd24cc 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.cpp +++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp @@ -64,8 +64,8 @@ int V4L2VideoDeviceTest::init() 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")); @@ -75,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)) @@ -98,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 d2de1a6d..7c9003ec 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.h +++ b/test/v4l2_videodevice/v4l2_videodevice_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * vl42device_test.h - libcamera v4l2device test base class + * libcamera v4l2device test base class */ #pragma once @@ -36,7 +36,7 @@ protected: std::string entity_; std::unique_ptr<libcamera::DeviceEnumerator> enumerator_; std::shared_ptr<libcamera::MediaDevice> media_; - libcamera::CameraSensor *sensor_; + std::unique_ptr<libcamera::CameraSensor> sensor_; libcamera::V4L2Subdevice *debayer_; libcamera::V4L2VideoDevice *capture_; std::vector<std::unique_ptr<libcamera::FrameBuffer>> buffers_; diff --git a/test/vector.cpp b/test/vector.cpp new file mode 100644 index 00000000..4fae960d --- /dev/null +++ b/test/vector.cpp @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Vector tests + */ + +#include "libcamera/internal/vector.h" + +#include <cmath> +#include <iostream> + +#include "test.h" + +using namespace libcamera; + +#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/yaml-parser.cpp b/test/yaml-parser.cpp index 2d92463a..1b22c87b 100644 --- a/test/yaml-parser.cpp +++ b/test/yaml-parser.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * yaml-parser.cpp - YAML parser operations tests + * YAML parser operations tests */ #include <array> @@ -34,10 +34,12 @@ static const string testYaml = "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" @@ -430,9 +432,10 @@ protected: if (testObjectType(listObj, "list", Type::List) != TestPass) return TestFail; - static constexpr std::array<const char *, 2> listValues{ + static constexpr std::array<const char *, 3> listValues{ "James", "Mary", + "", }; if (listObj.size() != listValues.size()) { @@ -465,16 +468,23 @@ protected: 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>, 3> dictValues{ { + static constexpr std::array<std::pair<const char *, int>, 4> dictValues{ { { "a", 1 }, { "c", 3 }, { "b", 2 }, + { "empty", -100 }, } }; size_t dictSize = dictValues.size(); @@ -505,7 +515,7 @@ protected: return TestFail; } - if (elem.get<int32_t>(0) != item.second) { + if (elem.get<int32_t>(-100) != item.second) { std::cerr << "Dictionary element " << i << " has wrong value" << std::endl; return TestFail; @@ -514,6 +524,42 @@ protected: 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()); |