<div dir="ltr"><div dir="ltr">Hi Niklas,<div><br></div><div>Thank you for your work, this looks quite interesting.  However, I have a high level question, see below.</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, 29 Jan 2021 at 16:53, Niklas Söderlund <<a href="mailto:niklas.soderlund@ragnatech.se">niklas.soderlund@ragnatech.se</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Add a compliance tool to ease testing of cameras on target. In contrast<br>
to the unit-tests under test/ that aims to test the internal components<br>
of libcamera the compliance aims to test application use-cases and to<br>
some extend the public API.<br>
<br>
This change adds the boilerplate code for a simple framework to write<br>
tests in as well as two simple test. The tests aims both to demonstrate<br>
the tool and to catch real problems. The tests added are:<br>
<br>
 - Test that if one queues exactly N requests to a camera exactly N<br>
   requests are eventually completed.<br>
<br>
 - Test that a configured camera can be started and stopped multiple<br>
   times in an attempt to exercise cleanup code paths otherwise not<br>
   often tested with 'cam' for example.<br>
<br>
Example pass run on Raspberry Pi:<br>
<br>
  $ LIBCAMERA_LOG_LEVELS="*:ERROR" lc-compliance -c "/base/soc/i2c0mux/i2c@1/imx219@10"<br>
  [2:36:17.672447527] [11896] ERROR V4L2 v4l2_device.cpp:191 'imx219 10-0010': Control 0x009a0922 not found<br>
  [2:36:17.673095289] [11896] ERROR V4L2 v4l2_subdevice.cpp:285 'imx219 10-0010': Unable to get rectangle 2 on pad 0: Invalid argument<br>
  Using camera /base/soc/i2c0mux/i2c@1/imx219@10<br>
  Test single capture cycles<br>
  - SKIP - Camera needs 4 requests, can't test only 1<br>
  - SKIP - Camera needs 4 requests, can't test only 2<br>
  - SKIP - Camera needs 4 requests, can't test only 3<br></blockquote><div><br></div><div>I'm a bit curious about the above 3 lines.  From what I make out, it is saying that the pipeline cannot run with < 4 on-the-go requests, is that correct?  Does that determination come from the buffer count returned by PipelineHandler::GenerateConfiguration()?  I *think* the Raspberry Pi stack should be able to handle any number of on-the-go requests.  I say think, because I do not normally run in that configuration, but I do know some of our users might have.  The buffer count of 4 returned by PipelineHandlerRPi::generateConfiguration() is a somewhat educated guess for a reasonable number of buffers.  Is this something we need to update in our pipeline handler for this to work?</div><div><br></div><div>Regards,</div><div>Naush</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
  - PASS - Balanced capture of 5 requests with 1 start cycles<br>
  - PASS - Balanced capture of 8 requests with 1 start cycles<br>
  - PASS - Balanced capture of 13 requests with 1 start cycles<br>
  - PASS - Balanced capture of 21 requests with 1 start cycles<br>
  - PASS - Balanced capture of 34 requests with 1 start cycles<br>
  - PASS - Balanced capture of 55 requests with 1 start cycles<br>
  - PASS - Balanced capture of 89 requests with 1 start cycles<br>
  Test multiple start/stop cycles<br>
  - SKIP - Camera needs 4 requests, can't test only 1<br>
  - SKIP - Camera needs 4 requests, can't test only 2<br>
  - SKIP - Camera needs 4 requests, can't test only 3<br>
  - PASS - Balanced capture of 5 requests with 3 start cycles<br>
  - PASS - Balanced capture of 8 requests with 3 start cycles<br>
  - PASS - Balanced capture of 13 requests with 3 start cycles<br>
  - PASS - Balanced capture of 21 requests with 3 start cycles<br>
  - PASS - Balanced capture of 34 requests with 3 start cycles<br>
  - PASS - Balanced capture of 55 requests with 3 start cycles<br>
  - PASS - Balanced capture of 89 requests with 3 start cycles<br>
  20 tests executed, 14 tests passed, 6 tests skipped and 0 tests failed<br>
<br>
Example fail run on IPU3:<br>
<br>
  - LIBCAMERA_LOG_LEVELS="*:ERROR" lc-compliance -c "\_SB_.PCI0.I2C2.CAM0"<br>
  - [0:31:36.992763102] [6420] ERROR V4L2 v4l2_device.cpp:191 'ov13858 8-0010': Control 0x009a0922 not found<br>
  - [0:31:36.992815436] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 2 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.992847049] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 1 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.992869029] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993152932] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993172833] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov13858 8-0010': The analogue crop rectangle has been defaulted to the active area size<br>
  - [0:31:36.993208149] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov13858 8-0010': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993224502] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov13858 8-0010': The analogue crop rectangle has been defaulted to the active area size<br>
  - [0:31:36.993354770] [6420] ERROR V4L2 v4l2_device.cpp:191 'ov5670 10-0036': Control 0x009a0922 not found<br>
  - [0:31:36.993377622] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 2 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993398093] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 1 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993415611] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993660192] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993678492] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov5670 10-0036': The analogue crop rectangle has been defaulted to the active area size<br>
  - [0:31:36.993724648] [6420] ERROR V4L2 v4l2_subdevice.cpp:285 'ov5670 10-0036': Unable to get rectangle 0 on pad 0: Inappropriate ioctl for device<br>
  - [0:31:36.993741032] [6420] ERROR CameraSensor camera_sensor.cpp:678 'ov5670 10-0036': The analogue crop rectangle has been defaulted to the active area size<br>
  - Using camera \_SB_.PCI0.I2C2.CAM0<br>
  - Test single capture cycles<br>
  - - SKIP - Camera needs 4 requests, can't test only 1<br>
  - - SKIP - Camera needs 4 requests, can't test only 2<br>
  - - SKIP - Camera needs 4 requests, can't test only 3<br>
  - - PASS - Balanced capture of 5 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 8 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 13 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 21 requests with 1 start cycles<br>
  - - PASS - Balanced capture of 34 requests with 1 start cycles<br>
  - [0:31:41.969783243] [6420] ERROR IPU3 cio2.cpp:264 CIO2 buffer underrun<br>
  <Application lockup here as the pipeline error is not propagated to the application><br>
<br>
Signed-off-by: Niklas Söderlund <<a href="mailto:niklas.soderlund@ragnatech.se" target="_blank">niklas.soderlund@ragnatech.se</a>><br>
---<br>
Hi,<br>
<br>
I have tested this on RkISP1, RPi and IPU3 on latest master [1]. I plan<br>
to post separate patches which starts to address some of the problems<br>
found while working. A fix for 'cam --captur=N' is coming shortly for<br>
example.<br>
<br>
I still have to find a good fix for a races found in the<br>
IPU3 pipeline between the CIO2 and IMGU (as showed above). What I don't<br>
(yet) have a plan for is how to fix that request queueing errors are not<br>
propagated in our application facing API leading the test application to<br>
wait forever to a request the library knows have failed, ideas welcome.<br>
<br>
1. 958c80a4f1c28301 ("android: camera_device: Set AE precapture trigger according to request")<br>
---<br>
 src/lc-compliance/main.cpp          | 139 ++++++++++++++++++<br>
 src/lc-compliance/meson.build       |  24 ++++<br>
 src/lc-compliance/results.cpp       |  75 ++++++++++<br>
 src/lc-compliance/results.h         |  45 ++++++<br>
 src/lc-compliance/single_stream.cpp | 210 ++++++++++++++++++++++++++++<br>
 src/lc-compliance/tests.h           |  16 +++<br>
 src/meson.build                     |   2 +<br>
 7 files changed, 511 insertions(+)<br>
 create mode 100644 src/lc-compliance/main.cpp<br>
 create mode 100644 src/lc-compliance/meson.build<br>
 create mode 100644 src/lc-compliance/results.cpp<br>
 create mode 100644 src/lc-compliance/results.h<br>
 create mode 100644 src/lc-compliance/single_stream.cpp<br>
 create mode 100644 src/lc-compliance/tests.h<br>
<br>
diff --git a/src/lc-compliance/main.cpp b/src/lc-compliance/main.cpp<br>
new file mode 100644<br>
index 0000000000000000..e1cbce7eac3df2bc<br>
--- /dev/null<br>
+++ b/src/lc-compliance/main.cpp<br>
@@ -0,0 +1,139 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * main.cpp - lc-compliance - The libcamera compliance tool<br>
+ */<br>
+<br>
+#include <iomanip><br>
+#include <iostream><br>
+#include <string.h><br>
+<br>
+#include <libcamera/libcamera.h><br>
+<br>
+#include "../cam/options.h"<br>
+#include "tests.h"<br>
+<br>
+using namespace libcamera;<br>
+<br>
+class Harness<br>
+{<br>
+public:<br>
+       Harness();<br>
+       ~Harness();<br>
+<br>
+       int exec(int argc, char **argv);<br>
+<br>
+private:<br>
+       enum {<br>
+               OptCamera = 'c',<br>
+               OptHelp = 'h',<br>
+       };<br>
+<br>
+       int parseOptions(int argc, char **argv);<br>
+       int init(int argc, char **argv);<br>
+<br>
+       OptionsParser::Options options_;<br>
+       std::unique_ptr<CameraManager> cm_;<br>
+       std::shared_ptr<Camera> camera_;<br>
+};<br>
+<br>
+Harness::Harness()<br>
+{<br>
+       cm_ = std::make_unique<CameraManager>();<br>
+}<br>
+<br>
+Harness::~Harness()<br>
+{<br>
+       if (camera_) {<br>
+               camera_->release();<br>
+               camera_.reset();<br>
+       }<br>
+<br>
+       cm_->stop();<br>
+}<br>
+<br>
+int Harness::exec(int argc, char **argv)<br>
+{<br>
+       int ret = init(argc, argv);<br>
+       if (ret)<br>
+               return ret;<br>
+<br>
+       std::vector<Results> results;<br>
+<br>
+       results.push_back(testSingleStream(camera_));<br>
+<br>
+       for (const Results &result : results) {<br>
+               ret = result.summary();<br>
+               if (ret)<br>
+                       return ret;<br>
+       }<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+int Harness::init(int argc, char **argv)<br>
+{<br>
+       int ret = parseOptions(argc, argv);<br>
+       if (ret < 0)<br>
+               return ret;<br>
+<br>
+       ret = cm_->start();<br>
+       if (ret) {<br>
+               std::cout << "Failed to start camera manager: "<br>
+                         << strerror(-ret) << std::endl;<br>
+               return ret;<br>
+       }<br>
+<br>
+       if (!options_.isSet(OptCamera)) {<br>
+               std::cout << "No camera specified, available cameras:" << std::endl;<br>
+               for (const std::shared_ptr<Camera> &cam : cm_->cameras())<br>
+                       std::cout << "- " << cam.get()->id() << std::endl;<br>
+               return -ENODEV;<br>
+       }<br>
+<br>
+       const std::string &cameraId = options_[OptCamera];<br>
+       camera_ = cm_->get(cameraId);<br>
+       if (!camera_) {<br>
+               std::cout << "Camera " << cameraId << " not found, available cameras:" << std::endl;<br>
+               for (const std::shared_ptr<Camera> &cam : cm_->cameras())<br>
+                       std::cout << "- " << cam.get()->id() << std::endl;<br>
+               return -ENODEV;<br>
+       }<br>
+<br>
+       if (camera_->acquire()) {<br>
+               std::cout << "Failed to acquire camera" << std::endl;<br>
+               return -EINVAL;<br>
+       }<br>
+<br>
+       std::cout << "Using camera " << cameraId << std::endl;<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+int Harness::parseOptions(int argc, char **argv)<br>
+{<br>
+       OptionsParser parser;<br>
+       parser.addOption(OptCamera, OptionString,<br>
+                        "Specify which camera to operate on, by id", "camera",<br>
+                        ArgumentRequired, "camera");<br>
+       parser.addOption(OptHelp, OptionNone, "Display this help message",<br>
+                        "help");<br>
+<br>
+       options_ = parser.parse(argc, argv);<br>
+       if (!options_.valid())<br>
+               return -EINVAL;<br>
+<br>
+       if (options_.empty() || options_.isSet(OptHelp)) {<br>
+               parser.usage();<br>
+               return options_.empty() ? -EINVAL : -EINTR;<br>
+       }<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+int main(int argc, char **argv)<br>
+{<br>
+       Harness harness;<br>
+       return harness.exec(argc, argv) ? EXIT_FAILURE : 0;<br>
+}<br>
diff --git a/src/lc-compliance/meson.build b/src/lc-compliance/meson.build<br>
new file mode 100644<br>
index 0000000000000000..d700a307c71405b4<br>
--- /dev/null<br>
+++ b/src/lc-compliance/meson.build<br>
@@ -0,0 +1,24 @@<br>
+# SPDX-License-Identifier: CC0-1.0<br>
+<br>
+libevent = dependency('libevent_pthreads', required : false)<br>
+<br>
+if not libevent.found()<br>
+    warning('libevent_pthreads not found, \'lc-compliance\' application will not be compiled')<br>
+    subdir_done()<br>
+endif<br>
+<br>
+lc_compliance_sources = files([<br>
+    '../cam/event_loop.cpp',<br>
+    '../cam/options.cpp',<br>
+    'main.cpp',<br>
+    'results.cpp',<br>
+    'single_stream.cpp',<br>
+])<br>
+<br>
+lc_compliance  = executable('lc-compliance', lc_compliance_sources,<br>
+                  dependencies : [<br>
+                      libatomic,<br>
+                      libcamera_dep,<br>
+                      libevent,<br>
+                  ],<br>
+                  install : true)<br>
diff --git a/src/lc-compliance/results.cpp b/src/lc-compliance/results.cpp<br>
new file mode 100644<br>
index 0000000000000000..fb4242bf49a3268b<br>
--- /dev/null<br>
+++ b/src/lc-compliance/results.cpp<br>
@@ -0,0 +1,75 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * results.cpp - Test result aggregator<br>
+ */<br>
+<br>
+#include "results.h"<br>
+<br>
+#include <iostream><br>
+<br>
+void Results::add(const Result &result)<br>
+{<br>
+       if (result.first == Pass)<br>
+               passed_++;<br>
+       else if (result.first == Fail)<br>
+               failed_++;<br>
+       else if (result.first == Skip)<br>
+               skipped_++;<br>
+<br>
+       printResult(result);<br>
+}<br>
+<br>
+void Results::add(Status status, const std::string &message)<br>
+{<br>
+       add({ status, message });<br>
+}<br>
+<br>
+void Results::fail(const std::string &message)<br>
+{<br>
+       add(Fail, message);<br>
+}<br>
+<br>
+void Results::pass(const std::string &message)<br>
+{<br>
+       add(Pass, message);<br>
+}<br>
+<br>
+void Results::skip(const std::string &message)<br>
+{<br>
+       add(Skip, message);<br>
+}<br>
+<br>
+int Results::summary() const<br>
+{<br>
+       if (failed_ + passed_ + skipped_ != planned_) {<br>
+               std::cout << "Planned and executed numer of tests differ "<br>
+                         << failed_ + passed_ + skipped_ << " executed "<br>
+                         << planned_ << " planned" << std::endl;<br>
+<br>
+               return -EINVAL;<br>
+       }<br>
+<br>
+       std::cout << planned_ << " tests executed, "<br>
+                 << passed_ << " tests passed, "<br>
+                 << skipped_ << " tests skipped and "<br>
+                 << failed_ << " tests failed " << std::endl;<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+void Results::printResult(const Result &result)<br>
+{<br>
+       std::string prefix;<br>
+<br>
+       /* \todo Make parsable as TAP. */<br>
+       if (result.first == Pass)<br>
+               prefix = "PASS";<br>
+       else if (result.first == Fail)<br>
+               prefix = "FAIL";<br>
+       else if (result.first == Skip)<br>
+               prefix = "SKIP";<br>
+<br>
+       std::cout << "- " << prefix << " - " << result.second << std::endl;<br>
+}<br>
diff --git a/src/lc-compliance/results.h b/src/lc-compliance/results.h<br>
new file mode 100644<br>
index 0000000000000000..a02fd5ab46edd62c<br>
--- /dev/null<br>
+++ b/src/lc-compliance/results.h<br>
@@ -0,0 +1,45 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * results.h - Test result aggregator<br>
+ */<br>
+#ifndef __LC_COMPLIANCE_RESULTS_H__<br>
+#define __LC_COMPLIANCE_RESULTS_H__<br>
+<br>
+#include <string><br>
+<br>
+class Results<br>
+{<br>
+public:<br>
+       enum Status {<br>
+               Fail,<br>
+               Pass,<br>
+               Skip,<br>
+       };<br>
+<br>
+       using Result = std::pair<Status, std::string>;<br>
+<br>
+       Results(unsigned int planned)<br>
+               : planned_(planned), passed_(0), failed_(0), skipped_(0)<br>
+       {<br>
+       }<br>
+<br>
+       void add(const Result &result);<br>
+       void add(Status status, const std::string &message);<br>
+       void fail(const std::string &message);<br>
+       void pass(const std::string &message);<br>
+       void skip(const std::string &message);<br>
+<br>
+       int summary() const;<br>
+<br>
+private:<br>
+       void printResult(const Result &result);<br>
+<br>
+       unsigned int planned_;<br>
+       unsigned int passed_;<br>
+       unsigned int failed_;<br>
+       unsigned int skipped_;<br>
+};<br>
+<br>
+#endif /* __LC_COMPLIANCE_RESULTS_H__ */<br>
diff --git a/src/lc-compliance/single_stream.cpp b/src/lc-compliance/single_stream.cpp<br>
new file mode 100644<br>
index 0000000000000000..d31b6f8d06487a85<br>
--- /dev/null<br>
+++ b/src/lc-compliance/single_stream.cpp<br>
@@ -0,0 +1,210 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * single_stream.cpp - Test a single camera stream<br>
+ */<br>
+<br>
+#include <iostream><br>
+<br>
+#include "../cam/event_loop.h"<br>
+#include "tests.h"<br>
+<br>
+using namespace libcamera;<br>
+<br>
+class SimpleCapture<br>
+{<br>
+public:<br>
+       SimpleCapture(std::shared_ptr<Camera> camera)<br>
+               : camera_(camera), allocator_(std::make_unique<FrameBufferAllocator>(camera))<br>
+       {<br>
+       }<br>
+<br>
+       Results::Result configure(StreamRole role);<br>
+       Results::Result start();<br>
+       Results::Result capture(unsigned int numRequests);<br>
+       Results::Result stop();<br>
+<br>
+private:<br>
+       int queueRequest(Request *request);<br>
+       void requestComplete(Request *request);<br>
+<br>
+       std::shared_ptr<libcamera::Camera> camera_;<br>
+       std::unique_ptr<FrameBufferAllocator> allocator_;<br>
+       std::unique_ptr<libcamera::CameraConfiguration> config_;<br>
+<br>
+       EventLoop *loop_;<br>
+       unsigned int queueCount_;<br>
+       unsigned int captureCount_;<br>
+       unsigned int captureLimit_;<br>
+};<br>
+<br>
+Results::Result SimpleCapture::configure(StreamRole role)<br>
+{<br>
+       config_ = camera_->generateConfiguration({ role });<br>
+<br>
+       if (config_->validate() != CameraConfiguration::Valid) {<br>
+               config_.reset();<br>
+               return { Results::Fail, "Configuration not valid" };<br>
+       }<br>
+<br>
+       if (camera_->configure(config_.get())) {<br>
+               config_.reset();<br>
+               return { Results::Fail, "Failed to configure camera" };<br>
+       }<br>
+<br>
+       return { Results::Pass, "Configure camera" };<br>
+}<br>
+<br>
+Results::Result SimpleCapture::start()<br>
+{<br>
+       Stream *stream = config_->at(0).stream();<br>
+       if (allocator_->allocate(stream) < 0)<br>
+               return { Results::Fail, "Failed to allocate buffers camera" };<br>
+<br>
+       if (camera_->start())<br>
+               return { Results::Fail, "Failed to start camera" };<br>
+<br>
+       camera_->requestCompleted.connect(this, &SimpleCapture::requestComplete);<br>
+<br>
+       return { Results::Pass, "Started camera" };<br>
+}<br>
+<br>
+Results::Result SimpleCapture::capture(unsigned int numRequests)<br>
+{<br>
+       Stream *stream = config_->at(0).stream();<br>
+       const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream);<br>
+<br>
+       /* No point in testing less requests then the camera depth. */<br>
+       if (buffers.size() > numRequests)<br>
+               return { Results::Skip, "Camera needs " + std::to_string(buffers.size()) + " requests, can't test only " + std::to_string(numRequests) };<br>
+<br>
+       queueCount_ = 0;<br>
+       captureCount_ = 0;<br>
+       captureLimit_ = numRequests;<br>
+<br>
+       /* Queue the camera recommended number of reqeuests. */<br>
+       std::vector<std::unique_ptr<libcamera::Request>> requests;<br>
+       for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {<br>
+               std::unique_ptr<Request> request = camera_->createRequest();<br>
+               if (!request)<br>
+                       return { Results::Fail, "Can't create request" };<br>
+<br>
+               if (request->addBuffer(stream, buffer.get()))<br>
+                       return { Results::Fail, "Can't set buffer for request" };<br>
+<br>
+               if (queueRequest(request.get()) < 0)<br>
+                       return { Results::Fail, "Failed to queue request" };<br>
+<br>
+               requests.push_back(std::move(request));<br>
+       }<br>
+<br>
+       /* Run capture session. */<br>
+       loop_ = new EventLoop();<br>
+       loop_->exec();<br>
+       delete loop_;<br>
+<br>
+       if (captureCount_ != captureLimit_)<br>
+               return { Results::Fail, "Got " + std::to_string(captureCount_) + " request, wanted " + std::to_string(captureLimit_) };<br>
+<br>
+       return { Results::Pass, "Balanced capture of " + std::to_string(numRequests) + " requests" };<br>
+}<br>
+<br>
+Results::Result SimpleCapture::stop()<br>
+{<br>
+       Stream *stream = config_->at(0).stream();<br>
+<br>
+       camera_->stop();<br>
+<br>
+       camera_->requestCompleted.disconnect(this, &SimpleCapture::requestComplete);<br>
+<br>
+       allocator_->free(stream);<br>
+<br>
+       return { Results::Pass, "Stopped camera" };<br>
+}<br>
+<br>
+int SimpleCapture::queueRequest(Request *request)<br>
+{<br>
+       queueCount_++;<br>
+       if (queueCount_ > captureLimit_)<br>
+               return 0;<br>
+<br>
+       return camera_->queueRequest(request);<br>
+}<br>
+<br>
+void SimpleCapture::requestComplete(Request *request)<br>
+{<br>
+       captureCount_++;<br>
+       if (captureCount_ >= captureLimit_) {<br>
+               loop_->exit(0);<br>
+               return;<br>
+       }<br>
+<br>
+       request->reuse(Request::ReuseBuffers);<br>
+       if (queueRequest(request))<br>
+               loop_->exit(-EINVAL);<br>
+}<br>
+<br>
+Results::Result testRequestBalance(std::shared_ptr<Camera> camera,<br>
+                                  unsigned int startCycles,<br>
+                                  unsigned int numRequests)<br>
+{<br>
+       SimpleCapture capture(camera);<br>
+       Results::Result ret;<br>
+<br>
+       ret = capture.configure(Viewfinder);<br>
+       if (ret.first != Results::Pass)<br>
+               return ret;<br>
+<br>
+       for (unsigned int starts = 0; starts < startCycles; starts++) {<br>
+               ret = capture.start();<br>
+               if (ret.first != Results::Pass)<br>
+                       return ret;<br>
+<br>
+               ret = capture.capture(numRequests);<br>
+               if (ret.first != Results::Pass) {<br>
+                       capture.stop();<br>
+                       return ret;<br>
+               }<br>
+<br>
+               ret = capture.stop();<br>
+               if (ret.first != Results::Pass)<br>
+                       return ret;<br>
+       }<br>
+<br>
+       return { Results::Pass, "Balanced capture of " + std::to_string(numRequests) + " requests with " + std::to_string(startCycles) + " start cycles" };<br>
+}<br>
+<br>
+Results testSingleStream(std::shared_ptr<Camera> camera)<br>
+{<br>
+       const std::vector<unsigned int> numRequests = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };<br>
+<br>
+       Results results(numRequests.size() * 2);<br>
+<br>
+       if (!camera)<br>
+               return results;<br>
+<br>
+       /*<br>
+        * Test single capture cycles<br>
+        *<br>
+        * Makes sure the camera completes the exact number of requests queued.<br>
+        * Example failure is a camera that needs N+M requests queued to<br>
+        * complete N requests to the application.<br>
+        */<br>
+       std::cout << "Test single capture cycles" << std::endl;<br>
+       for (unsigned int num : numRequests)<br>
+               results.add(testRequestBalance(camera, 1, num));<br>
+<br>
+       /*<br>
+        * Test multiple start/stop cycles<br>
+        *<br>
+        * Makes sure the camera supports multiple start/stop cycles.<br>
+        * Example failure is a camera that does not clean up correctly in its<br>
+        * error path but is only tested by single-capture applications.<br>
+        */<br>
+       std::cout << "Test multiple start/stop cycles" << std::endl;<br>
+       for (unsigned int num : numRequests)<br>
+               results.add(testRequestBalance(camera, 3, num));<br>
+<br>
+       return results;<br>
+}<br>
diff --git a/src/lc-compliance/tests.h b/src/lc-compliance/tests.h<br>
new file mode 100644<br>
index 0000000000000000..396605214e4b8980<br>
--- /dev/null<br>
+++ b/src/lc-compliance/tests.h<br>
@@ -0,0 +1,16 @@<br>
+/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
+/*<br>
+ * Copyright (C) 2020, Google Inc.<br>
+ *<br>
+ * tests.h - Test modules<br>
+ */<br>
+#ifndef __LC_COMPLIANCE_TESTS_H__<br>
+#define __LC_COMPLIANCE_TESTS_H__<br>
+<br>
+#include <libcamera/libcamera.h><br>
+<br>
+#include "results.h"<br>
+<br>
+Results testSingleStream(std::shared_ptr<libcamera::Camera> camera);<br>
+<br>
+#endif /* __LC_COMPLIANCE_TESTS_H__ */<br>
diff --git a/src/meson.build b/src/meson.build<br>
index 4b75f05878bcb702..a8e1af7adf2ca9c8 100644<br>
--- a/src/meson.build<br>
+++ b/src/meson.build<br>
@@ -18,6 +18,8 @@ subdir('android')<br>
 subdir('libcamera')<br>
 subdir('ipa')<br>
<br>
+subdir('lc-compliance')<br>
+<br>
 subdir('cam')<br>
 subdir('qcam')<br>
<br>
-- <br>
2.30.0<br>
<br>
_______________________________________________<br>
libcamera-devel mailing list<br>
<a href="mailto:libcamera-devel@lists.libcamera.org" target="_blank">libcamera-devel@lists.libcamera.org</a><br>
<a href="https://lists.libcamera.org/listinfo/libcamera-devel" rel="noreferrer" target="_blank">https://lists.libcamera.org/listinfo/libcamera-devel</a><br>
</blockquote></div></div>