Patch Detail
Show a patch.
GET /api/patches/11543/?format=api
{ "id": 11543, "url": "https://patchwork.libcamera.org/api/patches/11543/?format=api", "web_url": "https://patchwork.libcamera.org/patch/11543/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20210310154414.3560115-2-niklas.soderlund@ragnatech.se>", "date": "2021-03-10T15:44:13", "name": "[libcamera-devel,v3,1/2] lc-compliance: Add a libcamera compliance tool", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "2eded9dff6f35cb52f19b023bcf15dbfc12919f3", "submitter": { "id": 5, "url": "https://patchwork.libcamera.org/api/people/5/?format=api", "name": "Niklas Söderlund", "email": "niklas.soderlund@ragnatech.se" }, "delegate": { "id": 16, "url": "https://patchwork.libcamera.org/api/users/16/?format=api", "username": "neg", "first_name": "Niklas", "last_name": "Söderlund", "email": "niklas.soderlund@ragnatech.se" }, "mbox": "https://patchwork.libcamera.org/patch/11543/mbox/", "series": [ { "id": 1781, "url": "https://patchwork.libcamera.org/api/series/1781/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=1781", "date": "2021-03-10T15:44:12", "name": "lc-compliance: Add a libcamera compliance tool", "version": 3, "mbox": "https://patchwork.libcamera.org/series/1781/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/11543/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/11543/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 DF880BD80C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 10 Mar 2021 15:44:27 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AE59F602E6;\n\tWed, 10 Mar 2021 16:44:27 +0100 (CET)", "from vsp-unauthed02.binero.net (vsp-unauthed02.binero.net\n\t[195.74.38.227])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 6DFA0602E5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 10 Mar 2021 16:44:25 +0100 (CET)", "from bismarck.berto.se (p54ac5521.dip0.t-ipconnect.de\n\t[84.172.85.33])\n\tby bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA\n\tid 7ca7bd1f-81b7-11eb-b73f-0050569116f7;\n\tWed, 10 Mar 2021 16:44:23 +0100 (CET)" ], "X-Halon-ID": "7ca7bd1f-81b7-11eb-b73f-0050569116f7", "Authorized-sender": "niklas.soderlund@fsdn.se", "From": "=?utf-8?q?Niklas_S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>", "To": "libcamera-devel@lists.libcamera.org", "Date": "Wed, 10 Mar 2021 16:44:13 +0100", "Message-Id": "<20210310154414.3560115-2-niklas.soderlund@ragnatech.se>", "X-Mailer": "git-send-email 2.30.1", "In-Reply-To": "<20210310154414.3560115-1-niklas.soderlund@ragnatech.se>", "References": "<20210310154414.3560115-1-niklas.soderlund@ragnatech.se>", "MIME-Version": "1.0", "Subject": "[libcamera-devel] [PATCH v3 1/2] lc-compliance: Add a libcamera\n\tcompliance tool", "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>", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "base64", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "Add a compliance tool to ease testing of cameras. In contrast to the\nunit-tests under test/ that aims to test the internal components of\nlibcamera the compliance tool aims to test application use-cases and to\nsome extend the public API.\n\nThis change adds the boilerplate code of a simple framework for the\ncreation of tests. The tests aim both to demonstrate the tool and to\ncatch real problems. The tests added are:\n\n - Test that if one queues exactly N requests to a camera exactly N\n requests are eventually completed.\n\n - Test that a configured camera can be started and stopped multiple\n times in an attempt to exercise cleanup code paths otherwise not\n often tested with 'cam' for example.\n\nSigned-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n---\n* Changes since v1\n- Improve language in commit message and comments.\n- Test all roles as they may exercise different code paths in the\n pipeline.\n- Move SimpleCapture to its own .cpp/.h files.\n\n* Changes since v2\n- Fold in a use-after-free bug fix from Kieran, thanks!\n---\n src/lc-compliance/main.cpp | 139 +++++++++++++++++++++++++\n src/lc-compliance/meson.build | 25 +++++\n src/lc-compliance/results.cpp | 75 ++++++++++++++\n src/lc-compliance/results.h | 45 ++++++++\n src/lc-compliance/simple_capture.cpp | 149 +++++++++++++++++++++++++++\n src/lc-compliance/simple_capture.h | 54 ++++++++++\n src/lc-compliance/single_stream.cpp | 75 ++++++++++++++\n src/lc-compliance/tests.h | 16 +++\n src/meson.build | 2 +\n 9 files changed, 580 insertions(+)\n create mode 100644 src/lc-compliance/main.cpp\n create mode 100644 src/lc-compliance/meson.build\n create mode 100644 src/lc-compliance/results.cpp\n create mode 100644 src/lc-compliance/results.h\n create mode 100644 src/lc-compliance/simple_capture.cpp\n create mode 100644 src/lc-compliance/simple_capture.h\n create mode 100644 src/lc-compliance/single_stream.cpp\n create mode 100644 src/lc-compliance/tests.h", "diff": "diff --git a/src/lc-compliance/main.cpp b/src/lc-compliance/main.cpp\nnew file mode 100644\nindex 0000000000000000..e1cbce7eac3df2bc\n--- /dev/null\n+++ b/src/lc-compliance/main.cpp\n@@ -0,0 +1,139 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Google Inc.\n+ *\n+ * main.cpp - lc-compliance - The libcamera compliance tool\n+ */\n+\n+#include <iomanip>\n+#include <iostream>\n+#include <string.h>\n+\n+#include <libcamera/libcamera.h>\n+\n+#include \"../cam/options.h\"\n+#include \"tests.h\"\n+\n+using namespace libcamera;\n+\n+class Harness\n+{\n+public:\n+\tHarness();\n+\t~Harness();\n+\n+\tint exec(int argc, char **argv);\n+\n+private:\n+\tenum {\n+\t\tOptCamera = 'c',\n+\t\tOptHelp = 'h',\n+\t};\n+\n+\tint parseOptions(int argc, char **argv);\n+\tint init(int argc, char **argv);\n+\n+\tOptionsParser::Options options_;\n+\tstd::unique_ptr<CameraManager> cm_;\n+\tstd::shared_ptr<Camera> camera_;\n+};\n+\n+Harness::Harness()\n+{\n+\tcm_ = std::make_unique<CameraManager>();\n+}\n+\n+Harness::~Harness()\n+{\n+\tif (camera_) {\n+\t\tcamera_->release();\n+\t\tcamera_.reset();\n+\t}\n+\n+\tcm_->stop();\n+}\n+\n+int Harness::exec(int argc, char **argv)\n+{\n+\tint ret = init(argc, argv);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tstd::vector<Results> results;\n+\n+\tresults.push_back(testSingleStream(camera_));\n+\n+\tfor (const Results &result : results) {\n+\t\tret = result.summary();\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int Harness::init(int argc, char **argv)\n+{\n+\tint ret = parseOptions(argc, argv);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = cm_->start();\n+\tif (ret) {\n+\t\tstd::cout << \"Failed to start camera manager: \"\n+\t\t\t << strerror(-ret) << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\tif (!options_.isSet(OptCamera)) {\n+\t\tstd::cout << \"No camera specified, available cameras:\" << std::endl;\n+\t\tfor (const std::shared_ptr<Camera> &cam : cm_->cameras())\n+\t\t\tstd::cout << \"- \" << cam.get()->id() << std::endl;\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tconst std::string &cameraId = options_[OptCamera];\n+\tcamera_ = cm_->get(cameraId);\n+\tif (!camera_) {\n+\t\tstd::cout << \"Camera \" << cameraId << \" not found, available cameras:\" << std::endl;\n+\t\tfor (const std::shared_ptr<Camera> &cam : cm_->cameras())\n+\t\t\tstd::cout << \"- \" << cam.get()->id() << std::endl;\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tif (camera_->acquire()) {\n+\t\tstd::cout << \"Failed to acquire camera\" << std::endl;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tstd::cout << \"Using camera \" << cameraId << std::endl;\n+\n+\treturn 0;\n+}\n+\n+int Harness::parseOptions(int argc, char **argv)\n+{\n+\tOptionsParser parser;\n+\tparser.addOption(OptCamera, OptionString,\n+\t\t\t \"Specify which camera to operate on, by id\", \"camera\",\n+\t\t\t ArgumentRequired, \"camera\");\n+\tparser.addOption(OptHelp, OptionNone, \"Display this help message\",\n+\t\t\t \"help\");\n+\n+\toptions_ = parser.parse(argc, argv);\n+\tif (!options_.valid())\n+\t\treturn -EINVAL;\n+\n+\tif (options_.empty() || options_.isSet(OptHelp)) {\n+\t\tparser.usage();\n+\t\treturn options_.empty() ? -EINVAL : -EINTR;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tHarness harness;\n+\treturn harness.exec(argc, argv) ? EXIT_FAILURE : 0;\n+}\ndiff --git a/src/lc-compliance/meson.build b/src/lc-compliance/meson.build\nnew file mode 100644\nindex 0000000000000000..68164537c1055f28\n--- /dev/null\n+++ b/src/lc-compliance/meson.build\n@@ -0,0 +1,25 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libevent = dependency('libevent_pthreads', required : false)\n+\n+if not libevent.found()\n+ warning('libevent_pthreads not found, \\'lc-compliance\\' application will not be compiled')\n+ subdir_done()\n+endif\n+\n+lc_compliance_sources = files([\n+ '../cam/event_loop.cpp',\n+ '../cam/options.cpp',\n+ 'main.cpp',\n+ 'results.cpp',\n+ 'simple_capture.cpp',\n+ 'single_stream.cpp',\n+])\n+\n+lc_compliance = executable('lc-compliance', lc_compliance_sources,\n+ dependencies : [\n+ libatomic,\n+ libcamera_dep,\n+ libevent,\n+ ],\n+ install : true)\ndiff --git a/src/lc-compliance/results.cpp b/src/lc-compliance/results.cpp\nnew file mode 100644\nindex 0000000000000000..8c42eb2d6822aa60\n--- /dev/null\n+++ b/src/lc-compliance/results.cpp\n@@ -0,0 +1,75 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Google Inc.\n+ *\n+ * results.cpp - Test result aggregator\n+ */\n+\n+#include \"results.h\"\n+\n+#include <iostream>\n+\n+void Results::add(const Result &result)\n+{\n+\tif (result.first == Pass)\n+\t\tpassed_++;\n+\telse if (result.first == Fail)\n+\t\tfailed_++;\n+\telse if (result.first == Skip)\n+\t\tskipped_++;\n+\n+\tprintResult(result);\n+}\n+\n+void Results::add(Status status, const std::string &message)\n+{\n+\tadd({ status, message });\n+}\n+\n+void Results::fail(const std::string &message)\n+{\n+\tadd(Fail, message);\n+}\n+\n+void Results::pass(const std::string &message)\n+{\n+\tadd(Pass, message);\n+}\n+\n+void Results::skip(const std::string &message)\n+{\n+\tadd(Skip, message);\n+}\n+\n+int Results::summary() const\n+{\n+\tif (failed_ + passed_ + skipped_ != planned_) {\n+\t\tstd::cout << \"Planned and executed number of tests differ \"\n+\t\t\t << failed_ + passed_ + skipped_ << \" executed \"\n+\t\t\t << planned_ << \" planned\" << std::endl;\n+\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tstd::cout << planned_ << \" tests executed, \"\n+\t\t << passed_ << \" tests passed, \"\n+\t\t << skipped_ << \" tests skipped and \"\n+\t\t << failed_ << \" tests failed \" << std::endl;\n+\n+\treturn 0;\n+}\n+\n+void Results::printResult(const Result &result)\n+{\n+\tstd::string prefix;\n+\n+\t/* \\todo Make parsable as TAP. */\n+\tif (result.first == Pass)\n+\t\tprefix = \"PASS\";\n+\telse if (result.first == Fail)\n+\t\tprefix = \"FAIL\";\n+\telse if (result.first == Skip)\n+\t\tprefix = \"SKIP\";\n+\n+\tstd::cout << \"- \" << prefix << \" - \" << result.second << std::endl;\n+}\ndiff --git a/src/lc-compliance/results.h b/src/lc-compliance/results.h\nnew file mode 100644\nindex 0000000000000000..a02fd5ab46edd62c\n--- /dev/null\n+++ b/src/lc-compliance/results.h\n@@ -0,0 +1,45 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Google Inc.\n+ *\n+ * results.h - Test result aggregator\n+ */\n+#ifndef __LC_COMPLIANCE_RESULTS_H__\n+#define __LC_COMPLIANCE_RESULTS_H__\n+\n+#include <string>\n+\n+class Results\n+{\n+public:\n+\tenum Status {\n+\t\tFail,\n+\t\tPass,\n+\t\tSkip,\n+\t};\n+\n+\tusing Result = std::pair<Status, std::string>;\n+\n+\tResults(unsigned int planned)\n+\t\t: planned_(planned), passed_(0), failed_(0), skipped_(0)\n+\t{\n+\t}\n+\n+\tvoid add(const Result &result);\n+\tvoid add(Status status, const std::string &message);\n+\tvoid fail(const std::string &message);\n+\tvoid pass(const std::string &message);\n+\tvoid skip(const std::string &message);\n+\n+\tint summary() const;\n+\n+private:\n+\tvoid printResult(const Result &result);\n+\n+\tunsigned int planned_;\n+\tunsigned int passed_;\n+\tunsigned int failed_;\n+\tunsigned int skipped_;\n+};\n+\n+#endif /* __LC_COMPLIANCE_RESULTS_H__ */\ndiff --git a/src/lc-compliance/simple_capture.cpp b/src/lc-compliance/simple_capture.cpp\nnew file mode 100644\nindex 0000000000000000..fac8db379118efbd\n--- /dev/null\n+++ b/src/lc-compliance/simple_capture.cpp\n@@ -0,0 +1,149 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Google Inc.\n+ *\n+ * simple_capture.cpp - Simple capture helper\n+ */\n+\n+#include \"simple_capture.h\"\n+\n+using namespace libcamera;\n+\n+SimpleCapture::SimpleCapture(std::shared_ptr<Camera> camera)\n+\t: camera_(camera), allocator_(std::make_unique<FrameBufferAllocator>(camera))\n+{\n+}\n+\n+SimpleCapture::~SimpleCapture()\n+{\n+}\n+\n+Results::Result SimpleCapture::configure(StreamRole role)\n+{\n+\tconfig_ = camera_->generateConfiguration({ role });\n+\n+\tif (config_->validate() != CameraConfiguration::Valid) {\n+\t\tconfig_.reset();\n+\t\treturn { Results::Fail, \"Configuration not valid\" };\n+\t}\n+\n+\tif (camera_->configure(config_.get())) {\n+\t\tconfig_.reset();\n+\t\treturn { Results::Fail, \"Failed to configure camera\" };\n+\t}\n+\n+\treturn { Results::Pass, \"Configure camera\" };\n+}\n+\n+Results::Result SimpleCapture::start()\n+{\n+\tStream *stream = config_->at(0).stream();\n+\tif (allocator_->allocate(stream) < 0)\n+\t\treturn { Results::Fail, \"Failed to allocate buffers\" };\n+\n+\tif (camera_->start())\n+\t\treturn { Results::Fail, \"Failed to start camera\" };\n+\n+\tcamera_->requestCompleted.connect(this, &SimpleCapture::requestComplete);\n+\n+\treturn { Results::Pass, \"Started camera\" };\n+}\n+\n+Results::Result SimpleCapture::stop()\n+{\n+\tStream *stream = config_->at(0).stream();\n+\n+\tcamera_->stop();\n+\n+\tcamera_->requestCompleted.disconnect(this, &SimpleCapture::requestComplete);\n+\n+\tallocator_->free(stream);\n+\n+\treturn { Results::Pass, \"Stopped camera\" };\n+}\n+\n+/* SimpleCaptureBalanced */\n+\n+SimpleCaptureBalanced::SimpleCaptureBalanced(std::shared_ptr<Camera> camera)\n+\t: SimpleCapture(camera)\n+{\n+}\n+\n+Results::Result SimpleCaptureBalanced::capture(unsigned int numRequests)\n+{\n+\tResults::Result ret = start();\n+\tif (ret.first != Results::Pass)\n+\t\treturn ret;\n+\n+\tStream *stream = config_->at(0).stream();\n+\tconst std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream);\n+\n+\t/* No point in testing less requests then the camera depth. */\n+\tif (buffers.size() > numRequests) {\n+\t\t/* Cache buffers.size() before we destroy it in stop() */\n+\t\tint buffers_size = buffers.size();\n+\t\tstop();\n+\n+\t\treturn { Results::Skip, \"Camera needs \" + std::to_string(buffers_size)\n+\t\t\t+ \" requests, can't test only \" + std::to_string(numRequests) };\n+\t}\n+\n+\tqueueCount_ = 0;\n+\tcaptureCount_ = 0;\n+\tcaptureLimit_ = numRequests;\n+\n+\t/* Queue the recommended number of reqeuests. */\n+\tstd::vector<std::unique_ptr<libcamera::Request>> requests;\n+\tfor (const std::unique_ptr<FrameBuffer> &buffer : buffers) {\n+\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n+\t\tif (!request) {\n+\t\t\tstop();\n+\t\t\treturn { Results::Fail, \"Can't create request\" };\n+\t\t}\n+\n+\t\tif (request->addBuffer(stream, buffer.get())) {\n+\t\t\tstop();\n+\t\t\treturn { Results::Fail, \"Can't set buffer for request\" };\n+\t\t}\n+\n+\t\tif (queueRequest(request.get()) < 0) {\n+\t\t\tstop();\n+\t\t\treturn { Results::Fail, \"Failed to queue request\" };\n+\t\t}\n+\n+\t\trequests.push_back(std::move(request));\n+\t}\n+\n+\t/* Run capture session. */\n+\tloop_ = new EventLoop();\n+\tloop_->exec();\n+\tstop();\n+\tdelete loop_;\n+\n+\tif (captureCount_ != captureLimit_)\n+\t\treturn { Results::Fail, \"Got \" + std::to_string(captureCount_) + \" request, wanted \" + std::to_string(captureLimit_) };\n+\n+\treturn { Results::Pass, \"Balanced capture of \" + std::to_string(numRequests) + \" requests\" };\n+}\n+\n+int SimpleCaptureBalanced::queueRequest(Request *request)\n+{\n+\tqueueCount_++;\n+\tif (queueCount_ > captureLimit_)\n+\t\treturn 0;\n+\n+\treturn camera_->queueRequest(request);\n+}\n+\n+void SimpleCaptureBalanced::requestComplete(Request *request)\n+{\n+\tcaptureCount_++;\n+\tif (captureCount_ >= captureLimit_) {\n+\t\tloop_->exit(0);\n+\t\treturn;\n+\t}\n+\n+\trequest->reuse(Request::ReuseBuffers);\n+\tif (queueRequest(request))\n+\t\tloop_->exit(-EINVAL);\n+}\ndiff --git a/src/lc-compliance/simple_capture.h b/src/lc-compliance/simple_capture.h\nnew file mode 100644\nindex 0000000000000000..3a6afc538c623050\n--- /dev/null\n+++ b/src/lc-compliance/simple_capture.h\n@@ -0,0 +1,54 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Google Inc.\n+ *\n+ * simple_capture.h - Simple capture helper\n+ */\n+#ifndef __LC_COMPLIANCE_SIMPLE_CAPTURE_H__\n+#define __LC_COMPLIANCE_SIMPLE_CAPTURE_H__\n+\n+#include <memory>\n+\n+#include <libcamera/libcamera.h>\n+\n+#include \"../cam/event_loop.h\"\n+#include \"results.h\"\n+\n+class SimpleCapture\n+{\n+public:\n+\tResults::Result configure(libcamera::StreamRole role);\n+\n+protected:\n+\tSimpleCapture(std::shared_ptr<libcamera::Camera> camera);\n+\tvirtual ~SimpleCapture();\n+\n+\tResults::Result start();\n+\tResults::Result stop();\n+\n+\tvirtual void requestComplete(libcamera::Request *request) = 0;\n+\n+\tEventLoop *loop_;\n+\n+\tstd::shared_ptr<libcamera::Camera> camera_;\n+\tstd::unique_ptr<libcamera::FrameBufferAllocator> allocator_;\n+\tstd::unique_ptr<libcamera::CameraConfiguration> config_;\n+};\n+\n+class SimpleCaptureBalanced : public SimpleCapture\n+{\n+public:\n+\tSimpleCaptureBalanced(std::shared_ptr<libcamera::Camera> camera);\n+\n+\tResults::Result capture(unsigned int numRequests);\n+\n+private:\n+\tint queueRequest(libcamera::Request *request);\n+\tvoid requestComplete(libcamera::Request *request) override;\n+\n+\tunsigned int queueCount_;\n+\tunsigned int captureCount_;\n+\tunsigned int captureLimit_;\n+};\n+\n+#endif /* __LC_COMPLIANCE_SIMPLE_CAPTURE_H__ */\ndiff --git a/src/lc-compliance/single_stream.cpp b/src/lc-compliance/single_stream.cpp\nnew file mode 100644\nindex 0000000000000000..0ed6f5dcfb5a516d\n--- /dev/null\n+++ b/src/lc-compliance/single_stream.cpp\n@@ -0,0 +1,75 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Google Inc.\n+ *\n+ * single_stream.cpp - Test a single camera stream\n+ */\n+\n+#include <iostream>\n+\n+#include \"simple_capture.h\"\n+#include \"tests.h\"\n+\n+using namespace libcamera;\n+\n+Results::Result testRequestBalance(std::shared_ptr<Camera> camera,\n+\t\t\t\t StreamRole role, unsigned int startCycles,\n+\t\t\t\t unsigned int numRequests)\n+{\n+\tSimpleCaptureBalanced capture(camera);\n+\n+\tResults::Result ret = capture.configure(role);\n+\tif (ret.first != Results::Pass)\n+\t\treturn ret;\n+\n+\tfor (unsigned int starts = 0; starts < startCycles; starts++) {\n+\t\tret = capture.capture(numRequests);\n+\t\tif (ret.first != Results::Pass)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn { Results::Pass, \"Balanced capture of \" + std::to_string(numRequests) + \" requests with \" + std::to_string(startCycles) + \" start cycles\" };\n+}\n+\n+Results testSingleStream(std::shared_ptr<Camera> camera)\n+{\n+\tconst std::vector<std::pair<std::string, StreamRole>> roles = {\n+\t\t{ \"raw\", Raw },\n+\t\t{ \"still\", StillCapture },\n+\t\t{ \"video\", VideoRecording },\n+\t\t{ \"viewfinder\", Viewfinder },\n+\t};\n+\tconst std::vector<unsigned int> numRequests = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };\n+\n+\tResults results(numRequests.size() * roles.size() * 2);\n+\n+\tif (!camera)\n+\t\treturn results;\n+\n+\tfor (const auto &role : roles) {\n+\t\tstd::cout << \"= Test role \" << role.first << std::endl;\n+\t\t/*\n+\t\t * Test single capture cycles\n+\t\t *\n+\t\t * Makes sure the camera completes the exact number of requests queued.\n+\t\t * Example failure is a camera that needs N+M requests queued to\n+\t\t * complete N requests to the application.\n+\t\t */\n+\t\tstd::cout << \"* Test single capture cycles\" << std::endl;\n+\t\tfor (unsigned int num : numRequests)\n+\t\t\tresults.add(testRequestBalance(camera, role.second, 1, num));\n+\n+\t\t/*\n+\t\t * Test multiple start/stop cycles\n+\t\t *\n+\t\t * Makes sure the camera supports multiple start/stop cycles.\n+\t\t * Example failure is a camera that does not clean up correctly in its\n+\t\t * error path but is only tested by single-capture applications.\n+\t\t */\n+\t\tstd::cout << \"* Test multiple start/stop cycles\" << std::endl;\n+\t\tfor (unsigned int num : numRequests)\n+\t\t\tresults.add(testRequestBalance(camera, role.second, 3, num));\n+\t}\n+\n+\treturn results;\n+}\ndiff --git a/src/lc-compliance/tests.h b/src/lc-compliance/tests.h\nnew file mode 100644\nindex 0000000000000000..396605214e4b8980\n--- /dev/null\n+++ b/src/lc-compliance/tests.h\n@@ -0,0 +1,16 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Google Inc.\n+ *\n+ * tests.h - Test modules\n+ */\n+#ifndef __LC_COMPLIANCE_TESTS_H__\n+#define __LC_COMPLIANCE_TESTS_H__\n+\n+#include <libcamera/libcamera.h>\n+\n+#include \"results.h\"\n+\n+Results testSingleStream(std::shared_ptr<libcamera::Camera> camera);\n+\n+#endif /* __LC_COMPLIANCE_TESTS_H__ */\ndiff --git a/src/meson.build b/src/meson.build\nindex c908b0675773301d..a8a6a572e4cf9482 100644\n--- a/src/meson.build\n+++ b/src/meson.build\n@@ -20,6 +20,8 @@ subdir('android')\n subdir('libcamera')\n subdir('ipa')\n \n+subdir('lc-compliance')\n+\n subdir('cam')\n subdir('qcam')\n \n", "prefixes": [ "libcamera-devel", "v3", "1/2" ] }