summaryrefslogtreecommitdiff
path: root/src/apps/lc-compliance
diff options
context:
space:
mode:
Diffstat (limited to 'src/apps/lc-compliance')
-rw-r--r--src/apps/lc-compliance/capture_test.cpp135
-rw-r--r--src/apps/lc-compliance/environment.cpp2
-rw-r--r--src/apps/lc-compliance/environment.h4
-rw-r--r--src/apps/lc-compliance/helpers/capture.cpp176
-rw-r--r--src/apps/lc-compliance/helpers/capture.h45
-rw-r--r--src/apps/lc-compliance/main.cpp44
-rw-r--r--src/apps/lc-compliance/meson.build17
-rw-r--r--src/apps/lc-compliance/simple_capture.cpp190
-rw-r--r--src/apps/lc-compliance/simple_capture.h66
-rw-r--r--src/apps/lc-compliance/test_base.cpp28
-rw-r--r--src/apps/lc-compliance/test_base.h24
-rw-r--r--src/apps/lc-compliance/tests/capture_test.cpp145
12 files changed, 446 insertions, 430 deletions
diff --git a/src/apps/lc-compliance/capture_test.cpp b/src/apps/lc-compliance/capture_test.cpp
deleted file mode 100644
index 1dcfcf92..00000000
--- a/src/apps/lc-compliance/capture_test.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2020, Google Inc.
- * Copyright (C) 2021, Collabora Ltd.
- *
- * capture_test.cpp - Test camera capture
- */
-
-#include <iostream>
-
-#include <gtest/gtest.h>
-
-#include "environment.h"
-#include "simple_capture.h"
-
-using namespace libcamera;
-
-const std::vector<int> NUMREQUESTS = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
-const std::vector<StreamRole> ROLES = {
- StreamRole::Raw,
- StreamRole::StillCapture,
- StreamRole::VideoRecording,
- StreamRole::Viewfinder
-};
-
-class SingleStream : public testing::TestWithParam<std::tuple<StreamRole, int>>
-{
-public:
- static std::string nameParameters(const testing::TestParamInfo<SingleStream::ParamType> &info);
-
-protected:
- void SetUp() override;
- void TearDown() override;
-
- std::shared_ptr<Camera> camera_;
-};
-
-/*
- * We use gtest's SetUp() and TearDown() instead of constructor and destructor
- * in order to be able to assert on them.
- */
-void SingleStream::SetUp()
-{
- Environment *env = Environment::get();
-
- camera_ = env->cm()->get(env->cameraId());
-
- ASSERT_EQ(camera_->acquire(), 0);
-}
-
-void SingleStream::TearDown()
-{
- if (!camera_)
- return;
-
- camera_->release();
- camera_.reset();
-}
-
-std::string SingleStream::nameParameters(const testing::TestParamInfo<SingleStream::ParamType> &info)
-{
- std::map<StreamRole, std::string> rolesMap = {
- { StreamRole::Raw, "Raw" },
- { StreamRole::StillCapture, "StillCapture" },
- { StreamRole::VideoRecording, "VideoRecording" },
- { StreamRole::Viewfinder, "Viewfinder" }
- };
-
- std::string roleName = rolesMap[std::get<0>(info.param)];
- std::string numRequestsName = std::to_string(std::get<1>(info.param));
-
- return roleName + "_" + numRequestsName;
-}
-
-/*
- * Test single capture cycles
- *
- * Makes sure the camera completes the exact number of requests queued. Example
- * failure is a camera that completes less requests than the number of requests
- * queued.
- */
-TEST_P(SingleStream, Capture)
-{
- auto [role, numRequests] = GetParam();
-
- SimpleCaptureBalanced capture(camera_);
-
- capture.configure(role);
-
- capture.capture(numRequests);
-}
-
-/*
- * Test multiple start/stop cycles
- *
- * Makes sure the camera supports multiple start/stop cycles. Example failure is
- * a camera that does not clean up correctly in its error path but is only
- * tested by single-capture applications.
- */
-TEST_P(SingleStream, CaptureStartStop)
-{
- auto [role, numRequests] = GetParam();
- unsigned int numRepeats = 3;
-
- SimpleCaptureBalanced capture(camera_);
-
- capture.configure(role);
-
- for (unsigned int starts = 0; starts < numRepeats; starts++)
- capture.capture(numRequests);
-}
-
-/*
- * Test unbalanced stop
- *
- * Makes sure the camera supports a stop with requests queued. Example failure
- * is a camera that does not handle cancelation of buffers coming back from the
- * video device while stopping.
- */
-TEST_P(SingleStream, UnbalancedStop)
-{
- auto [role, numRequests] = GetParam();
-
- SimpleCaptureUnbalanced capture(camera_);
-
- capture.configure(role);
-
- capture.capture(numRequests);
-}
-
-INSTANTIATE_TEST_SUITE_P(CaptureTests,
- SingleStream,
- testing::Combine(testing::ValuesIn(ROLES),
- testing::ValuesIn(NUMREQUESTS)),
- SingleStream::nameParameters);
diff --git a/src/apps/lc-compliance/environment.cpp b/src/apps/lc-compliance/environment.cpp
index 5eb3775f..987264f1 100644
--- a/src/apps/lc-compliance/environment.cpp
+++ b/src/apps/lc-compliance/environment.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2021, Collabora Ltd.
*
- * environment.cpp - Common environment for tests
+ * Common environment for tests
*/
#include "environment.h"
diff --git a/src/apps/lc-compliance/environment.h b/src/apps/lc-compliance/environment.h
index 0debbcce..834c722e 100644
--- a/src/apps/lc-compliance/environment.h
+++ b/src/apps/lc-compliance/environment.h
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2021, Collabora Ltd.
*
- * environment.h - Common environment for tests
+ * Common environment for tests
*/
#pragma once
@@ -23,5 +23,5 @@ private:
Environment() = default;
std::string cameraId_;
- libcamera::CameraManager *cm_;
+ libcamera::CameraManager *cm_ = nullptr;
};
diff --git a/src/apps/lc-compliance/helpers/capture.cpp b/src/apps/lc-compliance/helpers/capture.cpp
new file mode 100644
index 00000000..2a3fa3b6
--- /dev/null
+++ b/src/apps/lc-compliance/helpers/capture.cpp
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020-2021, Google Inc.
+ *
+ * Simple capture helper
+ */
+
+#include "capture.h"
+
+#include <assert.h>
+
+#include <gtest/gtest.h>
+
+using namespace libcamera;
+
+Capture::Capture(std::shared_ptr<Camera> camera)
+ : camera_(std::move(camera)), allocator_(camera_)
+{
+}
+
+Capture::~Capture()
+{
+ stop();
+}
+
+void Capture::configure(libcamera::Span<const libcamera::StreamRole> roles)
+{
+ assert(!roles.empty());
+
+ config_ = camera_->generateConfiguration(roles);
+ if (!config_)
+ GTEST_SKIP() << "Roles not supported by camera";
+
+ ASSERT_EQ(config_->size(), roles.size()) << "Unexpected number of streams in configuration";
+
+ /*
+ * Set the buffers count to the largest value across all streams.
+ * \todo: Should all streams from a Camera have the same buffer count ?
+ */
+ auto largest =
+ std::max_element(config_->begin(), config_->end(),
+ [](const StreamConfiguration &l, const StreamConfiguration &r)
+ { return l.bufferCount < r.bufferCount; });
+
+ assert(largest != config_->end());
+
+ for (auto &cfg : *config_)
+ cfg.bufferCount = largest->bufferCount;
+
+ if (config_->validate() != CameraConfiguration::Valid) {
+ config_.reset();
+ FAIL() << "Configuration not valid";
+ }
+
+ if (camera_->configure(config_.get())) {
+ config_.reset();
+ FAIL() << "Failed to configure camera";
+ }
+}
+
+void Capture::run(unsigned int captureLimit, std::optional<unsigned int> queueLimit)
+{
+ assert(!queueLimit || captureLimit <= *queueLimit);
+
+ captureLimit_ = captureLimit;
+ queueLimit_ = queueLimit;
+
+ captureCount_ = queueCount_ = 0;
+
+ EventLoop loop;
+ loop_ = &loop;
+
+ start();
+
+ for (const auto &request : requests_)
+ queueRequest(request.get());
+
+ EXPECT_EQ(loop_->exec(), 0);
+
+ stop();
+
+ EXPECT_LE(captureLimit_, captureCount_);
+ EXPECT_LE(captureCount_, queueCount_);
+ EXPECT_TRUE(!queueLimit_ || queueCount_ <= *queueLimit_);
+}
+
+int Capture::queueRequest(libcamera::Request *request)
+{
+ if (queueLimit_ && queueCount_ >= *queueLimit_)
+ return 0;
+
+ int ret = camera_->queueRequest(request);
+ if (ret < 0)
+ return ret;
+
+ queueCount_ += 1;
+ return 0;
+}
+
+void Capture::requestComplete(Request *request)
+{
+ captureCount_++;
+ if (captureCount_ >= captureLimit_) {
+ loop_->exit(0);
+ return;
+ }
+
+ EXPECT_EQ(request->status(), Request::Status::RequestComplete)
+ << "Request didn't complete successfully";
+
+ request->reuse(Request::ReuseBuffers);
+ if (queueRequest(request))
+ loop_->exit(-EINVAL);
+}
+
+void Capture::start()
+{
+ assert(config_);
+ assert(!config_->empty());
+ assert(!allocator_.allocated());
+ assert(requests_.empty());
+
+ const auto bufferCount = config_->at(0).bufferCount;
+
+ /* No point in testing less requests then the camera depth. */
+ if (queueLimit_ && *queueLimit_ < bufferCount) {
+ GTEST_SKIP() << "Camera needs " << bufferCount
+ << " requests, can't test only " << *queueLimit_;
+ }
+
+ for (std::size_t i = 0; i < bufferCount; i++) {
+ std::unique_ptr<Request> request = camera_->createRequest();
+ ASSERT_TRUE(request) << "Can't create request";
+ requests_.push_back(std::move(request));
+ }
+
+ for (const auto &cfg : *config_) {
+ Stream *stream = cfg.stream();
+
+ int count = allocator_.allocate(stream);
+ ASSERT_GE(count, 0) << "Failed to allocate buffers";
+
+ const auto &buffers = allocator_.buffers(stream);
+ ASSERT_EQ(buffers.size(), bufferCount) << "Mismatching buffer count";
+
+ for (std::size_t i = 0; i < bufferCount; i++) {
+ ASSERT_EQ(requests_[i]->addBuffer(stream, buffers[i].get()), 0)
+ << "Failed to add buffer to request";
+ }
+ }
+
+ ASSERT_TRUE(allocator_.allocated());
+
+ camera_->requestCompleted.connect(this, &Capture::requestComplete);
+
+ ASSERT_EQ(camera_->start(), 0) << "Failed to start camera";
+}
+
+void Capture::stop()
+{
+ if (!config_ || !allocator_.allocated())
+ return;
+
+ camera_->stop();
+
+ camera_->requestCompleted.disconnect(this);
+
+ requests_.clear();
+
+ for (const auto &cfg : *config_) {
+ EXPECT_EQ(allocator_.free(cfg.stream()), 0)
+ << "Failed to free buffers associated with stream";
+ }
+
+ EXPECT_FALSE(allocator_.allocated());
+}
diff --git a/src/apps/lc-compliance/helpers/capture.h b/src/apps/lc-compliance/helpers/capture.h
new file mode 100644
index 00000000..ea01c11c
--- /dev/null
+++ b/src/apps/lc-compliance/helpers/capture.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020-2021, Google Inc.
+ *
+ * Simple capture helper
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+
+#include <libcamera/libcamera.h>
+
+#include "../common/event_loop.h"
+
+class Capture
+{
+public:
+ Capture(std::shared_ptr<libcamera::Camera> camera);
+ ~Capture();
+
+ void configure(libcamera::Span<const libcamera::StreamRole> roles);
+ void run(unsigned int captureLimit, std::optional<unsigned int> queueLimit = {});
+
+private:
+ LIBCAMERA_DISABLE_COPY_AND_MOVE(Capture)
+
+ void start();
+ void stop();
+
+ int queueRequest(libcamera::Request *request);
+ void requestComplete(libcamera::Request *request);
+
+ std::shared_ptr<libcamera::Camera> camera_;
+ libcamera::FrameBufferAllocator allocator_;
+ std::unique_ptr<libcamera::CameraConfiguration> config_;
+ std::vector<std::unique_ptr<libcamera::Request>> requests_;
+
+ EventLoop *loop_ = nullptr;
+ unsigned int captureLimit_ = 0;
+ std::optional<unsigned int> queueLimit_;
+ unsigned int captureCount_ = 0;
+ unsigned int queueCount_ = 0;
+};
diff --git a/src/apps/lc-compliance/main.cpp b/src/apps/lc-compliance/main.cpp
index 74e0d4df..e9f0ffbb 100644
--- a/src/apps/lc-compliance/main.cpp
+++ b/src/apps/lc-compliance/main.cpp
@@ -3,7 +3,7 @@
* Copyright (C) 2020, Google Inc.
* Copyright (C) 2021, Collabora Ltd.
*
- * main.cpp - lc-compliance - The libcamera compliance tool
+ * lc-compliance - The libcamera compliance tool
*/
#include <iomanip>
@@ -45,13 +45,11 @@ class ThrowListener : public testing::EmptyTestEventListener
static void listCameras(CameraManager *cm)
{
for (const std::shared_ptr<Camera> &cam : cm->cameras())
- std::cout << "- " << cam.get()->id() << std::endl;
+ std::cout << "- " << cam->id() << std::endl;
}
static int initCamera(CameraManager *cm, OptionsParser::Options options)
{
- std::shared_ptr<Camera> camera;
-
int ret = cm->start();
if (ret) {
std::cout << "Failed to start camera manager: "
@@ -66,7 +64,7 @@ static int initCamera(CameraManager *cm, OptionsParser::Options options)
}
const std::string &cameraId = options[OptCamera];
- camera = cm->get(cameraId);
+ std::shared_ptr<Camera> camera = cm->get(cameraId);
if (!camera) {
std::cout << "Camera " << cameraId << " not found, available cameras:" << std::endl;
listCameras(cm);
@@ -82,45 +80,27 @@ static int initCamera(CameraManager *cm, OptionsParser::Options options)
static int initGtestParameters(char *arg0, OptionsParser::Options options)
{
- const std::map<std::string, std::string> gtestFlags = { { "list", "--gtest_list_tests" },
- { "filter", "--gtest_filter" } };
-
- int argc = 0;
+ std::vector<const char *> argv;
std::string filterParam;
- /*
- * +2 to have space for both the 0th argument that is needed but not
- * used and the null at the end.
- */
- char **argv = new char *[(gtestFlags.size() + 2)];
- if (!argv)
- return -ENOMEM;
-
- argv[0] = arg0;
- argc++;
+ argv.push_back(arg0);
- if (options.isSet(OptList)) {
- argv[argc] = const_cast<char *>(gtestFlags.at("list").c_str());
- argc++;
- }
+ if (options.isSet(OptList))
+ argv.push_back("--gtest_list_tests");
if (options.isSet(OptFilter)) {
/*
* The filter flag needs to be passed as a single parameter, in
* the format --gtest_filter=filterStr
*/
- filterParam = gtestFlags.at("filter") + "=" +
- static_cast<const std::string &>(options[OptFilter]);
-
- argv[argc] = const_cast<char *>(filterParam.c_str());
- argc++;
+ filterParam = "--gtest_filter=" + options[OptFilter].toString();
+ argv.push_back(filterParam.c_str());
}
- argv[argc] = nullptr;
-
- ::testing::InitGoogleTest(&argc, argv);
+ argv.push_back(nullptr);
- delete[] argv;
+ int argc = argv.size();
+ ::testing::InitGoogleTest(&argc, const_cast<char **>(argv.data()));
return 0;
}
diff --git a/src/apps/lc-compliance/meson.build b/src/apps/lc-compliance/meson.build
index 51d9075a..80b9a160 100644
--- a/src/apps/lc-compliance/meson.build
+++ b/src/apps/lc-compliance/meson.build
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: CC0-1.0
-libgtest = dependency('gtest', required : get_option('lc-compliance'),
+libgtest = dependency('gtest', version : '>=1.10.0',
+ required : get_option('lc-compliance'),
fallback : ['gtest', 'gtest_dep'])
if opt_lc_compliance.disabled() or not libevent.found() or not libgtest.found()
@@ -12,9 +13,15 @@ lc_compliance_enabled = true
lc_compliance_sources = files([
'environment.cpp',
+ 'helpers/capture.cpp',
'main.cpp',
- 'simple_capture.cpp',
- 'capture_test.cpp',
+ 'test_base.cpp',
+ 'tests/capture_test.cpp',
+])
+
+lc_compliance_includes = ([
+ include_directories('.'),
+ include_directories('helpers/')
])
lc_compliance = executable('lc-compliance', lc_compliance_sources,
@@ -26,4 +33,6 @@ lc_compliance = executable('lc-compliance', lc_compliance_sources,
libevent,
libgtest,
],
- install : true)
+ include_directories : lc_compliance_includes,
+ install : true,
+ install_tag : 'bin-devel')
diff --git a/src/apps/lc-compliance/simple_capture.cpp b/src/apps/lc-compliance/simple_capture.cpp
deleted file mode 100644
index cf4d7cf3..00000000
--- a/src/apps/lc-compliance/simple_capture.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2020-2021, Google Inc.
- *
- * simple_capture.cpp - Simple capture helper
- */
-
-#include <gtest/gtest.h>
-
-#include "simple_capture.h"
-
-using namespace libcamera;
-
-SimpleCapture::SimpleCapture(std::shared_ptr<Camera> camera)
- : loop_(nullptr), camera_(camera),
- allocator_(std::make_unique<FrameBufferAllocator>(camera))
-{
-}
-
-SimpleCapture::~SimpleCapture()
-{
- stop();
-}
-
-void SimpleCapture::configure(StreamRole role)
-{
- config_ = camera_->generateConfiguration({ role });
-
- if (!config_) {
- std::cout << "Role not supported by camera" << std::endl;
- GTEST_SKIP();
- }
-
- if (config_->validate() != CameraConfiguration::Valid) {
- config_.reset();
- FAIL() << "Configuration not valid";
- }
-
- if (camera_->configure(config_.get())) {
- config_.reset();
- FAIL() << "Failed to configure camera";
- }
-}
-
-void SimpleCapture::start()
-{
- Stream *stream = config_->at(0).stream();
- int count = allocator_->allocate(stream);
-
- ASSERT_GE(count, 0) << "Failed to allocate buffers";
- EXPECT_EQ(count, config_->at(0).bufferCount) << "Allocated less buffers than expected";
-
- camera_->requestCompleted.connect(this, &SimpleCapture::requestComplete);
-
- ASSERT_EQ(camera_->start(), 0) << "Failed to start camera";
-}
-
-void SimpleCapture::stop()
-{
- if (!config_ || !allocator_->allocated())
- return;
-
- camera_->stop();
-
- camera_->requestCompleted.disconnect(this);
-
- Stream *stream = config_->at(0).stream();
- requests_.clear();
- allocator_->free(stream);
-}
-
-/* SimpleCaptureBalanced */
-
-SimpleCaptureBalanced::SimpleCaptureBalanced(std::shared_ptr<Camera> camera)
- : SimpleCapture(camera)
-{
-}
-
-void SimpleCaptureBalanced::capture(unsigned int numRequests)
-{
- start();
-
- Stream *stream = config_->at(0).stream();
- const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream);
-
- /* No point in testing less requests then the camera depth. */
- if (buffers.size() > numRequests) {
- std::cout << "Camera needs " + std::to_string(buffers.size())
- + " requests, can't test only "
- + std::to_string(numRequests) << std::endl;
- GTEST_SKIP();
- }
-
- queueCount_ = 0;
- captureCount_ = 0;
- captureLimit_ = numRequests;
-
- /* Queue the recommended number of reqeuests. */
- for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
- std::unique_ptr<Request> request = camera_->createRequest();
- ASSERT_TRUE(request) << "Can't create request";
-
- ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request";
-
- ASSERT_EQ(queueRequest(request.get()), 0) << "Failed to queue request";
-
- requests_.push_back(std::move(request));
- }
-
- /* Run capture session. */
- loop_ = new EventLoop();
- loop_->exec();
- stop();
- delete loop_;
-
- ASSERT_EQ(captureCount_, captureLimit_);
-}
-
-int SimpleCaptureBalanced::queueRequest(Request *request)
-{
- queueCount_++;
- if (queueCount_ > captureLimit_)
- return 0;
-
- return camera_->queueRequest(request);
-}
-
-void SimpleCaptureBalanced::requestComplete(Request *request)
-{
- captureCount_++;
- if (captureCount_ >= captureLimit_) {
- loop_->exit(0);
- return;
- }
-
- request->reuse(Request::ReuseBuffers);
- if (queueRequest(request))
- loop_->exit(-EINVAL);
-}
-
-/* SimpleCaptureUnbalanced */
-
-SimpleCaptureUnbalanced::SimpleCaptureUnbalanced(std::shared_ptr<Camera> camera)
- : SimpleCapture(camera)
-{
-}
-
-void SimpleCaptureUnbalanced::capture(unsigned int numRequests)
-{
- start();
-
- Stream *stream = config_->at(0).stream();
- const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream);
-
- captureCount_ = 0;
- captureLimit_ = numRequests;
-
- /* Queue the recommended number of reqeuests. */
- for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
- std::unique_ptr<Request> request = camera_->createRequest();
- ASSERT_TRUE(request) << "Can't create request";
-
- ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request";
-
- ASSERT_EQ(camera_->queueRequest(request.get()), 0) << "Failed to queue request";
-
- requests_.push_back(std::move(request));
- }
-
- /* Run capture session. */
- loop_ = new EventLoop();
- int status = loop_->exec();
- stop();
- delete loop_;
-
- ASSERT_EQ(status, 0);
-}
-
-void SimpleCaptureUnbalanced::requestComplete(Request *request)
-{
- captureCount_++;
- if (captureCount_ >= captureLimit_) {
- loop_->exit(0);
- return;
- }
-
- request->reuse(Request::ReuseBuffers);
- if (camera_->queueRequest(request))
- loop_->exit(-EINVAL);
-}
diff --git a/src/apps/lc-compliance/simple_capture.h b/src/apps/lc-compliance/simple_capture.h
deleted file mode 100644
index 2911d601..00000000
--- a/src/apps/lc-compliance/simple_capture.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2020-2021, Google Inc.
- *
- * simple_capture.h - Simple capture helper
- */
-
-#pragma once
-
-#include <memory>
-
-#include <libcamera/libcamera.h>
-
-#include "../common/event_loop.h"
-
-class SimpleCapture
-{
-public:
- void configure(libcamera::StreamRole role);
-
-protected:
- SimpleCapture(std::shared_ptr<libcamera::Camera> camera);
- virtual ~SimpleCapture();
-
- void start();
- void stop();
-
- virtual void requestComplete(libcamera::Request *request) = 0;
-
- EventLoop *loop_;
-
- std::shared_ptr<libcamera::Camera> camera_;
- std::unique_ptr<libcamera::FrameBufferAllocator> allocator_;
- std::unique_ptr<libcamera::CameraConfiguration> config_;
- std::vector<std::unique_ptr<libcamera::Request>> requests_;
-};
-
-class SimpleCaptureBalanced : public SimpleCapture
-{
-public:
- SimpleCaptureBalanced(std::shared_ptr<libcamera::Camera> camera);
-
- void capture(unsigned int numRequests);
-
-private:
- int queueRequest(libcamera::Request *request);
- void requestComplete(libcamera::Request *request) override;
-
- unsigned int queueCount_;
- unsigned int captureCount_;
- unsigned int captureLimit_;
-};
-
-class SimpleCaptureUnbalanced : public SimpleCapture
-{
-public:
- SimpleCaptureUnbalanced(std::shared_ptr<libcamera::Camera> camera);
-
- void capture(unsigned int numRequests);
-
-private:
- void requestComplete(libcamera::Request *request) override;
-
- unsigned int captureCount_;
- unsigned int captureLimit_;
-};
diff --git a/src/apps/lc-compliance/test_base.cpp b/src/apps/lc-compliance/test_base.cpp
new file mode 100644
index 00000000..c9957b9e
--- /dev/null
+++ b/src/apps/lc-compliance/test_base.cpp
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Collabora Ltd.
+ *
+ * test_base.cpp - Base definitions for tests
+ */
+
+#include "test_base.h"
+
+#include "environment.h"
+
+void CameraHolder::acquireCamera()
+{
+ Environment *env = Environment::get();
+
+ camera_ = env->cm()->get(env->cameraId());
+
+ ASSERT_EQ(camera_->acquire(), 0);
+}
+
+void CameraHolder::releaseCamera()
+{
+ if (!camera_)
+ return;
+
+ camera_->release();
+ camera_.reset();
+}
diff --git a/src/apps/lc-compliance/test_base.h b/src/apps/lc-compliance/test_base.h
new file mode 100644
index 00000000..52347749
--- /dev/null
+++ b/src/apps/lc-compliance/test_base.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Collabora Ltd.
+ *
+ * test_base.h - Base definitions for tests
+ */
+
+#ifndef __LC_COMPLIANCE_TEST_BASE_H__
+#define __LC_COMPLIANCE_TEST_BASE_H__
+
+#include <libcamera/libcamera.h>
+
+#include <gtest/gtest.h>
+
+class CameraHolder
+{
+protected:
+ void acquireCamera();
+ void releaseCamera();
+
+ std::shared_ptr<libcamera::Camera> camera_;
+};
+
+#endif /* __LC_COMPLIANCE_TEST_BASE_H__ */
diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp
new file mode 100644
index 00000000..29d8b7f8
--- /dev/null
+++ b/src/apps/lc-compliance/tests/capture_test.cpp
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ * Copyright (C) 2021, Collabora Ltd.
+ *
+ * Test camera capture
+ */
+
+#include "capture.h"
+
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "test_base.h"
+
+namespace {
+
+using namespace libcamera;
+
+class SimpleCapture : public testing::TestWithParam<std::tuple<std::vector<StreamRole>, int>>, public CameraHolder
+{
+public:
+ static std::string nameParameters(const testing::TestParamInfo<SimpleCapture::ParamType> &info);
+
+protected:
+ void SetUp() override;
+ void TearDown() override;
+};
+
+/*
+ * We use gtest's SetUp() and TearDown() instead of constructor and destructor
+ * in order to be able to assert on them.
+ */
+void SimpleCapture::SetUp()
+{
+ acquireCamera();
+}
+
+void SimpleCapture::TearDown()
+{
+ releaseCamera();
+}
+
+std::string SimpleCapture::nameParameters(const testing::TestParamInfo<SimpleCapture::ParamType> &info)
+{
+ const auto &[roles, numRequests] = info.param;
+ std::ostringstream ss;
+
+ for (StreamRole r : roles)
+ ss << r << '_';
+
+ ss << '_' << numRequests;
+
+ return ss.str();
+}
+
+/*
+ * Test single capture cycles
+ *
+ * Makes sure the camera completes the exact number of requests queued. Example
+ * failure is a camera that completes less requests than the number of requests
+ * queued.
+ */
+TEST_P(SimpleCapture, Capture)
+{
+ const auto &[roles, numRequests] = GetParam();
+
+ Capture capture(camera_);
+
+ capture.configure(roles);
+
+ capture.run(numRequests, numRequests);
+}
+
+/*
+ * Test multiple start/stop cycles
+ *
+ * Makes sure the camera supports multiple start/stop cycles. Example failure is
+ * a camera that does not clean up correctly in its error path but is only
+ * tested by single-capture applications.
+ */
+TEST_P(SimpleCapture, CaptureStartStop)
+{
+ const auto &[roles, numRequests] = GetParam();
+ unsigned int numRepeats = 3;
+
+ Capture capture(camera_);
+
+ capture.configure(roles);
+
+ for (unsigned int starts = 0; starts < numRepeats; starts++)
+ capture.run(numRequests, numRequests);
+}
+
+/*
+ * Test unbalanced stop
+ *
+ * Makes sure the camera supports a stop with requests queued. Example failure
+ * is a camera that does not handle cancelation of buffers coming back from the
+ * video device while stopping.
+ */
+TEST_P(SimpleCapture, UnbalancedStop)
+{
+ const auto &[roles, numRequests] = GetParam();
+
+ Capture capture(camera_);
+
+ capture.configure(roles);
+
+ capture.run(numRequests);
+}
+
+const int NUMREQUESTS[] = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
+
+const std::vector<StreamRole> SINGLEROLES[] = {
+ { StreamRole::Raw, },
+ { StreamRole::StillCapture, },
+ { StreamRole::VideoRecording, },
+ { StreamRole::Viewfinder, },
+};
+
+const std::vector<StreamRole> MULTIROLES[] = {
+ { StreamRole::Raw, StreamRole::StillCapture },
+ { StreamRole::Raw, StreamRole::VideoRecording },
+ { StreamRole::StillCapture, StreamRole::VideoRecording },
+ { StreamRole::VideoRecording, StreamRole::VideoRecording },
+};
+
+INSTANTIATE_TEST_SUITE_P(SingleStream,
+ SimpleCapture,
+ testing::Combine(testing::ValuesIn(SINGLEROLES),
+ testing::ValuesIn(NUMREQUESTS)),
+ SimpleCapture::nameParameters);
+
+INSTANTIATE_TEST_SUITE_P(MultiStream,
+ SimpleCapture,
+ testing::Combine(testing::ValuesIn(MULTIROLES),
+ testing::ValuesIn(NUMREQUESTS)),
+ SimpleCapture::nameParameters);
+
+} /* namespace */