From patchwork Mon Jun 29 16:30:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27121 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id CBF49C3261 for ; Mon, 29 Jun 2026 16:31:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 63CC065FB4; Mon, 29 Jun 2026 18:31:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HCUbhBuG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 86F4865F67 for ; Mon, 29 Jun 2026 18:30:30 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6203B1044 for ; Mon, 29 Jun 2026 18:29:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1782750587; bh=XDpIfMwN9sk/4wblvqoDdLcgCYGwYnvaFy8Y9/2q/aQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=HCUbhBuGCYomCT8qkihyY+W5rLjYaSA9N51gD/ZLEvmGDprCUEGuZwXwwWfD8YFvJ 9RK1E4KMtXALzvMTtuKn4Q1jVrJeN7iQFuzld96FJV34+cTzLHM9JoYh3wPZws03GV 1klJQQ4sO/MNwby0GE/Gxh+ZgZ6va17rHWzQhCHM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= 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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a couple tests that test the functioning of the buffer pool of a camera. Signed-off-by: Barnabás Pőcze --- src/apps/lc-compliance/tests/capture_test.cpp | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp index d7d6f0626e..f256ec3033 100644 --- a/src/apps/lc-compliance/tests/capture_test.cpp +++ b/src/apps/lc-compliance/tests/capture_test.cpp @@ -8,8 +8,10 @@ #include "capture.h" +#include #include #include +#include #include #include @@ -142,4 +144,263 @@ INSTANTIATE_TEST_SUITE_P(MultiStream, testing::ValuesIn(NUMREQUESTS)), SimpleCapture::nameParameters); +class TestWithCamera : public testing::Test, public CameraHolder +{ +protected: + void SetUp() override { acquireCamera(); } + void TearDown() override { releaseCamera(); } +}; + +class BufferPool : public TestWithCamera +{ +protected: + void SetUp() override + { + TestWithCamera::SetUp(); + + config_ = camera_->generateConfiguration({ StreamRole::Viewfinder }); + ASSERT_TRUE(config_); + ASSERT_FALSE(config_->empty()); + + ASSERT_EQ(camera_->configure(config_.get()), 0); + } + + void TearDown() override + { + config_.reset(); + TestWithCamera::TearDown(); + } + + std::vector> + createRequests(std::size_t count) + { + std::vector> requests; + + for (std::size_t i = 0; i < count; i++) { + auto request = camera_->createRequest(i); + [&] { ASSERT_TRUE(request); }(); + + for (const auto &cfg : *config_) + request->enableStream(cfg.stream(), true); + + requests.push_back(std::move(request)); + } + + return requests; + } + + std::unique_ptr config_; +}; + +template +struct ScopedSignalReceiver { + Signal &signal; + T *object = nullptr; + + template + ScopedSignalReceiver(Signal &s, T *o, Func f) + : signal(s), object(o) + { + signal.connect(o, std::move(f)); + } + + ~ScopedSignalReceiver() + { + signal.disconnect(object); + } +}; + +template +ScopedSignalReceiver(Signal &, T *) -> ScopedSignalReceiver; + +struct ScopedCameraStart { + Camera &camera; + + ScopedCameraStart(Camera &c) + : camera(c) + { + [&]() { ASSERT_EQ(camera.start(), 0); }(); + } + + ~ScopedCameraStart() + { + EXPECT_EQ(camera.stop(), 0); + } +}; + +TEST_F(BufferPool, BufferBeforeRequest) +{ + FrameBufferAllocator fba(camera_); + + std::size_t buffers = -1; + for (const auto &cfg : *config_) { + ASSERT_GT(fba.allocate(cfg.stream()), 0); + buffers = std::min(buffers, fba.buffers(cfg.stream()).size()); + } + + auto requests = createRequests(buffers); + + std::counting_semaphore<> requestCompletedSemaphore(0); + bool requestCompletedOk = true; + unsigned int requestCompleted = 0; + ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) { + if (requestCompleted < requests.size()) + requestCompletedOk &= request == requests[requestCompleted].get(); + + requestCompletedSemaphore.release(1); + requestCompleted += 1; + }); + + { + ScopedCameraStart scs(*camera_); + + for (const auto &cfg : *config_) { + Stream *stream = cfg.stream(); + for (const auto &buffer : fba.buffers(stream)) + EXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0); + } + + for (const auto &request : requests) + ASSERT_EQ(camera_->queueRequest(request.get()), 0); + + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(buffers); + + for (size_t i = 0; i < buffers; i++) { + if (!requestCompletedSemaphore.try_acquire_until(deadline)) + break; + } + } + + EXPECT_TRUE(requestCompletedOk); + ASSERT_FALSE(requestCompletedSemaphore.try_acquire()); + EXPECT_EQ(requestCompleted, buffers); +} + +TEST_F(BufferPool, RequestBeforeBuffer) +{ + FrameBufferAllocator fba(camera_); + + std::size_t buffers = -1; + for (const auto &cfg : *config_) { + ASSERT_GT(fba.allocate(cfg.stream()), 0); + buffers = std::min(buffers, fba.buffers(cfg.stream()).size()); + } + + auto requests = createRequests(buffers); + + std::counting_semaphore<> requestCompletedSemaphore(0); + bool requestCompletedOk = true; + unsigned int requestCompleted = 0; + ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) { + if (requestCompleted < requests.size()) + requestCompletedOk &= request == requests[requestCompleted].get(); + + requestCompletedSemaphore.release(1); + requestCompleted += 1; + }); + + { + ScopedCameraStart scs(*camera_); + + for (const auto &request : requests) + ASSERT_EQ(camera_->queueRequest(request.get()), 0); + + std::this_thread::sleep_for(std::chrono::seconds(2)); + ASSERT_FALSE(requestCompletedSemaphore.try_acquire()); + ASSERT_EQ(requestCompleted, 0); + + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(buffers); + + for (const auto &cfg : *config_) { + Stream *stream = cfg.stream(); + for (const auto &buffer : fba.buffers(stream)) + EXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0); + } + + for (size_t i = 0; i < buffers; i++) { + if (!requestCompletedSemaphore.try_acquire_until(deadline)) + break; + } + } + + EXPECT_TRUE(requestCompletedOk); + ASSERT_FALSE(requestCompletedSemaphore.try_acquire()); + EXPECT_EQ(requestCompleted, buffers); +} + +TEST_F(BufferPool, BufferWithoutRequest) +{ + FrameBufferAllocator fba(camera_); + std::set> buffers; + + for (const auto &cfg : *config_) { + Stream *stream = cfg.stream(); + ASSERT_GT(fba.allocate(stream), 0); + + for (const auto &buffer : fba.buffers(stream)) + buffers.insert({ stream, buffer.get() }); + } + + unsigned int requestCompleted = 0; + ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *) { + requestCompleted += 1; + }); + + bool bufferCompletedOk = true; + ScopedSignalReceiver bcr(camera_->bufferCompleted, this, [&](Request *request, const Stream *stream, FrameBuffer *buffer) { + bufferCompletedOk &= !request; + bufferCompletedOk &= buffer->metadata().status == libcamera::FrameMetadata::FrameCancelled; + bufferCompletedOk &= buffers.erase({ stream, buffer }); + }); + + { + ScopedCameraStart scs(*camera_); + + for (const auto &cfg : *config_) { + Stream *stream = cfg.stream(); + for (const auto &buffer : fba.buffers(stream)) + EXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0); + } + } + + EXPECT_EQ(requestCompleted, 0); + EXPECT_EQ(buffers.size(), 0); + EXPECT_TRUE(bufferCompletedOk); +} + +TEST_F(BufferPool, RequestWithoutBuffers) +{ + constexpr std::size_t kRequestCount = 128; + auto requests = createRequests(kRequestCount); + + unsigned int requestCompleted = 0; + bool requestCompletedOk = true; + ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) { + if (requestCompleted < requests.size()) { + requestCompletedOk &= request == requests[requestCompleted].get(); + requestCompletedOk &= request->status() == Request::Status::RequestCancelled; + } + + requestCompleted += 1; + }); + + unsigned int bufferCompleted = 0; + ScopedSignalReceiver bcr(camera_->bufferCompleted, this, [&](Request *, const Stream *, FrameBuffer *) { + bufferCompleted += 1; + }); + + { + ScopedCameraStart scs(*camera_); + + for (const auto &request : requests) + ASSERT_EQ(camera_->queueRequest(request.get()), 0); + + std::this_thread::sleep_for(std::chrono::seconds(2)); + } + + EXPECT_EQ(requestCompleted, requests.size()); + EXPECT_TRUE(requestCompletedOk); + EXPECT_EQ(bufferCompleted, 0); +} + } /* namespace */