From patchwork Thu Oct 29 16:06:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 28717 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 504E7C3302 for ; Thu, 29 Oct 2026 16:06:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B9072690DB; Thu, 29 Oct 2026 17:06:24 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hFB4PU1j"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 01DA8690D8 for ; Thu, 29 Oct 2026 17:06:20 +0100 (CET) Received: from ping.linuxembedded.co.uk (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5A2C5524; Thu, 29 Oct 2026 17:04:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793289847; bh=oDKdwOazUCAp5ohe672QQrjK8VeMkbLc3Ly80yIvrFk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=hFB4PU1jBZFJDZ5UJFEvWgo200e14KwBorK8oM2hh7zVbO+VNf1qSTOhD7ySqrbA6 BE5VEPqa31OrNNswljV7ACBMypECCpVUuM83jfJPLS+7jBLUOWTuFEQcz14nCPN76p Yv8hOLhKdHWVVL49AaafIlT5EKLBw7cZPQVg8rEk= From: Kieran Bingham Date: Thu, 29 Oct 2026 16:06:14 +0000 Subject: [PATCH v3 2/2] test: pipeline: Add a duplicate camera construction test MIME-Version: 1.0 Message-Id: <20261029-kbingham-duplicate-id-v3-2-0a6537277d50@ideasonboard.com> References: <20261029-kbingham-duplicate-id-v3-0-0a6537277d50@ideasonboard.com> In-Reply-To: <20261029-kbingham-duplicate-id-v3-0-0a6537277d50@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1793289979; l=6405; i=kieran.bingham@ideasonboard.com; s=20260207; h=from:subject:message-id; bh=oDKdwOazUCAp5ohe672QQrjK8VeMkbLc3Ly80yIvrFk=; b=aVp2zxXRDGZX5eFjPYggrsujxwdeAIHr2jy5L3k0skIYQtQBotBbVsGwqeIjIOsnwmShJHtOj /f0cZONA5EdDVwzLYaw1IBloXtm+mhjyeYBh/mm8eS9yxljkncKD8fs X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=FVXKN7YuwHc6UtbRUeTMAmranfsQomA+vnilfglWdaY= 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" Prepare a test pipeline handler which will allow us to construct custom cameras. Use this pipeline handler to specifically prepare an environment to be able to construct multiple cameras with the same ID to ensure we do not have any system regressions from this in the future. Acked-by: Jacopo Mondi Signed-off-by: Kieran Bingham --- v3: - Move the match state initialisation into the test and pass a parameter instead. - Make camera id string constexpr - Remove unused properties header. - Remove (void) unused markers by removing variable names from declarations. --- test/meson.build | 1 + test/pipeline/duplicate_camera_id.cpp | 190 ++++++++++++++++++++++++++++++++++ test/pipeline/meson.build | 14 +++ 3 files changed, 205 insertions(+) diff --git a/test/meson.build b/test/meson.build index 363a2829d895..62f8c7da8b30 100644 --- a/test/meson.build +++ b/test/meson.build @@ -32,6 +32,7 @@ subdir('ipa') subdir('ipc') subdir('log') subdir('media_device') +subdir('pipeline') subdir('process') subdir('py') subdir('serialization') diff --git a/test/pipeline/duplicate_camera_id.cpp b/test/pipeline/duplicate_camera_id.cpp new file mode 100644 index 000000000000..36512bccc961 --- /dev/null +++ b/test/pipeline/duplicate_camera_id.cpp @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy. + */ + +#include +#include + +#include + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/pipeline_handler.h" + +#include "test.h" + +using namespace libcamera; + +namespace { + +/* + * There's no easy way to communicate to our custom test pipeline + * handler. Use a global here to state and track how many cameras should + * be constructed. + */ +struct MatchState { + unsigned int requested; + unsigned int created; +} matchState; + +constexpr const char *cameraId = "NotVeryUniqueCameraId"; + +} /* namespace */ + +namespace libcamera { + +class TestCameraData : public Camera::Private +{ +public: + TestCameraData(PipelineHandler *pipe) + : Camera::Private(pipe) + { + } +}; + +class PipelineHandlerTest : public PipelineHandler +{ +public: + PipelineHandlerTest(CameraManager *manager) + : PipelineHandler(manager) + { + } + + std::unique_ptr + generateConfiguration(Camera *, + std::span) override + { + return {}; + } + + int configure(Camera *, CameraConfiguration *) override + { + return true; + } + + int exportFrameBuffers(Camera *, Stream *, + std::vector> *) override + { + return true; + } + + int start(Camera *, const ControlList *) override + { + return false; + } + + void stopDevice(Camera *) override + { + } + + int queueRequestDevice(Camera *, Request *) override + { + return false; + } + + bool match(DeviceEnumerator *) override + { + /* Only construct requested number of pipelines */ + if (matchState.created >= matchState.requested) + return false; + + std::unique_ptr data = std::make_unique(this); + + std::set streams; /* No streams yet on this camera... */ + std::shared_ptr camera = Camera::create(std::move(data), cameraId, streams); + + registerCamera(std::move(camera)); + + matchState.created++; + + return true; + } +}; + +REGISTER_PIPELINE_HANDLER(PipelineHandlerTest, "test") + +} /* namespace libcamera */ + +namespace { + +class CameraIdTest : public Test +{ +protected: + int init() override + { + /* Make sure CameraManager only uses our custom test pipeline */ + if (setenv("LIBCAMERA_PIPELINES_MATCH_LIST", "test", 1) != 0) { + std::cerr << "Failed to set pipeline match list" << std::endl; + return TestFail; + } + + return TestPass; + } + + int cameraConstructionTest(unsigned int qty) + { + libcamera::CameraManager cm; + + /* + * Re-initialise the global state used by the test pipeline + * handler when we call cm.start(). + */ + matchState.created = 0; + matchState.requested = qty; + + cm.start(); + + /* + * Even if we try to create multiple cameras, we always expect + * only one to succeed. + */ + const std::vector> &cameras = cm.cameras(); + if (cameras.size() != 1) { + std::cerr + << "Expected 1 camera, got " + << cameras.size() << std::endl; + return TestFail; + } + + /* + * I can't see how it could ever be anything but this here, but + * let's check anyway as the intent of the test is that we get + * only a single camera with the expected ID. + */ + const std::string &id = cameras[0]->id(); + if (id != cameraId) { + std::cerr + << "Expected '" << cameraId + << "', got '" << id << "'" << std::endl; + return TestFail; + } + + return TestPass; + } + + int run() override + { + int ret; + + /* + * Sanity check with a single camera construction first, and + * this also helps to run this test multiple times to make sure + * we're destroying and reconstructing the CameraManager and + * pipeline handler fully. + */ + ret = cameraConstructionTest(1); + if (ret != TestPass) + return ret; + + /* And now lets attempt duplicated camera constructions */ + ret = cameraConstructionTest(3); + if (ret != TestPass) + return ret; + + return TestPass; + } +}; + +} /* namespace */ + +TEST_REGISTER(CameraIdTest) diff --git a/test/pipeline/meson.build b/test/pipeline/meson.build new file mode 100644 index 000000000000..c1f4ee969202 --- /dev/null +++ b/test/pipeline/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: CC0-1.0 + +pipeline_tests = [ + {'name': 'duplicate_camera_id', 'sources': ['duplicate_camera_id.cpp']}, +] + +foreach test : pipeline_tests + exe = executable(test['name'], test['sources'], + dependencies : libcamera_private, + link_with : test_libraries, + include_directories : test_includes_internal) + + test(test['name'], exe, suite : 'pipeline', is_parallel : true) +endforeach