{"id":27121,"url":"https://patchwork.libcamera.org/api/patches/27121/?format=json","web_url":"https://patchwork.libcamera.org/patch/27121/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260629163017.863145-43-barnabas.pocze@ideasonboard.com>","date":"2026-06-29T16:30:05","name":"[RFC,v1,42/54] apps: lc-compliance: Add buffer pool tests","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"7e9162629c833135a41747dc619e7bf8e0d201a2","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/?format=json","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/27121/mbox/","series":[{"id":6025,"url":"https://patchwork.libcamera.org/api/series/6025/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=6025","date":"2026-06-29T16:29:23","name":"libcamera: Split requests and buffers","version":1,"mbox":"https://patchwork.libcamera.org/series/6025/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/27121/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/27121/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id CBF49C3261\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 29 Jun 2026 16:31:34 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 63CC065FB4;\n\tMon, 29 Jun 2026 18:31:34 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 86F4865F67\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:30:30 +0200 (CEST)","from pb-laptop.local (185.221.140.128.nat.pool.zt.hu\n\t[185.221.140.128])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 6203B1044\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:29:47 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"HCUbhBuG\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1782750587;\n\tbh=XDpIfMwN9sk/4wblvqoDdLcgCYGwYnvaFy8Y9/2q/aQ=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=HCUbhBuGCYomCT8qkihyY+W5rLjYaSA9N51gD/ZLEvmGDprCUEGuZwXwwWfD8YFvJ\n\t9RK1E4KMtXALzvMTtuKn4Q1jVrJeN7iQFuzld96FJV34+cTzLHM9JoYh3wPZws03GV\n\t1klJQQ4sO/MNwby0GE/Gxh+ZgZ6va17rHWzQhCHM=","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Subject":"[RFC PATCH v1 42/54] apps: lc-compliance: Add buffer pool tests","Date":"Mon, 29 Jun 2026 18:30:05 +0200","Message-ID":"<20260629163017.863145-43-barnabas.pocze@ideasonboard.com>","X-Mailer":"git-send-email 2.54.0","In-Reply-To":"<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>","References":"<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add a couple tests that test the functioning of the buffer pool of a camera.\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n---\n src/apps/lc-compliance/tests/capture_test.cpp | 261 ++++++++++++++++++\n 1 file changed, 261 insertions(+)","diff":"diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp\nindex d7d6f0626e..f256ec3033 100644\n--- a/src/apps/lc-compliance/tests/capture_test.cpp\n+++ b/src/apps/lc-compliance/tests/capture_test.cpp\n@@ -8,8 +8,10 @@\n \n #include \"capture.h\"\n \n+#include <semaphore>\n #include <sstream>\n #include <string>\n+#include <thread>\n #include <tuple>\n #include <vector>\n \n@@ -142,4 +144,263 @@ INSTANTIATE_TEST_SUITE_P(MultiStream,\n \t\t\t\t\t  testing::ValuesIn(NUMREQUESTS)),\n \t\t\t SimpleCapture::nameParameters);\n \n+class TestWithCamera : public testing::Test, public CameraHolder\n+{\n+protected:\n+\tvoid SetUp() override { acquireCamera(); }\n+\tvoid TearDown() override { releaseCamera(); }\n+};\n+\n+class BufferPool : public TestWithCamera\n+{\n+protected:\n+\tvoid SetUp() override\n+\t{\n+\t\tTestWithCamera::SetUp();\n+\n+\t\tconfig_ = camera_->generateConfiguration({ StreamRole::Viewfinder });\n+\t\tASSERT_TRUE(config_);\n+\t\tASSERT_FALSE(config_->empty());\n+\n+\t\tASSERT_EQ(camera_->configure(config_.get()), 0);\n+\t}\n+\n+\tvoid TearDown() override\n+\t{\n+\t\tconfig_.reset();\n+\t\tTestWithCamera::TearDown();\n+\t}\n+\n+\tstd::vector<std::unique_ptr<Request>>\n+\tcreateRequests(std::size_t count)\n+\t{\n+\t\tstd::vector<std::unique_ptr<Request>> requests;\n+\n+\t\tfor (std::size_t i = 0; i < count; i++) {\n+\t\t\tauto request = camera_->createRequest(i);\n+\t\t\t[&] { ASSERT_TRUE(request); }();\n+\n+\t\t\tfor (const auto &cfg : *config_)\n+\t\t\t\trequest->enableStream(cfg.stream(), true);\n+\n+\t\t\trequests.push_back(std::move(request));\n+\t\t}\n+\n+\t\treturn requests;\n+\t}\n+\n+\tstd::unique_ptr<CameraConfiguration> config_;\n+};\n+\n+template<typename T, typename... Args>\n+struct ScopedSignalReceiver {\n+\tSignal<Args...> &signal;\n+\tT *object = nullptr;\n+\n+\ttemplate<typename Func>\n+\tScopedSignalReceiver(Signal<Args...> &s, T *o, Func f)\n+\t\t: signal(s), object(o)\n+\t{\n+\t\tsignal.connect(o, std::move(f));\n+\t}\n+\n+\t~ScopedSignalReceiver()\n+\t{\n+\t\tsignal.disconnect(object);\n+\t}\n+};\n+\n+template<typename T, typename... Args>\n+ScopedSignalReceiver(Signal<Args...> &, T *) -> ScopedSignalReceiver<T, Args...>;\n+\n+struct ScopedCameraStart {\n+\tCamera &camera;\n+\n+\tScopedCameraStart(Camera &c)\n+\t\t: camera(c)\n+\t{\n+\t\t[&]() { ASSERT_EQ(camera.start(), 0); }();\n+\t}\n+\n+\t~ScopedCameraStart()\n+\t{\n+\t\tEXPECT_EQ(camera.stop(), 0);\n+\t}\n+};\n+\n+TEST_F(BufferPool, BufferBeforeRequest)\n+{\n+\tFrameBufferAllocator fba(camera_);\n+\n+\tstd::size_t buffers = -1;\n+\tfor (const auto &cfg : *config_) {\n+\t\tASSERT_GT(fba.allocate(cfg.stream()), 0);\n+\t\tbuffers = std::min(buffers, fba.buffers(cfg.stream()).size());\n+\t}\n+\n+\tauto requests = createRequests(buffers);\n+\n+\tstd::counting_semaphore<> requestCompletedSemaphore(0);\n+\tbool requestCompletedOk = true;\n+\tunsigned int requestCompleted = 0;\n+\tScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) {\n+\t\tif (requestCompleted < requests.size())\n+\t\t\trequestCompletedOk &= request == requests[requestCompleted].get();\n+\n+\t\trequestCompletedSemaphore.release(1);\n+\t\trequestCompleted += 1;\n+\t});\n+\n+\t{\n+\t\tScopedCameraStart scs(*camera_);\n+\n+\t\tfor (const auto &cfg : *config_) {\n+\t\t\tStream *stream = cfg.stream();\n+\t\t\tfor (const auto &buffer : fba.buffers(stream))\n+\t\t\t\tEXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0);\n+\t\t}\n+\n+\t\tfor (const auto &request : requests)\n+\t\t\tASSERT_EQ(camera_->queueRequest(request.get()), 0);\n+\n+\t\tauto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(buffers);\n+\n+\t\tfor (size_t i = 0; i < buffers; i++) {\n+\t\t\tif (!requestCompletedSemaphore.try_acquire_until(deadline))\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tEXPECT_TRUE(requestCompletedOk);\n+\tASSERT_FALSE(requestCompletedSemaphore.try_acquire());\n+\tEXPECT_EQ(requestCompleted, buffers);\n+}\n+\n+TEST_F(BufferPool, RequestBeforeBuffer)\n+{\n+\tFrameBufferAllocator fba(camera_);\n+\n+\tstd::size_t buffers = -1;\n+\tfor (const auto &cfg : *config_) {\n+\t\tASSERT_GT(fba.allocate(cfg.stream()), 0);\n+\t\tbuffers = std::min(buffers, fba.buffers(cfg.stream()).size());\n+\t}\n+\n+\tauto requests = createRequests(buffers);\n+\n+\tstd::counting_semaphore<> requestCompletedSemaphore(0);\n+\tbool requestCompletedOk = true;\n+\tunsigned int requestCompleted = 0;\n+\tScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) {\n+\t\tif (requestCompleted < requests.size())\n+\t\t\trequestCompletedOk &= request == requests[requestCompleted].get();\n+\n+\t\trequestCompletedSemaphore.release(1);\n+\t\trequestCompleted += 1;\n+\t});\n+\n+\t{\n+\t\tScopedCameraStart scs(*camera_);\n+\n+\t\tfor (const auto &request : requests)\n+\t\t\tASSERT_EQ(camera_->queueRequest(request.get()), 0);\n+\n+\t\tstd::this_thread::sleep_for(std::chrono::seconds(2));\n+\t\tASSERT_FALSE(requestCompletedSemaphore.try_acquire());\n+\t\tASSERT_EQ(requestCompleted, 0);\n+\n+\t\tauto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(buffers);\n+\n+\t\tfor (const auto &cfg : *config_) {\n+\t\t\tStream *stream = cfg.stream();\n+\t\t\tfor (const auto &buffer : fba.buffers(stream))\n+\t\t\t\tEXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0);\n+\t\t}\n+\n+\t\tfor (size_t i = 0; i < buffers; i++) {\n+\t\t\tif (!requestCompletedSemaphore.try_acquire_until(deadline))\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tEXPECT_TRUE(requestCompletedOk);\n+\tASSERT_FALSE(requestCompletedSemaphore.try_acquire());\n+\tEXPECT_EQ(requestCompleted, buffers);\n+}\n+\n+TEST_F(BufferPool, BufferWithoutRequest)\n+{\n+\tFrameBufferAllocator fba(camera_);\n+\tstd::set<std::pair<const Stream *, FrameBuffer *>> buffers;\n+\n+\tfor (const auto &cfg : *config_) {\n+\t\tStream *stream = cfg.stream();\n+\t\tASSERT_GT(fba.allocate(stream), 0);\n+\n+\t\tfor (const auto &buffer : fba.buffers(stream))\n+\t\t\tbuffers.insert({ stream, buffer.get() });\n+\t}\n+\n+\tunsigned int requestCompleted = 0;\n+\tScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *) {\n+\t\trequestCompleted += 1;\n+\t});\n+\n+\tbool bufferCompletedOk = true;\n+\tScopedSignalReceiver bcr(camera_->bufferCompleted, this, [&](Request *request, const Stream *stream, FrameBuffer *buffer) {\n+\t\tbufferCompletedOk &= !request;\n+\t\tbufferCompletedOk &= buffer->metadata().status == libcamera::FrameMetadata::FrameCancelled;\n+\t\tbufferCompletedOk &= buffers.erase({ stream, buffer });\n+\t});\n+\n+\t{\n+\t\tScopedCameraStart scs(*camera_);\n+\n+\t\tfor (const auto &cfg : *config_) {\n+\t\t\tStream *stream = cfg.stream();\n+\t\t\tfor (const auto &buffer : fba.buffers(stream))\n+\t\t\t\tEXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0);\n+\t\t}\n+\t}\n+\n+\tEXPECT_EQ(requestCompleted, 0);\n+\tEXPECT_EQ(buffers.size(), 0);\n+\tEXPECT_TRUE(bufferCompletedOk);\n+}\n+\n+TEST_F(BufferPool, RequestWithoutBuffers)\n+{\n+\tconstexpr std::size_t kRequestCount = 128;\n+\tauto requests = createRequests(kRequestCount);\n+\n+\tunsigned int requestCompleted = 0;\n+\tbool requestCompletedOk = true;\n+\tScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) {\n+\t\tif (requestCompleted < requests.size()) {\n+\t\t\trequestCompletedOk &= request == requests[requestCompleted].get();\n+\t\t\trequestCompletedOk &= request->status() == Request::Status::RequestCancelled;\n+\t\t}\n+\n+\t\trequestCompleted += 1;\n+\t});\n+\n+\tunsigned int bufferCompleted = 0;\n+\tScopedSignalReceiver bcr(camera_->bufferCompleted, this, [&](Request *, const Stream *, FrameBuffer *) {\n+\t\tbufferCompleted += 1;\n+\t});\n+\n+\t{\n+\t\tScopedCameraStart scs(*camera_);\n+\n+\t\tfor (const auto &request : requests)\n+\t\t\tASSERT_EQ(camera_->queueRequest(request.get()), 0);\n+\n+\t\tstd::this_thread::sleep_for(std::chrono::seconds(2));\n+\t}\n+\n+\tEXPECT_EQ(requestCompleted, requests.size());\n+\tEXPECT_TRUE(requestCompletedOk);\n+\tEXPECT_EQ(bufferCompleted, 0);\n+}\n+\n } /* namespace */\n","prefixes":["RFC","v1","42/54"]}