From 84ad104499d9efc0253dae1a60ee070ed375ad95 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 20 Oct 2022 00:44:55 +0300 Subject: Move test applications to src/apps/ The cam and qcam test application share code, currently through a crude hack that references the cam source files directly from the qcam meson.build file. To prepare for the introduction of hosting that code in a static library, move all applications to src/apps/. Signed-off-by: Laurent Pinchart Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- src/apps/lc-compliance/capture_test.cpp | 128 ++++++++++++++++++++ src/apps/lc-compliance/environment.cpp | 22 ++++ src/apps/lc-compliance/environment.h | 27 +++++ src/apps/lc-compliance/main.cpp | 193 ++++++++++++++++++++++++++++++ src/apps/lc-compliance/meson.build | 31 +++++ src/apps/lc-compliance/simple_capture.cpp | 191 +++++++++++++++++++++++++++++ src/apps/lc-compliance/simple_capture.h | 65 ++++++++++ 7 files changed, 657 insertions(+) create mode 100644 src/apps/lc-compliance/capture_test.cpp create mode 100644 src/apps/lc-compliance/environment.cpp create mode 100644 src/apps/lc-compliance/environment.h create mode 100644 src/apps/lc-compliance/main.cpp create mode 100644 src/apps/lc-compliance/meson.build create mode 100644 src/apps/lc-compliance/simple_capture.cpp create mode 100644 src/apps/lc-compliance/simple_capture.h (limited to 'src/apps/lc-compliance') diff --git a/src/apps/lc-compliance/capture_test.cpp b/src/apps/lc-compliance/capture_test.cpp new file mode 100644 index 00000000..52578207 --- /dev/null +++ b/src/apps/lc-compliance/capture_test.cpp @@ -0,0 +1,128 @@ +/* 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 + +#include + +#include "environment.h" +#include "simple_capture.h" + +using namespace libcamera; + +const std::vector NUMREQUESTS = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; +const std::vector ROLES = { Raw, StillCapture, VideoRecording, Viewfinder }; + +class SingleStream : public testing::TestWithParam> +{ +public: + static std::string nameParameters(const testing::TestParamInfo &info); + +protected: + void SetUp() override; + void TearDown() override; + + std::shared_ptr 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 &info) +{ + std::map rolesMap = { { Raw, "Raw" }, + { StillCapture, "StillCapture" }, + { VideoRecording, "VideoRecording" }, + { 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 new file mode 100644 index 00000000..5eb3775f --- /dev/null +++ b/src/apps/lc-compliance/environment.cpp @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Collabora Ltd. + * + * environment.cpp - Common environment for tests + */ + +#include "environment.h" + +using namespace libcamera; + +Environment *Environment::get() +{ + static Environment instance; + return &instance; +} + +void Environment::setup(CameraManager *cm, std::string cameraId) +{ + cm_ = cm; + cameraId_ = cameraId; +} diff --git a/src/apps/lc-compliance/environment.h b/src/apps/lc-compliance/environment.h new file mode 100644 index 00000000..0debbcce --- /dev/null +++ b/src/apps/lc-compliance/environment.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Collabora Ltd. + * + * environment.h - Common environment for tests + */ + +#pragma once + +#include + +class Environment +{ +public: + static Environment *get(); + + void setup(libcamera::CameraManager *cm, std::string cameraId); + + const std::string &cameraId() const { return cameraId_; } + libcamera::CameraManager *cm() const { return cm_; } + +private: + Environment() = default; + + std::string cameraId_; + libcamera::CameraManager *cm_; +}; diff --git a/src/apps/lc-compliance/main.cpp b/src/apps/lc-compliance/main.cpp new file mode 100644 index 00000000..7eb52ae4 --- /dev/null +++ b/src/apps/lc-compliance/main.cpp @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * Copyright (C) 2021, Collabora Ltd. + * + * main.cpp - lc-compliance - The libcamera compliance tool + */ + +#include +#include +#include + +#include + +#include + +#include "environment.h" +#include "../cam/options.h" + +using namespace libcamera; + +enum { + OptCamera = 'c', + OptList = 'l', + OptFilter = 'f', + OptHelp = 'h', +}; + +/* + * Make asserts act like exceptions, otherwise they only fail (or skip) the + * current function. From gtest documentation: + * https://google.github.io/googletest/advanced.html#asserting-on-subroutines-with-an-exception + */ +class ThrowListener : public testing::EmptyTestEventListener +{ + void OnTestPartResult(const testing::TestPartResult &result) override + { + if (result.type() == testing::TestPartResult::kFatalFailure || + result.type() == testing::TestPartResult::kSkip) + throw testing::AssertionException(result); + } +}; + +static void listCameras(CameraManager *cm) +{ + for (const std::shared_ptr &cam : cm->cameras()) + std::cout << "- " << cam.get()->id() << std::endl; +} + +static int initCamera(CameraManager *cm, OptionsParser::Options options) +{ + std::shared_ptr camera; + + int ret = cm->start(); + if (ret) { + std::cout << "Failed to start camera manager: " + << strerror(-ret) << std::endl; + return ret; + } + + if (!options.isSet(OptCamera)) { + std::cout << "No camera specified, available cameras:" << std::endl; + listCameras(cm); + return -ENODEV; + } + + const std::string &cameraId = options[OptCamera]; + camera = cm->get(cameraId); + if (!camera) { + std::cout << "Camera " << cameraId << " not found, available cameras:" << std::endl; + listCameras(cm); + return -ENODEV; + } + + Environment::get()->setup(cm, cameraId); + + std::cout << "Using camera " << cameraId << std::endl; + + return 0; +} + +static int initGtestParameters(char *arg0, OptionsParser::Options options) +{ + const std::map gtestFlags = { { "list", "--gtest_list_tests" }, + { "filter", "--gtest_filter" } }; + + int argc = 0; + 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++; + + if (options.isSet(OptList)) { + argv[argc] = const_cast(gtestFlags.at("list").c_str()); + argc++; + } + + 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(options[OptFilter]); + + argv[argc] = const_cast(filterParam.c_str()); + argc++; + } + + argv[argc] = nullptr; + + ::testing::InitGoogleTest(&argc, argv); + + delete[] argv; + + return 0; +} + +static int initGtest(char *arg0, OptionsParser::Options options) +{ + int ret = initGtestParameters(arg0, options); + if (ret) + return ret; + + testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener); + + return 0; +} + +static int parseOptions(int argc, char **argv, OptionsParser::Options *options) +{ + OptionsParser parser; + parser.addOption(OptCamera, OptionString, + "Specify which camera to operate on, by id", "camera", + ArgumentRequired, "camera"); + parser.addOption(OptList, OptionNone, "List all tests and exit", "list"); + parser.addOption(OptFilter, OptionString, + "Specify which tests to run", "filter", + ArgumentRequired, "filter"); + parser.addOption(OptHelp, OptionNone, "Display this help message", + "help"); + + *options = parser.parse(argc, argv); + if (!options->valid()) + return -EINVAL; + + if (options->isSet(OptHelp)) { + parser.usage(); + std::cerr << "Further options from Googletest can be passed as environment variables" + << std::endl; + return -EINTR; + } + + return 0; +} + +int main(int argc, char **argv) +{ + OptionsParser::Options options; + int ret = parseOptions(argc, argv, &options); + if (ret == -EINTR) + return EXIT_SUCCESS; + if (ret < 0) + return EXIT_FAILURE; + + std::unique_ptr cm = std::make_unique(); + + /* No need to initialize the camera if we'll just list tests */ + if (!options.isSet(OptList)) { + ret = initCamera(cm.get(), options); + if (ret) + return ret; + } + + ret = initGtest(argv[0], options); + if (ret) + return ret; + + ret = RUN_ALL_TESTS(); + + if (!options.isSet(OptList)) + cm->stop(); + + return ret; +} diff --git a/src/apps/lc-compliance/meson.build b/src/apps/lc-compliance/meson.build new file mode 100644 index 00000000..8b57474b --- /dev/null +++ b/src/apps/lc-compliance/meson.build @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: CC0-1.0 + +libevent = dependency('libevent_pthreads', required : get_option('lc-compliance')) +libgtest = dependency('gtest', required : get_option('lc-compliance'), + fallback : ['gtest', 'gtest_dep']) + +if not (libevent.found() and libgtest.found()) + lc_compliance_enabled = false + subdir_done() +endif + +lc_compliance_enabled = true + +lc_compliance_sources = files([ + '../cam/event_loop.cpp', + '../cam/options.cpp', + 'environment.cpp', + 'main.cpp', + 'simple_capture.cpp', + 'capture_test.cpp', +]) + +lc_compliance = executable('lc-compliance', lc_compliance_sources, + cpp_args : [ '-fexceptions' ], + dependencies : [ + libatomic, + libcamera_public, + libevent, + libgtest, + ], + install : true) diff --git a/src/apps/lc-compliance/simple_capture.cpp b/src/apps/lc-compliance/simple_capture.cpp new file mode 100644 index 00000000..ab5cb35c --- /dev/null +++ b/src/apps/lc-compliance/simple_capture.cpp @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020-2021, Google Inc. + * + * simple_capture.cpp - Simple capture helper + */ + +#include + +#include "simple_capture.h" + +using namespace libcamera; + +SimpleCapture::SimpleCapture(std::shared_ptr camera) + : loop_(nullptr), camera_(camera), + allocator_(std::make_unique(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(); + allocator_->free(stream); +} + +/* SimpleCaptureBalanced */ + +SimpleCaptureBalanced::SimpleCaptureBalanced(std::shared_ptr camera) + : SimpleCapture(camera) +{ +} + +void SimpleCaptureBalanced::capture(unsigned int numRequests) +{ + start(); + + Stream *stream = config_->at(0).stream(); + const std::vector> &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. */ + std::vector> requests; + for (const std::unique_ptr &buffer : buffers) { + std::unique_ptr 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) + : SimpleCapture(camera) +{ +} + +void SimpleCaptureUnbalanced::capture(unsigned int numRequests) +{ + start(); + + Stream *stream = config_->at(0).stream(); + const std::vector> &buffers = allocator_->buffers(stream); + + captureCount_ = 0; + captureLimit_ = numRequests; + + /* Queue the recommended number of reqeuests. */ + std::vector> requests; + for (const std::unique_ptr &buffer : buffers) { + std::unique_ptr 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 new file mode 100644 index 00000000..9d31f7cb --- /dev/null +++ b/src/apps/lc-compliance/simple_capture.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020-2021, Google Inc. + * + * simple_capture.h - Simple capture helper + */ + +#pragma once + +#include + +#include + +#include "../cam/event_loop.h" + +class SimpleCapture +{ +public: + void configure(libcamera::StreamRole role); + +protected: + SimpleCapture(std::shared_ptr camera); + virtual ~SimpleCapture(); + + void start(); + void stop(); + + virtual void requestComplete(libcamera::Request *request) = 0; + + EventLoop *loop_; + + std::shared_ptr camera_; + std::unique_ptr allocator_; + std::unique_ptr config_; +}; + +class SimpleCaptureBalanced : public SimpleCapture +{ +public: + SimpleCaptureBalanced(std::shared_ptr 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 camera); + + void capture(unsigned int numRequests); + +private: + void requestComplete(libcamera::Request *request) override; + + unsigned int captureCount_; + unsigned int captureLimit_; +}; -- cgit v1.2.1