An attempt at getting image data back

This commit is contained in:
2024-07-14 00:27:33 +02:00
parent e026bc93f7
commit 6452d2e774
1314 changed files with 218350 additions and 38 deletions

View File

@@ -0,0 +1,211 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Sebastian Fricke
*
* BayerFormat class tests
*/
#include <iostream>
#include <libcamera/transform.h>
#include "libcamera/internal/bayer_format.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class BayerFormatTest : public Test
{
protected:
int run()
{
/* An empty Bayer format has to be invalid. */
BayerFormat bayerFmt;
if (bayerFmt.isValid()) {
cerr << "An empty BayerFormat has to be invalid."
<< endl;
return TestFail;
}
/* A correct Bayer format has to be valid. */
bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
if (!bayerFmt.isValid()) {
cerr << "A correct BayerFormat has to be valid."
<< endl;
return TestFail;
}
/*
* Two bayer formats created with the same order and bit depth
* have to be equal.
*/
bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
BayerFormat bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 8,
BayerFormat::Packing::None);
if (bayerFmt != bayerFmtExpect) {
cerr << "Comparison of identical formats failed."
<< endl;
return TestFail;
}
/*
* Two Bayer formats created with the same order but with a
* different bitDepth are not equal.
*/
bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 12,
BayerFormat::Packing::None);
if (bayerFmt == bayerFmtExpect) {
cerr << "Comparison of different formats failed."
<< endl;
return TestFail;
}
/*
* Create a Bayer format with a V4L2PixelFormat and check if we
* get the same format after converting back to the V4L2 Format.
*/
V4L2PixelFormat v4l2FmtExpect = V4L2PixelFormat(
V4L2_PIX_FMT_SBGGR8);
bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2FmtExpect);
V4L2PixelFormat v4l2Fmt = bayerFmt.toV4L2PixelFormat();
if (v4l2Fmt != v4l2FmtExpect) {
cerr << "Expected: '" << v4l2FmtExpect
<< "' got: '" << v4l2Fmt << "'" << endl;
return TestFail;
}
/*
* Use an empty Bayer format and verify that no matching
* V4L2PixelFormat is found.
*/
v4l2FmtExpect = V4L2PixelFormat();
bayerFmt = BayerFormat();
v4l2Fmt = bayerFmt.toV4L2PixelFormat();
if (v4l2Fmt != v4l2FmtExpect) {
cerr << "Expected: empty V4L2PixelFormat got: '"
<< v4l2Fmt << "'" << endl;
return TestFail;
}
/*
* Check if we get the expected Bayer format BGGR8
* when we convert the V4L2PixelFormat (V4L2_PIX_FMT_SBGGR8)
* to a Bayer format.
*/
bayerFmtExpect = BayerFormat(BayerFormat::BGGR, 8,
BayerFormat::Packing::None);
v4l2Fmt = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8);
bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2Fmt);
if (bayerFmt != bayerFmtExpect) {
cerr << "Expected BayerFormat '"
<< bayerFmtExpect << "', got: '"
<< bayerFmt << "'" << endl;
return TestFail;
}
/*
* Confirm that a V4L2PixelFormat that is not found in
* the conversion table, doesn't yield a Bayer format.
*/
V4L2PixelFormat v4l2FmtUnknown = V4L2PixelFormat(
V4L2_PIX_FMT_BGRA444);
bayerFmt = BayerFormat::fromV4L2PixelFormat(v4l2FmtUnknown);
if (bayerFmt.isValid()) {
cerr << "Expected empty BayerFormat got: '"
<< bayerFmt << "'" << endl;
return TestFail;
}
/*
* Test if a valid Bayer format can be converted to a
* string representation.
*/
bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
if (bayerFmt.toString() != "BGGR-8") {
cerr << "String representation != 'BGGR-8' (got: '"
<< bayerFmt.toString() << "' ) " << endl;
return TestFail;
}
/*
* Determine if an empty Bayer format results in no
* string representation.
*/
bayerFmt = BayerFormat();
if (bayerFmt.toString() != "INVALID") {
cerr << "String representation != 'INVALID' (got: '"
<< bayerFmt.toString() << "' ) " << endl;
return TestFail;
}
/*
* Perform a horizontal Flip and make sure that the
* order is adjusted accordingly.
*/
bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
bayerFmtExpect = BayerFormat(BayerFormat::GBRG, 8,
BayerFormat::Packing::None);
BayerFormat hFlipFmt = bayerFmt.transform(Transform::HFlip);
if (hFlipFmt != bayerFmtExpect) {
cerr << "Horizontal flip of 'BGGR-8' should result in '"
<< bayerFmtExpect << "', got: '"
<< hFlipFmt << "'" << endl;
return TestFail;
}
/*
* Perform a vertical Flip and make sure that
* the order is adjusted accordingly.
*/
bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
bayerFmtExpect = BayerFormat(BayerFormat::GRBG, 8,
BayerFormat::Packing::None);
BayerFormat vFlipFmt = bayerFmt.transform(Transform::VFlip);
if (vFlipFmt != bayerFmtExpect) {
cerr << "Vertical flip of 'BGGR-8' should result in '"
<< bayerFmtExpect << "', got: '"
<< vFlipFmt << "'" << endl;
return TestFail;
}
/*
* Perform a transposition on a pixel order with both green
* pixels on the bottom left to top right diagonal and make
* sure, that it doesn't change.
*/
bayerFmt = BayerFormat(BayerFormat::BGGR, 8, BayerFormat::Packing::None);
BayerFormat transposeFmt = bayerFmt.transform(
Transform::Transpose);
if (transposeFmt != bayerFmt) {
cerr << "Transpose with both green pixels on the "
<< "antidiagonal should not change the order "
<< "(got '" << transposeFmt << "')"
<< endl;
return TestFail;
}
/*
* Perform a transposition on an pixel order with red and blue
* on the bottom left to top right diagonal and make sure
* that their positions are switched.
*/
bayerFmt = BayerFormat(BayerFormat::GBRG, 8, BayerFormat::Packing::None);
bayerFmtExpect = BayerFormat(BayerFormat::GRBG, 8,
BayerFormat::Packing::None);
transposeFmt = bayerFmt.transform(Transform::Transpose);
if (transposeFmt != bayerFmtExpect) {
cerr << "Transpose with the red & blue pixels on the "
<< "antidiagonal should switch their position "
<< "(got '" << transposeFmt << "')"
<< endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(BayerFormatTest)

View File

@@ -0,0 +1,178 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2018, Google Inc.
*
* ByteStreamBuffer tests
*/
#include <array>
#include <iostream>
#include "libcamera/internal/byte_stream_buffer.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class ByteStreamBufferTest : public Test
{
protected:
int run()
{
/*
* gcc 11.1.0 incorrectly raises a maybe-uninitialized warning
* when calling data.size() below (if the address sanitizer is
* disabled). Silence it by initializing the array.
*/
std::array<uint8_t, 100> data = {};
unsigned int i;
uint32_t value;
int ret;
/*
* Write mode.
*/
ByteStreamBuffer wbuf(data.data(), data.size());
if (wbuf.base() != data.data() || wbuf.size() != data.size() ||
wbuf.offset() != 0 || wbuf.overflow()) {
cerr << "Write buffer incorrectly constructed" << endl;
return TestFail;
}
/* Test write. */
value = 0x12345678;
ret = wbuf.write(&value);
if (ret || wbuf.offset() != 4 || wbuf.overflow() ||
*reinterpret_cast<uint32_t *>(data.data()) != 0x12345678) {
cerr << "Write failed on write buffer" << endl;
return TestFail;
}
/* Test write carve out. */
ByteStreamBuffer wco = wbuf.carveOut(10);
if (wco.base() != wbuf.base() + 4 || wco.size() != 10 ||
wco.offset() != 0 || wco.overflow() || wbuf.offset() != 14 ||
wbuf.overflow()) {
cerr << "Carving out write buffer failed" << endl;
return TestFail;
}
/* Test write on the carved out buffer. */
value = 0x87654321;
ret = wco.write(&value);
if (ret || wco.offset() != 4 || wco.overflow() ||
*reinterpret_cast<uint32_t *>(data.data() + 4) != 0x87654321) {
cerr << "Write failed on carve out buffer" << endl;
return TestFail;
}
if (wbuf.offset() != 14 || wbuf.overflow()) {
cerr << "Write on carve out buffer modified write buffer" << endl;
return TestFail;
}
/* Test read, this should fail. */
ret = wbuf.read(&value);
if (!ret || wbuf.overflow()) {
cerr << "Read should fail on write buffer" << endl;
return TestFail;
}
/* Test overflow on carved out buffer. */
for (i = 0; i < 2; ++i) {
ret = wco.write(&value);
if (ret < 0)
break;
}
if (i != 1 || !wco.overflow() || !wbuf.overflow()) {
cerr << "Write on carve out buffer failed to overflow" << endl;
return TestFail;
}
/* Test reinitialization of the buffer. */
wbuf = ByteStreamBuffer(data.data(), data.size());
if (wbuf.overflow() || wbuf.base() != data.data() ||
wbuf.offset() != 0) {
cerr << "Write buffer reinitialization failed" << endl;
return TestFail;
}
/*
* Read mode.
*/
ByteStreamBuffer rbuf(const_cast<const uint8_t *>(data.data()),
data.size());
if (rbuf.base() != data.data() || rbuf.size() != data.size() ||
rbuf.offset() != 0 || rbuf.overflow()) {
cerr << "Read buffer incorrectly constructed" << endl;
return TestFail;
}
/* Test read. */
value = 0;
ret = rbuf.read(&value);
if (ret || rbuf.offset() != 4 || rbuf.overflow() ||
value != 0x12345678) {
cerr << "Write failed on write buffer" << endl;
return TestFail;
}
/* Test read carve out. */
ByteStreamBuffer rco = rbuf.carveOut(10);
if (rco.base() != rbuf.base() + 4 || rco.size() != 10 ||
rco.offset() != 0 || rco.overflow() || rbuf.offset() != 14 ||
rbuf.overflow()) {
cerr << "Carving out read buffer failed" << endl;
return TestFail;
}
/* Test read on the carved out buffer. */
value = 0;
ret = rco.read(&value);
if (ret || rco.offset() != 4 || rco.overflow() || value != 0x87654321) {
cerr << "Read failed on carve out buffer" << endl;
return TestFail;
}
if (rbuf.offset() != 14 || rbuf.overflow()) {
cerr << "Read on carve out buffer modified read buffer" << endl;
return TestFail;
}
/* Test write, this should fail. */
ret = rbuf.write(&value);
if (!ret || rbuf.overflow()) {
cerr << "Write should fail on read buffer" << endl;
return TestFail;
}
/* Test overflow on carved out buffer. */
for (i = 0; i < 2; ++i) {
ret = rco.read(&value);
if (ret < 0)
break;
}
if (i != 1 || !rco.overflow() || !rbuf.overflow()) {
cerr << "Read on carve out buffer failed to overflow" << endl;
return TestFail;
}
/* Test reinitialization of the buffer. */
rbuf = ByteStreamBuffer(const_cast<const uint8_t *>(data.data()),
data.size());
if (rbuf.overflow() || rbuf.base() != data.data() ||
rbuf.offset() != 0) {
cerr << "Read buffer reinitialization failed" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ByteStreamBufferTest)

View File

@@ -0,0 +1,131 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Camera sensor tests
*/
#include <algorithm>
#include <iostream>
#include <linux/media-bus-format.h>
#include <libcamera/base/utils.h>
#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class CameraSensorTest : public Test
{
protected:
int init()
{
enumerator_ = DeviceEnumerator::create();
if (!enumerator_) {
cerr << "Failed to create device enumerator" << endl;
return TestFail;
}
if (enumerator_->enumerate()) {
cerr << "Failed to enumerate media devices" << endl;
return TestFail;
}
DeviceMatch dm("vimc");
media_ = enumerator_->search(dm);
if (!media_) {
cerr << "Unable to find \'vimc\' media device node" << endl;
return TestSkip;
}
MediaEntity *entity = media_->getEntityByName("Sensor A");
if (!entity) {
cerr << "Unable to find media entity 'Sensor A'" << endl;
return TestFail;
}
sensor_ = new CameraSensor(entity);
if (sensor_->init() < 0) {
cerr << "Unable to initialise camera sensor" << endl;
return TestFail;
}
lens_ = sensor_->focusLens();
if (lens_)
cout << "Found lens controller" << endl;
return TestPass;
}
int run()
{
if (sensor_->model() != "Sensor A") {
cerr << "Incorrect sensor model '" << sensor_->model()
<< "'" << endl;
return TestFail;
}
const std::vector<unsigned int> &codes = sensor_->mbusCodes();
auto iter = std::find(codes.begin(), codes.end(),
MEDIA_BUS_FMT_ARGB8888_1X32);
if (iter == codes.end()) {
cerr << "Sensor doesn't support ARGB8888_1X32" << endl;
return TestFail;
}
const std::vector<Size> &sizes = sensor_->sizes(*iter);
auto iter2 = std::find(sizes.begin(), sizes.end(),
Size(4096, 2160));
if (iter2 == sizes.end()) {
cerr << "Sensor doesn't support 4096x2160" << endl;
return TestFail;
}
const Size &resolution = sensor_->resolution();
if (resolution != Size(4096, 2160)) {
cerr << "Incorrect sensor resolution " << resolution << endl;
return TestFail;
}
/* Use an invalid format and make sure it's not selected. */
V4L2SubdeviceFormat format = sensor_->getFormat({ 0xdeadbeef,
MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_BGR888_1X24 },
Size(1024, 768));
if (format.code != MEDIA_BUS_FMT_SBGGR10_1X10 ||
format.size != Size(4096, 2160)) {
cerr << "Failed to get a suitable format, expected 4096x2160-0x"
<< utils::hex(MEDIA_BUS_FMT_SBGGR10_1X10)
<< ", got " << format << endl;
return TestFail;
}
if (lens_ && lens_->setFocusPosition(10)) {
cerr << "Failed to set lens focus position" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
delete sensor_;
}
private:
std::unique_ptr<DeviceEnumerator> enumerator_;
std::shared_ptr<MediaDevice> media_;
CameraSensor *sensor_;
CameraLens *lens_;
};
TEST_REGISTER(CameraSensorTest)

View File

@@ -0,0 +1,182 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera Camera API tests
*
* Test importing buffers exported from the VIVID output device into a Camera
*/
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_videodevice.h"
#include "buffer_source.h"
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std::chrono_literals;
namespace {
class BufferImportTest : public CameraTest, public Test
{
public:
BufferImportTest()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
void bufferComplete([[maybe_unused]] Request *request,
FrameBuffer *buffer)
{
if (buffer->metadata().status != FrameMetadata::FrameSuccess)
return;
completeBuffersCount_++;
}
void requestComplete(Request *request)
{
if (request->status() != Request::RequestComplete)
return;
const Request::BufferMap &buffers = request->buffers();
completeRequestsCount_++;
/* Create a new request. */
const Stream *stream = buffers.begin()->first;
FrameBuffer *buffer = buffers.begin()->second;
request->reuse();
request->addBuffer(stream, buffer);
camera_->queueRequest(request);
dispatcher_->interrupt();
}
int init() override
{
if (status_ != TestPass)
return status_;
config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
if (!config_ || config_->size() != 1) {
std::cout << "Failed to generate default configuration" << std::endl;
return TestFail;
}
dispatcher_ = Thread::current()->eventDispatcher();
return TestPass;
}
int run() override
{
StreamConfiguration &cfg = config_->at(0);
if (camera_->acquire()) {
std::cout << "Failed to acquire the camera" << std::endl;
return TestFail;
}
if (camera_->configure(config_.get())) {
std::cout << "Failed to set default configuration" << std::endl;
return TestFail;
}
Stream *stream = cfg.stream();
BufferSource source;
int ret = source.allocate(cfg);
if (ret != TestPass)
return ret;
for (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {
std::unique_ptr<Request> request = camera_->createRequest();
if (!request) {
std::cout << "Failed to create request" << std::endl;
return TestFail;
}
if (request->addBuffer(stream, buffer.get())) {
std::cout << "Failed to associating buffer with request" << std::endl;
return TestFail;
}
requests_.push_back(std::move(request));
}
completeRequestsCount_ = 0;
completeBuffersCount_ = 0;
camera_->bufferCompleted.connect(this, &BufferImportTest::bufferComplete);
camera_->requestCompleted.connect(this, &BufferImportTest::requestComplete);
if (camera_->start()) {
std::cout << "Failed to start camera" << std::endl;
return TestFail;
}
for (std::unique_ptr<Request> &request : requests_) {
if (camera_->queueRequest(request.get())) {
std::cout << "Failed to queue request" << std::endl;
return TestFail;
}
}
const unsigned int nFrames = cfg.bufferCount * 2;
Timer timer;
timer.start(500ms * nFrames);
while (timer.isRunning()) {
dispatcher_->processEvents();
if (completeRequestsCount_ > nFrames)
break;
}
if (completeRequestsCount_ < nFrames) {
std::cout << "Failed to capture enough frames (got "
<< completeRequestsCount_ << " expected at least "
<< nFrames << ")" << std::endl;
return TestFail;
}
if (completeRequestsCount_ != completeBuffersCount_) {
std::cout << "Number of completed buffers and requests differ" << std::endl;
return TestFail;
}
if (camera_->stop()) {
std::cout << "Failed to stop camera" << std::endl;
return TestFail;
}
return TestPass;
}
private:
EventDispatcher *dispatcher_;
std::vector<std::unique_ptr<Request>> requests_;
unsigned int completeBuffersCount_;
unsigned int completeRequestsCount_;
std::unique_ptr<CameraConfiguration> config_;
};
} /* namespace */
TEST_REGISTER(BufferImportTest)

View File

@@ -0,0 +1,264 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Google Inc.
*
* Test:
* - Multiple reconfigurations of the Camera without stopping the CameraManager
* - Validate there are no file descriptor leaks when using IPC
*/
#include <dirent.h>
#include <fstream>
#include <iostream>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/file.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include <libcamera/framebuffer_allocator.h>
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
namespace {
class CameraReconfigure : public CameraTest, public Test
{
public:
/* Initialize CameraTest with isolated IPA */
CameraReconfigure()
: CameraTest(kCamId_, true)
{
}
private:
static constexpr const char *kCamId_ = "platform/vimc.0 Sensor B";
static constexpr const char *kIpaProxyName_ = "vimc_ipa_proxy";
static constexpr unsigned int kNumOfReconfigures_ = 10;
void requestComplete(Request *request)
{
if (request->status() != Request::RequestComplete)
return;
const Request::BufferMap &buffers = request->buffers();
const Stream *stream = buffers.begin()->first;
FrameBuffer *buffer = buffers.begin()->second;
/* Reuse the request and re-queue it with the same buffers. */
request->reuse();
request->addBuffer(stream, buffer);
camera_->queueRequest(request);
}
int startAndStop()
{
StreamConfiguration &cfg = config_->at(0);
if (camera_->acquire()) {
cerr << "Failed to acquire the camera" << endl;
return TestFail;
}
if (camera_->configure(config_.get())) {
cerr << "Failed to set default configuration" << endl;
return TestFail;
}
Stream *stream = cfg.stream();
/*
* The configuration is consistent so we can re-use the
* same buffer allocation for each run.
*/
if (!allocated_) {
int ret = allocator_->allocate(stream);
if (ret < 0) {
cerr << "Failed to allocate buffers" << endl;
return TestFail;
}
allocated_ = true;
}
for (const unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
unique_ptr<Request> request = camera_->createRequest();
if (!request) {
cerr << "Failed to create request" << endl;
return TestFail;
}
if (request->addBuffer(stream, buffer.get())) {
cerr << "Failed to associate buffer with request" << endl;
return TestFail;
}
requests_.push_back(std::move(request));
}
camera_->requestCompleted.connect(this, &CameraReconfigure::requestComplete);
if (camera_->start()) {
cerr << "Failed to start camera" << endl;
return TestFail;
}
for (unique_ptr<Request> &request : requests_) {
if (camera_->queueRequest(request.get())) {
cerr << "Failed to queue request" << endl;
return TestFail;
}
}
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
Timer timer;
timer.start(100ms);
while (timer.isRunning())
dispatcher->processEvents();
if (camera_->stop()) {
cerr << "Failed to stop camera" << endl;
return TestFail;
}
if (camera_->release()) {
cerr << "Failed to release camera" << endl;
return TestFail;
}
camera_->requestCompleted.disconnect(this);
requests_.clear();
return 0;
}
int fdsOpen(pid_t pid)
{
string proxyFdPath = "/proc/" + to_string(pid) + "/fd";
DIR *dir;
struct dirent *ptr;
unsigned int openFds = 0;
dir = opendir(proxyFdPath.c_str());
if (dir == nullptr) {
int err = errno;
cerr << "Error opening " << proxyFdPath << ": "
<< strerror(-err) << endl;
return 0;
}
while ((ptr = readdir(dir)) != nullptr) {
if ((strcmp(ptr->d_name, ".") == 0) ||
(strcmp(ptr->d_name, "..") == 0))
continue;
openFds++;
}
closedir(dir);
return openFds;
}
pid_t findProxyPid()
{
string proxyPid;
string proxyName(kIpaProxyName_);
DIR *dir;
struct dirent *ptr;
dir = opendir("/proc");
while ((ptr = readdir(dir)) != nullptr) {
if (ptr->d_type != DT_DIR)
continue;
string pname("/proc/" + string(ptr->d_name) + "/comm");
if (File::exists(pname)) {
ifstream pfile(pname.c_str());
string comm;
getline(pfile, comm);
pfile.close();
proxyPid = comm == proxyName ? string(ptr->d_name) : "";
}
if (!proxyPid.empty())
break;
}
closedir(dir);
if (!proxyPid.empty())
return atoi(proxyPid.c_str());
return -1;
}
int init() override
{
if (status_ != TestPass)
return status_;
config_ = camera_->generateConfiguration({ StreamRole::StillCapture });
if (!config_ || config_->size() != 1) {
cerr << "Failed to generate default configuration" << endl;
return TestFail;
}
allocator_ = make_unique<FrameBufferAllocator>(camera_);
allocated_ = false;
return TestPass;
}
int run() override
{
unsigned int openFdsAtStart = 0;
unsigned int openFds = 0;
pid_t proxyPid = findProxyPid();
if (proxyPid < 0) {
cerr << "Cannot find " << kIpaProxyName_
<< " pid, exiting" << endl;
return TestFail;
}
openFdsAtStart = fdsOpen(proxyPid);
for (unsigned int i = 0; i < kNumOfReconfigures_; i++) {
startAndStop();
openFds = fdsOpen(proxyPid);
if (openFds == 0) {
cerr << "No open fds found whereas "
<< "open fds at start: " << openFdsAtStart
<< endl;
return TestFail;
}
if (openFds != openFdsAtStart) {
cerr << "Leaking fds for " << kIpaProxyName_
<< " - Open fds: " << openFds << " vs "
<< "Open fds at start: " << openFdsAtStart
<< endl;
return TestFail;
}
}
return TestPass;
}
bool allocated_;
vector<unique_ptr<Request>> requests_;
unique_ptr<CameraConfiguration> config_;
unique_ptr<FrameBufferAllocator> allocator_;
};
} /* namespace */
TEST_REGISTER(CameraReconfigure)

View File

@@ -0,0 +1,181 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera Camera API tests
*/
#include <iostream>
#include <libcamera/framebuffer_allocator.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
namespace {
class Capture : public CameraTest, public Test
{
public:
Capture()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
unsigned int completeBuffersCount_;
unsigned int completeRequestsCount_;
void bufferComplete([[maybe_unused]] Request *request,
FrameBuffer *buffer)
{
if (buffer->metadata().status != FrameMetadata::FrameSuccess)
return;
completeBuffersCount_++;
}
void requestComplete(Request *request)
{
if (request->status() != Request::RequestComplete)
return;
const Request::BufferMap &buffers = request->buffers();
completeRequestsCount_++;
/* Create a new request. */
const Stream *stream = buffers.begin()->first;
FrameBuffer *buffer = buffers.begin()->second;
request->reuse();
request->addBuffer(stream, buffer);
camera_->queueRequest(request);
dispatcher_->interrupt();
}
int init() override
{
if (status_ != TestPass)
return status_;
config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
if (!config_ || config_->size() != 1) {
cout << "Failed to generate default configuration" << endl;
return TestFail;
}
allocator_ = new FrameBufferAllocator(camera_);
dispatcher_ = Thread::current()->eventDispatcher();
return TestPass;
}
void cleanup() override
{
delete allocator_;
}
int run() override
{
StreamConfiguration &cfg = config_->at(0);
if (camera_->acquire()) {
cout << "Failed to acquire the camera" << endl;
return TestFail;
}
if (camera_->configure(config_.get())) {
cout << "Failed to set default configuration" << endl;
return TestFail;
}
Stream *stream = cfg.stream();
int ret = allocator_->allocate(stream);
if (ret < 0)
return TestFail;
for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
std::unique_ptr<Request> request = camera_->createRequest();
if (!request) {
cout << "Failed to create request" << endl;
return TestFail;
}
if (request->addBuffer(stream, buffer.get())) {
cout << "Failed to associate buffer with request" << endl;
return TestFail;
}
requests_.push_back(std::move(request));
}
completeRequestsCount_ = 0;
completeBuffersCount_ = 0;
camera_->bufferCompleted.connect(this, &Capture::bufferComplete);
camera_->requestCompleted.connect(this, &Capture::requestComplete);
if (camera_->start()) {
cout << "Failed to start camera" << endl;
return TestFail;
}
for (std::unique_ptr<Request> &request : requests_) {
if (camera_->queueRequest(request.get())) {
cout << "Failed to queue request" << endl;
return TestFail;
}
}
unsigned int nFrames = allocator_->buffers(stream).size() * 2;
Timer timer;
timer.start(500ms * nFrames);
while (timer.isRunning()) {
dispatcher_->processEvents();
if (completeRequestsCount_ > nFrames)
break;
}
if (completeRequestsCount_ < nFrames) {
cout << "Failed to capture enough frames (got "
<< completeRequestsCount_ << " expected at least "
<< nFrames * 2 << ")" << endl;
return TestFail;
}
if (completeRequestsCount_ != completeBuffersCount_) {
cout << "Number of completed buffers and requests differ" << endl;
return TestFail;
}
if (camera_->stop()) {
cout << "Failed to stop camera" << endl;
return TestFail;
}
return TestPass;
}
EventDispatcher *dispatcher_;
std::vector<std::unique_ptr<Request>> requests_;
std::unique_ptr<CameraConfiguration> config_;
FrameBufferAllocator *allocator_;
};
} /* namespace */
TEST_REGISTER(Capture)

View File

@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera Camera API tests
*/
#include <iostream>
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
namespace {
class ConfigurationDefault : public CameraTest, public Test
{
public:
ConfigurationDefault()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
int init() override
{
return status_;
}
int run() override
{
std::unique_ptr<CameraConfiguration> config;
/* Test asking for configuration for a video stream. */
config = camera_->generateConfiguration({ StreamRole::VideoRecording });
if (!config || config->size() != 1) {
cout << "Default configuration invalid" << endl;
return TestFail;
}
/*
* Test that asking for configuration for an empty array of
* stream roles returns an empty camera configuration.
*/
config = camera_->generateConfiguration({});
if (!config || config->size() != 0) {
cout << "Failed to retrieve configuration for empty roles list"
<< endl;
return TestFail;
}
return TestPass;
}
};
} /* namespace */
TEST_REGISTER(ConfigurationDefault)

View File

@@ -0,0 +1,106 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera Camera API tests
*/
#include <iostream>
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
namespace {
class ConfigurationSet : public CameraTest, public Test
{
public:
ConfigurationSet()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
int init() override
{
if (status_ != TestPass)
return status_;
config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
if (!config_ || config_->size() != 1) {
cout << "Failed to generate default configuration" << endl;
return TestFail;
}
return TestPass;
}
int run() override
{
StreamConfiguration &cfg = config_->at(0);
if (camera_->acquire()) {
cout << "Failed to acquire the camera" << endl;
return TestFail;
}
/* Test that setting the default configuration works. */
if (camera_->configure(config_.get())) {
cout << "Failed to set default configuration" << endl;
return TestFail;
}
/*
* Test that configuring the camera fails if it is not
* acquired, this will also test release and reacquiring
* of the camera.
*/
if (camera_->release()) {
cout << "Failed to release the camera" << endl;
return TestFail;
}
if (!camera_->configure(config_.get())) {
cout << "Setting configuration on a camera not acquired succeeded when it should have failed"
<< endl;
return TestFail;
}
if (camera_->acquire()) {
cout << "Failed to acquire the camera" << endl;
return TestFail;
}
/*
* Test that modifying the default configuration works. Doubling
* the default configuration of the VIMC camera is known to
* work.
*/
cfg.size.width *= 2;
cfg.size.height *= 2;
if (camera_->configure(config_.get())) {
cout << "Failed to set modified configuration" << endl;
return TestFail;
}
/*
* Test that setting an invalid configuration fails.
*/
cfg.size = { 0, 0 };
if (!camera_->configure(config_.get())) {
cout << "Invalid configuration incorrectly accepted" << endl;
return TestFail;
}
return TestPass;
}
std::unique_ptr<CameraConfiguration> config_;
};
} /* namespace */
TEST_REGISTER(ConfigurationSet)

View File

@@ -0,0 +1,20 @@
# SPDX-License-Identifier: CC0-1.0
# Tests are listed in order of complexity.
# They are not alphabetically sorted.
camera_tests = [
{'name': 'configuration_default', 'sources': ['configuration_default.cpp']},
{'name': 'configuration_set', 'sources': ['configuration_set.cpp']},
{'name': 'buffer_import', 'sources': ['buffer_import.cpp']},
{'name': 'statemachine', 'sources': ['statemachine.cpp']},
{'name': 'capture', 'sources': ['capture.cpp']},
{'name': 'camera_reconfigure', 'sources': ['camera_reconfigure.cpp']},
]
foreach test : camera_tests
exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'camera', is_parallel : false)
endforeach

View File

@@ -0,0 +1,216 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera Camera API tests
*/
#include <iostream>
#include <libcamera/framebuffer_allocator.h>
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
namespace {
class Statemachine : public CameraTest, public Test
{
public:
Statemachine()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
int testAvailable()
{
/* Test operations which should fail. */
if (camera_->configure(defconf_.get()) != -EACCES)
return TestFail;
if (camera_->createRequest())
return TestFail;
if (camera_->start() != -EACCES)
return TestFail;
Request request(camera_.get());
if (camera_->queueRequest(&request) != -EACCES)
return TestFail;
/* Test operations which should pass. */
if (camera_->release())
return TestFail;
if (camera_->stop())
return TestFail;
/* Test valid state transitions, end in Acquired state. */
if (camera_->acquire())
return TestFail;
return TestPass;
}
int testAcquired()
{
/* Test operations which should fail. */
if (camera_->acquire() != -EBUSY)
return TestFail;
if (camera_->createRequest())
return TestFail;
if (camera_->start() != -EACCES)
return TestFail;
Request request(camera_.get());
if (camera_->queueRequest(&request) != -EACCES)
return TestFail;
/* Test operations which should pass. */
if (camera_->stop())
return TestFail;
/* Test valid state transitions, end in Configured state. */
if (camera_->release())
return TestFail;
if (camera_->acquire())
return TestFail;
if (camera_->configure(defconf_.get()))
return TestFail;
return TestPass;
}
int testConfigured()
{
/* Test operations which should fail. */
if (camera_->acquire() != -EBUSY)
return TestFail;
Request request1(camera_.get());
if (camera_->queueRequest(&request1) != -EACCES)
return TestFail;
/* Test operations which should pass. */
std::unique_ptr<Request> request2 = camera_->createRequest();
if (!request2)
return TestFail;
if (camera_->stop())
return TestFail;
/* Test valid state transitions, end in Running state. */
if (camera_->release())
return TestFail;
if (camera_->acquire())
return TestFail;
if (camera_->configure(defconf_.get()))
return TestFail;
/* Use internally allocated buffers. */
allocator_ = new FrameBufferAllocator(camera_);
Stream *stream = *camera_->streams().begin();
if (allocator_->allocate(stream) < 0)
return TestFail;
if (camera_->start())
return TestFail;
return TestPass;
}
int testRuning()
{
/* Test operations which should fail. */
if (camera_->acquire() != -EBUSY)
return TestFail;
if (camera_->release() != -EBUSY)
return TestFail;
if (camera_->configure(defconf_.get()) != -EACCES)
return TestFail;
if (camera_->start() != -EACCES)
return TestFail;
/* Test operations which should pass. */
std::unique_ptr<Request> request = camera_->createRequest();
if (!request)
return TestFail;
Stream *stream = *camera_->streams().begin();
if (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))
return TestFail;
if (camera_->queueRequest(request.get()))
return TestFail;
/* Test valid state transitions, end in Available state. */
if (camera_->stop())
return TestFail;
delete allocator_;
if (camera_->release())
return TestFail;
return TestPass;
}
int init() override
{
if (status_ != TestPass)
return status_;
defconf_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
if (!defconf_) {
cout << "Failed to generate default configuration" << endl;
return TestFail;
}
return TestPass;
}
int run() override
{
if (testAvailable() != TestPass) {
cout << "State machine in Available state failed" << endl;
return TestFail;
}
if (testAcquired() != TestPass) {
cout << "State machine in Acquired state failed" << endl;
return TestFail;
}
if (testConfigured() != TestPass) {
cout << "State machine in Configured state failed" << endl;
return TestFail;
}
if (testRuning() != TestPass) {
cout << "State machine in Running state failed" << endl;
return TestFail;
}
return TestPass;
}
std::unique_ptr<CameraConfiguration> defconf_;
FrameBufferAllocator *allocator_;
};
} /* namespace */
TEST_REGISTER(Statemachine)

View File

@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2022, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*
* libcamera ColorSpace test
*/
#include <array>
#include <iostream>
#include <libcamera/color_space.h>
#include "test.h"
using namespace libcamera;
using namespace std;
class ColorSpaceTest : public Test
{
protected:
int run()
{
if (ColorSpace::toString(std::nullopt) != "Unset") {
std::cerr << "Conversion from nullopt to string failed" << std::endl;
return TestFail;
}
const std::array<std::pair<ColorSpace, std::string>, 10> colorSpaces = { {
{ ColorSpace::Raw, "RAW" },
{ ColorSpace::Srgb, "sRGB" },
{ ColorSpace::Sycc, "sYCC" },
{ ColorSpace::Smpte170m, "SMPTE170M" },
{ ColorSpace::Rec709, "Rec709" },
{ ColorSpace::Rec2020, "Rec2020" },
{
ColorSpace{
ColorSpace::Primaries::Raw,
ColorSpace::TransferFunction::Linear,
ColorSpace::YcbcrEncoding::None,
ColorSpace::Range::Limited
},
"RAW/Linear/None/Limited"
}, {
ColorSpace{
ColorSpace::Primaries::Smpte170m,
ColorSpace::TransferFunction::Srgb,
ColorSpace::YcbcrEncoding::Rec601,
ColorSpace::Range::Full
},
"SMPTE170M/sRGB/Rec601/Full"
}, {
ColorSpace{
ColorSpace::Primaries::Rec709,
ColorSpace::TransferFunction::Rec709,
ColorSpace::YcbcrEncoding::Rec709,
ColorSpace::Range::Full
},
"Rec709/Rec709/Rec709/Full"
}, {
ColorSpace{
ColorSpace::Primaries::Rec2020,
ColorSpace::TransferFunction::Linear,
ColorSpace::YcbcrEncoding::Rec2020,
ColorSpace::Range::Limited
},
"Rec2020/Linear/Rec2020/Limited"
},
} };
for (const auto &[colorSpace, name] : colorSpaces) {
if (colorSpace.toString() != name) {
std::cerr
<< "Conversion from ColorSpace to string failed: "
<< "expected " << name
<< ", got " << colorSpace.toString()
<< std::endl;
return TestFail;
}
if (ColorSpace::fromString(name) != colorSpace) {
std::cerr
<< "Conversion from string "
<< name << " to ColorSpace failed"
<< std::endl;
return TestFail;
}
}
if (ColorSpace::fromString("Invalid")) {
std::cerr << "Conversion from invalid name string to color space succeeded"
<< std::endl;
return TestFail;
}
if (ColorSpace::fromString("Rec709/Rec709/Rec710/Limited")) {
std::cerr << "Conversion from invalid component string to color space succeeded"
<< std::endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ColorSpaceTest)

View File

@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ControlInfo tests
*/
#include <iostream>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class ControlInfoTest : public Test
{
protected:
int run()
{
/*
* Test information retrieval from a range with no minimum and
* maximum.
*/
ControlInfo brightness;
if (brightness.min().type() != ControlType::ControlTypeNone ||
brightness.max().type() != ControlType::ControlTypeNone ||
brightness.def().type() != ControlType::ControlTypeNone) {
cout << "Invalid control range for Brightness" << endl;
return TestFail;
}
/*
* Test information retrieval from a control with a minimum and
* a maximum value, and an implicit default value.
*/
ControlInfo contrast(10, 200);
if (contrast.min().get<int32_t>() != 10 ||
contrast.max().get<int32_t>() != 200 ||
!contrast.def().isNone()) {
cout << "Invalid control range for Contrast" << endl;
return TestFail;
}
/*
* Test information retrieval from a control with boolean
* values.
*/
ControlInfo aeEnable({ false, true }, false);
if (aeEnable.min().get<bool>() != false ||
aeEnable.def().get<bool>() != false ||
aeEnable.max().get<bool>() != true) {
cout << "Invalid control range for AeEnable" << endl;
return TestFail;
}
if (aeEnable.values()[0].get<bool>() != false ||
aeEnable.values()[1].get<bool>() != true) {
cout << "Invalid control values for AeEnable" << endl;
return TestFail;
}
ControlInfo awbEnable(true);
if (awbEnable.min().get<bool>() != true ||
awbEnable.def().get<bool>() != true ||
awbEnable.max().get<bool>() != true) {
cout << "Invalid control range for AwbEnable" << endl;
return TestFail;
}
if (awbEnable.values()[0].get<bool>() != true) {
cout << "Invalid control values for AwbEnable" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ControlInfoTest)

View File

@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ControlInfoMap tests
*/
#include <iostream>
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include "libcamera/internal/camera_controls.h"
#include "camera_test.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class ControlInfoMapTest : public CameraTest, public Test
{
public:
ControlInfoMapTest()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
int init() override
{
return status_;
}
int run() override
{
const ControlInfoMap &infoMap = camera_->controls();
/* Test looking up a valid control by ControlId. */
if (infoMap.count(&controls::Brightness) != 1) {
cerr << "count() on valid control failed" << endl;
return TestFail;
}
if (infoMap.find(&controls::Brightness) == infoMap.end()) {
cerr << "find() on valid control failed" << endl;
return TestFail;
}
infoMap.at(&controls::Brightness);
/* Test looking up a valid control by numerical ID. */
if (infoMap.count(controls::Brightness.id()) != 1) {
cerr << "count() on valid ID failed" << endl;
return TestFail;
}
if (infoMap.find(controls::Brightness.id()) == infoMap.end()) {
cerr << "find() on valid ID failed" << endl;
return TestFail;
}
infoMap.at(controls::Brightness.id());
/* Test looking up an invalid control by numerical ID. */
if (infoMap.count(12345) != 0) {
cerr << "count() on invalid ID failed" << endl;
return TestFail;
}
if (infoMap.find(12345) != infoMap.end()) {
cerr << "find() on invalid ID failed" << endl;
return TestFail;
}
/* Test looking up a control on a default-constructed infoMap */
const ControlInfoMap emptyInfoMap;
if (emptyInfoMap.find(12345) != emptyInfoMap.end()) {
cerr << "find() on empty ControlInfoMap failed" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ControlInfoMapTest)

View File

@@ -0,0 +1,253 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ControlList tests
*/
#include <iostream>
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include "libcamera/internal/camera_controls.h"
#include "camera_test.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class ControlListTest : public CameraTest, public Test
{
public:
ControlListTest()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
int init() override
{
return status_;
}
int run() override
{
CameraControlValidator validator(camera_.get());
ControlList list(controls::controls, &validator);
/* Test that the list is initially empty. */
if (!list.empty()) {
cout << "List should to be empty" << endl;
return TestFail;
}
if (list.size() != 0) {
cout << "List should contain zero items" << endl;
return TestFail;
}
if (list.get(controls::Brightness)) {
cout << "List should not contain Brightness control" << endl;
return TestFail;
}
unsigned int count = 0;
for (auto iter = list.begin(); iter != list.end(); ++iter)
count++;
if (count != 0) {
cout << "List iteration should not produce any item" << endl;
return TestFail;
}
/*
* Set a control, and verify that the list now contains it, and
* nothing else.
*/
list.set(controls::Brightness, -0.5f);
if (list.empty()) {
cout << "List should not be empty" << endl;
return TestFail;
}
if (list.size() != 1) {
cout << "List should contain one item" << endl;
return TestFail;
}
if (!list.get(controls::Brightness)) {
cout << "List should contain Brightness control" << endl;
return TestFail;
}
count = 0;
for (auto iter = list.begin(); iter != list.end(); ++iter)
count++;
if (count != 1) {
cout << "List iteration should produce one item" << endl;
return TestFail;
}
if (list.get(controls::Brightness) != -0.5f) {
cout << "Incorrest Brightness control value" << endl;
return TestFail;
}
if (list.get(controls::Contrast)) {
cout << "List should not contain Contract control" << endl;
return TestFail;
}
/* Update the first control and set a second one. */
list.set(controls::Brightness, 0.0f);
list.set(controls::Contrast, 1.5f);
if (!list.get(controls::Brightness) ||
!list.get(controls::Contrast)) {
cout << "List should contain Brightness and Contrast controls"
<< endl;
return TestFail;
}
if (list.get(controls::Brightness) != 0.0f ||
list.get(controls::Contrast) != 1.5f) {
cout << "Failed to retrieve control value" << endl;
return TestFail;
}
/*
* Update both controls and verify that the container doesn't
* grow.
*/
list.set(controls::Brightness, 0.5f);
list.set(controls::Contrast, 1.1f);
if (list.get(controls::Brightness) != 0.5f ||
list.get(controls::Contrast) != 1.1f) {
cout << "Failed to update control value" << endl;
return TestFail;
}
if (list.size() != 2) {
cout << "List should contain two elements" << endl;
return TestFail;
}
/*
* Attempt to set an invalid control and verify that the
* operation failed.
*/
list.set(controls::AwbEnable, true);
if (list.get(controls::AwbEnable)) {
cout << "List shouldn't contain AwbEnable control" << endl;
return TestFail;
}
/*
* Create a new list with a new control and merge it with the
* existing one, verifying that the existing controls
* values don't get overwritten.
*/
ControlList mergeList(controls::controls, &validator);
mergeList.set(controls::Brightness, 0.7f);
mergeList.set(controls::Saturation, 0.4f);
mergeList.merge(list);
if (mergeList.size() != 3) {
cout << "Merged list should contain three elements" << endl;
return TestFail;
}
if (list.size() != 2) {
cout << "The list to merge should contain two elements"
<< endl;
return TestFail;
}
if (!mergeList.get(controls::Brightness) ||
!mergeList.get(controls::Contrast) ||
!mergeList.get(controls::Saturation)) {
cout << "Merged list does not contain all controls" << endl;
return TestFail;
}
if (mergeList.get(controls::Brightness) != 0.7f) {
cout << "Brightness control value changed after merging lists"
<< endl;
return TestFail;
}
if (mergeList.get(controls::Contrast) != 1.1f) {
cout << "Contrast control value changed after merging lists"
<< endl;
return TestFail;
}
if (mergeList.get(controls::Saturation) != 0.4f) {
cout << "Saturation control value changed after merging lists"
<< endl;
return TestFail;
}
/*
* Create two lists with overlapping controls. Merge them with
* overwriteExisting = true, verifying that the existing control
* values *get* overwritten.
*/
mergeList.clear();
mergeList.set(controls::Brightness, 0.7f);
mergeList.set(controls::Saturation, 0.4f);
list.clear();
list.set(controls::Brightness, 0.5f);
list.set(controls::Contrast, 1.1f);
mergeList.merge(list, ControlList::MergePolicy::OverwriteExisting);
if (mergeList.size() != 3) {
cout << "Merged list should contain three elements" << endl;
return TestFail;
}
if (list.size() != 2) {
cout << "The list to merge should contain two elements"
<< endl;
return TestFail;
}
if (!mergeList.get(controls::Brightness) ||
!mergeList.get(controls::Contrast) ||
!mergeList.get(controls::Saturation)) {
cout << "Merged list does not contain all controls" << endl;
return TestFail;
}
if (mergeList.get(controls::Brightness) != 0.5f) {
cout << "Brightness control value did not change after merging lists"
<< endl;
return TestFail;
}
if (mergeList.get(controls::Contrast) != 1.1f) {
cout << "Contrast control value changed after merging lists"
<< endl;
return TestFail;
}
if (mergeList.get(controls::Saturation) != 0.4f) {
cout << "Saturation control value changed after merging lists"
<< endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ControlListTest)

View File

@@ -0,0 +1,264 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ControlValue tests
*/
#include <algorithm>
#include <iostream>
#include <libcamera/controls.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class ControlValueTest : public Test
{
protected:
int run()
{
/*
* None type.
*/
ControlValue value;
if (!value.isNone() || value.isArray()) {
cerr << "Empty value is non-null" << endl;
return TestFail;
}
/*
* Bool type.
*/
value.set(true);
if (value.isNone() || value.isArray() ||
value.type() != ControlTypeBool) {
cerr << "Control type mismatch after setting to bool" << endl;
return TestFail;
}
if (value.get<bool>() != true) {
cerr << "Control value mismatch after setting to bool" << endl;
return TestFail;
}
if (value.toString() != "true") {
cerr << "Control string mismatch after setting to bool" << endl;
return TestFail;
}
std::array<bool, 2> bools{ true, false };
value.set(Span<bool>(bools));
if (value.isNone() || !value.isArray() ||
value.type() != ControlTypeBool) {
cerr << "Control type mismatch after setting to bool array" << endl;
return TestFail;
}
Span<const bool> boolsResult = value.get<Span<const bool>>();
if (bools.size() != boolsResult.size() ||
!std::equal(bools.begin(), bools.end(), boolsResult.begin())) {
cerr << "Control value mismatch after setting to bool" << endl;
return TestFail;
}
if (value.toString() != "[ true, false ]") {
cerr << "Control string mismatch after setting to bool array" << endl;
return TestFail;
}
/*
* Integer8 type.
*/
value.set(static_cast<uint8_t>(42));
if (value.isNone() || value.isArray() ||
value.type() != ControlTypeByte) {
cerr << "Control type mismatch after setting to uint8_t" << endl;
return TestFail;
}
if (value.get<uint8_t>() != 42) {
cerr << "Control value mismatch after setting to uint8_t" << endl;
return TestFail;
}
if (value.toString() != "42") {
cerr << "Control string mismatch after setting to uint8_t" << endl;
return TestFail;
}
std::array<uint8_t, 4> bytes{ 3, 14, 15, 9 };
value.set(Span<uint8_t>(bytes));
if (value.isNone() || !value.isArray() ||
value.type() != ControlTypeByte) {
cerr << "Control type mismatch after setting to uint8_t array" << endl;
return TestFail;
}
Span<const uint8_t> int8sResult = value.get<Span<const uint8_t>>();
if (bytes.size() != int8sResult.size() ||
!std::equal(bytes.begin(), bytes.end(), int8sResult.begin())) {
cerr << "Control value mismatch after setting to uint8_t array" << endl;
return TestFail;
}
if (value.toString() != "[ 3, 14, 15, 9 ]") {
cerr << "Control string mismatch after setting to uint8_t array" << endl;
return TestFail;
}
/*
* Integer32 type.
*/
value.set(0x42000000);
if (value.isNone() || value.isArray() ||
value.type() != ControlTypeInteger32) {
cerr << "Control type mismatch after setting to int32_t" << endl;
return TestFail;
}
if (value.get<int32_t>() != 0x42000000) {
cerr << "Control value mismatch after setting to int32_t" << endl;
return TestFail;
}
if (value.toString() != "1107296256") {
cerr << "Control string mismatch after setting to int32_t" << endl;
return TestFail;
}
std::array<int32_t, 4> int32s{ 3, 14, 15, 9 };
value.set(Span<int32_t>(int32s));
if (value.isNone() || !value.isArray() ||
value.type() != ControlTypeInteger32) {
cerr << "Control type mismatch after setting to int32_t array" << endl;
return TestFail;
}
Span<const int32_t> int32sResult = value.get<Span<const int32_t>>();
if (int32s.size() != int32sResult.size() ||
!std::equal(int32s.begin(), int32s.end(), int32sResult.begin())) {
cerr << "Control value mismatch after setting to int32_t array" << endl;
return TestFail;
}
if (value.toString() != "[ 3, 14, 15, 9 ]") {
cerr << "Control string mismatch after setting to int32_t array" << endl;
return TestFail;
}
/*
* Integer64 type.
*/
value.set(static_cast<int64_t>(-42));
if (value.isNone() || value.isArray() ||
value.type() != ControlTypeInteger64) {
cerr << "Control type mismatch after setting to int64_t" << endl;
return TestFail;
}
if (value.get<int64_t>() != -42) {
cerr << "Control value mismatch after setting to int64_t" << endl;
return TestFail;
}
if (value.toString() != "-42") {
cerr << "Control string mismatch after setting to int64_t" << endl;
return TestFail;
}
std::array<int64_t, 4> int64s{ 3, 14, 15, 9 };
value.set(Span<int64_t>(int64s));
if (value.isNone() || !value.isArray() ||
value.type() != ControlTypeInteger64) {
cerr << "Control type mismatch after setting to int64_t array" << endl;
return TestFail;
}
Span<const int64_t> int64sResult = value.get<Span<const int64_t>>();
if (int64s.size() != int64sResult.size() ||
!std::equal(int64s.begin(), int64s.end(), int64sResult.begin())) {
cerr << "Control value mismatch after setting to int64_t array" << endl;
return TestFail;
}
if (value.toString() != "[ 3, 14, 15, 9 ]") {
cerr << "Control string mismatch after setting to int64_t array" << endl;
return TestFail;
}
/*
* Float type.
*/
value.set(-0.42f);
if (value.isNone() || value.isArray() ||
value.type() != ControlTypeFloat) {
cerr << "Control type mismatch after setting to float" << endl;
return TestFail;
}
if (value.get<float>() != -0.42f) {
cerr << "Control value mismatch after setting to float" << endl;
return TestFail;
}
if (value.toString() != "-0.420000") {
cerr << "Control string mismatch after setting to float" << endl;
return TestFail;
}
std::array<float, 3> floats{ 3.141593, 2.718282, 299792458.0 };
value.set(Span<float>(floats));
if (value.isNone() || !value.isArray() ||
value.type() != ControlTypeFloat) {
cerr << "Control type mismatch after setting to float array" << endl;
return TestFail;
}
Span<const float> floatsResult = value.get<Span<const float>>();
if (floats.size() != floatsResult.size() ||
!std::equal(floats.begin(), floats.end(), floatsResult.begin())) {
cerr << "Control value mismatch after setting to float array" << endl;
return TestFail;
}
/*
* The string representation for the third value doesn't match
* the value in the floats array above, due to limited precision
* of the float type that can't properly represent the speed of
* light.
*/
if (value.toString() != "[ 3.141593, 2.718282, 299792448.000000 ]") {
cerr << "Control string mismatch after setting to float array" << endl;
return TestFail;
}
/*
* String type.
*/
std::string string{ "libcamera" };
value.set(string);
if (value.isNone() || !value.isArray() ||
value.type() != ControlTypeString ||
value.numElements() != string.size()) {
cerr << "Control type mismatch after setting to string" << endl;
return TestFail;
}
if (value.get<std::string>() != string) {
cerr << "Control value mismatch after setting to string" << endl;
return TestFail;
}
if (value.toString() != string) {
cerr << "Control string mismatch after setting to string" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ControlValueTest)

View File

@@ -0,0 +1,16 @@
# SPDX-License-Identifier: CC0-1.0
control_tests = [
{'name': 'control_info', 'sources': ['control_info.cpp']},
{'name': 'control_info_map', 'sources': ['control_info_map.cpp']},
{'name': 'control_list', 'sources': ['control_list.cpp']},
{'name': 'control_value', 'sources': ['control_value.cpp']},
]
foreach test : control_tests
exe = executable(test['name'], test['sources'],
dependencies : libcamera_public,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'controls', is_parallel : false)
endforeach

View File

@@ -0,0 +1,303 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* libcamera delayed controls test
*/
#include <iostream>
#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_videodevice.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class DelayedControlsTest : public Test
{
public:
DelayedControlsTest()
{
}
protected:
int init() override
{
enumerator_ = DeviceEnumerator::create();
if (!enumerator_) {
cerr << "Failed to create device enumerator" << endl;
return TestFail;
}
if (enumerator_->enumerate()) {
cerr << "Failed to enumerate media devices" << endl;
return TestFail;
}
DeviceMatch dm("vivid");
dm.add("vivid-000-vid-cap");
media_ = enumerator_->search(dm);
if (!media_) {
cerr << "vivid video device found" << endl;
return TestSkip;
}
dev_ = V4L2VideoDevice::fromEntityName(media_.get(), "vivid-000-vid-cap");
if (dev_->open()) {
cerr << "Failed to open video device" << endl;
return TestFail;
}
const ControlInfoMap &infoMap = dev_->controls();
/* Make sure the controls we require are present. */
if (infoMap.empty()) {
cerr << "Failed to enumerate controls" << endl;
return TestFail;
}
if (infoMap.find(V4L2_CID_BRIGHTNESS) == infoMap.end() ||
infoMap.find(V4L2_CID_CONTRAST) == infoMap.end()) {
cerr << "Missing controls" << endl;
return TestFail;
}
return TestPass;
}
int singleControlNoDelay()
{
std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
{ V4L2_CID_BRIGHTNESS, { 0, false } },
};
std::unique_ptr<DelayedControls> delayed =
std::make_unique<DelayedControls>(dev_.get(), delays);
ControlList ctrls;
/* Reset control to value not used in test. */
ctrls.set(V4L2_CID_BRIGHTNESS, 1);
dev_->setControls(&ctrls);
delayed->reset();
/* Trigger the first frame start event */
delayed->applyControls(0);
/* Test control without delay are set at once. */
for (unsigned int i = 1; i < 100; i++) {
int32_t value = 100 + i;
ctrls.set(V4L2_CID_BRIGHTNESS, value);
delayed->push(ctrls);
delayed->applyControls(i);
ControlList result = delayed->get(i);
int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
if (brightness != value) {
cerr << "Failed single control without delay"
<< " frame " << i
<< " expected " << value
<< " got " << brightness
<< endl;
return TestFail;
}
}
return TestPass;
}
int singleControlWithDelay()
{
std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
{ V4L2_CID_BRIGHTNESS, { 1, false } },
};
std::unique_ptr<DelayedControls> delayed =
std::make_unique<DelayedControls>(dev_.get(), delays);
ControlList ctrls;
/* Reset control to value that will be first in test. */
int32_t expected = 4;
ctrls.set(V4L2_CID_BRIGHTNESS, expected);
dev_->setControls(&ctrls);
delayed->reset();
/* Trigger the first frame start event */
delayed->applyControls(0);
/* Test single control with delay. */
for (unsigned int i = 1; i < 100; i++) {
int32_t value = 10 + i;
ctrls.set(V4L2_CID_BRIGHTNESS, value);
delayed->push(ctrls);
delayed->applyControls(i);
ControlList result = delayed->get(i);
int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
if (brightness != expected) {
cerr << "Failed single control with delay"
<< " frame " << i
<< " expected " << expected
<< " got " << brightness
<< endl;
return TestFail;
}
expected = value;
}
return TestPass;
}
int dualControlsWithDelay()
{
static const unsigned int maxDelay = 2;
std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
{ V4L2_CID_BRIGHTNESS, { 1, false } },
{ V4L2_CID_CONTRAST, { maxDelay, false } },
};
std::unique_ptr<DelayedControls> delayed =
std::make_unique<DelayedControls>(dev_.get(), delays);
ControlList ctrls;
/* Reset control to value that will be first two frames in test. */
int32_t expected = 200;
ctrls.set(V4L2_CID_BRIGHTNESS, expected);
ctrls.set(V4L2_CID_CONTRAST, expected + 1);
dev_->setControls(&ctrls);
delayed->reset();
/* Trigger the first frame start event */
delayed->applyControls(0);
/* Test dual control with delay. */
for (unsigned int i = 1; i < 100; i++) {
int32_t value = 10 + i;
ctrls.set(V4L2_CID_BRIGHTNESS, value);
ctrls.set(V4L2_CID_CONTRAST, value + 1);
delayed->push(ctrls);
delayed->applyControls(i);
ControlList result = delayed->get(i);
int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
if (brightness != expected || contrast != expected + 1) {
cerr << "Failed dual controls"
<< " frame " << i
<< " brightness " << brightness
<< " contrast " << contrast
<< " expected " << expected
<< endl;
return TestFail;
}
expected = i < maxDelay ? expected : value - 1;
}
return TestPass;
}
int dualControlsMultiQueue()
{
static const unsigned int maxDelay = 2;
std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
{ V4L2_CID_BRIGHTNESS, { 1, false } },
{ V4L2_CID_CONTRAST, { maxDelay, false } }
};
std::unique_ptr<DelayedControls> delayed =
std::make_unique<DelayedControls>(dev_.get(), delays);
ControlList ctrls;
/* Reset control to value that will be first two frames in test. */
int32_t expected = 100;
ctrls.set(V4L2_CID_BRIGHTNESS, expected);
ctrls.set(V4L2_CID_CONTRAST, expected);
dev_->setControls(&ctrls);
delayed->reset();
/* Trigger the first frame start event */
delayed->applyControls(0);
/*
* Queue all controls before any fake frame start. Note we
* can't queue up more then the delayed controls history size
* which is 16. Where one spot is used by the reset control.
*/
for (unsigned int i = 0; i < 15; i++) {
int32_t value = 10 + i;
ctrls.set(V4L2_CID_BRIGHTNESS, value);
ctrls.set(V4L2_CID_CONTRAST, value);
delayed->push(ctrls);
}
/* Process all queued controls. */
for (unsigned int i = 1; i < 16; i++) {
int32_t value = 10 + i - 1;
delayed->applyControls(i);
ControlList result = delayed->get(i);
int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
if (brightness != expected || contrast != expected) {
cerr << "Failed multi queue"
<< " frame " << i
<< " brightness " << brightness
<< " contrast " << contrast
<< " expected " << expected
<< endl;
return TestFail;
}
expected = i < maxDelay ? expected : value - 1;
}
return TestPass;
}
int run() override
{
int ret;
/* Test single control without delay. */
ret = singleControlNoDelay();
if (ret)
return ret;
/* Test single control with delay. */
ret = singleControlWithDelay();
if (ret)
return ret;
/* Test dual controls with different delays. */
ret = dualControlsWithDelay();
if (ret)
return ret;
/* Test control values produced faster than consumed. */
ret = dualControlsMultiQueue();
if (ret)
return ret;
return TestPass;
}
private:
std::unique_ptr<DeviceEnumerator> enumerator_;
std::shared_ptr<MediaDevice> media_;
std::unique_ptr<V4L2VideoDevice> dev_;
};
TEST_REGISTER(DelayedControlsTest)

View File

@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Event dispatcher test
*/
#include <chrono>
#include <iostream>
#include <signal.h>
#include <sys/time.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
static EventDispatcher *dispatcher;
static bool interrupt;
class EventDispatcherTest : public Test
{
protected:
static void sigAlarmHandler(int)
{
cout << "SIGALARM received" << endl;
if (interrupt)
dispatcher->interrupt();
}
int init()
{
dispatcher = Thread::current()->eventDispatcher();
struct sigaction sa = {};
sa.sa_handler = &sigAlarmHandler;
sigaction(SIGALRM, &sa, nullptr);
return 0;
}
int run()
{
Timer timer;
/* Event processing interruption by signal. */
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
timer.start(1000ms);
struct itimerval itimer = {};
itimer.it_value.tv_usec = 500000;
interrupt = false;
setitimer(ITIMER_REAL, &itimer, nullptr);
dispatcher->processEvents();
std::chrono::steady_clock::time_point stop = std::chrono::steady_clock::now();
std::chrono::steady_clock::duration duration = stop - start;
int msecs = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
if (abs(msecs - 1000) > 50) {
cout << "Event processing restart test failed" << endl;
return TestFail;
}
/* Event processing interruption. */
timer.start(1000ms);
dispatcher->interrupt();
dispatcher->processEvents();
if (!timer.isRunning()) {
cout << "Event processing immediate interruption failed" << endl;
return TestFail;
}
timer.start(1000ms);
itimer.it_value.tv_usec = 500000;
interrupt = true;
setitimer(ITIMER_REAL, &itimer, nullptr);
dispatcher->processEvents();
if (!timer.isRunning()) {
cout << "Event processing delayed interruption failed" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
}
};
TEST_REGISTER(EventDispatcherTest)

View File

@@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Threaded event test
*/
#include <chrono>
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class EventHandler : public Object
{
public:
EventHandler()
: notified_(false)
{
int ret = pipe(pipefd_);
if (ret < 0) {
ret = errno;
cout << "pipe() failed: " << strerror(ret) << endl;
}
notifier_ = new EventNotifier(pipefd_[0], EventNotifier::Read, this);
notifier_->activated.connect(this, &EventHandler::readReady);
}
~EventHandler()
{
delete notifier_;
close(pipefd_[0]);
close(pipefd_[1]);
}
int notify()
{
std::string data("H2G2");
ssize_t ret;
memset(data_, 0, sizeof(data_));
size_ = 0;
ret = write(pipefd_[1], data.data(), data.size());
if (ret < 0) {
cout << "Pipe write failed" << endl;
return TestFail;
}
return TestPass;
}
bool notified() const
{
return notified_;
}
private:
void readReady()
{
size_ = read(notifier_->fd(), data_, sizeof(data_));
notified_ = true;
}
EventNotifier *notifier_;
int pipefd_[2];
bool notified_;
char data_[16];
ssize_t size_;
};
class EventThreadTest : public Test
{
protected:
int init()
{
thread_.start();
handler_ = new EventHandler();
return TestPass;
}
int run()
{
/*
* Fire the event notifier and then move the notifier to a
* different thread. The notifier will not notice the event
* immediately as there is no event dispatcher loop running in
* the main thread. This tests that a notifier being moved to a
* different thread will correctly process already pending
* events in the new thread.
*/
handler_->notify();
handler_->moveToThread(&thread_);
this_thread::sleep_for(chrono::milliseconds(100));
if (!handler_->notified()) {
cout << "Thread event handling test failed" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
/*
* Object class instances must be destroyed from the thread
* they live in.
*/
handler_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
EventHandler *handler_;
Thread thread_;
};
TEST_REGISTER(EventThreadTest)

View File

@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Event test
*/
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
class EventTest : public Test
{
protected:
void readReady()
{
size_ = read(notifier_->fd(), data_, sizeof(data_));
notified_ = true;
}
int init()
{
notifier_ = nullptr;
return pipe(pipefd_);
}
int run()
{
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
std::string data("H2G2");
Timer timeout;
ssize_t ret;
notifier_ = new EventNotifier(pipefd_[0], EventNotifier::Read);
notifier_->activated.connect(this, &EventTest::readReady);
/* Test read notification with data. */
memset(data_, 0, sizeof(data_));
size_ = 0;
ret = write(pipefd_[1], data.data(), data.size());
if (ret < 0) {
cout << "Pipe write failed" << endl;
return TestFail;
}
timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
if (static_cast<size_t>(size_) != data.size()) {
cout << "Event notifier read ready test failed" << endl;
return TestFail;
}
/* Test read notification without data. */
notified_ = false;
timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
if (notified_) {
cout << "Event notifier read no ready test failed" << endl;
return TestFail;
}
/* Test read notifier disabling. */
notified_ = false;
notifier_->setEnabled(false);
ret = write(pipefd_[1], data.data(), data.size());
if (ret < 0) {
cout << "Pipe write failed" << endl;
return TestFail;
}
timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
if (notified_) {
cout << "Event notifier read disabling failed" << endl;
return TestFail;
}
/* Test read notifier enabling. */
notified_ = false;
notifier_->setEnabled(true);
timeout.start(100ms);
dispatcher->processEvents();
timeout.stop();
if (!notified_) {
cout << "Event notifier read enabling test failed" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
delete notifier_;
close(pipefd_[0]);
close(pipefd_[1]);
}
private:
int pipefd_[2];
EventNotifier *notifier_;
bool notified_;
char data_[16];
ssize_t size_;
};
TEST_REGISTER(EventTest)

View File

@@ -0,0 +1,361 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Google Inc.
*
* Fence test
*/
#include <iostream>
#include <memory>
#include <sys/eventfd.h>
#include <unistd.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include <libcamera/base/unique_fd.h>
#include <libcamera/base/utils.h>
#include <libcamera/fence.h>
#include <libcamera/framebuffer_allocator.h>
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
class FenceTest : public CameraTest, public Test
{
public:
FenceTest();
protected:
int init() override;
int run() override;
private:
int validateExpiredRequest(Request *request);
int validateRequest(Request *request);
void requestComplete(Request *request);
void requestRequeue(Request *request);
void signalFence();
EventDispatcher *dispatcher_;
UniqueFD eventFd_;
UniqueFD eventFd2_;
Timer fenceTimer_;
std::vector<std::unique_ptr<Request>> requests_;
std::unique_ptr<CameraConfiguration> config_;
std::unique_ptr<FrameBufferAllocator> allocator_;
Stream *stream_;
bool expectedCompletionResult_ = true;
bool setFence_ = true;
/*
* Request IDs track the number of requests that have completed. They
* are one-based, and don't wrap.
*/
unsigned int completedRequestId_;
unsigned int signalledRequestId_;
unsigned int expiredRequestId_;
unsigned int nbuffers_;
int efd2_;
int efd_;
};
FenceTest::FenceTest()
: CameraTest("platform/vimc.0 Sensor B")
{
}
int FenceTest::init()
{
/* Make sure the CameraTest constructor succeeded. */
if (status_ != TestPass)
return status_;
dispatcher_ = Thread::current()->eventDispatcher();
/*
* Create two eventfds to model the fences. This is enough to support the
* needs of libcamera which only needs to wait for read events through
* poll(). Once native support for fences will be available in the
* backend kernel APIs this will need to be replaced by a sw_sync fence,
* but that requires debugfs.
*/
eventFd_ = UniqueFD(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
eventFd2_ = UniqueFD(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
if (!eventFd_.isValid() || !eventFd2_.isValid()) {
cerr << "Unable to create eventfd" << endl;
return TestFail;
}
efd_ = eventFd_.get();
efd2_ = eventFd2_.get();
config_ = camera_->generateConfiguration({ StreamRole::Viewfinder });
if (!config_ || config_->size() != 1) {
cerr << "Failed to generate default configuration" << endl;
return TestFail;
}
if (camera_->acquire()) {
cerr << "Failed to acquire the camera" << endl;
return TestFail;
}
if (camera_->configure(config_.get())) {
cerr << "Failed to set default configuration" << endl;
return TestFail;
}
StreamConfiguration &cfg = config_->at(0);
stream_ = cfg.stream();
allocator_ = std::make_unique<FrameBufferAllocator>(camera_);
if (allocator_->allocate(stream_) < 0)
return TestFail;
nbuffers_ = allocator_->buffers(stream_).size();
if (nbuffers_ < 2) {
cerr << "Not enough buffers available" << endl;
return TestFail;
}
completedRequestId_ = 0;
/*
* All but two requests are queued without a fence. Request
* expiredRequestId_ will be queued with a fence that we won't signal
* (which is then expected to expire), and request signalledRequestId_
* will be queued with a fence that gets signalled. Select nbuffers_
* and nbuffers_ * 2 for those two requests, to space them by a few
* frames while still not requiring a long time for the test to
* complete.
*/
expiredRequestId_ = nbuffers_;
signalledRequestId_ = nbuffers_ * 2;
return TestPass;
}
int FenceTest::validateExpiredRequest(Request *request)
{
/* The last request is expected to fail. */
if (request->status() != Request::RequestCancelled) {
cerr << "The last request should have failed: " << endl;
return TestFail;
}
FrameBuffer *buffer = request->buffers().begin()->second;
std::unique_ptr<Fence> fence = buffer->releaseFence();
if (!fence) {
cerr << "The expired fence should be present" << endl;
return TestFail;
}
if (!fence->isValid()) {
cerr << "The expired fence should be valid" << endl;
return TestFail;
}
UniqueFD fd = fence->release();
if (fd.get() != efd_) {
cerr << "The expired fence file descriptor should not change" << endl;
return TestFail;
}
return TestPass;
}
int FenceTest::validateRequest(Request *request)
{
uint64_t cookie = request->cookie();
/* All requests but the last are expected to succeed. */
if (request->status() != Request::RequestComplete) {
cerr << "Unexpected request failure: " << cookie << endl;
return TestFail;
}
/* A successfully completed request should have the Fence closed. */
const Request::BufferMap &buffers = request->buffers();
FrameBuffer *buffer = buffers.begin()->second;
std::unique_ptr<Fence> bufferFence = buffer->releaseFence();
if (bufferFence) {
cerr << "Unexpected valid fence in completed request" << endl;
return TestFail;
}
return TestPass;
}
void FenceTest::requestRequeue(Request *request)
{
const Request::BufferMap &buffers = request->buffers();
const Stream *stream = buffers.begin()->first;
FrameBuffer *buffer = buffers.begin()->second;
request->reuse();
if (completedRequestId_ == signalledRequestId_ - nbuffers_ && setFence_) {
/*
* This is the request that will be used to test fence
* signalling when it completes next time. Add a fence to it,
* using efd2_. The main loop will signal the fence by using a
* timer to write to the efd2_ file descriptor before the fence
* expires.
*/
std::unique_ptr<Fence> fence =
std::make_unique<Fence>(std::move(eventFd2_));
request->addBuffer(stream, buffer, std::move(fence));
} else {
/* All the other requests continue to operate without fences. */
request->addBuffer(stream, buffer);
}
camera_->queueRequest(request);
}
void FenceTest::requestComplete(Request *request)
{
completedRequestId_++;
/*
* Request expiredRequestId_ is expected to fail as its fence has not
* been signalled.
*
* Validate the fence status but do not re-queue it.
*/
if (completedRequestId_ == expiredRequestId_) {
if (validateExpiredRequest(request) != TestPass)
expectedCompletionResult_ = false;
dispatcher_->interrupt();
return;
}
/* Validate all other requests. */
if (validateRequest(request) != TestPass) {
expectedCompletionResult_ = false;
dispatcher_->interrupt();
return;
}
requestRequeue(request);
/*
* Interrupt the dispatcher to return control to the main loop and
* activate the fenceTimer.
*/
dispatcher_->interrupt();
}
/* Callback to signal a fence waiting on the eventfd file descriptor. */
void FenceTest::signalFence()
{
uint64_t value = 1;
int ret;
ret = write(efd2_, &value, sizeof(value));
if (ret != sizeof(value))
cerr << "Failed to signal fence" << endl;
setFence_ = false;
dispatcher_->processEvents();
}
int FenceTest::run()
{
for (const auto &[i, buffer] : utils::enumerate(allocator_->buffers(stream_))) {
std::unique_ptr<Request> request = camera_->createRequest(i);
if (!request) {
cerr << "Failed to create request" << endl;
return TestFail;
}
int ret;
if (i == expiredRequestId_ - 1) {
/* This request will have a fence, and it will expire. */
std::unique_ptr<Fence> fence =
std::make_unique<Fence>(std::move(eventFd_));
if (!fence->isValid()) {
cerr << "Fence should be valid" << endl;
return TestFail;
}
ret = request->addBuffer(stream_, buffer.get(), std::move(fence));
} else {
/* All other requests will have no Fence. */
ret = request->addBuffer(stream_, buffer.get());
}
if (ret) {
cerr << "Failed to associate buffer with request" << endl;
return TestFail;
}
requests_.push_back(std::move(request));
}
camera_->requestCompleted.connect(this, &FenceTest::requestComplete);
if (camera_->start()) {
cerr << "Failed to start camera" << endl;
return TestFail;
}
for (std::unique_ptr<Request> &request : requests_) {
if (camera_->queueRequest(request.get())) {
cerr << "Failed to queue request" << endl;
return TestFail;
}
}
expectedCompletionResult_ = true;
/* This timer serves to signal fences associated with "signalledRequestId_" */
Timer fenceTimer;
fenceTimer.timeout.connect(this, &FenceTest::signalFence);
/*
* Loop long enough for all requests to complete, allowing 500ms per
* request.
*/
Timer timer;
timer.start(500ms * (signalledRequestId_ + 1));
while (timer.isRunning() && expectedCompletionResult_ &&
completedRequestId_ <= signalledRequestId_ + 1) {
if (completedRequestId_ == signalledRequestId_ - 1 && setFence_)
/*
* The request just before signalledRequestId_ has just
* completed. Request signalledRequestId_ has been
* queued with a fence, and libcamera is likely already
* waiting on the fence, or will soon. Start the timer
* to signal the fence in 10 msec.
*/
fenceTimer.start(10ms);
dispatcher_->processEvents();
}
camera_->requestCompleted.disconnect();
if (camera_->stop()) {
cerr << "Failed to stop camera" << endl;
return TestFail;
}
return expectedCompletionResult_ ? TestPass : TestFail;
}
TEST_REGISTER(FenceTest)

View File

@@ -0,0 +1,383 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* File I/O operations tests
*/
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <libcamera/base/file.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class FileTest : public Test
{
protected:
int init()
{
fileName_ = "/tmp/libcamera.test.XXXXXX";
int fd = mkstemp(&fileName_.front());
if (fd == -1)
return TestFail;
close(fd);
unlink(fileName_.c_str());
return TestPass;
}
int run()
{
/* Test static functions. */
if (!File::exists("/dev/null")) {
cerr << "Valid file not found" << endl;
return TestFail;
}
if (File::exists("/dev/null/invalid")) {
cerr << "Invalid file should not exist" << endl;
return TestFail;
}
if (File::exists("/dev")) {
cerr << "Directories should not be treated as files" << endl;
return TestFail;
}
/* Test unnamed file. */
File file;
if (!file.fileName().empty()) {
cerr << "Unnamed file has non-empty file name" << endl;
return TestFail;
}
if (file.exists()) {
cerr << "Unnamed file exists" << endl;
return TestFail;
}
if (file.isOpen()) {
cerr << "File is open after construction" << endl;
return TestFail;
}
if (file.openMode() != File::OpenModeFlag::NotOpen) {
cerr << "File has invalid open mode after construction"
<< endl;
return TestFail;
}
if (file.size() >= 0) {
cerr << "Unnamed file has a size" << endl;
return TestFail;
}
if (file.open(File::OpenModeFlag::ReadWrite)) {
cerr << "Opening unnamed file succeeded" << endl;
return TestFail;
}
if (file.error() == 0) {
cerr << "Open failure didn't set error" << endl;
return TestFail;
}
/* Test named file referring to an invalid file. */
file.setFileName("/dev/null/invalid");
if (file.fileName() != "/dev/null/invalid") {
cerr << "File reports incorrect file name" << endl;
return TestFail;
}
if (file.exists()) {
cerr << "Invalid file exists" << endl;
return TestFail;
}
if (file.isOpen()) {
cerr << "Invalid file is open after construction" << endl;
return TestFail;
}
if (file.openMode() != File::OpenModeFlag::NotOpen) {
cerr << "Invalid file has invalid open mode after construction"
<< endl;
return TestFail;
}
if (file.size() >= 0) {
cerr << "Invalid file has a size" << endl;
return TestFail;
}
if (file.open(File::OpenModeFlag::ReadWrite)) {
cerr << "Opening invalid file succeeded" << endl;
return TestFail;
}
/* Test named file referring to a valid file. */
file.setFileName("/dev/null");
if (!file.exists()) {
cerr << "Valid file does not exist" << endl;
return TestFail;
}
if (file.isOpen()) {
cerr << "Valid file is open after construction" << endl;
return TestFail;
}
if (file.openMode() != File::OpenModeFlag::NotOpen) {
cerr << "Valid file has invalid open mode after construction"
<< endl;
return TestFail;
}
if (file.size() >= 0) {
cerr << "Invalid file has a size" << endl;
return TestFail;
}
/* Test open and close. */
if (!file.open(File::OpenModeFlag::ReadWrite)) {
cerr << "Opening file failed" << endl;
return TestFail;
}
if (!file.isOpen()) {
cerr << "Open file reported as closed" << endl;
return TestFail;
}
if (file.openMode() != File::OpenModeFlag::ReadWrite) {
cerr << "Open file has invalid open mode" << endl;
return TestFail;
}
file.close();
if (file.isOpen()) {
cerr << "Closed file reported as open" << endl;
return TestFail;
}
if (file.openMode() != File::OpenModeFlag::NotOpen) {
cerr << "Closed file has invalid open mode" << endl;
return TestFail;
}
/* Test size(). */
file.setFileName(self());
if (file.size() >= 0) {
cerr << "File has valid size before open" << endl;
return TestFail;
}
file.open(File::OpenModeFlag::ReadOnly);
ssize_t size = file.size();
if (size <= 0) {
cerr << "File has invalid size after open" << endl;
return TestFail;
}
file.close();
/* Test file creation. */
file.setFileName(fileName_);
if (file.exists()) {
cerr << "Temporary file already exists" << endl;
return TestFail;
}
if (file.open(File::OpenModeFlag::ReadOnly)) {
cerr << "Read-only open succeeded on nonexistent file" << endl;
return TestFail;
}
if (!file.open(File::OpenModeFlag::WriteOnly)) {
cerr << "Write-only open failed on nonexistent file" << endl;
return TestFail;
}
if (!file.exists()) {
cerr << "Write-only open failed to create file" << endl;
return TestFail;
}
file.close();
/* Test read and write. */
std::array<uint8_t, 256> buffer = { 0 };
strncpy(reinterpret_cast<char *>(buffer.data()), "libcamera",
buffer.size());
if (file.read(buffer) >= 0) {
cerr << "Read succeeded on closed file" << endl;
return TestFail;
}
if (file.write(buffer) >= 0) {
cerr << "Write succeeded on closed file" << endl;
return TestFail;
}
file.open(File::OpenModeFlag::ReadOnly);
if (file.write(buffer) >= 0) {
cerr << "Write succeeded on read-only file" << endl;
return TestFail;
}
file.close();
file.open(File::OpenModeFlag::ReadWrite);
if (file.write({ buffer.data(), 9 }) != 9) {
cerr << "Write test failed" << endl;
return TestFail;
}
if (file.read(buffer) != 0) {
cerr << "Read at end of file test failed" << endl;
return TestFail;
}
if (file.seek(0) != 0) {
cerr << "Seek test failed" << endl;
return TestFail;
}
if (file.read(buffer) != 9) {
cerr << "Read test failed" << endl;
return TestFail;
}
if (file.pos() != 9) {
cerr << "Position test failed" << endl;
return TestFail;
}
file.close();
/* Test mapping and unmapping. */
file.setFileName(self());
file.open(File::OpenModeFlag::ReadOnly);
Span<uint8_t> data = file.map();
if (data.empty()) {
cerr << "Mapping of complete file failed" << endl;
return TestFail;
}
if (data.size() != static_cast<size_t>(size)) {
cerr << "Mapping of complete file has invalid size" << endl;
return TestFail;
}
if (!file.unmap(data.data())) {
cerr << "Unmapping of complete file failed" << endl;
return TestFail;
}
data = file.map(4096, 8192);
if (data.empty()) {
cerr << "Mapping of file region failed" << endl;
return TestFail;
}
if (data.size() != 8192) {
cerr << "Mapping of file region has invalid size" << endl;
return TestFail;
}
if (!file.unmap(data.data())) {
cerr << "Unmapping of file region failed" << endl;
return TestFail;
}
file.close();
/* Test private mapping. */
file.setFileName(fileName_);
file.open(File::OpenModeFlag::ReadWrite);
data = file.map(0, -1, File::MapFlag::Private);
if (data.empty()) {
cerr << "Private mapping failed" << endl;
return TestFail;
}
std::string str{ reinterpret_cast<char *>(data.data()), data.size() };
if (str != "libcamera") {
cerr << "Invalid contents of private mapping" << endl;
return TestFail;
}
memcpy(data.data(), "LIBCAMERA", 9);
if (!file.unmap(data.data())) {
cerr << "Private unmapping failed" << endl;
return TestFail;
}
data = file.map();
str = { reinterpret_cast<char *>(data.data()), data.size() };
if (str != "libcamera") {
cerr << "Private mapping changed file contents" << endl;
return TestFail;
}
/* Test shared mapping. */
data = file.map();
if (data.empty()) {
cerr << "Shared mapping failed" << endl;
return TestFail;
}
memcpy(data.data(), "LIBCAMERA", 9);
if (!file.unmap(data.data())) {
cerr << "Shared unmapping failed" << endl;
return TestFail;
}
data = file.map();
str = { reinterpret_cast<char *>(data.data()), data.size() };
if (str != "LIBCAMERA") {
cerr << "Shared mapping failed to change file contents"
<< endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
unlink(fileName_.c_str());
}
private:
std::string fileName_;
};
TEST_REGISTER(FileTest)

View File

@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* Flags tests
*/
#include <iostream>
#include <libcamera/base/flags.h>
#include "test.h"
using namespace libcamera;
using namespace std;
class FlagsTest : public Test
{
protected:
enum class Option {
First = (1 << 0),
Second = (1 << 1),
Third = (1 << 2),
};
using Options = Flags<Option>;
enum class Mode {
Alpha = (1 << 0),
Beta = (1 << 1),
Gamma = (1 << 2),
};
using Modes = Flags<Mode>;
int run() override;
};
namespace libcamera {
LIBCAMERA_FLAGS_ENABLE_OPERATORS(FlagsTest::Option)
} /* namespace libcamera */
int FlagsTest::run()
{
/* Commented-out constructs are expected not to compile. */
/* Flags<int> f; */
/*
* Unary operators with enum argument.
*/
Options options;
if (options) {
cerr << "Default-constructed Flags<> is not zero" << endl;
return TestFail;
}
options |= Option::First;
if (!options || options != Option::First) {
cerr << "Unary bitwise OR with enum failed" << endl;
return TestFail;
}
/* options &= Mode::Alpha; */
/* options |= Mode::Beta; */
/* options ^= Mode::Gamma; */
options &= ~Option::First;
if (options) {
cerr << "Unary bitwise AND with enum failed" << endl;
return TestFail;
}
options ^= Option::Second;
if (options != Option::Second) {
cerr << "Unary bitwise XOR with enum failed" << endl;
return TestFail;
}
options = Options();
/*
* Unary operators with Flags argument.
*/
options |= Options(Option::First);
if (options != Option::First) {
cerr << "Unary bitwise OR with Flags<> failed" << endl;
return TestFail;
}
/* options &= Options(Mode::Alpha); */
/* options |= Options(Mode::Beta); */
/* options ^= Options(Mode::Gamma); */
options &= ~Options(Option::First);
if (options) {
cerr << "Unary bitwise AND with Flags<> failed" << endl;
return TestFail;
}
options ^= Options(Option::Second);
if (options != Option::Second) {
cerr << "Unary bitwise XOR with Flags<> failed" << endl;
return TestFail;
}
options = Options();
/*
* Binary operators with enum argument.
*/
options = options | Option::First;
if (!(options & Option::First)) {
cerr << "Binary bitwise OR with enum failed" << endl;
return TestFail;
}
/* options = options & Mode::Alpha; */
/* options = options | Mode::Beta; */
/* options = options ^ Mode::Gamma; */
options = options & ~Option::First;
if (options != (Option::First & Option::Second)) {
cerr << "Binary bitwise AND with enum failed" << endl;
return TestFail;
}
options = options ^ (Option::First ^ Option::Second);
if (options != (Option::First | Option::Second)) {
cerr << "Binary bitwise XOR with enum failed" << endl;
return TestFail;
}
options = Options();
/*
* Binary operators with Flags argument.
*/
options |= Options(Option::First);
if (!(options & Option::First)) {
cerr << "Binary bitwise OR with Flags<> failed" << endl;
return TestFail;
}
/* options = options & Options(Mode::Alpha); */
/* options = options | Options(Mode::Beta); */
/* options = options ^ Options(Mode::Gamma); */
options = options & ~Options(Option::First);
if (options) {
cerr << "Binary bitwise AND with Flags<> failed" << endl;
return TestFail;
}
options = options ^ Options(Option::Second);
if (options != Option::Second) {
cerr << "Binary bitwise XOR with Flags<> failed" << endl;
return TestFail;
}
options = Options();
/*
* Conversion operators.
*/
options |= Option::First | Option::Second | Option::Third;
if (static_cast<Options::Type>(options) != 7) {
cerr << "Cast to underlying type failed" << endl;
return TestFail;
}
/*
* Conversion of the result of ninary operators on the underlying enum.
*/
/* unsigned int val1 = Option::First; */
/* unsigned int val2 = ~Option::First; */
/* unsigned int val3 = Option::First | Option::Second; */
/* Option val4 = ~Option::First; */
/* Option val5 = Option::First | Option::Second; */
return TestPass;
}
TEST_REGISTER(FlagsTest)

View File

@@ -0,0 +1,488 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Geometry classes tests
*/
#include <iostream>
#include <libcamera/geometry.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class GeometryTest : public Test
{
protected:
template<typename T>
bool compare(const T &lhs, const T &rhs,
bool (*op)(const T &lhs, const T &rhs),
const char *opName, bool expect)
{
bool result = op(lhs, rhs);
if (result != expect) {
cout << lhs << opName << " " << rhs
<< "test failed" << std::endl;
return false;
}
return true;
}
int run()
{
/*
* Point tests
*/
/* Equality */
if (!compare(Point(50, 100), Point(50, 100), &operator==, "==", true))
return TestFail;
if (!compare(Point(-50, 100), Point(-50, 100), &operator==, "==", true))
return TestFail;
if (!compare(Point(50, -100), Point(50, -100), &operator==, "==", true))
return TestFail;
if (!compare(Point(-50, -100), Point(-50, -100), &operator==, "==", true))
return TestFail;
/* Inequality */
if (!compare(Point(50, 100), Point(50, 100), &operator!=, "!=", false))
return TestFail;
if (!compare(Point(-50, 100), Point(-50, 100), &operator!=, "!=", false))
return TestFail;
if (!compare(Point(50, -100), Point(50, -100), &operator!=, "!=", false))
return TestFail;
if (!compare(Point(-50, -100), Point(-50, -100), &operator!=, "!=", false))
return TestFail;
if (!compare(Point(-50, 100), Point(50, 100), &operator!=, "!=", true))
return TestFail;
if (!compare(Point(50, -100), Point(50, 100), &operator!=, "!=", true))
return TestFail;
if (!compare(Point(-50, -100), Point(50, 100), &operator!=, "!=", true))
return TestFail;
/* Negation */
if (Point(50, 100) != -Point(-50, -100) ||
Point(50, 100) == -Point(50, -100) ||
Point(50, 100) == -Point(-50, 100)) {
cout << "Point negation test failed" << endl;
return TestFail;
}
/* Default constructor */
if (Point() != Point(0, 0)) {
cout << "Default constructor test failed" << endl;
return TestFail;
}
/*
* Size tests
*/
if (!Size().isNull() || !Size(0, 0).isNull()) {
cout << "Null size incorrectly reported as not null" << endl;
return TestFail;
}
if (Size(0, 100).isNull() || Size(100, 0).isNull() || Size(100, 100).isNull()) {
cout << "Non-null size incorrectly reported as null" << endl;
return TestFail;
}
/*
* Test alignDownTo(), alignUpTo(), boundTo(), expandTo(),
* growBy() and shrinkBy()
*/
Size s(50, 50);
s.alignDownTo(16, 16);
if (s != Size(48, 48)) {
cout << "Size::alignDownTo() test failed" << endl;
return TestFail;
}
s.alignUpTo(32, 32);
if (s != Size(64, 64)) {
cout << "Size::alignUpTo() test failed" << endl;
return TestFail;
}
s.boundTo({ 40, 40 });
if (s != Size(40, 40)) {
cout << "Size::boundTo() test failed" << endl;
return TestFail;
}
s.expandTo({ 50, 50 });
if (s != Size(50, 50)) {
cout << "Size::expandTo() test failed" << endl;
return TestFail;
}
s.growBy({ 10, 20 });
if (s != Size(60, 70)) {
cout << "Size::growBy() test failed" << endl;
return TestFail;
}
s.shrinkBy({ 20, 10 });
if (s != Size(40, 60)) {
cout << "Size::shrinkBy() test failed" << endl;
return TestFail;
}
s.shrinkBy({ 100, 100 });
if (s != Size(0, 0)) {
cout << "Size::shrinkBy() clamp test failed" << endl;
return TestFail;
}
s = Size(50,50).alignDownTo(16, 16).alignUpTo(32, 32)
.boundTo({ 40, 80 }).expandTo({ 16, 80 })
.growBy({ 4, 4 }).shrinkBy({ 10, 20 });
if (s != Size(34, 64)) {
cout << "Size chained in-place modifiers test failed" << endl;
return TestFail;
}
/*
* Test alignedDownTo(), alignedUpTo(), boundedTo(),
* expandedTo(), grownBy() and shrunkBy()
*/
if (Size(0, 0).alignedDownTo(16, 8) != Size(0, 0) ||
Size(1, 1).alignedDownTo(16, 8) != Size(0, 0) ||
Size(16, 8).alignedDownTo(16, 8) != Size(16, 8)) {
cout << "Size::alignedDownTo() test failed" << endl;
return TestFail;
}
if (Size(0, 0).alignedUpTo(16, 8) != Size(0, 0) ||
Size(1, 1).alignedUpTo(16, 8) != Size(16, 8) ||
Size(16, 8).alignedUpTo(16, 8) != Size(16, 8)) {
cout << "Size::alignedUpTo() test failed" << endl;
return TestFail;
}
if (Size(0, 0).boundedTo({ 100, 100 }) != Size(0, 0) ||
Size(200, 50).boundedTo({ 100, 100 }) != Size(100, 50) ||
Size(50, 200).boundedTo({ 100, 100 }) != Size(50, 100)) {
cout << "Size::boundedTo() test failed" << endl;
return TestFail;
}
if (Size(0, 0).expandedTo({ 100, 100 }) != Size(100, 100) ||
Size(200, 50).expandedTo({ 100, 100 }) != Size(200, 100) ||
Size(50, 200).expandedTo({ 100, 100 }) != Size(100, 200)) {
cout << "Size::expandedTo() test failed" << endl;
return TestFail;
}
if (Size(0, 0).grownBy({ 10, 20 }) != Size(10, 20) ||
Size(200, 50).grownBy({ 10, 20 }) != Size(210, 70)) {
cout << "Size::grownBy() test failed" << endl;
return TestFail;
}
if (Size(200, 50).shrunkBy({ 10, 20 }) != Size(190, 30) ||
Size(200, 50).shrunkBy({ 10, 100 }) != Size(190, 0) ||
Size(200, 50).shrunkBy({ 300, 20 }) != Size(0, 30)) {
cout << "Size::shrunkBy() test failed" << endl;
return TestFail;
}
/* Aspect ratio tests */
if (Size(0, 0).boundedToAspectRatio(Size(4, 3)) != Size(0, 0) ||
Size(1920, 1440).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) ||
Size(1920, 1440).boundedToAspectRatio(Size(65536, 36864)) != Size(1920, 1080) ||
Size(1440, 1920).boundedToAspectRatio(Size(9, 16)) != Size(1080, 1920) ||
Size(1920, 1080).boundedToAspectRatio(Size(4, 3)) != Size(1440, 1080) ||
Size(1920, 1080).boundedToAspectRatio(Size(65536, 49152)) != Size(1440, 1080) ||
Size(1024, 1024).boundedToAspectRatio(Size(1, 1)) != Size(1024, 1024) ||
Size(1920, 1080).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) ||
Size(200, 100).boundedToAspectRatio(Size(16, 9)) != Size(177, 100) ||
Size(300, 200).boundedToAspectRatio(Size(16, 9)) != Size(300, 168)) {
cout << "Size::boundedToAspectRatio() test failed" << endl;
return TestFail;
}
if (Size(0, 0).expandedToAspectRatio(Size(4, 3)) != Size(0, 0) ||
Size(1920, 1440).expandedToAspectRatio(Size(16, 9)) != Size(2560, 1440) ||
Size(1920, 1440).expandedToAspectRatio(Size(65536, 36864)) != Size(2560, 1440) ||
Size(1440, 1920).expandedToAspectRatio(Size(9, 16)) != Size(1440, 2560) ||
Size(1920, 1080).expandedToAspectRatio(Size(4, 3)) != Size(1920, 1440) ||
Size(1920, 1080).expandedToAspectRatio(Size(65536, 49152)) != Size(1920, 1440) ||
Size(1024, 1024).expandedToAspectRatio(Size(1, 1)) != Size(1024, 1024) ||
Size(1920, 1080).expandedToAspectRatio(Size(16, 9)) != Size(1920, 1080) ||
Size(200, 100).expandedToAspectRatio(Size(16, 9)) != Size(200, 112) ||
Size(300, 200).expandedToAspectRatio(Size(16, 9)) != Size(355, 200)) {
cout << "Size::expandedToAspectRatio() test failed" << endl;
return TestFail;
}
/* Size::centeredTo() tests */
if (Size(0, 0).centeredTo(Point(50, 100)) != Rectangle(50, 100, 0, 0) ||
Size(0, 0).centeredTo(Point(-50, -100)) != Rectangle(-50, -100, 0, 0) ||
Size(100, 200).centeredTo(Point(50, 100)) != Rectangle(0, 0, 100, 200) ||
Size(100, 200).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 100, 200) ||
Size(101, 201).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 101, 201) ||
Size(101, 201).centeredTo(Point(-51, -101)) != Rectangle(-101, -201, 101, 201)) {
cout << "Size::centeredTo() test failed" << endl;
return TestFail;
}
/* Scale a size by a float */
if (Size(1000, 2000) * 2.0 != Size(2000, 4000) ||
Size(300, 100) * 0.5 != Size(150, 50) ||
Size(1, 2) * 1.6 != Size(1, 3)) {
cout << "Size::operator*() failed" << endl;
return TestFail;
}
if (Size(1000, 2000) / 2.0 != Size(500, 1000) ||
Size(300, 100) / 0.5 != Size(600, 200) ||
Size(1000, 2000) / 3.0 != Size(333, 666)) {
cout << "Size::operator*() failed" << endl;
return TestFail;
}
s = Size(300, 100);
s *= 0.3333;
if (s != Size(99, 33)) {
cout << "Size::operator*() test failed" << endl;
return TestFail;
}
s = Size(300, 100);
s /= 3;
if (s != Size(100, 33)) {
cout << "Size::operator*() test failed" << endl;
return TestFail;
}
/* Test Size equality and inequality. */
if (!compare(Size(100, 100), Size(100, 100), &operator==, "==", true))
return TestFail;
if (!compare(Size(100, 100), Size(100, 100), &operator!=, "!=", false))
return TestFail;
if (!compare(Size(100, 100), Size(200, 100), &operator==, "==", false))
return TestFail;
if (!compare(Size(100, 100), Size(200, 100), &operator!=, "!=", true))
return TestFail;
if (!compare(Size(100, 100), Size(100, 200), &operator==, "==", false))
return TestFail;
if (!compare(Size(100, 100), Size(100, 200), &operator!=, "!=", true))
return TestFail;
/* Test Size ordering based on combined with and height. */
if (!compare(Size(100, 100), Size(200, 200), &operator<, "<", true))
return TestFail;
if (!compare(Size(100, 100), Size(200, 200), &operator<=, "<=", true))
return TestFail;
if (!compare(Size(100, 100), Size(200, 200), &operator>, ">", false))
return TestFail;
if (!compare(Size(100, 100), Size(200, 200), &operator>=, ">=", false))
return TestFail;
if (!compare(Size(200, 200), Size(100, 100), &operator<, "<", false))
return TestFail;
if (!compare(Size(200, 200), Size(100, 100), &operator<=, "<=", false))
return TestFail;
if (!compare(Size(200, 200), Size(100, 100), &operator>, ">", true))
return TestFail;
if (!compare(Size(200, 200), Size(100, 100), &operator>=, ">=", true))
return TestFail;
/* Test Size ordering based on area (with overlapping sizes). */
if (!compare(Size(200, 100), Size(100, 400), &operator<, "<", true))
return TestFail;
if (!compare(Size(200, 100), Size(100, 400), &operator<=, "<=", true))
return TestFail;
if (!compare(Size(200, 100), Size(100, 400), &operator>, ">", false))
return TestFail;
if (!compare(Size(200, 100), Size(100, 400), &operator>=, ">=", false))
return TestFail;
if (!compare(Size(100, 400), Size(200, 100), &operator<, "<", false))
return TestFail;
if (!compare(Size(100, 400), Size(200, 100), &operator<=, "<=", false))
return TestFail;
if (!compare(Size(100, 400), Size(200, 100), &operator>, ">", true))
return TestFail;
if (!compare(Size(100, 400), Size(200, 100), &operator>=, ">=", true))
return TestFail;
/* Test Size ordering based on width (with identical areas). */
if (!compare(Size(100, 200), Size(200, 100), &operator<, "<", true))
return TestFail;
if (!compare(Size(100, 200), Size(200, 100), &operator<=, "<=", true))
return TestFail;
if (!compare(Size(100, 200), Size(200, 100), &operator>, ">", false))
return TestFail;
if (!compare(Size(100, 200), Size(200, 100), &operator>=, ">=", false))
return TestFail;
if (!compare(Size(200, 100), Size(100, 200), &operator<, "<", false))
return TestFail;
if (!compare(Size(200, 100), Size(100, 200), &operator<=, "<=", false))
return TestFail;
if (!compare(Size(200, 100), Size(100, 200), &operator>, ">", true))
return TestFail;
if (!compare(Size(200, 100), Size(100, 200), &operator>=, ">=", true))
return TestFail;
/*
* Rectangle tests
*/
/* Test Rectangle::isNull(). */
if (!Rectangle(0, 0, 0, 0).isNull() ||
!Rectangle(1, 1, 0, 0).isNull()) {
cout << "Null rectangle incorrectly reported as not null" << endl;
return TestFail;
}
if (Rectangle(0, 0, 0, 1).isNull() ||
Rectangle(0, 0, 1, 0).isNull() ||
Rectangle(0, 0, 1, 1).isNull()) {
cout << "Non-null rectangle incorrectly reported as null" << endl;
return TestFail;
}
/* Rectangle::size(), Rectangle::topLeft() and Rectangle::center() tests */
if (Rectangle(-1, -2, 3, 4).size() != Size(3, 4) ||
Rectangle(0, 0, 100000, 200000).size() != Size(100000, 200000)) {
cout << "Rectangle::size() test failed" << endl;
return TestFail;
}
if (Rectangle(1, 2, 3, 4).topLeft() != Point(1, 2) ||
Rectangle(-1, -2, 3, 4).topLeft() != Point(-1, -2)) {
cout << "Rectangle::topLeft() test failed" << endl;
return TestFail;
}
if (Rectangle(0, 0, 300, 400).center() != Point(150, 200) ||
Rectangle(-1000, -2000, 300, 400).center() != Point(-850, -1800) ||
Rectangle(10, 20, 301, 401).center() != Point(160, 220) ||
Rectangle(11, 21, 301, 401).center() != Point(161, 221) ||
Rectangle(-1011, -2021, 301, 401).center() != Point(-861, -1821)) {
cout << "Rectangle::center() test failed" << endl;
return TestFail;
}
/* Rectangle::boundedTo() (intersection function) */
if (Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
Rectangle(0, 0, 1000, 2000) ||
Rectangle(-500, -1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
Rectangle(0, 0, 500, 1000) ||
Rectangle(500, 1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
Rectangle(500, 1000, 500, 1000) ||
Rectangle(300, 400, 50, 100).boundedTo(Rectangle(0, 0, 1000, 2000)) !=
Rectangle(300, 400, 50, 100) ||
Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(300, 400, 50, 100)) !=
Rectangle(300, 400, 50, 100) ||
Rectangle(0, 0, 100, 100).boundedTo(Rectangle(50, 100, 100, 100)) !=
Rectangle(50, 100, 50, 0) ||
Rectangle(0, 0, 100, 100).boundedTo(Rectangle(100, 50, 100, 100)) !=
Rectangle(100, 50, 0, 50) ||
Rectangle(-10, -20, 10, 20).boundedTo(Rectangle(10, 20, 100, 100)) !=
Rectangle(10, 20, 0, 0)) {
cout << "Rectangle::boundedTo() test failed" << endl;
return TestFail;
}
/* Rectangle::enclosedIn() tests */
if (Rectangle(10, 20, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
Rectangle(10, 20, 300, 400) ||
Rectangle(-100, -200, 3000, 4000).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
Rectangle(-10, -20, 1300, 1400) ||
Rectangle(-100, -200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
Rectangle(-10, -20, 300, 400) ||
Rectangle(5100, 6200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) !=
Rectangle(990, 980, 300, 400) ||
Rectangle(100, -300, 150, 200).enclosedIn(Rectangle(50, 0, 200, 300)) !=
Rectangle(100, 0, 150, 200) ||
Rectangle(100, -300, 150, 1200).enclosedIn(Rectangle(50, 0, 200, 300)) !=
Rectangle(100, 0, 150, 300) ||
Rectangle(-300, 100, 200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) !=
Rectangle(0, 100, 200, 150) ||
Rectangle(-300, 100, 1200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) !=
Rectangle(0, 100, 300, 150)) {
cout << "Rectangle::enclosedIn() test failed" << endl;
return TestFail;
}
/* Rectange::scaledBy() tests */
if (Rectangle(10, 20, 300, 400).scaledBy(Size(0, 0), Size(1, 1)) !=
Rectangle(0, 0, 0, 0) ||
Rectangle(10, -20, 300, 400).scaledBy(Size(32768, 65536), Size(32768, 32768)) !=
Rectangle(10, -40, 300, 800) ||
Rectangle(-30000, 10000, 20000, 20000).scaledBy(Size(7, 7), Size(7, 7)) !=
Rectangle(-30000, 10000, 20000, 20000) ||
Rectangle(-20, -30, 320, 240).scaledBy(Size(1280, 960), Size(640, 480)) !=
Rectangle(-40, -60, 640, 480) ||
Rectangle(1, 1, 2026, 1510).scaledBy(Size(4056, 3024), Size(2028, 1512)) !=
Rectangle(2, 2, 4052, 3020)) {
cout << "Rectangle::scaledBy() test failed" << endl;
return TestFail;
}
/* Rectangle::translatedBy() tests */
if (Rectangle(10, -20, 300, 400).translatedBy(Point(-30, 40)) !=
Rectangle(-20, 20, 300, 400) ||
Rectangle(-10, 20, 400, 300).translatedBy(Point(50, -60)) !=
Rectangle(40, -40, 400, 300)) {
cout << "Rectangle::translatedBy() test failed" << endl;
return TestFail;
}
/* Rectangle::scaleBy() tests */
Rectangle r(-20, -30, 320, 240);
r.scaleBy(Size(1280, 960), Size(640, 480));
if (r != Rectangle(-40, -60, 640, 480)) {
cout << "Rectangle::scaleBy() test failed" << endl;
return TestFail;
}
r = Rectangle(1, 1, 2026, 1510);
r.scaleBy(Size(4056, 3024), Size(2028, 1512));
if (r != Rectangle(2, 2, 4052, 3020)) {
cout << "Rectangle::scaleBy() test failed" << endl;
return TestFail;
}
/* Rectangle::translateBy() tests */
r = Rectangle(10, -20, 300, 400);
r.translateBy(Point(-30, 40));
if (r != Rectangle(-20, 20, 300, 400)) {
cout << "Rectangle::translateBy() test failed" << endl;
return TestFail;
}
r = Rectangle(-10, 20, 400, 300);
r.translateBy(Point(50, -60));
if (r != Rectangle(40, -40, 400, 300)) {
cout << "Rectangle::translateBy() test failed" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(GeometryTest)

View File

@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2023, Umang Jain <umang.jain@ideasonboard.com>
*
* GStreamer single stream capture test
*/
#include <vector>
#include <libcamera/libcamera.h>
#include <gst/gst.h>
#include "gstreamer_test.h"
#include "test.h"
using namespace std;
class GstreamerDeviceProviderTest : public GstreamerTest, public Test
{
public:
GstreamerDeviceProviderTest()
: GstreamerTest()
{
}
protected:
int init() override
{
if (status_ != TestPass)
return status_;
return TestPass;
}
int run() override
{
g_autoptr(GstDeviceProvider) provider = NULL;
GList *devices, *l;
std::vector<std::string> cameraNames;
std::unique_ptr<libcamera::CameraManager> cm;
cm = std::make_unique<libcamera::CameraManager>();
cm->start();
for (auto &camera : cm->cameras())
cameraNames.push_back(camera->id());
cm->stop();
cm.reset();
provider = gst_device_provider_factory_get_by_name("libcameraprovider");
devices = gst_device_provider_get_devices(provider);
for (l = devices; l != NULL; l = g_list_next(l)) {
GstDevice *device = GST_DEVICE(l->data);
g_autofree gchar *gst_name;
bool matched = false;
g_autoptr(GstElement) element = gst_device_create_element(device, NULL);
g_object_get(element, "camera-name", &gst_name, NULL);
for (auto name : cameraNames) {
if (strcmp(name.c_str(), gst_name) == 0) {
matched = true;
break;
}
}
if (!matched)
return TestFail;
}
g_list_free_full(devices, (GDestroyNotify)gst_object_unref);
return TestPass;
}
};
TEST_REGISTER(GstreamerDeviceProviderTest)

View File

@@ -0,0 +1,111 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Vedant Paranjape
*
* GStreamer multi stream capture test
*/
#include <iostream>
#include <unistd.h>
#include <libcamera/libcamera.h>
#include <gst/gst.h>
#include "gstreamer_test.h"
#include "test.h"
#if !GST_CHECK_VERSION(1, 19, 1)
static inline GstPad *gst_element_request_pad_simple(GstElement *element,
const gchar *name)
{
return gst_element_get_request_pad(element, name);
}
#endif
using namespace std;
class GstreamerMultiStreamTest : public GstreamerTest, public Test
{
public:
GstreamerMultiStreamTest()
: GstreamerTest(2)
{
}
protected:
int init() override
{
if (status_ != TestPass)
return status_;
const gchar *streamDescription = "queue ! fakesink";
g_autoptr(GError) error = NULL;
stream0_ = gst_parse_bin_from_description_full(streamDescription, TRUE,
NULL,
GST_PARSE_FLAG_FATAL_ERRORS,
&error);
if (!stream0_) {
g_printerr("Stream0 could not be created (%s)\n", error->message);
return TestFail;
}
g_object_ref_sink(stream0_);
stream1_ = gst_parse_bin_from_description_full(streamDescription, TRUE,
NULL,
GST_PARSE_FLAG_FATAL_ERRORS,
&error);
if (!stream1_) {
g_printerr("Stream1 could not be created (%s)\n", error->message);
return TestFail;
}
g_object_ref_sink(stream1_);
if (createPipeline() != TestPass)
return TestFail;
return TestPass;
}
int run() override
{
/* Build the pipeline */
gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
stream0_, stream1_, NULL);
g_autoptr(GstPad) src_pad = gst_element_get_static_pad(libcameraSrc_, "src");
g_autoptr(GstPad) request_pad = gst_element_request_pad_simple(libcameraSrc_, "src_%u");
{
g_autoptr(GstPad) queue0_sink_pad = gst_element_get_static_pad(stream0_, "sink");
g_autoptr(GstPad) queue1_sink_pad = gst_element_get_static_pad(stream1_, "sink");
if (gst_pad_link(src_pad, queue0_sink_pad) != GST_PAD_LINK_OK ||
gst_pad_link(request_pad, queue1_sink_pad) != GST_PAD_LINK_OK) {
g_printerr("Pads could not be linked.\n");
return TestFail;
}
}
if (startPipeline() != TestPass)
return TestFail;
if (processEvent() != TestPass)
return TestFail;
return TestPass;
}
void cleanup() override
{
g_clear_object(&stream0_);
g_clear_object(&stream1_);
}
private:
GstElement *stream0_;
GstElement *stream1_;
};
TEST_REGISTER(GstreamerMultiStreamTest)

View File

@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Vedant Paranjape
*
* GStreamer single stream capture test
*/
#include <iostream>
#include <unistd.h>
#include <gst/gst.h>
#include "gstreamer_test.h"
#include "test.h"
using namespace std;
class GstreamerSingleStreamTest : public GstreamerTest, public Test
{
public:
GstreamerSingleStreamTest()
: GstreamerTest()
{
}
protected:
int init() override
{
if (status_ != TestPass)
return status_;
fakesink_ = gst_element_factory_make("fakesink", nullptr);
if (!fakesink_) {
g_printerr("Your installation is missing 'fakesink'\n");
return TestFail;
}
g_object_ref_sink(fakesink_);
return createPipeline();
}
int run() override
{
/* Build the pipeline */
gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, fakesink_, nullptr);
if (!gst_element_link(libcameraSrc_, fakesink_)) {
g_printerr("Elements could not be linked.\n");
return TestFail;
}
if (startPipeline() != TestPass)
return TestFail;
if (processEvent() != TestPass)
return TestFail;
return TestPass;
}
void cleanup() override
{
g_clear_object(&fakesink_);
}
private:
GstElement *fakesink_;
};
TEST_REGISTER(GstreamerSingleStreamTest)

View File

@@ -0,0 +1,174 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Vedant Paranjape
*
* libcamera Gstreamer element API tests
*/
#include <libcamera/libcamera.h>
#include <libcamera/base/utils.h>
#if HAVE_ASAN
#include <sanitizer/asan_interface.h>
#endif
#include "gstreamer_test.h"
#include "test.h"
using namespace std;
#if HAVE_ASAN
extern "C" {
const char *__asan_default_options()
{
/*
* Disable leak detection due to a known global variable initialization
* leak in glib's g_quark_init(). This should ideally be handled by
* using a suppression file instead of disabling leak detection.
*/
return "detect_leaks=false";
}
}
#endif
GstreamerTest::GstreamerTest(unsigned int numStreams)
: pipeline_(nullptr), libcameraSrc_(nullptr)
{
/*
* GStreamer by default spawns a process to run the gst-plugin-scanner
* helper. If libcamera is compiled with ASan enabled, and as GStreamer
* is most likely not, this causes the ASan link order check to fail
* when gst-plugin-scanner dlopen()s the plugin as many libraries will
* have already been loaded by then. Fix this issue by disabling
* spawning of a child helper process when scanning the build directory
* for plugins.
*/
gst_registry_fork_set_enabled(false);
/* Initialize GStreamer */
g_autoptr(GError) errInit = NULL;
if (!gst_init_check(nullptr, nullptr, &errInit)) {
g_printerr("Could not initialize GStreamer: %s\n",
errInit ? errInit->message : "unknown error");
status_ = TestFail;
return;
}
/*
* Atleast one camera should be available with numStreams streams,
* otherwise skip the test entirely.
*/
if (!checkMinCameraStreamsAndSetCameraName(numStreams)) {
status_ = TestSkip;
return;
}
status_ = TestPass;
}
bool GstreamerTest::checkMinCameraStreamsAndSetCameraName(unsigned int numStreams)
{
libcamera::CameraManager cm;
bool cameraFound = false;
cm.start();
for (auto &camera : cm.cameras()) {
if (camera->streams().size() < numStreams)
continue;
cameraFound = true;
cameraName_ = camera->id();
break;
}
cm.stop();
return cameraFound;
}
GstreamerTest::~GstreamerTest()
{
g_clear_object(&pipeline_);
g_clear_object(&libcameraSrc_);
gst_deinit();
}
int GstreamerTest::createPipeline()
{
libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");
pipeline_ = gst_pipeline_new("test-pipeline");
if (!libcameraSrc_ || !pipeline_) {
g_printerr("Unable to create pipeline %p.%p\n",
libcameraSrc_, pipeline_);
return TestFail;
}
g_object_set(libcameraSrc_, "camera-name", cameraName_.c_str(), NULL);
g_object_ref_sink(libcameraSrc_);
return TestPass;
}
int GstreamerTest::startPipeline()
{
GstStateChangeReturn ret;
/* Start playing */
ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr("Unable to set the pipeline to the playing state.\n");
return TestFail;
}
return TestPass;
}
int GstreamerTest::processEvent()
{
/* Wait until error or EOS or timeout after 2 seconds */
constexpr GstMessageType msgType =
static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
constexpr GstClockTime timeout = 2 * GST_SECOND;
g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);
g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);
gst_element_set_state(pipeline_, GST_STATE_NULL);
/* Parse error message */
if (msg == NULL)
return TestPass;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
printError(msg);
break;
case GST_MESSAGE_EOS:
g_print("End-Of-Stream reached.\n");
break;
default:
g_printerr("Unexpected message received.\n");
break;
}
return TestFail;
}
void GstreamerTest::printError(GstMessage *msg)
{
g_autoptr(GError) err = NULL;
g_autofree gchar *debug_info = NULL;
gst_message_parse_error(msg, &err, &debug_info);
g_printerr("Error received from element %s: %s\n",
GST_OBJECT_NAME(msg->src), err->message);
g_printerr("Debugging information: %s\n",
debug_info ? debug_info : "none");
}

View File

@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Vedant Paranjape
*
* GStreamer test base class
*/
#pragma once
#include <iostream>
#include <unistd.h>
#include <gst/gst.h>
class GstreamerTest
{
public:
GstreamerTest(unsigned int numStreams = 1);
virtual ~GstreamerTest();
protected:
virtual int createPipeline();
int startPipeline();
int processEvent();
void printError(GstMessage *msg);
std::string cameraName_;
GstElement *pipeline_;
GstElement *libcameraSrc_;
int status_;
private:
bool checkMinCameraStreamsAndSetCameraName(unsigned int numStreams);
};

View File

@@ -0,0 +1,28 @@
# SPDX-License-Identifier: CC0-1.0
if not gst_enabled
subdir_done()
endif
gstreamer_tests = [
{'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']},
{'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']},
{'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']},
]
gstreamer_dep = dependency('gstreamer-1.0', required : true)
gstreamer_test_args = []
if asan_enabled
gstreamer_test_args += ['-D', 'HAVE_ASAN=1']
endif
foreach test : gstreamer_tests
exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp',
cpp_args : gstreamer_test_args,
dependencies : [libcamera_private, gstreamer_dep],
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'gstreamer', is_parallel : false, env : gst_env)
endforeach

View File

@@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Umang Jain <email@uajain.com>
*
* Test cameraAdded/cameraRemoved signals in CameraManager
*/
#include <dirent.h>
#include <fstream>
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/file.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "test.h"
using namespace libcamera;
using namespace std::chrono_literals;
class HotplugTest : public Test
{
protected:
void cameraAddedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
{
cameraAdded_ = true;
}
void cameraRemovedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
{
cameraRemoved_ = true;
}
int init()
{
if (!File::exists("/sys/module/uvcvideo")) {
std::cout << "uvcvideo driver is not loaded, skipping" << std::endl;
return TestSkip;
}
if (geteuid() != 0) {
std::cout << "This test requires root permissions, skipping" << std::endl;
return TestSkip;
}
cm_ = new CameraManager();
if (cm_->start()) {
std::cout << "Failed to start camera manager" << std::endl;
return TestFail;
}
cameraAdded_ = false;
cameraRemoved_ = false;
cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);
return 0;
}
int run()
{
DIR *dir;
struct dirent *dirent;
std::string uvcDeviceDir;
dir = opendir(uvcDriverDir_.c_str());
/* Find a UVC device directory, which we can bind/unbind. */
while ((dirent = readdir(dir)) != nullptr) {
if (!File::exists(uvcDriverDir_ + dirent->d_name + "/video4linux"))
continue;
uvcDeviceDir = dirent->d_name;
break;
}
closedir(dir);
/* If no UVC device found, skip the test. */
if (uvcDeviceDir.empty())
return TestSkip;
/* Unbind a camera and process events. */
std::ofstream(uvcDriverDir_ + "unbind", std::ios::binary)
<< uvcDeviceDir;
Timer timer;
timer.start(1000ms);
while (timer.isRunning() && !cameraRemoved_)
Thread::current()->eventDispatcher()->processEvents();
if (!cameraRemoved_) {
std::cout << "Camera unplug not detected" << std::endl;
return TestFail;
}
/* Bind the camera again and process events. */
std::ofstream(uvcDriverDir_ + "bind", std::ios::binary)
<< uvcDeviceDir;
timer.start(1000ms);
while (timer.isRunning() && !cameraAdded_)
Thread::current()->eventDispatcher()->processEvents();
if (!cameraAdded_) {
std::cout << "Camera plug not detected" << std::endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
cm_->stop();
delete cm_;
}
private:
CameraManager *cm_;
static const std::string uvcDriverDir_;
bool cameraRemoved_;
bool cameraAdded_;
};
const std::string HotplugTest::uvcDriverDir_ = "/sys/bus/usb/drivers/uvcvideo/";
TEST_REGISTER(HotplugTest)

View File

@@ -0,0 +1,184 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Test the IPA interface
*/
#include <fcntl.h>
#include <iostream>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <libcamera/ipa/vimc_ipa_proxy.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/ipa_module.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/process.h"
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
class IPAInterfaceTest : public Test, public Object
{
public:
IPAInterfaceTest()
: trace_(ipa::vimc::IPAOperationNone), notifier_(nullptr), fd_(-1)
{
}
~IPAInterfaceTest()
{
delete notifier_;
ipa_.reset();
ipaManager_.reset();
}
protected:
int init() override
{
ipaManager_ = make_unique<IPAManager>();
/* Create a pipeline handler for vimc. */
const std::vector<PipelineHandlerFactoryBase *> &factories =
PipelineHandlerFactoryBase::factories();
for (const PipelineHandlerFactoryBase *factory : factories) {
if (factory->name() == "vimc") {
pipe_ = factory->create(nullptr);
break;
}
}
if (!pipe_) {
cerr << "Vimc pipeline not found" << endl;
return TestPass;
}
/* Create and open the communication FIFO. */
int ret = mkfifo(ipa::vimc::VimcIPAFIFOPath.c_str(), S_IRUSR | S_IWUSR);
if (ret) {
ret = errno;
cerr << "Failed to create IPA test FIFO at '"
<< ipa::vimc::VimcIPAFIFOPath << "': " << strerror(ret)
<< endl;
return TestFail;
}
ret = open(ipa::vimc::VimcIPAFIFOPath.c_str(), O_RDONLY | O_NONBLOCK);
if (ret < 0) {
ret = errno;
cerr << "Failed to open IPA test FIFO at '"
<< ipa::vimc::VimcIPAFIFOPath << "': " << strerror(ret)
<< endl;
unlink(ipa::vimc::VimcIPAFIFOPath.c_str());
return TestFail;
}
fd_ = ret;
notifier_ = new EventNotifier(fd_, EventNotifier::Read, this);
notifier_->activated.connect(this, &IPAInterfaceTest::readTrace);
return TestPass;
}
int run() override
{
EventDispatcher *dispatcher = thread()->eventDispatcher();
Timer timer;
ipa_ = IPAManager::createIPA<ipa::vimc::IPAProxyVimc>(pipe_.get(), 0, 0);
if (!ipa_) {
cerr << "Failed to create VIMC IPA interface" << endl;
return TestFail;
}
/* Test initialization of IPA module. */
std::string conf = ipa_->configurationFile("vimc.conf");
Flags<ipa::vimc::TestFlag> inFlags;
Flags<ipa::vimc::TestFlag> outFlags;
int ret = ipa_->init(IPASettings{ conf, "vimc" },
ipa::vimc::IPAOperationInit,
inFlags, &outFlags);
if (ret < 0) {
cerr << "IPA interface init() failed" << endl;
return TestFail;
}
timer.start(1000ms);
while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationInit)
dispatcher->processEvents();
if (trace_ != ipa::vimc::IPAOperationInit) {
cerr << "Failed to test IPA initialization sequence"
<< endl;
return TestFail;
}
/* Test start of IPA module. */
ipa_->start();
timer.start(1000ms);
while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationStart)
dispatcher->processEvents();
if (trace_ != ipa::vimc::IPAOperationStart) {
cerr << "Failed to test IPA start sequence" << endl;
return TestFail;
}
/* Test stop of IPA module. */
ipa_->stop();
timer.start(1000ms);
while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationStop)
dispatcher->processEvents();
if (trace_ != ipa::vimc::IPAOperationStop) {
cerr << "Failed to test IPA stop sequence" << endl;
return TestFail;
}
return TestPass;
}
void cleanup() override
{
close(fd_);
unlink(ipa::vimc::VimcIPAFIFOPath.c_str());
}
private:
void readTrace()
{
ssize_t s = read(notifier_->fd(), &trace_, sizeof(trace_));
if (s < 0) {
int ret = errno;
cerr << "Failed to read from IPA test FIFO at '"
<< ipa::vimc::VimcIPAFIFOPath << "': " << strerror(ret)
<< endl;
trace_ = ipa::vimc::IPAOperationNone;
}
}
ProcessManager processManager_;
std::shared_ptr<PipelineHandler> pipe_;
std::unique_ptr<ipa::vimc::IPAProxyVimc> ipa_;
std::unique_ptr<IPAManager> ipaManager_;
enum ipa::vimc::IPAOperationCode trace_;
EventNotifier *notifier_;
int fd_;
};
TEST_REGISTER(IPAInterfaceTest)

View File

@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Test loading of the VIMC IPA module and verify its info
*/
#include <iostream>
#include <string.h>
#include "libcamera/internal/ipa_module.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class IPAModuleTest : public Test
{
protected:
int runTest(const string &path, const struct IPAModuleInfo &testInfo)
{
int ret = 0;
IPAModule *ll = new IPAModule(path);
if (!ll->isValid()) {
cerr << "test IPA module " << path << " is invalid"
<< endl;
delete ll;
return -1;
}
const struct IPAModuleInfo &info = ll->info();
if (memcmp(&info, &testInfo, sizeof(info))) {
cerr << "IPA module information mismatch: expected:" << endl
<< "moduleAPIVersion = " << testInfo.moduleAPIVersion << endl
<< "pipelineVersion = " << testInfo.pipelineVersion << endl
<< "pipelineName = " << testInfo.pipelineName << endl
<< "name = " << testInfo.name
<< "got: " << endl
<< "moduleAPIVersion = " << info.moduleAPIVersion << endl
<< "pipelineVersion = " << info.pipelineVersion << endl
<< "pipelineName = " << info.pipelineName << endl
<< "name = " << info.name << endl;
}
delete ll;
return ret;
}
int run() override
{
int count = 0;
const struct IPAModuleInfo testInfo = {
IPA_MODULE_API_VERSION,
0,
"vimc",
"vimc",
};
count += runTest("src/ipa/vimc/ipa_vimc.so", testInfo);
if (count < 0)
return TestFail;
return TestPass;
}
};
TEST_REGISTER(IPAModuleTest)

View File

@@ -0,0 +1,17 @@
# SPDX-License-Identifier: CC0-1.0
subdir('rkisp1')
ipa_test = [
{'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']},
{'name': 'ipa_interface_test', 'sources': ['ipa_interface_test.cpp']},
]
foreach test : ipa_test
exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers,
dependencies : [libcamera_private, libipa_dep],
link_with : [test_libraries],
include_directories : [test_includes_internal])
test(test['name'], exe, suite : 'ipa')
endforeach

View File

@@ -0,0 +1,15 @@
# SPDX-License-Identifier: CC0-1.0
rkisp1_ipa_test = [
{'name': 'rkisp1-utils', 'sources': ['rkisp1-utils.cpp']},
]
foreach test : rkisp1_ipa_test
exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers,
dependencies : [libcamera_private, libipa_dep],
link_with : [test_libraries],
include_directories : [test_includes_internal,
'../../../src/ipa/rkisp1/'])
test(test['name'], exe, suite : 'ipa')
endforeach

View File

@@ -0,0 +1,108 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
*
* Miscellaneous utility tests
*/
#include <cmath>
#include <iostream>
#include <map>
#include <stdint.h>
#include "../src/ipa/rkisp1/utils.h"
#include "test.h"
using namespace std;
using namespace libcamera;
using namespace ipa::rkisp1;
class RkISP1UtilsTest : public Test
{
protected:
/* R for real, I for integer */
template<unsigned int IntPrec, unsigned int FracPrec, typename I, typename R>
int testFixedToFloat(I input, R expected)
{
R out = utils::fixedToFloatingPoint<IntPrec, FracPrec, R>(input);
R prec = 1.0 / (1 << FracPrec);
if (std::abs(out - expected) > prec) {
cerr << "Reverse conversion expected " << input
<< " to convert to " << expected
<< ", got " << out << std::endl;
return TestFail;
}
return TestPass;
}
template<unsigned int IntPrec, unsigned int FracPrec, typename T>
int testSingleFixedPoint(double input, T expected)
{
T ret = utils::floatingToFixedPoint<IntPrec, FracPrec, T>(input);
if (ret != expected) {
cerr << "Expected " << input << " to convert to "
<< expected << ", got " << ret << std::endl;
return TestFail;
}
/*
* The precision check is fairly arbitrary but is based on what
* the rkisp1 is capable of in the crosstalk module.
*/
double f = utils::fixedToFloatingPoint<IntPrec, FracPrec, double>(ret);
if (std::abs(f - input) > 0.005) {
cerr << "Reverse conversion expected " << ret
<< " to convert to " << input
<< ", got " << f << std::endl;
return TestFail;
}
return TestPass;
}
int testFixedPoint()
{
/*
* The second 7.992 test is to test that unused bits don't
* affect the result.
*/
std::map<double, uint16_t> testCases = {
{ 7.992, 0x3ff },
{ 0.2, 0x01a },
{ -0.2, 0x7e6 },
{ -0.8, 0x79a },
{ -0.4, 0x7cd },
{ -1.4, 0x74d },
{ -8, 0x400 },
{ 0, 0 },
};
int ret;
for (const auto &testCase : testCases) {
ret = testSingleFixedPoint<4, 7, uint16_t>(testCase.first,
testCase.second);
if (ret != TestPass)
return ret;
}
/* Special case with a superfluous one in the unused bits */
ret = testFixedToFloat<4, 7, uint16_t, double>(0xbff, 7.992);
if (ret != TestPass)
return ret;
return TestPass;
}
int run()
{
/* fixed point conversion test */
if (testFixedPoint() != TestPass)
return TestFail;
return TestPass;
}
};
TEST_REGISTER(RkISP1UtilsTest)

View File

@@ -0,0 +1,15 @@
# SPDX-License-Identifier: CC0-1.0
ipc_tests = [
{'name': 'unixsocket_ipc', 'sources': ['unixsocket_ipc.cpp']},
{'name': 'unixsocket', 'sources': ['unixsocket.cpp']},
]
foreach test : ipc_tests
exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'ipc')
endforeach

View File

@@ -0,0 +1,511 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Unix socket IPC test
*/
#include <algorithm>
#include <array>
#include <fcntl.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "libcamera/internal/ipc_unixsocket.h"
#include "test.h"
#define CMD_CLOSE 0
#define CMD_REVERSE 1
#define CMD_LEN_CALC 2
#define CMD_LEN_CMP 3
#define CMD_JOIN 4
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
namespace {
int calculateLength(int fd)
{
lseek(fd, 0, 0);
int size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, 0);
return size;
}
} /* namespace */
class UnixSocketTestSlave
{
public:
UnixSocketTestSlave()
: exitCode_(EXIT_FAILURE), exit_(false)
{
dispatcher_ = Thread::current()->eventDispatcher();
ipc_.readyRead.connect(this, &UnixSocketTestSlave::readyRead);
}
int run(UniqueFD fd)
{
if (ipc_.bind(std::move(fd))) {
cerr << "Failed to connect to IPC channel" << endl;
return EXIT_FAILURE;
}
while (!exit_)
dispatcher_->processEvents();
ipc_.close();
return exitCode_;
}
private:
void readyRead()
{
IPCUnixSocket::Payload message, response;
int ret;
ret = ipc_.receive(&message);
if (ret) {
cerr << "Receive message failed: " << ret << endl;
return;
}
const uint8_t cmd = message.data[0];
switch (cmd) {
case CMD_CLOSE:
stop(0);
break;
case CMD_REVERSE: {
response.data = message.data;
std::reverse(response.data.begin() + 1, response.data.end());
ret = ipc_.send(response);
if (ret < 0) {
cerr << "Reverse failed" << endl;
stop(ret);
}
break;
}
case CMD_LEN_CALC: {
int size = 0;
for (int fd : message.fds)
size += calculateLength(fd);
response.data.resize(1 + sizeof(size));
response.data[0] = cmd;
memcpy(response.data.data() + 1, &size, sizeof(size));
ret = ipc_.send(response);
if (ret < 0) {
cerr << "Calc failed" << endl;
stop(ret);
}
break;
}
case CMD_LEN_CMP: {
int size = 0;
for (int fd : message.fds)
size += calculateLength(fd);
int cmp;
memcpy(&cmp, message.data.data() + 1, sizeof(cmp));
if (cmp != size) {
cerr << "Compare failed" << endl;
stop(-ERANGE);
}
break;
}
case CMD_JOIN: {
int outfd = open("/tmp", O_TMPFILE | O_RDWR,
S_IRUSR | S_IWUSR);
if (outfd < 0) {
cerr << "Create out file failed" << endl;
stop(outfd);
return;
}
for (int fd : message.fds) {
while (true) {
char buf[32];
ssize_t num = read(fd, &buf, sizeof(buf));
if (num < 0) {
cerr << "Read failed" << endl;
close(outfd);
stop(-EIO);
return;
} else if (!num)
break;
if (write(outfd, buf, num) < 0) {
cerr << "Write failed" << endl;
close(outfd);
stop(-EIO);
return;
}
}
close(fd);
}
lseek(outfd, 0, 0);
response.data.push_back(CMD_JOIN);
response.fds.push_back(outfd);
ret = ipc_.send(response);
if (ret < 0) {
cerr << "Join failed" << endl;
stop(ret);
}
close(outfd);
break;
}
default:
cerr << "Unknown command " << cmd << endl;
stop(-EINVAL);
break;
}
}
void stop(int code)
{
exitCode_ = code;
exit_ = true;
}
IPCUnixSocket ipc_;
EventDispatcher *dispatcher_;
int exitCode_;
bool exit_;
};
class UnixSocketTest : public Test
{
protected:
int slaveStart(int fd)
{
pid_ = fork();
if (pid_ == -1)
return TestFail;
if (!pid_) {
std::string arg = std::to_string(fd);
execl(self().c_str(), self().c_str(), arg.c_str(), nullptr);
/* Only get here if exec fails. */
exit(TestFail);
}
return TestPass;
}
int slaveStop()
{
int status;
if (pid_ < 0)
return TestFail;
if (waitpid(pid_, &status, 0) < 0)
return TestFail;
if (!WIFEXITED(status) || WEXITSTATUS(status))
return TestFail;
return TestPass;
}
int testReverse()
{
IPCUnixSocket::Payload message, response;
int ret;
message.data = { CMD_REVERSE, 1, 2, 3, 4, 5 };
ret = call(message, &response);
if (ret)
return ret;
std::reverse(response.data.begin() + 1, response.data.end());
if (message.data != response.data)
return TestFail;
return 0;
}
int testEmptyFail()
{
IPCUnixSocket::Payload message;
return ipc_.send(message) != -EINVAL;
}
int testCalc()
{
IPCUnixSocket::Payload message, response;
int sizeOut, sizeIn, ret;
sizeOut = prepareFDs(&message, 2);
if (sizeOut < 0)
return sizeOut;
message.data.push_back(CMD_LEN_CALC);
ret = call(message, &response);
if (ret)
return ret;
memcpy(&sizeIn, response.data.data() + 1, sizeof(sizeIn));
if (sizeOut != sizeIn)
return TestFail;
return 0;
}
int testCmp()
{
IPCUnixSocket::Payload message;
int size;
size = prepareFDs(&message, 7);
if (size < 0)
return size;
message.data.resize(1 + sizeof(size));
message.data[0] = CMD_LEN_CMP;
memcpy(message.data.data() + 1, &size, sizeof(size));
if (ipc_.send(message))
return TestFail;
return 0;
}
int testFdOrder()
{
IPCUnixSocket::Payload message, response;
int ret;
static const char *strings[2] = {
"Foo",
"Bar",
};
int fds[2];
for (unsigned int i = 0; i < std::size(strings); i++) {
unsigned int len = strlen(strings[i]);
fds[i] = open("/tmp", O_TMPFILE | O_RDWR,
S_IRUSR | S_IWUSR);
if (fds[i] < 0)
return TestFail;
ret = write(fds[i], strings[i], len);
if (ret < 0)
return TestFail;
lseek(fds[i], 0, 0);
message.fds.push_back(fds[i]);
}
message.data.push_back(CMD_JOIN);
ret = call(message, &response);
if (ret)
return ret;
for (unsigned int i = 0; i < std::size(strings); i++) {
unsigned int len = strlen(strings[i]);
char buf[len];
close(fds[i]);
if (read(response.fds[0], &buf, len) <= 0)
return TestFail;
if (memcmp(buf, strings[i], len))
return TestFail;
}
close(response.fds[0]);
return 0;
}
int init()
{
callResponse_ = nullptr;
return 0;
}
int run()
{
UniqueFD slavefd = ipc_.create();
if (!slavefd.isValid())
return TestFail;
if (slaveStart(slavefd.release())) {
cerr << "Failed to start slave" << endl;
return TestFail;
}
ipc_.readyRead.connect(this, &UnixSocketTest::readyRead);
/* Test reversing a string, this test sending only data. */
if (testReverse()) {
cerr << "Reverse array test failed" << endl;
return TestFail;
}
/* Test that an empty message fails. */
if (testEmptyFail()) {
cerr << "Empty message test failed" << endl;
return TestFail;
}
/* Test offloading a calculation, this test sending only FDs. */
if (testCalc()) {
cerr << "Calc test failed" << endl;
return TestFail;
}
/* Test fire and forget, this tests sending data and FDs. */
if (testCmp()) {
cerr << "Cmp test failed" << endl;
return TestFail;
}
/* Test order of file descriptors. */
if (testFdOrder()) {
cerr << "fd order test failed" << endl;
return TestFail;
}
/* Close slave connection. */
IPCUnixSocket::Payload close;
close.data.push_back(CMD_CLOSE);
if (ipc_.send(close)) {
cerr << "Closing IPC channel failed" << endl;
return TestFail;
}
ipc_.close();
if (slaveStop()) {
cerr << "Failed to stop slave" << endl;
return TestFail;
}
return TestPass;
}
private:
int call(const IPCUnixSocket::Payload &message, IPCUnixSocket::Payload *response)
{
Timer timeout;
int ret;
callDone_ = false;
callResponse_ = response;
ret = ipc_.send(message);
if (ret)
return ret;
timeout.start(2s);
while (!callDone_) {
if (!timeout.isRunning()) {
cerr << "Call timeout!" << endl;
callResponse_ = nullptr;
return -ETIMEDOUT;
}
Thread::current()->eventDispatcher()->processEvents();
}
callResponse_ = nullptr;
return 0;
}
void readyRead()
{
if (!callResponse_) {
cerr << "Read ready without expecting data, fail." << endl;
return;
}
if (ipc_.receive(callResponse_)) {
cerr << "Receive message failed" << endl;
return;
}
callDone_ = true;
}
int prepareFDs(IPCUnixSocket::Payload *message, unsigned int num)
{
int fd = open(self().c_str(), O_RDONLY);
if (fd < 0)
return fd;
int size = 0;
for (unsigned int i = 0; i < num; i++) {
int clone = dup(fd);
if (clone < 0)
return clone;
size += calculateLength(clone);
message->fds.push_back(clone);
}
close(fd);
return size;
}
pid_t pid_;
IPCUnixSocket ipc_;
bool callDone_;
IPCUnixSocket::Payload *callResponse_;
};
/*
* Can't use TEST_REGISTER() as single binary needs to act as both proxy
* master and slave.
*/
int main(int argc, char **argv)
{
if (argc == 2) {
UniqueFD ipcfd = UniqueFD(std::stoi(argv[1]));
UnixSocketTestSlave slave;
return slave.run(std::move(ipcfd));
}
UnixSocketTest test;
test.setArgs(argc, argv);
return test.execute();
}

View File

@@ -0,0 +1,233 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* Unix socket IPC test
*/
#include <algorithm>
#include <fcntl.h>
#include <iostream>
#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include <libcamera/base/utils.h>
#include "libcamera/internal/ipa_data_serializer.h"
#include "libcamera/internal/ipc_pipe.h"
#include "libcamera/internal/ipc_pipe_unixsocket.h"
#include "libcamera/internal/process.h"
#include "test.h"
using namespace std;
using namespace libcamera;
enum {
CmdExit = 0,
CmdGetSync = 1,
CmdSetAsync = 2,
};
const int32_t kInitialValue = 1337;
const int32_t kChangedValue = 9001;
class UnixSocketTestIPCSlave
{
public:
UnixSocketTestIPCSlave()
: value_(kInitialValue), exitCode_(EXIT_FAILURE), exit_(false)
{
dispatcher_ = Thread::current()->eventDispatcher();
ipc_.readyRead.connect(this, &UnixSocketTestIPCSlave::readyRead);
}
int run(UniqueFD fd)
{
if (ipc_.bind(std::move(fd))) {
cerr << "Failed to connect to IPC channel" << endl;
return EXIT_FAILURE;
}
while (!exit_)
dispatcher_->processEvents();
ipc_.close();
return exitCode_;
}
private:
void readyRead()
{
IPCUnixSocket::Payload message;
int ret;
ret = ipc_.receive(&message);
if (ret) {
cerr << "Receive message failed: " << ret << endl;
return;
}
IPCMessage ipcMessage(message);
uint32_t cmd = ipcMessage.header().cmd;
switch (cmd) {
case CmdExit: {
exit_ = true;
break;
}
case CmdGetSync: {
IPCMessage::Header header = { cmd, ipcMessage.header().cookie };
IPCMessage response(header);
vector<uint8_t> buf;
tie(buf, ignore) = IPADataSerializer<int32_t>::serialize(value_);
response.data().insert(response.data().end(), buf.begin(), buf.end());
ret = ipc_.send(response.payload());
if (ret < 0) {
cerr << "Reply failed" << endl;
stop(ret);
}
break;
}
case CmdSetAsync: {
value_ = IPADataSerializer<int32_t>::deserialize(ipcMessage.data());
break;
}
}
}
void stop(int code)
{
exitCode_ = code;
exit_ = true;
}
int32_t value_;
IPCUnixSocket ipc_;
EventDispatcher *dispatcher_;
int exitCode_;
bool exit_;
};
class UnixSocketTestIPC : public Test
{
protected:
int init()
{
return 0;
}
int setValue(int32_t val)
{
IPCMessage msg(CmdSetAsync);
tie(msg.data(), ignore) = IPADataSerializer<int32_t>::serialize(val);
int ret = ipc_->sendAsync(msg);
if (ret < 0) {
cerr << "Failed to call set value" << endl;
return ret;
}
return 0;
}
int getValue()
{
IPCMessage msg(CmdGetSync);
IPCMessage buf;
int ret = ipc_->sendSync(msg, &buf);
if (ret < 0) {
cerr << "Failed to call get value" << endl;
return ret;
}
return IPADataSerializer<int32_t>::deserialize(buf.data());
}
int exit()
{
IPCMessage msg(CmdExit);
int ret = ipc_->sendAsync(msg);
if (ret < 0) {
cerr << "Failed to call exit" << endl;
return ret;
}
return 0;
}
int run()
{
ipc_ = std::make_unique<IPCPipeUnixSocket>("", self().c_str());
if (!ipc_->isConnected()) {
cerr << "Failed to create IPCPipe" << endl;
return TestFail;
}
int ret = getValue();
if (ret != kInitialValue) {
cerr << "Wrong initial value, expected "
<< kInitialValue << ", got " << ret << endl;
return TestFail;
}
ret = setValue(kChangedValue);
if (ret < 0) {
cerr << "Failed to set value: " << strerror(-ret) << endl;
return TestFail;
}
ret = getValue();
if (ret != kChangedValue) {
cerr << "Wrong set value, expected " << kChangedValue
<< ", got " << ret << endl;
return TestFail;
}
ret = exit();
if (ret < 0) {
cerr << "Failed to exit: " << strerror(-ret) << endl;
return TestFail;
}
return TestPass;
}
private:
ProcessManager processManager_;
unique_ptr<IPCPipeUnixSocket> ipc_;
};
/*
* Can't use TEST_REGISTER() as single binary needs to act as both client and
* server
*/
int main(int argc, char **argv)
{
/* IPCPipeUnixSocket passes IPA module path in argv[1] */
if (argc == 3) {
UniqueFD ipcfd = UniqueFD(std::stoi(argv[2]));
UnixSocketTestIPCSlave slave;
return slave.run(std::move(ipcfd));
}
UnixSocketTestIPC test;
test.setArgs(argc, argv);
return test.execute();
}

View File

@@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* A provider of external buffers, suitable for use in tests.
*/
#include "buffer_source.h"
#include <iostream>
#include <memory>
#include "libcamera/internal/device_enumerator.h"
#include "test.h"
using namespace libcamera;
BufferSource::BufferSource()
{
}
BufferSource::~BufferSource()
{
if (media_)
media_->release();
}
int BufferSource::allocate(const StreamConfiguration &config)
{
/* Locate and open the video device. */
std::string videoDeviceName = "vivid-000-vid-out";
std::unique_ptr<DeviceEnumerator> enumerator =
DeviceEnumerator::create();
if (!enumerator) {
std::cout << "Failed to create device enumerator" << std::endl;
return TestFail;
}
if (enumerator->enumerate()) {
std::cout << "Failed to enumerate media devices" << std::endl;
return TestFail;
}
DeviceMatch dm("vivid");
dm.add(videoDeviceName);
media_ = enumerator->search(dm);
if (!media_) {
std::cout << "No vivid output device available" << std::endl;
return TestSkip;
}
std::unique_ptr<V4L2VideoDevice> video = V4L2VideoDevice::fromEntityName(media_.get(), videoDeviceName);
if (!video) {
std::cout << "Failed to get video device from entity "
<< videoDeviceName << std::endl;
return TestFail;
}
if (video->open()) {
std::cout << "Unable to open " << videoDeviceName << std::endl;
return TestFail;
}
/* Configure the format. */
V4L2DeviceFormat format;
if (video->getFormat(&format)) {
std::cout << "Failed to get format on output device" << std::endl;
return TestFail;
}
format.size = config.size;
format.fourcc = video->toV4L2PixelFormat(config.pixelFormat);
if (video->setFormat(&format)) {
std::cout << "Failed to set format on output device" << std::endl;
return TestFail;
}
if (video->allocateBuffers(config.bufferCount, &buffers_) < 0) {
std::cout << "Failed to allocate buffers" << std::endl;
return TestFail;
}
video->close();
return TestPass;
}
const std::vector<std::unique_ptr<FrameBuffer>> &BufferSource::buffers()
{
return buffers_;
}

View File

@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* libcamera camera test helper to create FrameBuffers
*/
#pragma once
#include <libcamera/stream.h>
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_videodevice.h"
class BufferSource
{
public:
BufferSource();
~BufferSource();
int allocate(const libcamera::StreamConfiguration &config);
const std::vector<std::unique_ptr<libcamera::FrameBuffer>> &buffers();
private:
std::shared_ptr<libcamera::MediaDevice> media_;
std::vector<std::unique_ptr<libcamera::FrameBuffer>> buffers_;
};

View File

@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera Camera API tests
*/
#include <iostream>
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
CameraTest::CameraTest(const char *name, bool isolate)
{
cm_ = new CameraManager();
if (isolate)
setenv("LIBCAMERA_IPA_FORCE_ISOLATION", "1", 1);
if (cm_->start()) {
cerr << "Failed to start camera manager" << endl;
status_ = TestFail;
return;
}
camera_ = cm_->get(name);
if (!camera_) {
cerr << "Can not find '" << name << "' camera" << endl;
status_ = TestSkip;
return;
}
/* Sanity check that the camera has streams. */
if (camera_->streams().empty()) {
cerr << "Camera has no stream" << endl;
status_ = TestFail;
return;
}
status_ = TestPass;
}
CameraTest::~CameraTest()
{
if (camera_) {
camera_->release();
camera_.reset();
}
cm_->stop();
delete cm_;
}

View File

@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera camera test base class
*/
#pragma once
#include <memory>
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
class CameraTest
{
public:
CameraTest(const char *name, bool isolate = false);
~CameraTest();
protected:
libcamera::CameraManager *cm_;
std::shared_ptr<libcamera::Camera> camera_;
int status_;
};

View File

@@ -0,0 +1,24 @@
# SPDX-License-Identifier: CC0-1.0
libtest_sources = files([
'buffer_source.cpp',
'camera_test.cpp',
'test.cpp',
])
libtest_includes = include_directories('.')
test_includes_public = [
libtest_includes,
]
test_includes_internal = [
test_includes_public,
]
libtest = static_library('libtest', libtest_sources,
dependencies : libcamera_private,
include_directories : test_includes_internal)
test_libraries = [libtest]

View File

@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2018, Google Inc.
*
* libcamera test base class
*/
#include <stdlib.h>
#include "test.h"
Test::Test()
{
}
Test::~Test()
{
}
void Test::setArgs([[maybe_unused]] int argc, char *argv[])
{
self_ = argv[0];
}
int Test::execute()
{
int ret;
ret = init();
if (ret)
return ret;
ret = run();
cleanup();
return ret;
}

View File

@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2018, Google Inc.
*
* libcamera test base class
*/
#pragma once
#include <sstream>
#include <string>
enum TestStatus {
TestPass = 0,
TestFail = -1,
TestSkip = 77,
};
class Test
{
public:
Test();
virtual ~Test();
void setArgs(int argc, char *argv[]);
int execute();
const std::string &self() const { return self_; }
protected:
virtual int init() { return 0; }
virtual int run() = 0;
virtual void cleanup() {}
private:
std::string self_;
};
#define TEST_REGISTER(Klass) \
int main(int argc, char *argv[]) \
{ \
Klass klass; \
klass.setArgs(argc, argv); \
return klass.execute(); \
}

View File

@@ -0,0 +1,154 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* log API test
*/
#include <algorithm>
#include <fcntl.h>
#include <iostream>
#include <list>
#include <sstream>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <libcamera/base/log.h>
#include <libcamera/logging.h>
#include "test.h"
using namespace std;
using namespace libcamera;
LOG_DEFINE_CATEGORY(LogAPITest)
class LogAPITest : public Test
{
protected:
void doLogging()
{
logSetLevel("LogAPITest", "DEBUG");
LOG(LogAPITest, Info) << "good 1";
logSetLevel("LogAPITest", "WARN");
LOG(LogAPITest, Info) << "bad";
logSetLevel("LogAPITest", "ERROR");
LOG(LogAPITest, Error) << "good 3";
LOG(LogAPITest, Info) << "bad";
logSetLevel("LogAPITest", "WARN");
LOG(LogAPITest, Warning) << "good 5";
LOG(LogAPITest, Info) << "bad";
}
int verifyOutput(istream &is)
{
list<int> goodList = { 1, 3, 5 };
string line;
while (getline(is, line)) {
if (goodList.empty()) {
cout << "Too many log lines" << endl;
return TestFail;
}
unsigned int digit = line.back() - '0';
unsigned int expect = goodList.front();
goodList.pop_front();
if (digit != expect) {
cout << "Incorrect log line" << endl;
return TestFail;
}
}
if (!goodList.empty()) {
cout << "Too few log lines" << endl;
return TestFail;
}
return TestPass;
}
int testFile()
{
int fd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0) {
cerr << "Failed to open tmp log file" << endl;
return TestFail;
}
char path[32];
snprintf(path, sizeof(path), "/proc/self/fd/%u", fd);
if (logSetFile(path) < 0) {
cerr << "Failed to set log file" << endl;
close(fd);
return TestFail;
}
doLogging();
char buf[1000];
memset(buf, 0, sizeof(buf));
lseek(fd, 0, SEEK_SET);
if (read(fd, buf, sizeof(buf)) < 0) {
cerr << "Failed to read tmp log file" << endl;
close(fd);
return TestFail;
}
close(fd);
istringstream iss(buf);
return verifyOutput(iss);
}
int testStream()
{
stringstream log;
/* Never fails, so no need to check return value */
logSetStream(&log);
doLogging();
return verifyOutput(log);
}
int testTarget()
{
logSetTarget(LoggingTargetNone);
logSetLevel("LogAPITest", "DEBUG");
LOG(LogAPITest, Info) << "don't crash please";
if (!logSetTarget(LoggingTargetFile))
return TestFail;
if (!logSetTarget(LoggingTargetStream))
return TestFail;
return TestPass;
}
int run() override
{
int ret = testFile();
if (ret != TestPass)
return TestFail;
ret = testStream();
if (ret != TestPass)
return TestFail;
ret = testTarget();
if (ret != TestPass)
return TestFail;
return TestPass;
}
};
TEST_REGISTER(LogAPITest)

View File

@@ -0,0 +1,165 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Logging in isolated child process test
*/
#include <fcntl.h>
#include <iostream>
#include <random>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
#include <libcamera/logging.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/log.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include <libcamera/base/utils.h>
#include "libcamera/internal/process.h"
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
static const string message("hello from the child");
LOG_DEFINE_CATEGORY(LogProcessTest)
class LogProcessTestChild
{
public:
int run(int status, int num)
{
usleep(50000);
string logPath = "/tmp/libcamera.worker.test." +
to_string(num) + ".log";
if (logSetFile(logPath.c_str()) < 0)
return TestSkip;
LOG(LogProcessTest, Warning) << message;
return status;
}
};
class LogProcessTest : public Test
{
protected:
int init()
{
random_device random;
num_ = random();
logPath_ = "/tmp/libcamera.worker.test." +
to_string(num_) + ".log";
proc_.finished.connect(this, &LogProcessTest::procFinished);
return 0;
}
int run()
{
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
Timer timeout;
int exitCode = 42;
vector<std::string> args;
args.push_back(to_string(exitCode));
args.push_back(to_string(num_));
int ret = proc_.start(self(), args);
if (ret) {
cerr << "failed to start process" << endl;
return TestFail;
}
timeout.start(2s);
while (timeout.isRunning())
dispatcher->processEvents();
if (exitStatus_ != Process::NormalExit) {
cerr << "process did not exit normally: " << exitStatus_
<< endl;
return TestFail;
}
if (exitCode_ == TestSkip)
return TestSkip;
if (exitCode_ != exitCode) {
cerr << "exit code should be " << exitCode
<< ", actual is " << exitCode_ << endl;
return TestFail;
}
int fd = open(logPath_.c_str(), O_RDONLY, S_IRUSR);
if (fd < 0) {
cerr << "failed to open tmp log file" << endl;
return TestFail;
}
char buf[200];
memset(buf, 0, sizeof(buf));
if (read(fd, buf, sizeof(buf)) < 0) {
cerr << "Failed to read tmp log file" << endl;
close(fd);
return TestFail;
}
close(fd);
string str(buf);
if (str.find(message) == string::npos) {
cerr << "Received message is not correct (received "
<< str.length() << " bytes)" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
unlink(logPath_.c_str());
}
private:
void procFinished(enum Process::ExitStatus exitStatus, int exitCode)
{
exitStatus_ = exitStatus;
exitCode_ = exitCode;
}
ProcessManager processManager_;
Process proc_;
Process::ExitStatus exitStatus_ = Process::NotExited;
string logPath_;
int exitCode_;
int num_;
};
/*
* Can't use TEST_REGISTER() as single binary needs to act as both
* parent and child processes.
*/
int main(int argc, char **argv)
{
if (argc == 3) {
int status = std::stoi(argv[1]);
int num = std::stoi(argv[2]);
LogProcessTestChild child;
return child.run(status, num);
}
LogProcessTest test;
test.setArgs(argc, argv);
return test.execute();
}

View File

@@ -0,0 +1,15 @@
# SPDX-License-Identifier: CC0-1.0
log_test = [
{'name': 'log_api', 'sources': ['log_api.cpp']},
{'name': 'log_process', 'sources': ['log_process.cpp']},
]
foreach test : log_test
exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'log')
endforeach

View File

@@ -0,0 +1,117 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* libcamera internal MappedBuffer tests
*/
#include <iostream>
#include <libcamera/framebuffer_allocator.h>
#include "libcamera/internal/mapped_framebuffer.h"
#include "camera_test.h"
#include "test.h"
using namespace libcamera;
using namespace std;
namespace {
class MappedBufferTest : public CameraTest, public Test
{
public:
MappedBufferTest()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
int init() override
{
if (status_ != TestPass)
return status_;
config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
if (!config_ || config_->size() != 1) {
cout << "Failed to generate default configuration" << endl;
return TestFail;
}
allocator_ = new FrameBufferAllocator(camera_);
StreamConfiguration &cfg = config_->at(0);
if (camera_->acquire()) {
cout << "Failed to acquire the camera" << endl;
return TestFail;
}
if (camera_->configure(config_.get())) {
cout << "Failed to set default configuration" << endl;
return TestFail;
}
stream_ = cfg.stream();
int ret = allocator_->allocate(stream_);
if (ret < 0)
return TestFail;
return TestPass;
}
void cleanup() override
{
delete allocator_;
}
int run() override
{
const std::unique_ptr<FrameBuffer> &buffer = allocator_->buffers(stream_).front();
std::vector<MappedBuffer> maps;
MappedFrameBuffer map(buffer.get(), MappedFrameBuffer::MapFlag::Read);
if (!map.isValid()) {
cout << "Failed to successfully map buffer" << endl;
return TestFail;
}
/* Make sure we can move it. */
maps.emplace_back(std::move(map));
/* But copying is prevented, it would cause double-unmap. */
// MappedFrameBuffer map_copy = map;
/* Local map should be invalid (after move). */
if (map.isValid()) {
cout << "Post-move map should not be valid" << endl;
return TestFail;
}
/* Test for multiple successful maps on the same buffer. */
MappedFrameBuffer write_map(buffer.get(), MappedFrameBuffer::MapFlag::Write);
if (!write_map.isValid()) {
cout << "Failed to map write buffer" << endl;
return TestFail;
}
MappedFrameBuffer rw_map(buffer.get(), MappedFrameBuffer::MapFlag::ReadWrite);
if (!rw_map.isValid()) {
cout << "Failed to map RW buffer" << endl;
return TestFail;
}
return TestPass;
}
private:
std::unique_ptr<CameraConfiguration> config_;
FrameBufferAllocator *allocator_;
Stream *stream_;
};
} /* namespace */
TEST_REGISTER(MappedBufferTest)

View File

@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* media_device_acquire.cpp- Test acquire/release of a MediaDevice
*/
#include "media_device_test.h"
using namespace libcamera;
class MediaDeviceAcquire : public MediaDeviceTest
{
int run()
{
if (!media_->acquire())
return TestFail;
if (media_->acquire())
return TestFail;
media_->release();
if (!media_->acquire())
return TestFail;
media_->release();
return TestPass;
}
};
TEST_REGISTER(MediaDeviceAcquire)

View File

@@ -0,0 +1,222 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Tests link handling on VIMC media device
*/
#include <iostream>
#include "media_device_test.h"
using namespace libcamera;
using namespace std;
/*
* This link test requires a vimc device in order to exercise the
* MediaObject link handling API on a graph with a predetermined topology.
*
* vimc is a Media Controller kernel driver that creates virtual devices.
* From a userspace point of view they appear as normal media controller
* devices, but are not backed by any particular piece of hardware. They can
* thus be used for testing purpose without depending on a particular hardware
* platform.
*
* If no vimc device is found (most likely because the vimc driver is not
* loaded) the test is skipped.
*/
class MediaDeviceLinkTest : public MediaDeviceTest
{
int init()
{
int ret = MediaDeviceTest::init();
if (ret)
return ret;
if (!media_->acquire()) {
cerr << "Unable to acquire media device "
<< media_->deviceNode() << endl;
return TestFail;
}
return TestPass;
}
int run()
{
/*
* First of all disable all links in the media graph to
* ensure we start from a known state.
*/
if (media_->disableLinks()) {
cerr << "Failed to disable all links in the media graph";
return TestFail;
}
/*
* Test if link can be consistently retrieved through the
* different functions the media device offers.
*/
string linkName("'Debayer A':[1] -> 'Scaler':[0]'");
MediaLink *link = media_->link("Debayer A", 1, "Scaler", 0);
if (!link) {
cerr << "Unable to find link: " << linkName
<< " using lookup by name" << endl;
return TestFail;
}
MediaEntity *source = media_->getEntityByName("Debayer A");
if (!source) {
cerr << "Unable to find entity: 'Debayer A'" << endl;
return TestFail;
}
MediaEntity *sink = media_->getEntityByName("Scaler");
if (!sink) {
cerr << "Unable to find entity: 'Scaler'" << endl;
return TestFail;
}
MediaLink *link2 = media_->link(source, 1, sink, 0);
if (!link2) {
cerr << "Unable to find link: " << linkName
<< " using lookup by entity" << endl;
return TestFail;
}
if (link != link2) {
cerr << "Link lookup by name and by entity don't match"
<< endl;
return TestFail;
}
link2 = media_->link(source->getPadByIndex(1),
sink->getPadByIndex(0));
if (!link2) {
cerr << "Unable to find link: " << linkName
<< " using lookup by pad" << endl;
return TestFail;
}
if (link != link2) {
cerr << "Link lookup by name and by pad don't match"
<< endl;
return TestFail;
}
/* After reset the link shall not be enabled. */
if (link->flags() & MEDIA_LNK_FL_ENABLED) {
cerr << "Link " << linkName
<< " should not be enabled after a device reset"
<< endl;
return TestFail;
}
/* Enable the link and test if enabling was successful. */
if (link->setEnabled(true)) {
cerr << "Failed to enable link: " << linkName
<< endl;
return TestFail;
}
if (!(link->flags() & MEDIA_LNK_FL_ENABLED)) {
cerr << "Link " << linkName
<< " was enabled but it is reported as disabled"
<< endl;
return TestFail;
}
/* Disable the link and test if disabling was successful. */
if (link->setEnabled(false)) {
cerr << "Failed to disable link: " << linkName
<< endl;
return TestFail;
}
if (link->flags() & MEDIA_LNK_FL_ENABLED) {
cerr << "Link " << linkName
<< " was disabled but it is reported as enabled"
<< endl;
return TestFail;
}
/* Try to get a non existing link. */
linkName = "'Sensor A':[1] -> 'Scaler':[0]";
link = media_->link("Sensor A", 1, "Scaler", 0);
if (link) {
cerr << "Link lookup for " << linkName
<< " succeeded but link does not exist"
<< endl;
return TestFail;
}
/* Now get an immutable link and try to disable it. */
linkName = "'Sensor A':[0] -> 'Raw Capture 0':[0]";
link = media_->link("Sensor A", 0, "Raw Capture 0", 0);
if (!link) {
cerr << "Unable to find link: " << linkName
<< " using lookup by name" << endl;
return TestFail;
}
if (!(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) {
cerr << "Link " << linkName
<< " should be 'IMMUTABLE'" << endl;
return TestFail;
}
/* Disabling an immutable link shall fail. */
if (!link->setEnabled(false)) {
cerr << "Disabling immutable link " << linkName
<< " succeeded but should have failed" << endl;
return TestFail;
}
/*
* Enable an disabled link, and verify it is disabled again
* after disabling all links in the media graph.
*/
linkName = "'Debayer B':[1] -> 'Scaler':[0]'";
link = media_->link("Debayer B", 1, "Scaler", 0);
if (!link) {
cerr << "Unable to find link: " << linkName
<< " using lookup by name" << endl;
return TestFail;
}
if (link->setEnabled(true)) {
cerr << "Failed to enable link: " << linkName
<< endl;
return TestFail;
}
if (!(link->flags() & MEDIA_LNK_FL_ENABLED)) {
cerr << "Link " << linkName
<< " was enabled but it is reported as disabled"
<< endl;
return TestFail;
}
if (media_->disableLinks()) {
cerr << "Failed to disable all links in the media graph";
return TestFail;
}
if (link->flags() & MEDIA_LNK_FL_ENABLED) {
cerr << "All links in the media graph have been disabled"
<< " but link " << linkName
<< " is still reported as enabled" << endl;
return TestFail;
}
return 0;
}
void cleanup()
{
media_->release();
}
};
TEST_REGISTER(MediaDeviceLinkTest)

View File

@@ -0,0 +1,150 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2018-2019, Google Inc.
*
* Print out media devices
*/
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "libcamera/internal/media_device.h"
#include "test.h"
using namespace libcamera;
using namespace std;
/*
* MediaDevicePrintTest takes all media devices found in the system and print
* them out to verify correctness.
*
* If no accessible media device is found, the test is skipped.
*/
class MediaDevicePrintTest : public Test
{
protected:
int init() { return 0; }
int run();
void cleanup() {}
private:
int testMediaDevice(string deviceNode);
void printMediaGraph(const MediaDevice &media, ostream &os);
void printLinkFlags(const MediaLink *link, ostream &os);
void printNode(const MediaPad *pad, ostream &os);
};
void MediaDevicePrintTest::printNode(const MediaPad *pad, ostream &os)
{
const MediaEntity *entity = pad->entity();
os << "\"" << entity->name() << "\"["
<< pad->index() << "]";
}
void MediaDevicePrintTest::printLinkFlags(const MediaLink *link, ostream &os)
{
unsigned int flags = link->flags();
os << " [";
if (flags) {
os << (flags & MEDIA_LNK_FL_ENABLED ? "ENABLED," : "")
<< (flags & MEDIA_LNK_FL_IMMUTABLE ? "IMMUTABLE" : "");
}
os << "]\n";
}
/*
* For each entity in the media graph, printout links directed to its sinks
* and source pads.
*/
void MediaDevicePrintTest::printMediaGraph(const MediaDevice &media, ostream &os)
{
os << "\n" << media.driver() << " - " << media.deviceNode() << "\n\n";
for (auto const &entity : media.entities()) {
os << "\"" << entity->name() << "\"\n";
for (auto const &sink : entity->pads()) {
if (!(sink->flags() & MEDIA_PAD_FL_SINK))
continue;
os << " [" << sink->index() << "]" << ": Sink\n";
for (auto const &link : sink->links()) {
os << "\t";
printNode(sink, os);
os << " <- ";
printNode(link->source(), os);
printLinkFlags(link, os);
}
os << "\n";
}
for (auto const &source : entity->pads()) {
if (!(source->flags() & MEDIA_PAD_FL_SOURCE))
continue;
os << " [" << source->index() << "]" << ": Source\n";
for (auto const &link : source->links()) {
os << "\t";
printNode(source, os);
os << " -> ";
printNode(link->sink(), os);
printLinkFlags(link, os);
}
os << "\n";
}
}
os.flush();
}
/* Test a single media device. */
int MediaDevicePrintTest::testMediaDevice(const string deviceNode)
{
MediaDevice dev(deviceNode);
int ret;
ret = dev.populate();
if (ret)
return ret;
/* Print out the media graph. */
printMediaGraph(dev, cerr);
return 0;
}
/* Run tests on all media devices. */
#define MAX_MEDIA_DEV 256
int MediaDevicePrintTest::run()
{
const string deviceNode("/dev/media");
unsigned int i;
int ret = 77; /* skip test exit code */
/*
* Run the test sequence on all media device found in the
* system, if any.
*/
for (i = 0; i < MAX_MEDIA_DEV; i++) {
string mediadev = deviceNode + to_string(i);
struct stat pstat = {};
if (stat(mediadev.c_str(), &pstat))
continue;
ret = testMediaDevice(mediadev);
if (ret)
return ret;
}
return ret;
}
TEST_REGISTER(MediaDevicePrintTest)

View File

@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera media device test base class
*/
#include <iostream>
#include "media_device_test.h"
using namespace libcamera;
using namespace std;
int MediaDeviceTest::init()
{
enumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());
if (!enumerator_) {
cerr << "Failed to create device enumerator" << endl;
return TestFail;
}
if (enumerator_->enumerate()) {
cerr << "Failed to enumerate media devices" << endl;
return TestFail;
}
DeviceMatch dm("vimc");
media_ = enumerator_->search(dm);
if (!media_) {
cerr << "No VIMC media device found: skip test" << endl;
return TestSkip;
}
return TestPass;
}

View File

@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera media device test base class
*/
#pragma once
#include <memory>
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "test.h"
class MediaDeviceTest : public Test
{
public:
MediaDeviceTest()
: media_(nullptr), enumerator_(nullptr) {}
protected:
int init();
std::shared_ptr<libcamera::MediaDevice> media_;
private:
std::unique_ptr<libcamera::DeviceEnumerator> enumerator_;
};

View File

@@ -0,0 +1,24 @@
# SPDX-License-Identifier: CC0-1.0
lib_mdev_test_sources = files([
'media_device_test.cpp',
])
media_device_tests = [
{'name': 'media_device_acquire', 'sources': ['media_device_acquire.cpp']},
{'name': 'media_device_print_test', 'sources': ['media_device_print_test.cpp']},
{'name': 'media_device_link_test', 'sources': ['media_device_link_test.cpp']},
]
lib_mdev_test = static_library('lib_mdev_test', lib_mdev_test_sources,
dependencies : libcamera_private,
include_directories : test_includes_internal)
foreach test : media_device_tests
exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : [test_libraries, lib_mdev_test],
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'media_device', is_parallel : false)
endforeach

View File

@@ -0,0 +1,129 @@
# SPDX-License-Identifier: CC0-1.0
if not get_option('test')
test_enabled = false
subdir_done()
endif
test_enabled = true
# When ASan is enabled, find the path to the ASan runtime needed by multiple
# tests. This currently works with gcc only, as clang uses different file names
# depending on the compiler version and target architecture.
asan_enabled = false
asan_runtime_missing = false
if get_option('b_sanitize').contains('address')
asan_enabled = true
if cc.get_id() == 'gcc'
asan_runtime = run_command(cc, '-print-file-name=libasan.so', check : true).stdout().strip()
else
asan_runtime_missing = true
endif
endif
subdir('libtest')
subdir('camera')
subdir('controls')
subdir('gstreamer')
subdir('ipa')
subdir('ipc')
subdir('log')
subdir('media_device')
subdir('process')
subdir('py')
subdir('serialization')
subdir('stream')
subdir('v4l2_compat')
subdir('v4l2_subdevice')
subdir('v4l2_videodevice')
public_tests = [
{'name': 'color-space', 'sources': ['color-space.cpp']},
{'name': 'geometry', 'sources': ['geometry.cpp']},
{'name': 'public-api', 'sources': ['public-api.cpp']},
{'name': 'signal', 'sources': ['signal.cpp']},
{'name': 'span', 'sources': ['span.cpp']},
{'name': 'transform', 'sources': ['transform.cpp']},
]
internal_tests = [
{'name': 'bayer-format', 'sources': ['bayer-format.cpp']},
{'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']},
{'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']},
{'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']},
{'name': 'event', 'sources': ['event.cpp']},
{'name': 'event-dispatcher', 'sources': ['event-dispatcher.cpp']},
{'name': 'event-thread', 'sources': ['event-thread.cpp']},
{'name': 'file', 'sources': ['file.cpp']},
{'name': 'flags', 'sources': ['flags.cpp']},
{'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']},
{'name': 'message', 'sources': ['message.cpp']},
{'name': 'object', 'sources': ['object.cpp']},
{'name': 'object-delete', 'sources': ['object-delete.cpp']},
{'name': 'object-invoke', 'sources': ['object-invoke.cpp']},
{'name': 'pixel-format', 'sources': ['pixel-format.cpp']},
{'name': 'shared-fd', 'sources': ['shared-fd.cpp']},
{'name': 'signal-threads', 'sources': ['signal-threads.cpp']},
{'name': 'threads', 'sources': 'threads.cpp', 'dependencies': [libthreads]},
{'name': 'timer', 'sources': ['timer.cpp']},
{'name': 'timer-fail', 'sources': ['timer-fail.cpp'], 'should_fail': true},
{'name': 'timer-thread', 'sources': ['timer-thread.cpp']},
{'name': 'unique-fd', 'sources': ['unique-fd.cpp']},
{'name': 'utils', 'sources': ['utils.cpp']},
{'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']},
]
internal_non_parallel_tests = [
{'name': 'fence', 'sources': ['fence.cpp']},
{'name': 'mapped-buffer', 'sources': ['mapped-buffer.cpp']},
]
foreach test : public_tests
deps = [libcamera_public]
if 'dependencies' in test
deps += test['dependencies']
endif
exe = executable(test['name'], test['sources'],
dependencies : deps,
implicit_include_directories : false,
link_with : test_libraries,
include_directories : test_includes_public)
test(test['name'], exe, should_fail : test.get('should_fail', false))
endforeach
foreach test : internal_tests
deps = [libcamera_private]
if 'dependencies' in test
deps += test['dependencies']
endif
exe = executable(test['name'], test['sources'],
dependencies : deps,
implicit_include_directories : false,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, should_fail : test.get('should_fail', false))
endforeach
foreach test : internal_non_parallel_tests
deps = [libcamera_private]
if 'dependencies' in test
deps += test['dependencies']
endif
exe = executable(test['name'], test['sources'],
dependencies : deps,
implicit_include_directories : false,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe,
is_parallel : false,
should_fail : test.get('should_fail', false))
endforeach

View File

@@ -0,0 +1,170 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Messages test
*/
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>
#include <libcamera/base/message.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class MessageReceiver : public Object
{
public:
enum Status {
NoMessage,
InvalidThread,
MessageReceived,
};
MessageReceiver(Object *parent = nullptr)
: Object(parent), status_(NoMessage)
{
}
Status status() const { return status_; }
void reset() { status_ = NoMessage; }
protected:
void message(Message *msg)
{
if (msg->type() != Message::None) {
Object::message(msg);
return;
}
if (thread() != Thread::current())
status_ = InvalidThread;
else
status_ = MessageReceived;
}
private:
Status status_;
};
class RecursiveMessageReceiver : public Object
{
public:
RecursiveMessageReceiver()
: child_(this), success_(false)
{
}
bool success() const { return success_; }
protected:
void message([[maybe_unused]] Message *msg)
{
if (msg->type() != Message::None) {
Object::message(msg);
return;
}
child_.postMessage(std::make_unique<Message>(Message::None));
/*
* If the child has already received the message, something is
* wrong.
*/
if (child_.status() != MessageReceiver::NoMessage)
return;
Thread::current()->dispatchMessages(Message::None);
/* The child should now have received the message. */
if (child_.status() == MessageReceiver::MessageReceived)
success_ = true;
}
private:
MessageReceiver child_;
bool success_;
};
class MessageTest : public Test
{
protected:
int run()
{
Message::Type msgType[2] = {
Message::registerMessageType(),
Message::registerMessageType(),
};
if (msgType[0] != Message::UserMessage ||
msgType[1] != Message::UserMessage + 1) {
cout << "Failed to register message types" << endl;
return TestFail;
}
MessageReceiver *receiver = new MessageReceiver();
receiver->moveToThread(&thread_);
thread_.start();
receiver->postMessage(std::make_unique<Message>(Message::None));
this_thread::sleep_for(chrono::milliseconds(100));
MessageReceiver::Status status = receiver->status();
receiver->deleteLater();
switch (status) {
case MessageReceiver::NoMessage:
cout << "No message received" << endl;
return TestFail;
case MessageReceiver::InvalidThread:
cout << "Message received in incorrect thread" << endl;
return TestFail;
default:
break;
}
/*
* Test recursive calls to Thread::dispatchMessages(). Messages
* should be delivered correctly, without crashes or memory
* leaks. Two messages need to be posted to ensure we don't only
* test the simple case of a queue containing a single message.
*/
RecursiveMessageReceiver *recursiveReceiver = new RecursiveMessageReceiver();
recursiveReceiver->moveToThread(&thread_);
recursiveReceiver->postMessage(std::make_unique<Message>(Message::None));
recursiveReceiver->postMessage(std::make_unique<Message>(Message::UserMessage));
this_thread::sleep_for(chrono::milliseconds(10));
bool success = recursiveReceiver->success();
recursiveReceiver->deleteLater();
if (!success) {
cout << "Recursive message delivery failed" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
thread_.exit(0);
thread_.wait();
}
private:
Thread thread_;
};
TEST_REGISTER(MessageTest)

View File

@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* Object deletion tests
*/
#include <iostream>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class TestObject : public Object
{
public:
TestObject(unsigned int *count)
: deleteCount_(count)
{
}
~TestObject()
{
/* Count the deletions from the correct thread. */
if (thread() == Thread::current())
(*deleteCount_)++;
}
unsigned int *deleteCount_;
};
class DeleterThread : public Thread
{
public:
DeleterThread(Object *obj)
: object_(obj)
{
}
protected:
void run()
{
object_->deleteLater();
}
private:
Object *object_;
};
class ObjectDeleteTest : public Test
{
protected:
int run()
{
/*
* Test that deferred deletion is executed from the object's
* thread, not the caller's thread.
*/
unsigned int count = 0;
TestObject *obj = new TestObject(&count);
DeleterThread delThread(obj);
delThread.start();
delThread.wait();
Thread::current()->dispatchMessages(Message::Type::DeferredDelete);
if (count != 1) {
cout << "Failed to dispatch DeferredDelete (" << count << ")" << endl;
return TestFail;
}
/*
* Test that multiple calls to deleteLater() delete the object
* once only.
*/
count = 0;
obj = new TestObject(&count);
obj->deleteLater();
obj->deleteLater();
Thread::current()->dispatchMessages(Message::Type::DeferredDelete);
if (count != 1) {
cout << "Multiple deleteLater() failed (" << count << ")" << endl;
return TestFail;
}
/*
* Test that deleteLater() works properly when called just
* before the object's thread exits.
*/
Thread boundThread;
boundThread.start();
count = 0;
obj = new TestObject(&count);
obj->moveToThread(&boundThread);
obj->deleteLater();
boundThread.exit();
boundThread.wait();
if (count != 1) {
cout << "Object deletion right before thread exit failed (" << count << ")" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ObjectDeleteTest)

View File

@@ -0,0 +1,203 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Cross-thread Object method invocation test
*/
#include <iostream>
#include <thread>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class InvokedObject : public Object
{
public:
enum Status {
NoCall,
InvalidThread,
CallReceived,
};
InvokedObject()
: status_(NoCall)
{
}
Status status() const { return status_; }
int value() const { return value_; }
void reset()
{
status_ = NoCall;
value_ = 0;
}
void method(int value)
{
if (Thread::current() != thread())
status_ = InvalidThread;
else
status_ = CallReceived;
value_ = value;
}
void methodWithReference([[maybe_unused]] const int &value)
{
}
int methodWithReturn()
{
return 42;
}
private:
Status status_;
int value_;
};
class ObjectInvokeTest : public Test
{
protected:
int run()
{
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
/*
* Test that queued method invocation in the same thread goes
* through the event dispatcher.
*/
object_.invokeMethod(&InvokedObject::method,
ConnectionTypeQueued, 42);
if (object_.status() != InvokedObject::NoCall) {
cerr << "Method not invoked asynchronously" << endl;
return TestFail;
}
dispatcher->processEvents();
switch (object_.status()) {
case InvokedObject::NoCall:
cout << "Method not invoked for main thread" << endl;
return TestFail;
case InvokedObject::InvalidThread:
cout << "Method invoked in incorrect thread for main thread" << endl;
return TestFail;
default:
break;
}
if (object_.value() != 42) {
cout << "Method invoked with incorrect value for main thread" << endl;
return TestFail;
}
/*
* Test that blocking invocation is delivered directly when the
* caller and callee live in the same thread.
*/
object_.reset();
object_.invokeMethod(&InvokedObject::method,
ConnectionTypeBlocking, 42);
switch (object_.status()) {
case InvokedObject::NoCall:
cout << "Method not invoked for main thread (blocking)" << endl;
return TestFail;
case InvokedObject::InvalidThread:
cout << "Method invoked in incorrect thread for main thread (blocking)" << endl;
return TestFail;
default:
break;
}
/*
* Move the object to a thread and verify that auto method
* invocation is delivered in the correct thread.
*/
object_.reset();
object_.moveToThread(&thread_);
thread_.start();
object_.invokeMethod(&InvokedObject::method,
ConnectionTypeBlocking, 42);
switch (object_.status()) {
case InvokedObject::NoCall:
cout << "Method not invoked for custom thread" << endl;
return TestFail;
case InvokedObject::InvalidThread:
cout << "Method invoked in incorrect thread for custom thread" << endl;
return TestFail;
default:
break;
}
if (object_.value() != 42) {
cout << "Method invoked with incorrect value for custom thread" << endl;
return TestFail;
}
/* Test that direct method invocation bypasses threads. */
object_.reset();
object_.invokeMethod(&InvokedObject::method,
ConnectionTypeDirect, 42);
switch (object_.status()) {
case InvokedObject::NoCall:
cout << "Method not invoked for custom thread" << endl;
return TestFail;
case InvokedObject::CallReceived:
cout << "Method invoked in incorrect thread for direct call" << endl;
return TestFail;
default:
break;
}
if (object_.value() != 42) {
cout << "Method invoked with incorrect value for direct call" << endl;
return TestFail;
}
/*
* Test invoking a method that takes reference arguments. This
* targets compilation, there's no need to check runtime
* results.
*/
object_.invokeMethod(&InvokedObject::methodWithReference,
ConnectionTypeBlocking, 42);
/* Test invoking a method that returns a value. */
int ret = object_.invokeMethod(&InvokedObject::methodWithReturn,
ConnectionTypeBlocking);
if (ret != 42) {
cout << "Method invoked return incorrect value (" << ret
<< ")" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
thread_.exit(0);
thread_.wait();
}
private:
Thread thread_;
InvokedObject object_;
};
TEST_REGISTER(ObjectInvokeTest)

View File

@@ -0,0 +1,157 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Object tests
*/
#include <iostream>
#include <libcamera/base/message.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class InstrumentedObject : public Object
{
public:
enum Status {
NoMessage,
MessageReceived,
};
InstrumentedObject(Object *parent = nullptr)
: Object(parent), status_(NoMessage)
{
}
Status status() const { return status_; }
void reset() { status_ = NoMessage; }
protected:
void message(Message *msg) override
{
if (msg->type() == Message::ThreadMoveMessage)
status_ = MessageReceived;
Object::message(msg);
}
private:
Status status_;
};
class ObjectTest : public Test
{
protected:
int init()
{
/*
* Create a hierarchy of objects:
* A -> B -> C
* \->D
* E
*/
a_ = new InstrumentedObject();
b_ = new InstrumentedObject(a_);
c_ = new InstrumentedObject(b_);
d_ = new InstrumentedObject(a_);
e_ = new InstrumentedObject();
f_ = nullptr;
return TestPass;
}
int run()
{
/* Verify the parent-child relationships. */
if (a_->parent() != nullptr || b_->parent() != a_ ||
c_->parent() != b_ || d_->parent() != a_ ||
e_->parent() != nullptr) {
cout << "Incorrect parent-child relationships" << endl;
return TestFail;
}
/*
* Verify that moving an object with no parent to a different
* thread succeeds.
*/
e_->moveToThread(&thread_);
if (e_->thread() != &thread_ || e_->thread() == Thread::current()) {
cout << "Failed to move object to thread" << endl;
return TestFail;
}
/*
* Verify that moving an object with a parent to a different
* thread fails. This results in an undefined behaviour, the
* test thus depends on the internal implementation returning
* without performing any change.
*/
b_->moveToThread(&thread_);
if (b_->thread() != Thread::current()) {
cout << "Moving object with parent to thread shouldn't succeed" << endl;
return TestFail;
}
/*
* Verify that moving an object with children to a different
* thread moves all the children.
*/
a_->moveToThread(&thread_);
if (a_->thread() != &thread_ || b_->thread() != &thread_ ||
c_->thread() != &thread_ || d_->thread() != &thread_) {
cout << "Failed to move children to thread" << endl;
return TestFail;
}
/* Verify that objects are bound to the thread of their parent. */
f_ = new InstrumentedObject(d_);
if (f_->thread() != &thread_) {
cout << "Failed to bind child to parent thread" << endl;
return TestFail;
}
/* Verify that objects receive a ThreadMoveMessage when moved. */
if (a_->status() != InstrumentedObject::MessageReceived ||
b_->status() != InstrumentedObject::MessageReceived ||
c_->status() != InstrumentedObject::MessageReceived ||
d_->status() != InstrumentedObject::MessageReceived ||
e_->status() != InstrumentedObject::MessageReceived) {
cout << "Moving object didn't deliver ThreadMoveMessage" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
delete a_;
delete b_;
delete c_;
delete d_;
delete e_;
delete f_;
}
private:
InstrumentedObject *a_;
InstrumentedObject *b_;
InstrumentedObject *c_;
InstrumentedObject *d_;
InstrumentedObject *e_;
InstrumentedObject *f_;
Thread thread_;
};
TEST_REGISTER(ObjectTest)

View File

@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Kaaira Gupta
* libcamera pixel format handling test
*/
#include <iostream>
#include <vector>
#include <libcamera/formats.h>
#include <libcamera/pixel_format.h>
#include <libcamera/base/utils.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class PixelFormatTest : public Test
{
protected:
int run()
{
std::vector<std::pair<PixelFormat, const char *>> formatsMap{
{ formats::R8, "R8" },
{ formats::SRGGB10_CSI2P, "SRGGB10_CSI2P" },
{ PixelFormat(0, 0), "<INVALID>" },
{ PixelFormat(0x20203843), "<C8 >" }
};
for (const auto &format : formatsMap) {
if ((format.first).toString() != format.second) {
cerr << "Failed to convert PixelFormat "
<< utils::hex(format.first.fourcc()) << " to string"
<< endl;
return TestFail;
}
}
if (PixelFormat().toString() != "<INVALID>") {
cerr << "Failed to convert default PixelFormat to string"
<< endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(PixelFormatTest)

View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: CC0-1.0
process_tests = [
{'name': 'process_test', 'sources': ['process_test.cpp']},
]
foreach test : process_tests
exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'process', is_parallel : false)
endforeach

View File

@@ -0,0 +1,112 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Process test
*/
#include <iostream>
#include <unistd.h>
#include <vector>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include <libcamera/base/utils.h>
#include "libcamera/internal/process.h"
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
class ProcessTestChild
{
public:
int run(int status)
{
usleep(50000);
return status;
}
};
class ProcessTest : public Test
{
public:
ProcessTest()
: exitStatus_(Process::NotExited), exitCode_(-1)
{
}
protected:
int run()
{
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
Timer timeout;
int exitCode = 42;
vector<std::string> args;
args.push_back(to_string(exitCode));
proc_.finished.connect(this, &ProcessTest::procFinished);
/* Test that kill() on an unstarted process is safe. */
proc_.kill();
/* Test starting the process and retrieving the exit code. */
int ret = proc_.start(self(), args);
if (ret) {
cerr << "failed to start process" << endl;
return TestFail;
}
timeout.start(2000ms);
while (timeout.isRunning() && exitStatus_ == Process::NotExited)
dispatcher->processEvents();
if (exitStatus_ != Process::NormalExit) {
cerr << "process did not exit normally" << endl;
return TestFail;
}
if (exitCode != exitCode_) {
cerr << "exit code should be " << exitCode
<< ", actual is " << exitCode_ << endl;
return TestFail;
}
return TestPass;
}
private:
void procFinished(enum Process::ExitStatus exitStatus, int exitCode)
{
exitStatus_ = exitStatus;
exitCode_ = exitCode;
}
ProcessManager processManager_;
Process proc_;
enum Process::ExitStatus exitStatus_;
int exitCode_;
};
/*
* Can't use TEST_REGISTER() as single binary needs to act as both
* parent and child processes.
*/
int main(int argc, char **argv)
{
if (argc == 2) {
int status = std::stoi(argv[1]);
ProcessTestChild child;
return child.run(status);
}
ProcessTest test;
test.setArgs(argc, argv);
return test.execute();
}

View File

@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Google Inc.
*
* Public API validation
*/
#include <libcamera/libcamera.h>
#include "test.h"
class PublicAPITest : public Test
{
int run()
{
#ifdef LIBCAMERA_BASE_PRIVATE
#error "Public interfaces should not be exposed to LIBCAMERA_BASE_PRIVATE"
return TestFail;
#else
return TestPass;
#endif
}
};
TEST_REGISTER(PublicAPITest)

View File

@@ -0,0 +1,32 @@
# SPDX-License-Identifier: CC0-1.0
if not pycamera_enabled
subdir_done()
endif
# If ASan is enabled, the link order runtime check will fail as Python is not
# linked to ASan. LD_PRELOAD the ASan runtime if available, or skip the test
# otherwise.
if asan_runtime_missing
warning('Unable to get path to ASan runtime, Python test disabled')
subdir_done()
endif
pymod = import('python')
py3 = pymod.find_installation('python3')
pypathdir = meson.project_build_root() / 'src' / 'py'
py_env = ['PYTHONPATH=' + pypathdir]
if asan_enabled
# Disable leak detection as the Python interpreter is full of leaks.
py_env += ['LD_PRELOAD=' + asan_runtime, 'ASAN_OPTIONS=detect_leaks=0']
endif
test('pyunittests',
py3,
args : files('unittests.py'),
env : py_env,
suite : 'pybindings',
is_parallel : false)

View File

@@ -0,0 +1,365 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
from collections import defaultdict
import gc
import libcamera as libcam
import selectors
import typing
import unittest
import weakref
class BaseTestCase(unittest.TestCase):
def assertZero(self, a, msg=None):
self.assertEqual(a, 0, msg)
def assertIsAlive(self, wr, msg='object not alive'):
self.assertIsNotNone(wr(), msg)
def assertIsDead(self, wr, msg='object not dead'):
self.assertIsNone(wr(), msg)
def assertIsAllAlive(self, wr_list, msg='object not alive'):
self.assertTrue(all([wr() for wr in wr_list]), msg)
def assertIsAllDead(self, wr_list, msg='object not dead'):
self.assertTrue(all([not wr() for wr in wr_list]), msg)
class SimpleTestMethods(BaseTestCase):
def test_get_ref(self):
cm = libcam.CameraManager.singleton()
wr_cm = weakref.ref(cm)
cam = cm.get('platform/vimc.0 Sensor B')
self.assertIsNotNone(cam)
wr_cam = weakref.ref(cam)
del cm
gc.collect()
self.assertIsAlive(wr_cm)
del cam
gc.collect()
self.assertIsDead(wr_cm)
self.assertIsDead(wr_cam)
def test_acquire_release(self):
cm = libcam.CameraManager.singleton()
cam = cm.get('platform/vimc.0 Sensor B')
self.assertIsNotNone(cam)
cam.acquire()
cam.release()
def test_double_acquire(self):
cm = libcam.CameraManager.singleton()
cam = cm.get('platform/vimc.0 Sensor B')
self.assertIsNotNone(cam)
cam.acquire()
libcam.log_set_level('Camera', 'FATAL')
with self.assertRaises(RuntimeError):
cam.acquire()
libcam.log_set_level('Camera', 'ERROR')
cam.release()
# I expected exception here, but looks like double release works fine
cam.release()
def test_version(self):
cm = libcam.CameraManager.singleton()
self.assertIsInstance(cm.version, str)
class CameraTesterBase(BaseTestCase):
cm: typing.Any
cam: typing.Any
def setUp(self):
self.cm = libcam.CameraManager.singleton()
self.cam = next((cam for cam in self.cm.cameras if 'platform/vimc' in cam.id), None)
if self.cam is None:
self.cm = None
self.skipTest('No vimc found')
self.cam.acquire()
self.wr_cam = weakref.ref(self.cam)
self.wr_cm = weakref.ref(self.cm)
def tearDown(self):
# If a test fails, the camera may be in running state. So always stop.
self.cam.stop()
self.cam.release()
self.cam = None
self.cm = None
self.assertIsDead(self.wr_cm)
self.assertIsDead(self.wr_cam)
class AllocatorTestMethods(CameraTesterBase):
def test_allocator(self):
cam = self.cam
camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture])
self.assertTrue(camconfig.size == 1)
wr_camconfig = weakref.ref(camconfig)
streamconfig = camconfig.at(0)
wr_streamconfig = weakref.ref(streamconfig)
cam.configure(camconfig)
stream = streamconfig.stream
wr_stream = weakref.ref(stream)
# stream should keep streamconfig and camconfig alive
del streamconfig
del camconfig
gc.collect()
self.assertIsAlive(wr_camconfig)
self.assertIsAlive(wr_streamconfig)
allocator = libcam.FrameBufferAllocator(cam)
num_bufs = allocator.allocate(stream)
self.assertTrue(num_bufs > 0)
wr_allocator = weakref.ref(allocator)
buffers = allocator.buffers(stream)
self.assertIsNotNone(buffers)
del buffers
buffer = allocator.buffers(stream)[0]
self.assertIsNotNone(buffer)
wr_buffer = weakref.ref(buffer)
del allocator
gc.collect()
self.assertIsAlive(wr_buffer)
self.assertIsAlive(wr_allocator)
self.assertIsAlive(wr_stream)
del buffer
gc.collect()
self.assertIsDead(wr_buffer)
self.assertIsDead(wr_allocator)
self.assertIsAlive(wr_stream)
del stream
gc.collect()
self.assertIsDead(wr_stream)
self.assertIsDead(wr_camconfig)
self.assertIsDead(wr_streamconfig)
class SimpleCaptureMethods(CameraTesterBase):
def test_blocking(self):
cm = self.cm
cam = self.cam
camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture])
self.assertTrue(camconfig.size == 1)
streamconfig = camconfig.at(0)
fmts = streamconfig.formats
self.assertIsNotNone(fmts)
fmts = None
cam.configure(camconfig)
stream = streamconfig.stream
allocator = libcam.FrameBufferAllocator(cam)
num_bufs = allocator.allocate(stream)
self.assertTrue(num_bufs > 0)
num_bufs = len(allocator.buffers(stream))
reqs = []
for i in range(num_bufs):
req = cam.create_request(i)
self.assertIsNotNone(req)
buffer = allocator.buffers(stream)[i]
req.add_buffer(stream, buffer)
reqs.append(req)
buffer = None
cam.start()
for req in reqs:
cam.queue_request(req)
reqs = None
gc.collect()
sel = selectors.DefaultSelector()
sel.register(cm.event_fd, selectors.EVENT_READ)
reqs = []
while True:
events = sel.select()
if not events:
continue
ready_reqs = cm.get_ready_requests()
reqs += ready_reqs
if len(reqs) == num_bufs:
break
for i, req in enumerate(reqs):
self.assertTrue(i == req.cookie)
reqs = None
gc.collect()
cam.stop()
def test_select(self):
cm = self.cm
cam = self.cam
camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture])
self.assertTrue(camconfig.size == 1)
streamconfig = camconfig.at(0)
fmts = streamconfig.formats
self.assertIsNotNone(fmts)
fmts = None
cam.configure(camconfig)
stream = streamconfig.stream
allocator = libcam.FrameBufferAllocator(cam)
num_bufs = allocator.allocate(stream)
self.assertTrue(num_bufs > 0)
num_bufs = len(allocator.buffers(stream))
reqs = []
for i in range(num_bufs):
req = cam.create_request(i)
self.assertIsNotNone(req)
buffer = allocator.buffers(stream)[i]
req.add_buffer(stream, buffer)
reqs.append(req)
buffer = None
cam.start()
for req in reqs:
cam.queue_request(req)
reqs = None
gc.collect()
sel = selectors.DefaultSelector()
sel.register(cm.event_fd, selectors.EVENT_READ)
reqs = []
running = True
while running:
events = sel.select()
for _ in events:
ready_reqs = cm.get_ready_requests()
reqs += ready_reqs
if len(reqs) == num_bufs:
running = False
self.assertTrue(len(reqs) == num_bufs)
for i, req in enumerate(reqs):
self.assertTrue(i == req.cookie)
reqs = None
gc.collect()
cam.stop()
# Recursively expand slist's objects into olist, using seen to track already
# processed objects.
def _getr(slist, olist, seen):
for e in slist:
if id(e) in seen:
continue
seen.add(id(e))
olist.append(e)
tl = gc.get_referents(e)
if tl:
_getr(tl, olist, seen)
def get_all_objects(ignored=[]):
gcl = gc.get_objects()
olist = []
seen = set()
seen.add(id(gcl))
seen.add(id(olist))
seen.add(id(seen))
seen.update(set([id(o) for o in ignored]))
_getr(gcl, olist, seen)
return olist
def create_type_count_map(olist):
map = defaultdict(int)
for o in olist:
map[type(o)] += 1
return map
def diff_type_count_maps(before, after):
return [(k, after[k] - before[k]) for k in after if after[k] != before[k]]
if __name__ == '__main__':
# \todo This is an attempt to see the Python objects that are not collected,
# but this doesn't work very well, as things always leak a bit.
test_leaks = False
if test_leaks:
gc.unfreeze()
gc.collect()
obs_before = get_all_objects()
unittest.main(exit=False)
if test_leaks:
gc.unfreeze()
gc.collect()
obs_after = get_all_objects([obs_before]) # type: ignore
before = create_type_count_map(obs_before) # type: ignore
after = create_type_count_map(obs_after)
leaks = diff_type_count_maps(before, after)
if len(leaks) > 0:
print(leaks)

View File

@@ -0,0 +1,176 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Serialize and deserialize controls
*/
#include <iostream>
#include <libcamera/camera.h>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include "libcamera/internal/byte_stream_buffer.h"
#include "libcamera/internal/control_serializer.h"
#include "serialization_test.h"
#include "test.h"
using namespace std;
using namespace libcamera;
class ControlSerializationTest : public SerializationTest
{
protected:
int init() override
{
return status_;
}
int run() override
{
ControlSerializer serializer(ControlSerializer::Role::Proxy);
ControlSerializer deserializer(ControlSerializer::Role::Worker);
std::vector<uint8_t> infoData;
std::vector<uint8_t> listData;
size_t size;
int ret;
/* Create a control list with three controls. */
const ControlInfoMap &infoMap = camera_->controls();
ControlList list(infoMap);
list.set(controls::Brightness, 0.5f);
list.set(controls::Contrast, 1.2f);
list.set(controls::Saturation, 0.2f);
/*
* Serialize the control list, this should fail as the control
* info map hasn't been serialized.
*/
size = serializer.binarySize(list);
listData.resize(size);
ByteStreamBuffer buffer(listData.data(), listData.size());
ret = serializer.serialize(list, buffer);
if (!ret) {
cerr << "List serialization without info map should have failed"
<< endl;
return TestFail;
}
if (buffer.overflow() || buffer.offset()) {
cerr << "Failed list serialization modified the buffer"
<< endl;
return TestFail;
}
/* Serialize the control info map. */
size = serializer.binarySize(infoMap);
infoData.resize(size);
buffer = ByteStreamBuffer(infoData.data(), infoData.size());
ret = serializer.serialize(infoMap, buffer);
if (ret < 0) {
cerr << "Failed to serialize ControlInfoMap" << endl;
return TestFail;
}
if (buffer.overflow()) {
cerr << "Overflow when serializing ControlInfoMap" << endl;
return TestFail;
}
/* Serialize the control list, this should now succeed. */
size = serializer.binarySize(list);
listData.resize(size);
buffer = ByteStreamBuffer(listData.data(), listData.size());
ret = serializer.serialize(list, buffer);
if (ret) {
cerr << "Failed to serialize ControlList" << endl;
return TestFail;
}
if (buffer.overflow()) {
cerr << "Overflow when serializing ControlList" << endl;
return TestFail;
}
/*
* Deserialize the control list, this should fail as the control
* info map hasn't been deserialized.
*/
buffer = ByteStreamBuffer(const_cast<const uint8_t *>(listData.data()),
listData.size());
ControlList newList = deserializer.deserialize<ControlList>(buffer);
if (!newList.empty()) {
cerr << "List deserialization without info map should have failed"
<< endl;
return TestFail;
}
if (buffer.overflow()) {
cerr << "Failed list deserialization modified the buffer"
<< endl;
return TestFail;
}
/* Deserialize the control info map and verify the contents. */
buffer = ByteStreamBuffer(const_cast<const uint8_t *>(infoData.data()),
infoData.size());
ControlInfoMap newInfoMap = deserializer.deserialize<ControlInfoMap>(buffer);
if (newInfoMap.empty()) {
cerr << "Failed to deserialize ControlInfoMap" << endl;
return TestFail;
}
if (buffer.overflow()) {
cerr << "Overflow when deserializing ControlInfoMap" << endl;
return TestFail;
}
if (!equals(infoMap, newInfoMap)) {
cerr << "Deserialized map doesn't match original" << endl;
return TestFail;
}
/* Make sure control limits looked up by id are not changed. */
const ControlInfo &newLimits = newInfoMap.at(&controls::Brightness);
const ControlInfo &initialLimits = infoMap.at(&controls::Brightness);
if (newLimits.min() != initialLimits.min() ||
newLimits.max() != initialLimits.max()) {
cerr << "The brightness control limits have changed" << endl;
return TestFail;
}
/* Deserialize the control list and verify the contents. */
buffer = ByteStreamBuffer(const_cast<const uint8_t *>(listData.data()),
listData.size());
newList = deserializer.deserialize<ControlList>(buffer);
if (newList.empty()) {
cerr << "Failed to deserialize ControlList" << endl;
return TestFail;
}
if (buffer.overflow()) {
cerr << "Overflow when deserializing ControlList" << endl;
return TestFail;
}
if (!equals(list, newList)) {
cerr << "Deserialized list doesn't match original" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(ControlSerializationTest)

View File

@@ -0,0 +1,181 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* Test generated serializer
*/
#include <algorithm>
#include <tuple>
#include <vector>
#include "test.h"
#include "test_ipa_interface.h"
#include "test_ipa_serializer.h"
using namespace std;
using namespace libcamera;
class IPAGeneratedSerializerTest : public Test
{
protected:
int init() override
{
return TestPass;
}
int run() override
{
#define TEST_FIELD_EQUALITY(struct1, struct2, field) \
if (struct1.field != struct2.field) { \
cerr << #field << " field incorrect: expected \"" \
<< t.field << "\", got \"" << u.field << "\"" << endl;\
return TestFail; \
}
#define TEST_SCOPED_ENUM_EQUALITY(struct1, struct2, field) \
if (struct1.field != struct2.field) { \
cerr << #field << " field incorrect" << endl; \
return TestFail; \
}
ipa::test::TestStruct t, u;
t.m = {
{ "a", "z" },
{ "b", "z" },
{ "c", "z" },
{ "d", "z" },
{ "e", "z" },
};
t.a = { "a", "b", "c", "d", "e" };
t.s1 = "hello world";
t.s2 = "goodbye";
t.s3 = "lorem ipsum";
t.i = 58527;
t.c = ipa::test::IPAOperationInit;
t.e = ipa::test::ErrorFlags::Error1;
Flags<ipa::test::ErrorFlags> flags;
flags |= ipa::test::ErrorFlags::Error1;
flags |= ipa::test::ErrorFlags::Error2;
t.f = flags;
std::vector<uint8_t> serialized;
std::tie(serialized, ignore) =
IPADataSerializer<ipa::test::TestStruct>::serialize(t);
u = IPADataSerializer<ipa::test::TestStruct>::deserialize(serialized);
if (!equals(t.m, u.m))
return TestFail;
if (!equals(t.a, u.a))
return TestFail;
TEST_FIELD_EQUALITY(t, u, s1);
TEST_FIELD_EQUALITY(t, u, s2);
TEST_FIELD_EQUALITY(t, u, s3);
TEST_FIELD_EQUALITY(t, u, i);
TEST_FIELD_EQUALITY(t, u, c);
TEST_SCOPED_ENUM_EQUALITY(t, u, e);
TEST_SCOPED_ENUM_EQUALITY(t, u, f);
/* Test vector of generated structs */
std::vector<ipa::test::TestStruct> v = { t, u };
std::vector<ipa::test::TestStruct> w;
std::tie(serialized, ignore) =
IPADataSerializer<vector<ipa::test::TestStruct>>::serialize(v);
w = IPADataSerializer<vector<ipa::test::TestStruct>>::deserialize(serialized);
if (!equals(v[0].m, w[0].m) ||
!equals(v[1].m, w[1].m))
return TestFail;
if (!equals(v[0].a, w[0].a) ||
!equals(v[1].a, w[1].a))
return TestFail;
TEST_FIELD_EQUALITY(v[0], w[0], s1);
TEST_FIELD_EQUALITY(v[0], w[0], s2);
TEST_FIELD_EQUALITY(v[0], w[0], s3);
TEST_FIELD_EQUALITY(v[0], w[0], i);
TEST_FIELD_EQUALITY(v[0], w[0], c);
TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], e);
TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], f);
TEST_FIELD_EQUALITY(v[1], w[1], s1);
TEST_FIELD_EQUALITY(v[1], w[1], s2);
TEST_FIELD_EQUALITY(v[1], w[1], s3);
TEST_FIELD_EQUALITY(v[1], w[1], i);
TEST_FIELD_EQUALITY(v[1], w[1], c);
TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], e);
TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], f);
return TestPass;
}
private:
bool equals(const map<string, string> &lhs, const map<string, string> &rhs)
{
bool eq = lhs.size() == rhs.size() &&
equal(lhs.begin(), lhs.end(), rhs.begin(),
[](auto &a, auto &b) { return a.first == b.first &&
a.second == b.second; });
if (eq)
return true;
cerr << "lhs:" << endl;
for (const auto &pair : lhs)
cerr << "- " << pair.first << ": "
<< pair.second << endl;
cerr << "rhs:" << endl;
for (const auto &pair : rhs)
cerr << "- " << pair.first << ": "
<< pair.second << endl;
return false;
}
bool equals(const vector<string> &lhs, const vector<string> &rhs)
{
bool eq = lhs.size() == rhs.size();
if (!eq) {
cerr << "sizes not equal" << endl;
return false;
}
for (unsigned int i = 0; i < lhs.size(); i++)
if (lhs[i] != rhs[i])
eq = false;
if (eq)
return true;
cerr << "lhs:" << endl;
for (const auto &str : lhs)
cerr << "- " << str << endl;
cerr << "rhs:" << endl;
for (const auto &str : rhs)
cerr << "- " << str << endl;
return false;
}
};
TEST_REGISTER(IPAGeneratedSerializerTest)

View File

@@ -0,0 +1,40 @@
# SPDX-License-Identifier: CC0-1.0
# test.mojom-module
mojom = custom_target('test_mojom_module',
input : 'test.mojom',
output : 'test.mojom-module',
command : [
mojom_parser,
'--output-root', meson.project_build_root(),
'--input-root', meson.project_source_root(),
'--mojoms', '@INPUT@'
])
# test_ipa_interface.h
generated_test_header = custom_target('test_ipa_interface_h',
input : mojom,
output : 'test_ipa_interface.h',
depends : mojom_templates,
command : [
mojom_generator, 'generate',
'-g', 'libcamera',
'--bytecode_path', mojom_templates_dir,
'--libcamera_generate_header',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
])
# test_ipa_serializer.h
generated_test_serializer = custom_target('test_ipa_serializer_h',
input : mojom,
output : 'test_ipa_serializer.h',
depends : mojom_templates,
command : [
mojom_generator, 'generate',
'-g', 'libcamera',
'--bytecode_path', mojom_templates_dir,
'--libcamera_generate_serializer',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
])

View File

@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
module ipa.test;
enum IPAOperationCode {
IPAOperationNone,
IPAOperationInit,
IPAOperationStart,
IPAOperationStop,
};
[scopedEnum] enum ErrorFlags {
Error1 = 0x1,
Error2 = 0x2,
Error3 = 0x4,
Error4 = 0x8,
};
struct IPASettings {};
struct TestStruct {
map<string, string> m;
array<string> a;
string s1;
string s2;
int32 i;
string s3;
IPAOperationCode c;
ErrorFlags e;
[flags] ErrorFlags f;
};
interface IPATestInterface {
init(IPASettings settings) => (int32 ret);
start() => (int32 ret);
stop();
test(TestStruct s);
};
interface IPATestEventInterface {
dummyEvent(uint32 val);
};

View File

@@ -0,0 +1,19 @@
# SPDX-License-Identifier: CC0-1.0
subdir('include/libcamera/ipa')
exe = executable('generated_serializer_test',
[
'generated_serializer_test.cpp',
generated_test_header,
generated_test_serializer,
],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : [
test_includes_internal,
'./include',
])
test('generated_serializer_test', exe,
suite : 'generated_serializer', is_parallel : false)

View File

@@ -0,0 +1,436 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* Test serializing/deserializing with IPADataSerializer
*/
#include <algorithm>
#include <cxxabi.h>
#include <fcntl.h>
#include <iostream>
#include <limits>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tuple>
#include <unistd.h>
#include <vector>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "libcamera/internal/ipa_data_serializer.h"
#include "serialization_test.h"
#include "test.h"
using namespace std;
using namespace libcamera;
static const ControlInfoMap Controls = ControlInfoMap({
{ &controls::AeEnable, ControlInfo(false, true) },
{ &controls::ExposureTime, ControlInfo(0, 999999) },
{ &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },
{ &controls::ColourGains, ControlInfo(0.0f, 32.0f) },
{ &controls::Brightness, ControlInfo(-1.0f, 1.0f) },
}, controls::controls);
namespace libcamera {
static bool operator==(const ControlInfoMap &lhs, const ControlInfoMap &rhs)
{
return SerializationTest::equals(lhs, rhs);
}
} /* namespace libcamera */
template<typename T>
int testPodSerdes(T in)
{
std::vector<uint8_t> buf;
std::vector<SharedFD> fds;
std::tie(buf, fds) = IPADataSerializer<T>::serialize(in);
T out = IPADataSerializer<T>::deserialize(buf, fds);
if (in == out)
return TestPass;
char *name = abi::__cxa_demangle(typeid(T).name(), nullptr,
nullptr, nullptr);
cerr << "Deserialized " << name << " doesn't match original" << endl;
free(name);
return TestFail;
}
template<typename T>
int testVectorSerdes(const std::vector<T> &in,
ControlSerializer *cs = nullptr)
{
std::vector<uint8_t> buf;
std::vector<SharedFD> fds;
std::tie(buf, fds) = IPADataSerializer<std::vector<T>>::serialize(in, cs);
std::vector<T> out = IPADataSerializer<std::vector<T>>::deserialize(buf, fds, cs);
if (in == out)
return TestPass;
char *name = abi::__cxa_demangle(typeid(T).name(), nullptr,
nullptr, nullptr);
cerr << "Deserialized std::vector<" << name
<< "> doesn't match original" << endl;
free(name);
return TestFail;
}
template<typename K, typename V>
int testMapSerdes(const std::map<K, V> &in,
ControlSerializer *cs = nullptr)
{
std::vector<uint8_t> buf;
std::vector<SharedFD> fds;
std::tie(buf, fds) = IPADataSerializer<std::map<K, V>>::serialize(in, cs);
std::map<K, V> out = IPADataSerializer<std::map<K, V>>::deserialize(buf, fds, cs);
if (in == out)
return TestPass;
char *nameK = abi::__cxa_demangle(typeid(K).name(), nullptr,
nullptr, nullptr);
char *nameV = abi::__cxa_demangle(typeid(V).name(), nullptr,
nullptr, nullptr);
cerr << "Deserialized std::map<" << nameK << ", " << nameV
<< "> doesn't match original" << endl;
free(nameK);
free(nameV);
return TestFail;
}
class IPADataSerializerTest : public CameraTest, public Test
{
public:
IPADataSerializerTest()
: CameraTest("platform/vimc.0 Sensor B")
{
}
protected:
int init() override
{
return status_;
}
int run() override
{
int ret;
ret = testControls();
if (ret != TestPass)
return ret;
ret = testVector();
if (ret != TestPass)
return ret;
ret = testMap();
if (ret != TestPass)
return ret;
ret = testPod();
if (ret != TestPass)
return ret;
return TestPass;
}
private:
ControlList generateControlList(const ControlInfoMap &infoMap)
{
/* Create a control list with three controls. */
ControlList list(infoMap);
list.set(controls::Brightness, 0.5f);
list.set(controls::Contrast, 1.2f);
list.set(controls::Saturation, 0.2f);
return list;
}
int testControls()
{
ControlSerializer cs(ControlSerializer::Role::Proxy);
const ControlInfoMap &infoMap = camera_->controls();
ControlList list = generateControlList(infoMap);
std::vector<uint8_t> infoMapBuf;
std::tie(infoMapBuf, std::ignore) =
IPADataSerializer<ControlInfoMap>::serialize(infoMap, &cs);
std::vector<uint8_t> listBuf;
std::tie(listBuf, std::ignore) =
IPADataSerializer<ControlList>::serialize(list, &cs);
const ControlInfoMap infoMapOut =
IPADataSerializer<ControlInfoMap>::deserialize(infoMapBuf, &cs);
ControlList listOut = IPADataSerializer<ControlList>::deserialize(listBuf, &cs);
if (!SerializationTest::equals(infoMap, infoMapOut)) {
cerr << "Deserialized map doesn't match original" << endl;
return TestFail;
}
if (!SerializationTest::equals(list, listOut)) {
cerr << "Deserialized list doesn't match original" << endl;
return TestFail;
}
return TestPass;
}
int testVector()
{
ControlSerializer cs(ControlSerializer::Role::Proxy);
/*
* We don't test SharedFD serdes because it dup()s, so we
* can't check for equality.
*/
std::vector<uint8_t> vecUint8 = { 1, 2, 3, 4, 5, 6 };
std::vector<uint16_t> vecUint16 = { 1, 2, 3, 4, 5, 6 };
std::vector<uint32_t> vecUint32 = { 1, 2, 3, 4, 5, 6 };
std::vector<uint64_t> vecUint64 = { 1, 2, 3, 4, 5, 6 };
std::vector<int8_t> vecInt8 = { 1, 2, 3, -4, 5, -6 };
std::vector<int16_t> vecInt16 = { 1, 2, 3, -4, 5, -6 };
std::vector<int32_t> vecInt32 = { 1, 2, 3, -4, 5, -6 };
std::vector<int64_t> vecInt64 = { 1, 2, 3, -4, 5, -6 };
std::vector<float> vecFloat = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
std::vector<double> vecDouble = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
std::vector<bool> vecBool = { true, true, false, false, true, false };
std::vector<std::string> vecString = { "foo", "bar", "baz" };
std::vector<ControlInfoMap> vecControlInfoMap = {
camera_->controls(),
Controls,
};
std::vector<uint8_t> buf;
std::vector<SharedFD> fds;
if (testVectorSerdes(vecUint8) != TestPass)
return TestFail;
if (testVectorSerdes(vecUint16) != TestPass)
return TestFail;
if (testVectorSerdes(vecUint32) != TestPass)
return TestFail;
if (testVectorSerdes(vecUint64) != TestPass)
return TestFail;
if (testVectorSerdes(vecInt8) != TestPass)
return TestFail;
if (testVectorSerdes(vecInt16) != TestPass)
return TestFail;
if (testVectorSerdes(vecInt32) != TestPass)
return TestFail;
if (testVectorSerdes(vecInt64) != TestPass)
return TestFail;
if (testVectorSerdes(vecFloat) != TestPass)
return TestFail;
if (testVectorSerdes(vecDouble) != TestPass)
return TestFail;
if (testVectorSerdes(vecBool) != TestPass)
return TestFail;
if (testVectorSerdes(vecString) != TestPass)
return TestFail;
if (testVectorSerdes(vecControlInfoMap, &cs) != TestPass)
return TestFail;
return TestPass;
}
int testMap()
{
ControlSerializer cs(ControlSerializer::Role::Proxy);
/*
* Realistically, only string and integral keys.
* Test simple, complex, and nested compound value.
*/
std::map<uint64_t, std::string> mapUintStr =
{ { 101, "foo" }, { 102, "bar" }, { 103, "baz" } };
std::map<int64_t, std::string> mapIntStr =
{ { 101, "foo" }, { -102, "bar" }, { -103, "baz" } };
std::map<std::string, std::string> mapStrStr =
{ { "a", "foo" }, { "b", "bar" }, { "c", "baz" } };
std::map<uint64_t, ControlInfoMap> mapUintCIM =
{ { 201, camera_->controls() }, { 202, Controls } };
std::map<int64_t, ControlInfoMap> mapIntCIM =
{ { 201, camera_->controls() }, { -202, Controls } };
std::map<std::string, ControlInfoMap> mapStrCIM =
{ { "a", camera_->controls() }, { "b", Controls } };
std::map<uint64_t, std::vector<uint8_t>> mapUintBVec =
{ { 301, { 1, 2, 3 } }, { 302, { 4, 5, 6 } }, { 303, { 7, 8, 9 } } };
std::map<int64_t, std::vector<uint8_t>> mapIntBVec =
{ { 301, { 1, 2, 3 } }, { -302, { 4, 5, 6} }, { -303, { 7, 8, 9 } } };
std::map<std::string, std::vector<uint8_t>> mapStrBVec =
{ { "a", { 1, 2, 3 } }, { "b", { 4, 5, 6 } }, { "c", { 7, 8, 9 } } };
std::vector<uint8_t> buf;
std::vector<SharedFD> fds;
if (testMapSerdes(mapUintStr) != TestPass)
return TestFail;
if (testMapSerdes(mapIntStr) != TestPass)
return TestFail;
if (testMapSerdes(mapStrStr) != TestPass)
return TestFail;
if (testMapSerdes(mapUintCIM, &cs) != TestPass)
return TestFail;
if (testMapSerdes(mapIntCIM, &cs) != TestPass)
return TestFail;
if (testMapSerdes(mapStrCIM, &cs) != TestPass)
return TestFail;
if (testMapSerdes(mapUintBVec) != TestPass)
return TestFail;
if (testMapSerdes(mapIntBVec) != TestPass)
return TestFail;
if (testMapSerdes(mapStrBVec) != TestPass)
return TestFail;
return TestPass;
}
int testPod()
{
uint32_t u32min = std::numeric_limits<uint32_t>::min();
uint32_t u32max = std::numeric_limits<uint32_t>::max();
uint32_t u32one = 1;
int32_t i32min = std::numeric_limits<int32_t>::min();
int32_t i32max = std::numeric_limits<int32_t>::max();
int32_t i32one = 1;
uint64_t u64min = std::numeric_limits<uint64_t>::min();
uint64_t u64max = std::numeric_limits<uint64_t>::max();
uint64_t u64one = 1;
int64_t i64min = std::numeric_limits<int64_t>::min();
int64_t i64max = std::numeric_limits<int64_t>::max();
int64_t i64one = 1;
float flow = std::numeric_limits<float>::lowest();
float fmin = std::numeric_limits<float>::min();
float fmax = std::numeric_limits<float>::max();
float falmostOne = 1 + 1.0e-37;
double dlow = std::numeric_limits<double>::lowest();
double dmin = std::numeric_limits<double>::min();
double dmax = std::numeric_limits<double>::max();
double dalmostOne = 1 + 1.0e-307;
bool t = true;
bool f = false;
std::stringstream ss;
for (unsigned int i = 0; i < (1 << 11); i++)
ss << "0123456789";
std::string strLong = ss.str();
std::string strEmpty = "";
std::vector<uint8_t> buf;
std::vector<SharedFD> fds;
if (testPodSerdes(u32min) != TestPass)
return TestFail;
if (testPodSerdes(u32max) != TestPass)
return TestFail;
if (testPodSerdes(u32one) != TestPass)
return TestFail;
if (testPodSerdes(i32min) != TestPass)
return TestFail;
if (testPodSerdes(i32max) != TestPass)
return TestFail;
if (testPodSerdes(i32one) != TestPass)
return TestFail;
if (testPodSerdes(u64min) != TestPass)
return TestFail;
if (testPodSerdes(u64max) != TestPass)
return TestFail;
if (testPodSerdes(u64one) != TestPass)
return TestFail;
if (testPodSerdes(i64min) != TestPass)
return TestFail;
if (testPodSerdes(i64max) != TestPass)
return TestFail;
if (testPodSerdes(i64one) != TestPass)
return TestFail;
if (testPodSerdes(flow) != TestPass)
return TestFail;
if (testPodSerdes(fmin) != TestPass)
return TestFail;
if (testPodSerdes(fmax) != TestPass)
return TestFail;
if (testPodSerdes(falmostOne) != TestPass)
return TestFail;
if (testPodSerdes(dlow) != TestPass)
return TestFail;
if (testPodSerdes(dmin) != TestPass)
return TestFail;
if (testPodSerdes(dmax) != TestPass)
return TestFail;
if (testPodSerdes(dalmostOne) != TestPass)
return TestFail;
if (testPodSerdes(t) != TestPass)
return TestFail;
if (testPodSerdes(f) != TestPass)
return TestFail;
if (testPodSerdes(strLong) != TestPass)
return TestFail;
if (testPodSerdes(strEmpty) != TestPass)
return TestFail;
return TestPass;
}
};
TEST_REGISTER(IPADataSerializerTest)

View File

@@ -0,0 +1,16 @@
# SPDX-License-Identifier: CC0-1.0
subdir('generated_serializer')
serialization_tests = [
{'name': 'control_serialization', 'sources': ['control_serialization.cpp']},
{'name': 'ipa_data_serializer_test', 'sources': ['ipa_data_serializer_test.cpp']},
]
foreach test : serialization_tests
exe = executable(test['name'], test['sources'], 'serialization_test.cpp',
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'serialization', is_parallel : false)
endforeach

View File

@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Base class for serialization tests
*/
#include "serialization_test.h"
#include <algorithm>
#include <iostream>
#include <map>
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
#include <libcamera/controls.h>
#include "test.h"
using namespace std;
using namespace libcamera;
bool SerializationTest::equals(const ControlInfoMap &lhs, const ControlInfoMap &rhs)
{
std::map<unsigned int, ControlInfo> rlhs;
std::transform(lhs.begin(), lhs.end(), std::inserter(rlhs, rlhs.end()),
[](const ControlInfoMap::value_type &v)
-> decltype(rlhs)::value_type
{
return { v.first->id(), v.second };
});
std::map<unsigned int, ControlInfo> rrhs;
std::transform(rhs.begin(), rhs.end(), std::inserter(rrhs, rrhs.end()),
[](const ControlInfoMap::value_type &v)
-> decltype(rrhs)::value_type
{
return { v.first->id(), v.second };
});
if (rlhs == rrhs)
return true;
cerr << "lhs:" << endl;
for (const auto &value : rlhs)
cerr << "- " << value.first << ": "
<< value.second.toString() << endl;
cerr << "rhs:" << endl;
for (const auto &value : rrhs)
cerr << "- " << value.first << ": "
<< value.second.toString() << endl;
return false;
}
bool SerializationTest::equals(const ControlList &lhs, const ControlList &rhs)
{
std::map<unsigned int, ControlValue> rlhs;
std::transform(lhs.begin(), lhs.end(), std::inserter(rlhs, rlhs.end()),
[](const std::pair<unsigned int, ControlValue> &v)
-> decltype(rlhs)::value_type
{
return { v.first, v.second };
});
std::map<unsigned int, ControlValue> rrhs;
std::transform(rhs.begin(), rhs.end(), std::inserter(rrhs, rrhs.end()),
[](const std::pair<unsigned int, ControlValue> &v)
-> decltype(rrhs)::value_type
{
return { v.first, v.second };
});
if (rlhs == rrhs)
return true;
cerr << "lhs:" << endl;
for (const auto &value : rlhs)
cerr << "- " << value.first << ": "
<< value.second.toString() << endl;
cerr << "rhs:" << endl;
for (const auto &value : rrhs)
cerr << "- " << value.first << ": "
<< value.second.toString() << endl;
return false;
}

View File

@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Base class for serialization tests
*/
#pragma once
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
#include <libcamera/controls.h>
#include "camera_test.h"
#include "test.h"
class SerializationTest : public CameraTest, public Test
{
public:
SerializationTest()
: CameraTest("platform/vimc.0 Sensor B")
{
}
static bool equals(const libcamera::ControlInfoMap &lhs,
const libcamera::ControlInfoMap &rhs);
static bool equals(const libcamera::ControlList &lhs,
const libcamera::ControlList &rhs);
};

View File

@@ -0,0 +1,244 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* SharedFD test
*/
#include <fcntl.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <libcamera/base/shared_fd.h>
#include <libcamera/base/utils.h>
#include "test.h"
using namespace libcamera;
using namespace std;
class SharedFDTest : public Test
{
protected:
int init()
{
desc1_ = nullptr;
desc2_ = nullptr;
fd_ = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
if (fd_ < 0)
return TestFail;
/* Cache inode number of temp file. */
struct stat s;
if (fstat(fd_, &s))
return TestFail;
inodeNr_ = s.st_ino;
return 0;
}
int run()
{
/* Test creating empty SharedFD. */
desc1_ = new SharedFD();
if (desc1_->get() != -1) {
std::cout << "Failed fd numerical check (default constructor)"
<< std::endl;
return TestFail;
}
delete desc1_;
desc1_ = nullptr;
/*
* Test creating SharedFD by copying numerical file
* descriptor.
*/
desc1_ = new SharedFD(fd_);
if (desc1_->get() == fd_) {
std::cout << "Failed fd numerical check (lvalue ref constructor)"
<< std::endl;
return TestFail;
}
if (!isValidFd(fd_) || !isValidFd(desc1_->get())) {
std::cout << "Failed fd validity after construction (lvalue ref constructor)"
<< std::endl;
return TestFail;
}
int fd = desc1_->get();
delete desc1_;
desc1_ = nullptr;
if (!isValidFd(fd_) || isValidFd(fd)) {
std::cout << "Failed fd validity after destruction (lvalue ref constructor)"
<< std::endl;
return TestFail;
}
/*
* Test creating SharedFD by taking ownership of
* numerical file descriptor.
*/
int dupFd = dup(fd_);
int dupFdCopy = dupFd;
desc1_ = new SharedFD(std::move(dupFd));
if (desc1_->get() != dupFdCopy) {
std::cout << "Failed fd numerical check (rvalue ref constructor)"
<< std::endl;
return TestFail;
}
if (dupFd != -1 || !isValidFd(fd_) || !isValidFd(desc1_->get())) {
std::cout << "Failed fd validity after construction (rvalue ref constructor)"
<< std::endl;
return TestFail;
}
fd = desc1_->get();
delete desc1_;
desc1_ = nullptr;
if (!isValidFd(fd_) || isValidFd(fd)) {
std::cout << "Failed fd validity after destruction (rvalue ref constructor)"
<< std::endl;
return TestFail;
}
/* Test creating SharedFD from other SharedFD. */
desc1_ = new SharedFD(fd_);
desc2_ = new SharedFD(*desc1_);
if (desc1_->get() == fd_ || desc2_->get() == fd_ ||
desc1_->get() != desc2_->get()) {
std::cout << "Failed fd numerical check (copy constructor)"
<< std::endl;
return TestFail;
}
if (!isValidFd(desc1_->get()) || !isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after construction (copy constructor)"
<< std::endl;
return TestFail;
}
delete desc1_;
desc1_ = nullptr;
if (!isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after destruction (copy constructor)"
<< std::endl;
return TestFail;
}
delete desc2_;
desc2_ = nullptr;
/* Test creating SharedFD by taking over other SharedFD. */
desc1_ = new SharedFD(fd_);
fd = desc1_->get();
desc2_ = new SharedFD(std::move(*desc1_));
if (desc1_->get() != -1 || desc2_->get() != fd) {
std::cout << "Failed fd numerical check (move constructor)"
<< std::endl;
return TestFail;
}
if (!isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after construction (move constructor)"
<< std::endl;
return TestFail;
}
delete desc1_;
desc1_ = nullptr;
delete desc2_;
desc2_ = nullptr;
/* Test creating SharedFD by copy assignment. */
desc1_ = new SharedFD();
desc2_ = new SharedFD(fd_);
fd = desc2_->get();
*desc1_ = *desc2_;
if (desc1_->get() != fd || desc2_->get() != fd) {
std::cout << "Failed fd numerical check (copy assignment)"
<< std::endl;
return TestFail;
}
if (!isValidFd(desc1_->get()) || !isValidFd(desc2_->get())) {
std::cout << "Failed fd validity after construction (copy assignment)"
<< std::endl;
return TestFail;
}
delete desc1_;
desc1_ = nullptr;
delete desc2_;
desc2_ = nullptr;
/* Test creating SharedFD by move assignment. */
desc1_ = new SharedFD();
desc2_ = new SharedFD(fd_);
fd = desc2_->get();
*desc1_ = std::move(*desc2_);
if (desc1_->get() != fd || desc2_->get() != -1) {
std::cout << "Failed fd numerical check (move assignment)"
<< std::endl;
return TestFail;
}
if (!isValidFd(desc1_->get())) {
std::cout << "Failed fd validity after construction (move assignment)"
<< std::endl;
return TestFail;
}
delete desc1_;
desc1_ = nullptr;
delete desc2_;
desc2_ = nullptr;
return TestPass;
}
void cleanup()
{
delete desc2_;
delete desc1_;
if (fd_ > 0)
close(fd_);
}
private:
bool isValidFd(int fd)
{
struct stat s;
if (fstat(fd, &s))
return false;
/* Check that inode number matches cached temp file. */
return s.st_ino == inodeNr_;
}
int fd_;
ino_t inodeNr_;
SharedFD *desc1_, *desc2_;
};
TEST_REGISTER(SharedFDTest)

View File

@@ -0,0 +1,134 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Cross-thread signal delivery test
*/
#include <chrono>
#include <iostream>
#include <thread>
#include <libcamera/base/message.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/utils.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class SignalReceiver : public Object
{
public:
enum Status {
NoSignal,
InvalidThread,
SignalReceived,
};
SignalReceiver()
: status_(NoSignal)
{
}
Status status() const { return status_; }
int value() const { return value_; }
void reset()
{
status_ = NoSignal;
value_ = 0;
}
void slot(int value)
{
if (Thread::current() != thread())
status_ = InvalidThread;
else
status_ = SignalReceived;
value_ = value;
}
private:
Status status_;
int value_;
};
class SignalThreadsTest : public Test
{
protected:
int init()
{
receiver_ = new SignalReceiver();
signal_.connect(receiver_, &SignalReceiver::slot);
return TestPass;
}
int run()
{
/* Test that a signal is received in the main thread. */
signal_.emit(0);
switch (receiver_->status()) {
case SignalReceiver::NoSignal:
cout << "No signal received for direct connection" << endl;
return TestFail;
case SignalReceiver::InvalidThread:
cout << "Signal received in incorrect thread "
"for direct connection" << endl;
return TestFail;
default:
break;
}
/*
* Move the object to a thread and verify that the signal is
* correctly delivered, with the correct data.
*/
receiver_->reset();
receiver_->moveToThread(&thread_);
thread_.start();
signal_.emit(42);
this_thread::sleep_for(chrono::milliseconds(100));
switch (receiver_->status()) {
case SignalReceiver::NoSignal:
cout << "No signal received for message connection" << endl;
return TestFail;
case SignalReceiver::InvalidThread:
cout << "Signal received in incorrect thread "
"for message connection" << endl;
return TestFail;
default:
break;
}
if (receiver_->value() != 42) {
cout << "Signal received with incorrect value" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
receiver_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
SignalReceiver *receiver_;
Thread thread_;
Signal<int> signal_;
};
TEST_REGISTER(SignalThreadsTest)

View File

@@ -0,0 +1,361 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Signal test
*/
#include <iostream>
#include <string.h>
#include <libcamera/base/object.h>
#include <libcamera/base/signal.h>
#include "test.h"
using namespace std;
using namespace libcamera;
static int valueStatic_ = 0;
static void slotStatic(int value)
{
valueStatic_ = value;
}
static int slotStaticReturn()
{
return 0;
}
class SlotObject : public Object
{
public:
void slot()
{
valueStatic_ = 1;
}
};
class BaseClass
{
public:
/*
* A virtual function is required in the base class, otherwise the
* compiler will always store Object before BaseClass in memory.
*/
virtual ~BaseClass()
{
}
unsigned int data_[32];
};
class SlotMulti : public BaseClass, public Object
{
public:
void slot()
{
valueStatic_ = 1;
}
};
class SignalTest : public Test
{
protected:
void slotVoid()
{
called_ = true;
}
void slotDisconnect()
{
called_ = true;
signalVoid_.disconnect(this, &SignalTest::slotDisconnect);
}
void slotInteger1(int value)
{
values_[0] = value;
}
void slotInteger2(int value)
{
values_[1] = value;
}
void slotMultiArgs(int value, const std::string &name)
{
values_[2] = value;
name_ = name;
}
int slotReturn()
{
return 0;
}
int init()
{
return 0;
}
int run()
{
/* ----------------- Signal -> !Object tests ---------------- */
/* Test signal emission and reception. */
called_ = false;
signalVoid_.connect(this, &SignalTest::slotVoid);
signalVoid_.emit();
if (!called_) {
cout << "Signal emission test failed" << endl;
return TestFail;
}
/* Test signal with parameters. */
values_[2] = 0;
name_.clear();
signalMultiArgs_.connect(this, &SignalTest::slotMultiArgs);
signalMultiArgs_.emit(42, "H2G2");
if (values_[2] != 42 || name_ != "H2G2") {
cout << "Signal parameters test failed" << endl;
return TestFail;
}
/* Test signal connected to multiple slots. */
memset(values_, 0, sizeof(values_));
valueStatic_ = 0;
signalInt_.connect(this, &SignalTest::slotInteger1);
signalInt_.connect(this, &SignalTest::slotInteger2);
signalInt_.connect(&slotStatic);
signalInt_.emit(42);
if (values_[0] != 42 || values_[1] != 42 || values_[2] != 0 ||
valueStatic_ != 42) {
cout << "Signal multi slot test failed" << endl;
return TestFail;
}
/* Test disconnection of a single slot. */
memset(values_, 0, sizeof(values_));
signalInt_.disconnect(this, &SignalTest::slotInteger2);
signalInt_.emit(42);
if (values_[0] != 42 || values_[1] != 0 || values_[2] != 0) {
cout << "Signal slot disconnection test failed" << endl;
return TestFail;
}
/* Test disconnection of a whole object. */
memset(values_, 0, sizeof(values_));
signalInt_.disconnect(this);
signalInt_.emit(42);
if (values_[0] != 0 || values_[1] != 0 || values_[2] != 0) {
cout << "Signal object disconnection test failed" << endl;
return TestFail;
}
/* Test disconnection of a whole signal. */
memset(values_, 0, sizeof(values_));
signalInt_.connect(this, &SignalTest::slotInteger1);
signalInt_.connect(this, &SignalTest::slotInteger2);
signalInt_.disconnect();
signalInt_.emit(42);
if (values_[0] != 0 || values_[1] != 0 || values_[2] != 0) {
cout << "Signal object disconnection test failed" << endl;
return TestFail;
}
/* Test disconnection from slot. */
signalVoid_.disconnect();
signalVoid_.connect(this, &SignalTest::slotDisconnect);
signalVoid_.emit();
called_ = false;
signalVoid_.emit();
if (called_) {
cout << "Signal disconnection from slot test failed" << endl;
return TestFail;
}
/*
* Test connecting to slots that return a value. This targets
* compilation, there's no need to check runtime results.
*/
signalVoid_.connect(slotStaticReturn);
signalVoid_.connect(this, &SignalTest::slotReturn);
/* Test signal connection to a lambda. */
int value = 0;
signalInt_.connect(this, [&](int v) { value = v; });
signalInt_.emit(42);
if (value != 42) {
cout << "Signal connection to lambda failed" << endl;
return TestFail;
}
signalInt_.disconnect(this);
signalInt_.emit(0);
if (value != 42) {
cout << "Signal disconnection from lambda failed" << endl;
return TestFail;
}
/* ----------------- Signal -> Object tests ----------------- */
/*
* Test automatic disconnection on object deletion. Connect two
* signals to ensure all instances are disconnected.
*/
signalVoid_.disconnect();
signalVoid2_.disconnect();
SlotObject *slotObject = new SlotObject();
signalVoid_.connect(slotObject, &SlotObject::slot);
signalVoid2_.connect(slotObject, &SlotObject::slot);
delete slotObject;
valueStatic_ = 0;
signalVoid_.emit();
signalVoid2_.emit();
if (valueStatic_ != 0) {
cout << "Signal disconnection on object deletion test failed" << endl;
return TestFail;
}
/*
* Test that signal deletion disconnects objects. This shall
* not generate any valgrind warning.
*/
Signal<> *dynamicSignal = new Signal<>();
slotObject = new SlotObject();
dynamicSignal->connect(slotObject, &SlotObject::slot);
delete dynamicSignal;
delete slotObject;
/*
* Test that signal manual disconnection from Object removes
* the signal for the object. This shall not generate any
* valgrind warning.
*/
dynamicSignal = new Signal<>();
slotObject = new SlotObject();
dynamicSignal->connect(slotObject, &SlotObject::slot);
dynamicSignal->disconnect(slotObject);
delete dynamicSignal;
delete slotObject;
/*
* Test that signal manual disconnection from all slots removes
* the signal for the object. This shall not generate any
* valgrind warning.
*/
dynamicSignal = new Signal<>();
slotObject = new SlotObject();
dynamicSignal->connect(slotObject, &SlotObject::slot);
dynamicSignal->disconnect();
delete dynamicSignal;
delete slotObject;
/* Exercise the Object slot code paths. */
slotObject = new SlotObject();
signalVoid_.connect(slotObject, &SlotObject::slot);
valueStatic_ = 0;
signalVoid_.emit();
if (valueStatic_ == 0) {
cout << "Signal delivery for Object test failed" << endl;
return TestFail;
}
delete slotObject;
/* Test signal connection to a lambda. */
slotObject = new SlotObject();
value = 0;
signalInt_.connect(slotObject, [&](int v) { value = v; });
signalInt_.emit(42);
if (value != 42) {
cout << "Signal connection to Object lambda failed" << endl;
return TestFail;
}
signalInt_.disconnect(slotObject);
signalInt_.emit(0);
if (value != 42) {
cout << "Signal disconnection from Object lambda failed" << endl;
return TestFail;
}
delete slotObject;
/* --------- Signal -> Object (multiple inheritance) -------- */
/*
* Test automatic disconnection on object deletion. Connect two
* signals to ensure all instances are disconnected.
*/
signalVoid_.disconnect();
signalVoid2_.disconnect();
SlotMulti *slotMulti = new SlotMulti();
signalVoid_.connect(slotMulti, &SlotMulti::slot);
signalVoid2_.connect(slotMulti, &SlotMulti::slot);
delete slotMulti;
valueStatic_ = 0;
signalVoid_.emit();
signalVoid2_.emit();
if (valueStatic_ != 0) {
cout << "Signal disconnection on object deletion test failed" << endl;
return TestFail;
}
/*
* Test that signal deletion disconnects objects. This shall
* not generate any valgrind warning.
*/
dynamicSignal = new Signal<>();
slotMulti = new SlotMulti();
dynamicSignal->connect(slotMulti, &SlotMulti::slot);
delete dynamicSignal;
delete slotMulti;
/* Exercise the Object slot code paths. */
slotMulti = new SlotMulti();
signalVoid_.connect(slotMulti, &SlotMulti::slot);
valueStatic_ = 0;
signalVoid_.emit();
if (valueStatic_ == 0) {
cout << "Signal delivery for Object test failed" << endl;
return TestFail;
}
delete slotMulti;
return TestPass;
}
void cleanup()
{
}
private:
Signal<> signalVoid_;
Signal<> signalVoid2_;
Signal<int> signalInt_;
Signal<int, const std::string &> signalMultiArgs_;
bool called_;
int values_[3];
std::string name_;
};
TEST_REGISTER(SignalTest)

View File

@@ -0,0 +1,205 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* Span tests
*/
/*
* Include first to ensure the header is self-contained, as there's no span.cpp
* in libcamera.
*/
#include <libcamera/base/span.h>
#include <array>
#include <iostream>
#include <vector>
#include "test.h"
using namespace std;
using namespace libcamera;
class SpanTest : public Test
{
protected:
int run()
{
int i[4]{ 1, 2, 3, 4 };
std::array<int, 4> a{ 1, 2, 3, 4 };
const std::array<int, 4> ca{ 1, 2, 3, 4 };
std::vector<int> v{ 1, 2, 3, 4 };
const std::vector<int> cv{ 1, 2, 3, 4 };
/*
* Compile-test construction and usage of spans with static
* extent. Commented-out tests are expected not to compile, or
* to generate undefined behaviour.
*/
Span<int, 0>{};
/* Span<int, 4>{}; */
Span<int, 4>{ &i[0], 4 };
Span<int, 4>{ &i[0], &i[3] };
Span<int, 4>{ i };
/* Span<float, 4>{ i }; */
/* Span<int, 2>{ i }; */
Span<int, 4>{ a };
Span<const int, 4>{ a };
/* Span<float, 4>{ a }; */
/* Span<int, 2>{ a }; */
Span<const int, 4>{ ca };
/* Span<const int, 2>{ ca }; */
/* Span<const float, 4>{ ca }; */
/* Span<int, 4>{ ca }; */
Span<int, 4>{ v };
Span<const int, 4>{ v };
/* Span<float, 4>{ v }; */
Span<const int, 4>{ v };
/* Span<int, 4>{ v }; */
/* Span<const float, 4>{ v }; */
Span<int, 4> staticSpan{ i };
Span<int, 4>{ staticSpan };
Span<const int, 4>{ staticSpan };
/* Span<const int, 2>{ staticSpan }; */
staticSpan = Span<int, 4>{ v };
if (*staticSpan.begin() != 1) {
std::cout << "Span<static_extent>::begin() failed" << std::endl;
return TestFail;
}
if (*staticSpan.cbegin() != 1) {
std::cout << "Span<static_extent>::cbegin() failed" << std::endl;
return TestFail;
}
staticSpan.end();
staticSpan.cend();
if (*staticSpan.rbegin() != 4) {
std::cout << "Span<static_extent>::rbegin() failed" << std::endl;
return TestFail;
}
if (*staticSpan.crbegin() != 4) {
std::cout << "Span<static_extent>::crbegin() failed" << std::endl;
return TestFail;
}
staticSpan.rend();
staticSpan.crend();
staticSpan.front();
staticSpan.back();
staticSpan[0];
staticSpan.data();
staticSpan.size();
staticSpan.size_bytes();
staticSpan.empty();
staticSpan.first<2>();
staticSpan.first(2);
/* staticSpan.first<6>(); */
/* staticSpan.first(6); */
staticSpan.last<2>();
staticSpan.last(2);
/* staticSpan.last<6>(); */
/* staticSpan.last(6); */
staticSpan.subspan<1>();
staticSpan.subspan<1, 2>();
staticSpan.subspan(1);
staticSpan.subspan(1, 2);
/* staticSpan.subspan(2, 4); */
/*
* Compile-test construction and usage of spans with dynamic
* extent. Commented-out tests are expected not to compile, or
* to generate undefined behaviour.
*/
Span<int>{};
Span<int>{ &i[0], 4 };
Span<int>{ &i[0], &i[3] };
Span<int>{ i };
/* Span<float>{ i }; */
Span<int>{ a };
Span<const int>{ a };
/* Span<float>{ a }; */
Span<const int>{ ca };
/* Span<const float>{ca}; */
/* Span<int>{ca}; */
Span<int>{ v };
Span<const int>{ v };
/* Span<float>{ v }; */
Span<const int>{ v };
/* Span<int>{ v }; */
/* Span<const float>{ v }; */
Span<int> dynamicSpan{ i };
Span<int>{ dynamicSpan };
Span<const int>{ dynamicSpan };
dynamicSpan = Span<int>{ a };
if (*dynamicSpan.begin() != 1) {
std::cout << "Span<dynamic_extent>::begin() failed" << std::endl;
return TestFail;
}
if (*dynamicSpan.cbegin() != 1) {
std::cout << "Span<dynamic_extent>::cbegin() failed" << std::endl;
return TestFail;
}
dynamicSpan.end();
dynamicSpan.cend();
if (*dynamicSpan.rbegin() != 4) {
std::cout << "Span<dynamic_extent>::rbegin() failed" << std::endl;
return TestFail;
}
if (*dynamicSpan.crbegin() != 4) {
std::cout << "Span<dynamic_extent>::crbegin() failed" << std::endl;
return TestFail;
}
dynamicSpan.rend();
dynamicSpan.crend();
dynamicSpan.front();
dynamicSpan.back();
dynamicSpan[0];
dynamicSpan.data();
dynamicSpan.size();
dynamicSpan.size_bytes();
dynamicSpan.empty();
dynamicSpan.first<2>();
dynamicSpan.first(2);
/* dynamicSpan.first<6>(); */
/* dynamicSpan.first(6); */
dynamicSpan.last<2>();
dynamicSpan.last(2);
/* dynamicSpan.last<6>(); */
/* dynamicSpan.last(6); */
dynamicSpan.subspan<1>();
dynamicSpan.subspan<1, 2>();
dynamicSpan.subspan(1);
dynamicSpan.subspan(1, 2);
/* dynamicSpan.subspan(2, 4); */
return TestPass;
}
};
TEST_REGISTER(SpanTest)

View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: CC0-1.0
stream_tests = [
{'name': 'stream_colorspace', 'sources': ['stream_colorspace.cpp']},
{'name': 'stream_formats', 'sources': ['stream_formats.cpp']},
]
foreach test : stream_tests
exe = executable(test['name'], test['sources'],
dependencies : libcamera_public,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'stream')
endforeach

View File

@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2022, Ideas on Board Oy.
*
* Stream colorspace adjustment test
*/
#include <iostream>
#include <libcamera/camera.h>
#include <libcamera/formats.h>
#include <libcamera/stream.h>
#include "test.h"
using namespace libcamera;
using namespace std;
class TestCameraConfiguration : public CameraConfiguration
{
public:
TestCameraConfiguration()
: CameraConfiguration()
{
}
Status validate() override
{
return validateColorSpaces();
}
};
class StreamColorSpaceTest : public Test
{
protected:
int run()
{
TestCameraConfiguration config;
StreamConfiguration cfg;
cfg.size = { 640, 320 };
cfg.pixelFormat = formats::YUV422;
cfg.colorSpace = ColorSpace::Srgb;
config.addConfiguration(cfg);
StreamConfiguration &streamCfg = config.at(0);
/*
* YUV pixelformat with sRGB colorspace should have Y'CbCr encoding
* adjusted.
*/
config.validate();
if (streamCfg.colorSpace->ycbcrEncoding == ColorSpace::YcbcrEncoding::None) {
cerr << "YUV format must have YCbCr encoding" << endl;
return TestFail;
}
/*
* For YUV pixelFormat, encoding should be picked up according
* to primaries and transfer function, if 'None' is specified.
*/
streamCfg.pixelFormat = formats::YUV422;
streamCfg.colorSpace = ColorSpace(ColorSpace::Primaries::Rec2020,
ColorSpace::TransferFunction::Rec709,
ColorSpace::YcbcrEncoding::None,
ColorSpace::Range::Limited);
config.validate();
if (streamCfg.colorSpace->ycbcrEncoding != ColorSpace::YcbcrEncoding::Rec2020) {
cerr << "Failed to adjust colorspace Y'CbCr encoding according"
<< " to primaries and transfer function" << endl;
return TestFail;
}
/* For RGB pixelFormat, Sycc colorspace should get adjusted to sRGB. */
streamCfg.pixelFormat = formats::RGB888;
streamCfg.colorSpace = ColorSpace::Sycc;
config.validate();
if (streamCfg.colorSpace != ColorSpace::Srgb) {
cerr << "RGB format's colorspace should be set to Srgb" << endl;
return TestFail;
}
/* Raw formats should always set colorspace to ColorSpace::Raw. */
streamCfg.pixelFormat = formats::SBGGR8;
streamCfg.colorSpace = ColorSpace::Rec709;
config.validate();
if (streamCfg.colorSpace != ColorSpace::Raw) {
cerr << "Raw format must always have Raw colorspace" << endl;
return TestFail;
}
return TestPass;
}
};
TEST_REGISTER(StreamColorSpaceTest)

View File

@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* StreamFormats test
*/
#include <iostream>
#include <libcamera/geometry.h>
#include <libcamera/stream.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class StreamFormatsTest : public Test
{
protected:
int testSizes(std::string name, std::vector<Size> test, std::vector<Size> valid)
{
bool pass = false;
for (Size &size : test) {
pass = false;
for (Size &validSize : valid) {
if (size == validSize) {
pass = true;
break;
}
}
if (!pass)
break;
}
if (!pass) {
cout << "Failed " << name << endl;
cout << "Sizes to test:" << endl;
for (Size &size : test)
cout << size << endl;
cout << "Valid sizes:" << endl;
for (Size &size : valid)
cout << size << endl;
return TestFail;
}
return TestPass;
}
int run()
{
/* Test discrete sizes */
StreamFormats discrete({
{ PixelFormat(1), { SizeRange({ 100, 100 }), SizeRange({ 200, 200 }) } },
{ PixelFormat(2), { SizeRange({ 300, 300 }), SizeRange({ 400, 400 }) } },
});
if (testSizes("discrete 1", discrete.sizes(PixelFormat(1)),
{ Size(100, 100), Size(200, 200) }))
return TestFail;
if (testSizes("discrete 2", discrete.sizes(PixelFormat(2)),
{ Size(300, 300), Size(400, 400) }))
return TestFail;
/* Test range sizes */
StreamFormats range({
{ PixelFormat(1), { SizeRange({ 640, 480 }, { 640, 480 }) } },
{ PixelFormat(2), { SizeRange({ 640, 480 }, { 800, 600 }, 8, 8) } },
{ PixelFormat(3), { SizeRange({ 640, 480 }, { 800, 600 }, 16, 16) } },
{ PixelFormat(4), { SizeRange({ 128, 128 }, { 4096, 4096 }, 128, 128) } },
});
if (testSizes("range 1", range.sizes(PixelFormat(1)), { Size(640, 480) }))
return TestFail;
if (testSizes("range 2", range.sizes(PixelFormat(2)), {
Size(640, 480), Size(720, 480),
Size(720, 576), Size(768, 480),
Size(800, 600) }))
return TestFail;
if (testSizes("range 3", range.sizes(PixelFormat(3)), {
Size(640, 480), Size(720, 480),
Size(720, 576), Size(768, 480) }))
return TestFail;
if (testSizes("range 4", range.sizes(PixelFormat(4)), {
Size(1024, 768), Size(1280, 1024),
Size(2048, 1152), Size(2048, 1536),
Size(2560, 2048), Size(3200, 2048), }))
return TestFail;
return TestPass;
}
};
TEST_REGISTER(StreamFormatsTest)

View File

@@ -0,0 +1,176 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Threads test
*/
#include <chrono>
#include <iostream>
#include <memory>
#include <pthread.h>
#include <thread>
#include <time.h>
#include <libcamera/base/thread.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class DelayThread : public Thread
{
public:
DelayThread(chrono::steady_clock::duration duration)
: duration_(duration)
{
}
protected:
void run()
{
this_thread::sleep_for(duration_);
}
private:
chrono::steady_clock::duration duration_;
};
class CancelThread : public Thread
{
public:
CancelThread(bool &cancelled)
: cancelled_(cancelled)
{
}
protected:
void run()
{
cancelled_ = true;
/*
* Cancel the thread and call a guaranteed cancellation point
* (nanosleep).
*/
pthread_cancel(pthread_self());
struct timespec req{ 0, 100*000*000 };
nanosleep(&req, nullptr);
cancelled_ = false;
}
private:
bool &cancelled_;
};
class ThreadTest : public Test
{
protected:
int init()
{
return 0;
}
int run()
{
/* Test Thread() retrieval for the main thread. */
Thread *mainThread = Thread::current();
if (!mainThread) {
cout << "Thread::current() failed to main thread"
<< endl;
return TestFail;
}
if (!mainThread->isRunning()) {
cout << "Main thread is not running" << endl;
return TestFail;
}
/* Test starting the main thread, the test shall not crash. */
mainThread->start();
/* Test the running state of a custom thread. */
std::unique_ptr<Thread> thread = std::make_unique<Thread>();
thread->start();
if (!thread->isRunning()) {
cout << "Thread is not running after being started"
<< endl;
return TestFail;
}
thread->exit(0);
thread->wait();
if (thread->isRunning()) {
cout << "Thread is still running after finishing"
<< endl;
return TestFail;
}
/* Test waiting for completion with a timeout. */
thread = std::make_unique<DelayThread>(chrono::milliseconds(500));
thread->start();
thread->exit(0);
bool timeout = !thread->wait(chrono::milliseconds(100));
if (!timeout) {
cout << "Waiting for thread didn't time out" << endl;
return TestFail;
}
timeout = !thread->wait(chrono::milliseconds(1000));
if (timeout) {
cout << "Waiting for thread timed out" << endl;
return TestFail;
}
/* Test waiting on a thread that isn't running. */
thread = std::make_unique<Thread>();
timeout = !thread->wait();
if (timeout) {
cout << "Waiting for non-started thread timed out" << endl;
return TestFail;
}
thread->start();
thread->exit(0);
thread->wait();
timeout = !thread->wait();
if (timeout) {
cout << "Waiting for already stopped thread timed out" << endl;
return TestFail;
}
/* Test thread cleanup upon abnormal termination. */
bool cancelled = false;
bool finished = false;
thread = std::make_unique<CancelThread>(cancelled);
thread->finished.connect(this, [&finished]() { finished = true; });
thread->start();
thread->exit(0);
thread->wait(chrono::milliseconds(1000));
if (!cancelled || !finished) {
cout << "Cleanup failed upon abnormal termination" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
}
};
TEST_REGISTER(ThreadTest)

View File

@@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2024, Ideas on Board Oy
*
* Threaded timer failure test
*/
#include <chrono>
#include <iostream>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
class TimeoutHandler : public Object
{
public:
TimeoutHandler()
: timer_(this), timeout_(false)
{
timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler);
}
void start()
{
timer_.start(100ms);
}
bool timeout() const
{
return timeout_;
}
private:
void timeoutHandler()
{
timeout_ = true;
}
Timer timer_;
bool timeout_;
};
class TimerFailTest : public Test
{
protected:
int init()
{
thread_.start();
timeout_ = new TimeoutHandler();
timeout_->moveToThread(&thread_);
return TestPass;
}
int run()
{
/*
* Test that the forbidden operation of starting the timer from
* another thread results in a failure. We need to interrupt the
* event dispatcher to make sure we don't succeed simply because
* the event dispatcher hasn't noticed the timer restart.
*/
timeout_->start();
thread_.eventDispatcher()->interrupt();
this_thread::sleep_for(chrono::milliseconds(200));
/*
* The wrong start() call should result in an assertion in debug
* builds, and a timeout in release builds. The test is
* therefore marked in meson.build as expected to fail. We need
* to return TestPass in the unexpected (usually known as
* "fail") case, and TestFail otherwise.
*/
if (timeout_->timeout()) {
cout << "Timer start from wrong thread succeeded unexpectedly"
<< endl;
return TestPass;
}
return TestFail;
}
void cleanup()
{
/*
* Object class instances must be destroyed from the thread
* they live in.
*/
timeout_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
TimeoutHandler *timeout_;
Thread thread_;
};
TEST_REGISTER(TimerFailTest)

View File

@@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Threaded timer test
*/
#include <chrono>
#include <iostream>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
class TimeoutHandler : public Object
{
public:
TimeoutHandler()
: timer_(this), timeout_(false)
{
timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler);
timer_.start(100ms);
}
bool timeout() const
{
return timeout_;
}
private:
void timeoutHandler()
{
timeout_ = true;
}
Timer timer_;
bool timeout_;
};
class TimerThreadTest : public Test
{
protected:
int init()
{
thread_.start();
timeout_ = new TimeoutHandler();
timeout_->moveToThread(&thread_);
return TestPass;
}
int run()
{
/*
* Test that the timer expires and emits the timeout signal in
* the thread it belongs to.
*/
this_thread::sleep_for(chrono::milliseconds(200));
if (!timeout_->timeout()) {
cout << "Timer expiration test failed" << endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
/*
* Object class instances must be destroyed from the thread
* they live in.
*/
timeout_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
TimeoutHandler *timeout_;
Thread thread_;
};
TEST_REGISTER(TimerThreadTest)

View File

@@ -0,0 +1,212 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Timer test
*/
#include <chrono>
#include <iostream>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "test.h"
using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
class ManagedTimer : public Timer
{
public:
ManagedTimer()
: Timer(), count_(0)
{
timeout.connect(this, &ManagedTimer::timeoutHandler);
}
void start(std::chrono::milliseconds msec)
{
count_ = 0;
start_ = std::chrono::steady_clock::now();
expiration_ = std::chrono::steady_clock::time_point();
Timer::start(msec);
}
void start(std::chrono::steady_clock::time_point deadline)
{
count_ = 0;
start_ = std::chrono::steady_clock::now();
expiration_ = std::chrono::steady_clock::time_point();
Timer::start(deadline);
}
int jitter()
{
std::chrono::steady_clock::duration duration = expiration_ - deadline();
return abs(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}
bool hasFailed()
{
return isRunning() || count_ != 1 || jitter() > 50;
}
private:
void timeoutHandler()
{
expiration_ = std::chrono::steady_clock::now();
count_++;
}
unsigned int count_;
std::chrono::steady_clock::time_point start_;
std::chrono::steady_clock::time_point expiration_;
};
class TimerTest : public Test
{
protected:
int init()
{
return 0;
}
int run()
{
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
ManagedTimer timer;
ManagedTimer timer2;
/* Timer expiration. */
timer.start(1000ms);
if (!timer.isRunning()) {
cout << "Timer expiration test failed" << endl;
return TestFail;
}
dispatcher->processEvents();
if (timer.hasFailed()) {
cout << "Timer expiration test failed" << endl;
return TestFail;
}
/*
* 32 bit wrap test
* Nanosecond resolution in a 32 bit value wraps at 4.294967
* seconds (0xFFFFFFFF / 1000000)
*/
timer.start(4295ms);
dispatcher->processEvents();
if (timer.hasFailed()) {
cout << "Timer expiration test failed" << endl;
return TestFail;
}
/* Timer restart. */
timer.start(500ms);
if (!timer.isRunning()) {
cout << "Timer restart test failed" << endl;
return TestFail;
}
dispatcher->processEvents();
if (timer.hasFailed()) {
cout << "Timer restart test failed" << endl;
return TestFail;
}
/* Timer restart before expiration. */
timer.start(50ms);
timer.start(100ms);
timer.start(150ms);
dispatcher->processEvents();
if (timer.hasFailed()) {
cout << "Timer restart before expiration test failed" << endl;
return TestFail;
}
/* Timer with absolute deadline. */
timer.start(std::chrono::steady_clock::now() + std::chrono::milliseconds(200));
dispatcher->processEvents();
if (timer.hasFailed()) {
cout << "Absolute deadline test failed" << endl;
return TestFail;
}
/* Two timers. */
timer.start(1000ms);
timer2.start(300ms);
dispatcher->processEvents();
if (!timer.isRunning()) {
cout << "Two timers test failed" << endl;
return TestFail;
}
if (timer2.jitter() > 50) {
cout << "Two timers test failed" << endl;
return TestFail;
}
dispatcher->processEvents();
if (timer.jitter() > 50) {
cout << "Two timers test failed" << endl;
return TestFail;
}
/* Restart timer before expiration. */
timer.start(1000ms);
timer2.start(300ms);
dispatcher->processEvents();
if (timer2.jitter() > 50) {
cout << "Two timers test failed" << endl;
return TestFail;
}
timer.start(1000ms);
dispatcher->processEvents();
if (timer.jitter() > 50) {
cout << "Two timers test failed" << endl;
return TestFail;
}
/*
* Test that dynamically allocated timers are stopped when
* deleted. This will result in a crash on failure.
*/
ManagedTimer *dyntimer = new ManagedTimer();
dyntimer->start(100ms);
delete dyntimer;
timer.start(200ms);
dispatcher->processEvents();
return TestPass;
}
void cleanup()
{
}
};
TEST_REGISTER(TimerTest)

View File

@@ -0,0 +1,329 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2023, Ideas On Board Oy
*
* Transform and Orientation tests
*/
#include <iostream>
#include <libcamera/orientation.h>
#include <libcamera/transform.h>
#include "test.h"
using namespace std;
using namespace libcamera;
class TransformTest : public Test
{
protected:
int run();
};
int TransformTest::run()
{
/*
* RotationTestEntry collects two Orientation and one Transform that
* gets combined to validate that (o1 / o2 = T) and (o1 = o2 * T)
*
* o1 / o2 = t computes the Transform to apply to o2 to obtain o1
* o2 * t = o1 combines o2 with t by applying o2 first then t
*
* The comments on the (most complex) transform show how applying to
* an image with orientation o2 the Transform t allows to obtain o1.
*
* The image with basic rotation0 is assumed to be:
*
* AB
* CD
*
* And the Transform operators are:
*
* V = vertical flip
* H = horizontal flip
* T = transpose
*
* the operator '* (T|V)' applies V first then T.
*/
static const struct RotationTestEntry {
Orientation o1;
Orientation o2;
Transform t;
} testEntries[] = {
/* Test identities transforms first. */
{
Orientation::Rotate0, Orientation::Rotate0,
Transform::Identity,
},
{
Orientation::Rotate0Mirror, Orientation::Rotate0Mirror,
Transform::Identity,
},
{
Orientation::Rotate180, Orientation::Rotate180,
Transform::Identity,
},
{
Orientation::Rotate180Mirror, Orientation::Rotate180Mirror,
Transform::Identity,
},
{
Orientation::Rotate90, Orientation::Rotate90,
Transform::Identity,
},
{
Orientation::Rotate90Mirror, Orientation::Rotate90Mirror,
Transform::Identity,
},
{
Orientation::Rotate270, Orientation::Rotate270,
Transform::Identity,
},
{
Orientation::Rotate270Mirror, Orientation::Rotate270Mirror,
Transform::Identity,
},
/*
* Combine 0 and 180 degrees rotation as they're the most common
* ones.
*/
{
/*
* o2 t o1
* --------------------------
* CD * (H|V) = BA AB
* BA CD CD
*/
Orientation::Rotate0, Orientation::Rotate180,
Transform::Rot180,
},
{
/*
* o2 t o1
* --------------------------
* AB * (H|V) = CD DC
* CD AB BA
*/
Orientation::Rotate180, Orientation::Rotate0,
Transform::Rot180
},
/* Test that transpositions are handled correctly. */
{
/*
* o2 t o1
* --------------------------
* AB * (T|V) = CD CA
* CD AB DB
*/
Orientation::Rotate90, Orientation::Rotate0,
Transform::Rot90,
},
{
/*
* o2 t o1
* --------------------------
* CA * (T|H) = AC AB
* DB BD CD
*/
Orientation::Rotate0, Orientation::Rotate90,
Transform::Rot270,
},
{
/*
* o2 t o1
* --------------------------
* AB * (T|H) = BA BD
* CD DC AC
*/
Orientation::Rotate270, Orientation::Rotate0,
Transform::Rot270,
},
{
/*
* o2 t o1
* --------------------------
* BD * (T|V) = AC AB
* AC BD CD
*/
Orientation::Rotate0, Orientation::Rotate270,
Transform::Rot90,
},
{
/*
* o2 t o1
* --------------------------
* CD * (T|H) = DC DA
* BA AB CB
*/
Orientation::Rotate90, Orientation::Rotate180,
Transform::Rot270,
},
{
/*
* o2 t o1
* --------------------------
* DA * (T|V) = CB CD
* CB DA BA
*/
Orientation::Rotate180, Orientation::Rotate90,
Transform::Rot90,
},
{
/*
* o2 t o1
* --------------------------
* CD * (T|V) = BA BC
* BA CD AD
*/
Orientation::Rotate270, Orientation::Rotate180,
Transform::Rot90,
},
{
/*
* o2 t o1
* --------------------------
* BC * (T|H) = CB CD
* AD DA BA
*/
Orientation::Rotate180, Orientation::Rotate270,
Transform::Rot270,
},
{
/*
* o2 t o1
* --------------------------
* DA * (V|H) = AD BC
* CB BC AD
*/
Orientation::Rotate270, Orientation::Rotate90,
Transform::Rot180,
},
/* Test that mirroring is handled correctly. */
{
Orientation::Rotate0, Orientation::Rotate0Mirror,
Transform::HFlip
},
{
Orientation::Rotate0Mirror, Orientation::Rotate0,
Transform::HFlip
},
{
Orientation::Rotate180, Orientation::Rotate180Mirror,
Transform::HFlip
},
{
Orientation::Rotate180Mirror, Orientation::Rotate180,
Transform::HFlip
},
{
Orientation::Rotate90, Orientation::Rotate90Mirror,
Transform::HFlip
},
{
Orientation::Rotate90Mirror, Orientation::Rotate90,
Transform::HFlip
},
{
Orientation::Rotate270, Orientation::Rotate270Mirror,
Transform::HFlip
},
{
Orientation::Rotate270Mirror, Orientation::Rotate270,
Transform::HFlip
},
{
Orientation::Rotate0, Orientation::Rotate0Mirror,
Transform::HFlip
},
/*
* More exotic transforms which include Transpositions and
* mirroring.
*/
{
/*
* o2 t o1
* ------------------
* BC * (V) = AD
* AD BC
*/
Orientation::Rotate90Mirror, Orientation::Rotate270,
Transform::VFlip,
},
{
/*
* o2 t o1
* ------------------
* CB * (T) = CD
* DA BA
*/
Orientation::Rotate180, Orientation::Rotate270Mirror,
Transform::Transpose,
},
{
/*
* o2 t o1
* ------------------
* AD * (T) = AB
* BC DC
*/
Orientation::Rotate0, Orientation::Rotate90Mirror,
Transform::Transpose,
},
{
/*
* o2 t o1
* ------------------
* AD * (V) = BC
* BC AD
*/
Orientation::Rotate270, Orientation::Rotate90Mirror,
Transform::VFlip,
},
{
/*
* o2 t o1
* ------------------
* DA * (V) = CB
* CB DA
*/
Orientation::Rotate270Mirror, Orientation::Rotate90,
Transform::VFlip,
},
{
/*
* o2 t o1
* --------------------------
* CB * (V|H) = BC AD
* DA AD BC
*/
Orientation::Rotate90Mirror, Orientation::Rotate270Mirror,
Transform::Rot180,
},
};
for (const auto &entry : testEntries) {
Transform transform = entry.o1 / entry.o2;
if (transform != entry.t) {
cerr << "Failed to validate: " << entry.o1
<< " / " << entry.o2
<< " = " << transformToString(entry.t) << endl;
cerr << "Got back: "
<< transformToString(transform) << endl;
return TestFail;
}
Orientation adjusted = entry.o2 * entry.t;
if (adjusted != entry.o1) {
cerr << "Failed to validate: " << entry.o2
<< " * " << transformToString(entry.t)
<< " = " << entry.o1 << endl;
cerr << "Got back: " << adjusted << endl;
return TestFail;
}
}
return TestPass;
}
TEST_REGISTER(TransformTest)

View File

@@ -0,0 +1,220 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021, Google Inc.
*
* UniqueFD test
*/
#include <fcntl.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <libcamera/base/unique_fd.h>
#include <libcamera/base/utils.h>
#include "test.h"
using namespace libcamera;
using namespace std;
class UniqueFDTest : public Test
{
protected:
int init() override
{
return createFd();
}
int run() override
{
/* Test creating empty UniqueFD. */
UniqueFD fd;
if (fd.isValid() || fd.get() != -1) {
std::cout << "Failed fd check (default constructor)"
<< std::endl;
return TestFail;
}
/* Test creating UniqueFD from numerical file descriptor. */
UniqueFD fd2(fd_);
if (!fd2.isValid() || fd2.get() != fd_) {
std::cout << "Failed fd check (fd constructor)"
<< std::endl;
return TestFail;
}
if (!isValidFd(fd_)) {
std::cout << "Failed fd validity (fd constructor)"
<< std::endl;
return TestFail;
}
/* Test move constructor. */
UniqueFD fd3(std::move(fd2));
if (!fd3.isValid() || fd3.get() != fd_) {
std::cout << "Failed fd check (move constructor)"
<< std::endl;
return TestFail;
}
if (fd2.isValid() || fd2.get() != -1) {
std::cout << "Failed moved fd check (move constructor)"
<< std::endl;
return TestFail;
}
if (!isValidFd(fd_)) {
std::cout << "Failed fd validity (move constructor)"
<< std::endl;
return TestFail;
}
/* Test move assignment operator. */
fd = std::move(fd3);
if (!fd.isValid() || fd.get() != fd_) {
std::cout << "Failed fd check (move assignment)"
<< std::endl;
return TestFail;
}
if (fd3.isValid() || fd3.get() != -1) {
std::cout << "Failed moved fd check (move assignment)"
<< std::endl;
return TestFail;
}
if (!isValidFd(fd_)) {
std::cout << "Failed fd validity (move assignment)"
<< std::endl;
return TestFail;
}
/* Test swapping. */
fd2.swap(fd);
if (!fd2.isValid() || fd2.get() != fd_) {
std::cout << "Failed fd check (swap)"
<< std::endl;
return TestFail;
}
if (fd.isValid() || fd.get() != -1) {
std::cout << "Failed swapped fd check (swap)"
<< std::endl;
return TestFail;
}
if (!isValidFd(fd_)) {
std::cout << "Failed fd validity (swap)"
<< std::endl;
return TestFail;
}
/* Test release. */
int numFd = fd2.release();
if (fd2.isValid() || fd2.get() != -1) {
std::cout << "Failed fd check (release)"
<< std::endl;
return TestFail;
}
if (numFd != fd_) {
std::cout << "Failed released fd check (release)"
<< std::endl;
return TestFail;
}
if (!isValidFd(fd_)) {
std::cout << "Failed fd validity (release)"
<< std::endl;
return TestFail;
}
/* Test reset assignment. */
fd.reset(numFd);
if (!fd.isValid() || fd.get() != fd_) {
std::cout << "Failed fd check (reset assignment)"
<< std::endl;
return TestFail;
}
if (!isValidFd(fd_)) {
std::cout << "Failed fd validity (reset assignment)"
<< std::endl;
return TestFail;
}
/* Test reset destruction. */
fd.reset();
if (fd.isValid() || fd.get() != -1) {
std::cout << "Failed fd check (reset destruction)"
<< std::endl;
return TestFail;
}
if (isValidFd(fd_)) {
std::cout << "Failed fd validity (reset destruction)"
<< std::endl;
return TestFail;
}
/* Test destruction. */
if (createFd() == TestFail) {
std::cout << "Failed to recreate test fd"
<< std::endl;
return TestFail;
}
{
UniqueFD fd4(fd_);
}
if (isValidFd(fd_)) {
std::cout << "Failed fd validity (destruction)"
<< std::endl;
return TestFail;
}
return TestPass;
}
void cleanup() override
{
if (fd_ > 0)
close(fd_);
}
private:
int createFd()
{
fd_ = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
if (fd_ < 0)
return TestFail;
/* Cache inode number of temp file. */
struct stat s;
if (fstat(fd_, &s))
return TestFail;
inodeNr_ = s.st_ino;
return 0;
}
bool isValidFd(int fd)
{
struct stat s;
if (fstat(fd, &s))
return false;
/* Check that inode number matches cached temp file. */
return s.st_ino == inodeNr_;
}
int fd_;
ino_t inodeNr_;
};
TEST_REGISTER(UniqueFDTest)

View File

@@ -0,0 +1,314 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2018, Google Inc.
*
* Miscellaneous utility tests
*/
#include <iostream>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include <libcamera/base/span.h>
#include <libcamera/base/utils.h>
#include <libcamera/geometry.h>
#include "test.h"
using namespace std;
using namespace libcamera;
using namespace std::literals::chrono_literals;
class UtilsTest : public Test
{
protected:
int testDirname()
{
static const std::vector<std::string> paths = {
"",
"///",
"/bin",
"/usr/bin",
"//etc////",
"//tmp//d//",
"current_file",
"./current_file",
"./current_dir/",
"current_dir/",
};
static const std::vector<std::string> expected = {
".",
"/",
"/",
"/usr",
"/",
"//tmp",
".",
".",
".",
".",
};
std::vector<std::string> results;
for (const auto &path : paths)
results.push_back(utils::dirname(path));
if (results != expected) {
cerr << "utils::dirname() tests failed" << endl;
cerr << "expected: " << endl;
for (const auto &path : expected)
cerr << "\t" << path << endl;
cerr << "results: " << endl;
for (const auto &path : results)
cerr << "\t" << path << endl;
return TestFail;
}
return TestPass;
}
int testEnumerate()
{
std::vector<unsigned int> integers{ 1, 2, 3, 4, 5 };
unsigned int i = 0;
for (auto [index, value] : utils::enumerate(integers)) {
if (index != i || value != i + 1) {
cerr << "utils::enumerate(<vector>) test failed: i=" << i
<< ", index=" << index << ", value=" << value
<< std::endl;
return TestFail;
}
/* Verify that we can modify the value. */
--value;
++i;
}
if (integers != std::vector<unsigned int>{ 0, 1, 2, 3, 4 }) {
cerr << "Failed to modify container in enumerated range loop" << endl;
return TestFail;
}
Span<const unsigned int> span{ integers };
i = 0;
for (auto [index, value] : utils::enumerate(span)) {
if (index != i || value != i) {
cerr << "utils::enumerate(<span>) test failed: i=" << i
<< ", index=" << index << ", value=" << value
<< std::endl;
return TestFail;
}
++i;
}
const unsigned int array[] = { 0, 2, 4, 6, 8 };
i = 0;
for (auto [index, value] : utils::enumerate(array)) {
if (index != i || value != i * 2) {
cerr << "utils::enumerate(<array>) test failed: i=" << i
<< ", index=" << index << ", value=" << value
<< std::endl;
return TestFail;
}
++i;
}
return TestPass;
}
int testDuration()
{
std::ostringstream os;
utils::Duration exposure;
double ratio;
exposure = 25ms + 25ms;
if (exposure.get<std::micro>() != 50000.0) {
cerr << "utils::Duration failed to return microsecond count";
return TestFail;
}
exposure = 1.0s / 4;
if (exposure != 250ms) {
cerr << "utils::Duration failed scalar divide test";
return TestFail;
}
exposure = 5000.5us;
if (!exposure) {
cerr << "utils::Duration failed boolean test";
return TestFail;
}
os << exposure;
if (os.str() != "5000.50us") {
cerr << "utils::Duration operator << failed";
return TestFail;
}
exposure = 100ms;
ratio = exposure / 25ms;
if (ratio != 4.0) {
cerr << "utils::Duration failed ratio test";
return TestFail;
}
return TestPass;
}
int run()
{
/* utils::hex() test. */
std::ostringstream os;
std::string ref;
os << utils::hex(static_cast<int8_t>(0x42)) << " ";
ref += "0x42 ";
os << utils::hex(static_cast<uint8_t>(0x42)) << " ";
ref += "0x42 ";
os << utils::hex(static_cast<int16_t>(0x42)) << " ";
ref += "0x0042 ";
os << utils::hex(static_cast<uint16_t>(0x42)) << " ";
ref += "0x0042 ";
os << utils::hex(static_cast<int32_t>(0x42)) << " ";
ref += "0x00000042 ";
os << utils::hex(static_cast<uint32_t>(0x42)) << " ";
ref += "0x00000042 ";
os << utils::hex(static_cast<int64_t>(0x42)) << " ";
ref += "0x0000000000000042 ";
os << utils::hex(static_cast<uint64_t>(0x42)) << " ";
ref += "0x0000000000000042 ";
os << utils::hex(static_cast<int8_t>(0x42), 6) << " ";
ref += "0x000042 ";
os << utils::hex(static_cast<uint8_t>(0x42), 1) << " ";
ref += "0x42 ";
os << utils::hex(static_cast<int16_t>(0x42), 6) << " ";
ref += "0x000042 ";
os << utils::hex(static_cast<uint16_t>(0x42), 1) << " ";
ref += "0x42 ";
os << utils::hex(static_cast<int32_t>(0x42), 4) << " ";
ref += "0x0042 ";
os << utils::hex(static_cast<uint32_t>(0x42), 1) << " ";
ref += "0x42 ";
os << utils::hex(static_cast<int64_t>(0x42), 4) << " ";
ref += "0x0042 ";
os << utils::hex(static_cast<uint64_t>(0x42), 1) << " ";
ref += "0x42 ";
std::string s = os.str();
if (s != ref) {
cerr << "utils::hex() test failed, expected '" << ref
<< "', got '" << s << "'";
return TestFail;
}
/* utils::join() and utils::split() test. */
std::vector<std::string> elements = {
"/bin",
"/usr/bin",
"",
"",
};
std::string path;
for (const auto &element : elements)
path += (path.empty() ? "" : ":") + element;
if (path != utils::join(elements, ":")) {
cerr << "utils::join() test failed" << endl;
return TestFail;
}
std::vector<std::string> dirs;
for (const auto &dir : utils::split(path, ":"))
dirs.push_back(dir);
if (dirs != elements) {
cerr << "utils::split() test failed" << endl;
return TestFail;
}
const auto &split = utils::split(path, ":");
dirs = std::vector<std::string>{ split.begin(), split.end() };
if (dirs != elements) {
cerr << "utils::split() LegacyInputIterator test failed" << endl;
return TestFail;
}
/* utils::join() with conversion function test. */
std::vector<Size> sizes = { { 0, 0 }, { 100, 100 } };
s = utils::join(sizes, "/", [](const Size &size) {
return size.toString();
});
if (s != "0x0/100x100") {
cerr << "utils::join() with conversion test failed" << endl;
return TestFail;
}
/* utils::dirname() tests. */
if (TestPass != testDirname())
return TestFail;
/* utils::map_keys() test. */
const std::map<std::string, unsigned int> map{
{ "zero", 0 },
{ "one", 1 },
{ "two", 2 },
};
std::vector<std::string> expectedKeys{
"zero",
"one",
"two",
};
std::sort(expectedKeys.begin(), expectedKeys.end());
const std::vector<std::string> keys = utils::map_keys(map);
if (keys != expectedKeys) {
cerr << "utils::map_keys() test failed" << endl;
return TestFail;
}
/* utils::alignUp() and utils::alignDown() tests. */
if (utils::alignDown(6, 3) != 6 || utils::alignDown(7, 3) != 6) {
cerr << "utils::alignDown test failed" << endl;
return TestFail;
}
if (utils::alignUp(6, 3) != 6 || utils::alignUp(7, 3) != 9) {
cerr << "utils::alignUp test failed" << endl;
return TestFail;
}
/* utils::enumerate() test. */
if (testEnumerate() != TestPass)
return TestFail;
/* utils::Duration test. */
if (testDuration() != TestPass)
return TestFail;
return TestPass;
}
};
TEST_REGISTER(UtilsTest)

View File

@@ -0,0 +1,29 @@
# SPDX-License-Identifier: CC0-1.0
if not is_variable('v4l2_compat')
subdir_done()
endif
# If ASan is enabled but the ASan runtime shared library is missing,
# v4l2_compat_test.py won't be able to LD_PRELOAD it, resulting in a link order
# runtime check failure as v4l2-ctl and v4l2-compliance are not linked to ASan.
# Skip the test in that case.
if asan_runtime_missing
warning('Unable to get path to ASan runtime, v4l2_compat test disabled')
subdir_done()
endif
v4l2_compat_test = files('v4l2_compat_test.py')
v4l2_compat_args = []
if asan_enabled
v4l2_compat_args += ['-s', asan_runtime]
endif
v4l2_compat_args += [v4l2_compat]
test('v4l2_compat_test', v4l2_compat_test,
args : v4l2_compat_args,
suite : 'v4l2_compat',
timeout : 60)

View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2020, Google Inc.
#
# Author: Paul Elder <paul.elder@ideasonboard.com>
#
# Test the V4L2 compatibility layer
import argparse
import glob
import os
from packaging import version
import re
import shutil
import signal
import subprocess
import sys
MIN_V4L_UTILS_VERSION = version.parse("1.21.0")
TestPass = 0
TestFail = -1
TestSkip = 77
supported_pipelines = [
'bcm2835-isp',
'uvcvideo',
'vimc',
]
def grep(exp, arr):
return [s for s in arr if re.search(exp, s)]
def run_with_stdout(*args, env={}):
try:
with open(os.devnull, 'w') as devnull:
output = subprocess.check_output(args, env=env, stderr=devnull)
ret = 0
except subprocess.CalledProcessError as err:
output = err.output
ret = err.returncode
return ret, output.decode('utf-8').split('\n')
def extract_result(result):
res = result.split(', ')
ret = {}
ret['total'] = int(res[0].split(': ')[-1])
ret['succeeded'] = int(res[1].split(': ')[-1])
ret['failed'] = int(res[2].split(': ')[-1])
ret['warnings'] = int(res[3].split(': ')[-1])
ret['device'] = res[0].split()[4].strip(':')
ret['driver'] = res[0].split()[2]
return ret
def test_v4l2_compliance(v4l2_compliance, ld_preload, device, base_driver):
ret, output = run_with_stdout(v4l2_compliance, '-s', '-d', device, env={'LD_PRELOAD': ld_preload})
if ret < 0:
output.append(f'Test for {device} terminated due to signal {signal.Signals(-ret).name}')
return TestFail, output
result = extract_result(output[-2])
if result['failed'] == 0:
return TestPass, output
# vimc will fail s_fmt because it only supports framesizes that are
# multiples of 3
if base_driver == 'vimc' and result['failed'] == 1:
failures = grep('fail', output)
if re.search('S_FMT cannot handle an invalid format', failures[0]) is None:
return TestFail, output
return TestPass, output
return TestFail, output
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--all', action='store_true',
help='Test all available cameras')
parser.add_argument('-s', '--sanitizer', type=str,
help='Path to the address sanitizer (ASan) runtime')
parser.add_argument('-v', '--verbose', action='store_true',
help='Make the output verbose')
parser.add_argument('v4l2_compat', type=str,
help='Path to v4l2-compat.so')
args = parser.parse_args(argv[1:])
# Compute the LD_PRELOAD value by first loading ASan (if specified) and
# then the V4L2 compat layer.
ld_preload = []
if args.sanitizer:
ld_preload.append(args.sanitizer)
ld_preload.append(args.v4l2_compat)
ld_preload = ':'.join(ld_preload)
v4l2_compliance = shutil.which('v4l2-compliance')
if v4l2_compliance is None:
print('v4l2-compliance is not available')
return TestSkip
ret, out = run_with_stdout(v4l2_compliance, '--version')
if ret != 0 or version.parse(out[0].split()[1].replace(',', '')) < MIN_V4L_UTILS_VERSION:
print('v4l2-compliance version >= 1.21.0 required')
return TestSkip
v4l2_ctl = shutil.which('v4l2-ctl')
if v4l2_ctl is None:
print('v4l2-ctl is not available')
return TestSkip
ret, out = run_with_stdout(v4l2_ctl, '--version')
if ret != 0 or version.parse(out[0].split()[-1]) < MIN_V4L_UTILS_VERSION:
print('v4l2-ctl version >= 1.21.0 required')
return TestSkip
dev_nodes = glob.glob('/dev/video*')
if len(dev_nodes) == 0:
print('no video nodes available to test with')
return TestSkip
failed = []
drivers_tested = {}
for device in dev_nodes:
ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device, env={'LD_PRELOAD': ld_preload})
if ret < 0:
failed.append(device)
print(f'v4l2-ctl failed on {device} with v4l2-compat')
continue
driver = grep('Driver name', out)[0].split(':')[-1].strip()
if driver != "libcamera":
continue
ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device)
if ret < 0:
failed.append(device)
print(f'v4l2-ctl failed on {device} without v4l2-compat')
continue
driver = grep('Driver name', out)[0].split(':')[-1].strip()
if driver not in supported_pipelines:
continue
# TODO: Add kernel version check when vimc supports scaling
if driver == "vimc":
continue
if not args.all and driver in drivers_tested:
continue
print(f'Testing {device} with {driver} driver... ', end='')
ret, msg = test_v4l2_compliance(v4l2_compliance, ld_preload, device, driver)
if ret == TestFail:
failed.append(device)
print('failed')
else:
print('success')
if ret == TestFail or args.verbose:
print('\n'.join(msg))
drivers_tested[driver] = True
if len(drivers_tested) == 0:
print(f'No compatible drivers found')
return TestSkip
if len(failed) > 0:
print(f'Failed {len(failed)} tests:')
for device in failed:
print(f'- {device}')
return TestPass if not failed else TestFail
if __name__ == '__main__':
sys.exit(main(sys.argv))

View File

@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera V4L2 Subdevice format handling test
*/
#include <iostream>
#include <vector>
#include <libcamera/geometry.h>
#include <libcamera/base/utils.h>
#include "libcamera/internal/v4l2_subdevice.h"
#include "v4l2_subdevice_test.h"
using namespace std;
using namespace libcamera;
/* List image formats on the "Scaler" subdevice of vimc media device. */
class ListFormatsTest : public V4L2SubdeviceTest
{
protected:
int run() override;
private:
void printFormats(unsigned int pad, unsigned code,
const std::vector<SizeRange> &sizes);
};
void ListFormatsTest::printFormats(unsigned int pad,
unsigned int code,
const std::vector<SizeRange> &sizes)
{
cout << "Enumerate formats on pad " << pad << endl;
for (const SizeRange &size : sizes) {
cout << " mbus code: " << utils::hex(code, 4) << endl;
cout << " min width: " << dec << size.min.width << endl;
cout << " min height: " << dec << size.min.height << endl;
cout << " max width: " << dec << size.max.width << endl;
cout << " max height: " << dec << size.max.height << endl;
}
}
int ListFormatsTest::run()
{
/* List all formats available on existing "Scaler" pads. */
V4L2Subdevice::Formats formats;
formats = scaler_->formats(0);
if (formats.empty()) {
cerr << "Failed to list formats on pad 0 of subdevice "
<< scaler_->entity()->name() << endl;
return TestFail;
}
for (unsigned int code : utils::map_keys(formats))
printFormats(0, code, formats[code]);
formats = scaler_->formats(1);
if (formats.empty()) {
cerr << "Failed to list formats on pad 1 of subdevice "
<< scaler_->entity()->name() << endl;
return TestFail;
}
for (unsigned int code : utils::map_keys(formats))
printFormats(1, code, formats[code]);
/* List format on a non-existing pad, format vector shall be empty. */
formats = scaler_->formats(2);
if (!formats.empty()) {
cerr << "Listing formats on non-existing pad 2 of subdevice "
<< scaler_->entity()->name()
<< " should return an empty format list" << endl;
return TestFail;
}
return TestPass;
}
TEST_REGISTER(ListFormatsTest)

View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: CC0-1.0
v4l2_subdevice_tests = [
{'name': 'list_formats', 'sources': ['list_formats.cpp']},
{'name': 'test_formats', 'sources': ['test_formats.cpp']},
]
foreach test : v4l2_subdevice_tests
exe = executable(test['name'], test['sources'], 'v4l2_subdevice_test.cpp',
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
test(test['name'], exe, suite : 'v4l2_subdevice', is_parallel : false)
endforeach

View File

@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera V4L2 Subdevice format handling test
*/
#include <iostream>
#include <limits.h>
#include "libcamera/internal/v4l2_subdevice.h"
#include "v4l2_subdevice_test.h"
using namespace std;
using namespace libcamera;
/* Test format handling on the "Scaler" subdevice of vimc media device. */
class FormatHandlingTest : public V4L2SubdeviceTest
{
protected:
int run() override;
};
int FormatHandlingTest::run()
{
V4L2SubdeviceFormat format = {};
/*
* Get format on a non-existing Scaler pad: expect failure.
*/
int ret = scaler_->getFormat(2, &format);
if (!ret) {
cerr << "Getting format on a non existing pad should fail" << endl;
return TestFail;
}
ret = scaler_->getFormat(0, &format);
if (ret) {
cerr << "Failed to get format" << endl;
return TestFail;
}
/*
* Set unrealistic image resolutions and make sure it gets updated.
*/
format.size = { UINT_MAX, UINT_MAX };
ret = scaler_->setFormat(0, &format);
if (ret) {
cerr << "Failed to set format: image resolution is wrong, but "
<< "setFormat() should not fail." << endl;
return TestFail;
}
if (format.size.width == UINT_MAX ||
format.size.height == UINT_MAX) {
cerr << "Failed to update image format" << endl;
return TestFail;
}
format.size = { 0, 0 };
ret = scaler_->setFormat(0, &format);
if (ret) {
cerr << "Failed to set format: image resolution is wrong, but "
<< "setFormat() should not fail." << endl;
return TestFail;
}
if (format.size.isNull()) {
cerr << "Failed to update image format" << endl;
return TestFail;
}
return TestPass;
}
TEST_REGISTER(FormatHandlingTest)

View File

@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* VIMC-based V4L2 subdevice test
*/
#include <iostream>
#include <string.h>
#include <sys/stat.h>
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "v4l2_subdevice_test.h"
using namespace std;
using namespace libcamera;
/*
* This test runs on vimc media device. For a description of vimc, in the
* context of libcamera testing, please refer to
* 'test/media_device/media_device_link_test.cpp' file.
*
* If the vimc module is not loaded, the test gets skipped.
*/
int V4L2SubdeviceTest::init()
{
enumerator_ = DeviceEnumerator::create();
if (!enumerator_) {
cerr << "Failed to create device enumerator" << endl;
return TestFail;
}
if (enumerator_->enumerate()) {
cerr << "Failed to enumerate media devices" << endl;
return TestFail;
}
DeviceMatch dm("vimc");
media_ = enumerator_->search(dm);
if (!media_) {
cerr << "Unable to find \'vimc\' media device node" << endl;
return TestSkip;
}
MediaEntity *videoEntity = media_->getEntityByName("Scaler");
if (!videoEntity) {
cerr << "Unable to find media entity 'Scaler'" << endl;
return TestFail;
}
scaler_ = new V4L2Subdevice(videoEntity);
if (scaler_->open()) {
cerr << "Unable to open video subdevice "
<< scaler_->entity()->deviceNode() << endl;
return TestSkip;
}
return 0;
}
void V4L2SubdeviceTest::cleanup()
{
delete scaler_;
}

View File

@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* VIMC-based V4L2 subdevice test
*/
#pragma once
#include <libcamera/framebuffer.h>
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "test.h"
class V4L2SubdeviceTest : public Test
{
public:
V4L2SubdeviceTest()
: scaler_(nullptr)
{
}
protected:
int init() override;
void cleanup() override;
std::unique_ptr<libcamera::DeviceEnumerator> enumerator_;
std::shared_ptr<libcamera::MediaDevice> media_;
libcamera::V4L2Subdevice *scaler_;
};

View File

@@ -0,0 +1,252 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* Test the buffer cache different operation modes
*/
#include <iostream>
#include <random>
#include <vector>
#include <libcamera/formats.h>
#include <libcamera/stream.h>
#include "buffer_source.h"
#include "test.h"
using namespace libcamera;
namespace {
class BufferCacheTest : public Test
{
public:
/*
* Test that a cache with the same size as there are buffers results in
* a sequential run over; 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, ...
*
* The test is only valid when the cache size is as least as big as the
* number of buffers.
*/
int testSequential(V4L2BufferCache *cache,
const std::vector<std::unique_ptr<FrameBuffer>> &buffers)
{
for (unsigned int i = 0; i < buffers.size() * 100; i++) {
int nBuffer = i % buffers.size();
int index = cache->get(*buffers[nBuffer].get());
if (index != nBuffer) {
std::cout << "Expected index " << nBuffer
<< " got " << index << std::endl;
return TestFail;
}
cache->put(index);
}
return TestPass;
}
/*
* Test that randomly putting buffers to the cache always results in a
* valid index.
*/
int testRandom(V4L2BufferCache *cache,
const std::vector<std::unique_ptr<FrameBuffer>> &buffers)
{
std::uniform_int_distribution<> dist(0, buffers.size() - 1);
for (unsigned int i = 0; i < buffers.size() * 100; i++) {
int nBuffer = dist(generator_);
int index = cache->get(*buffers[nBuffer].get());
if (index < 0) {
std::cout << "Failed lookup from cache"
<< std::endl;
return TestFail;
}
cache->put(index);
}
return TestPass;
}
/*
* Test that using a buffer more frequently keeps it hot in the cache at
* all times.
*/
int testHot(V4L2BufferCache *cache,
const std::vector<std::unique_ptr<FrameBuffer>> &buffers,
unsigned int hotFrequency)
{
/* Run the random test on the cache to make it messy. */
if (testRandom(cache, buffers) != TestPass)
return TestFail;
std::uniform_int_distribution<> dist(0, buffers.size() - 1);
/* Pick a hot buffer at random and store its index. */
int hotBuffer = dist(generator_);
int hotIndex = cache->get(*buffers[hotBuffer].get());
cache->put(hotIndex);
/*
* Queue hot buffer at the requested frequency and make sure
* it stays hot.
*/
for (unsigned int i = 0; i < buffers.size() * 100; i++) {
int nBuffer, index;
bool hotQueue = i % hotFrequency == 0;
if (hotQueue)
nBuffer = hotBuffer;
else
nBuffer = dist(generator_);
index = cache->get(*buffers[nBuffer].get());
if (index < 0) {
std::cout << "Failed lookup from cache"
<< std::endl;
return TestFail;
}
if (hotQueue && index != hotIndex) {
std::cout << "Hot buffer got cold"
<< std::endl;
return TestFail;
}
cache->put(index);
}
return TestPass;
}
int testIsEmpty(const std::vector<std::unique_ptr<FrameBuffer>> &buffers)
{
V4L2BufferCache cache(buffers.size());
if (!cache.isEmpty())
return TestFail;
for (auto const &buffer : buffers) {
FrameBuffer &b = *buffer.get();
cache.get(b);
}
if (cache.isEmpty())
return TestFail;
unsigned int i;
for (i = 0; i < buffers.size() - 1; i++)
cache.put(i);
if (cache.isEmpty())
return TestFail;
cache.put(i);
if (!cache.isEmpty())
return TestFail;
return TestPass;
}
int init() override
{
std::random_device rd;
unsigned int seed = rd();
std::cout << "Random seed is " << seed << std::endl;
generator_.seed(seed);
return TestPass;
}
int run() override
{
const unsigned int numBuffers = 8;
StreamConfiguration cfg;
cfg.pixelFormat = formats::YUYV;
cfg.size = Size(600, 800);
cfg.bufferCount = numBuffers;
BufferSource source;
int ret = source.allocate(cfg);
if (ret != TestPass)
return ret;
const std::vector<std::unique_ptr<FrameBuffer>> &buffers =
source.buffers();
if (buffers.size() != numBuffers) {
std::cout << "Got " << buffers.size()
<< " buffers, expected " << numBuffers
<< std::endl;
return TestFail;
}
/*
* Test cache of same size as there are buffers, the cache is
* created from a list of buffers and will be pre-populated.
*/
V4L2BufferCache cacheFromBuffers(buffers);
if (testSequential(&cacheFromBuffers, buffers) != TestPass)
return TestFail;
if (testRandom(&cacheFromBuffers, buffers) != TestPass)
return TestFail;
if (testHot(&cacheFromBuffers, buffers, numBuffers) != TestPass)
return TestFail;
/*
* Test cache of same size as there are buffers, the cache is
* not pre-populated.
*/
V4L2BufferCache cacheFromNumbers(numBuffers);
if (testSequential(&cacheFromNumbers, buffers) != TestPass)
return TestFail;
if (testRandom(&cacheFromNumbers, buffers) != TestPass)
return TestFail;
if (testHot(&cacheFromNumbers, buffers, numBuffers) != TestPass)
return TestFail;
/*
* Test cache half the size of number of buffers used, the cache
* is not pre-populated.
*/
V4L2BufferCache cacheHalf(numBuffers / 2);
if (testRandom(&cacheHalf, buffers) != TestPass)
return TestFail;
if (testHot(&cacheHalf, buffers, numBuffers / 2) != TestPass)
return TestFail;
/*
* Test that the isEmpty function reports the correct result at
* various levels of cache fullness.
*/
if (testIsEmpty(buffers) != TestPass)
return TestFail;
return TestPass;
}
private:
std::mt19937 generator_;
};
} /* namespace */
TEST_REGISTER(BufferCacheTest)

View File

@@ -0,0 +1,206 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera V4L2 API tests
*
* Validate the function of exporting buffers from a V4L2VideoDevice and
* the ability to import them to another V4L2VideoDevice instance.
* Ensure that the Buffers can successfully be queued and dequeued
* between both devices.
*/
#include <iostream>
#include <libcamera/framebuffer.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "v4l2_videodevice_test.h"
using namespace libcamera;
using namespace std::chrono_literals;
class BufferSharingTest : public V4L2VideoDeviceTest
{
public:
BufferSharingTest()
: V4L2VideoDeviceTest("vivid", "vivid-000-vid-cap"),
output_(nullptr), framesCaptured_(0), framesOutput_(0) {}
protected:
int init()
{
int ret = V4L2VideoDeviceTest::init();
if (ret)
return ret;
/* media_ already represents VIVID */
MediaEntity *entity = media_->getEntityByName("vivid-000-vid-out");
if (!entity)
return TestSkip;
output_ = new V4L2VideoDevice(entity);
if (!output_) {
std::cout << "Failed to create output device" << std::endl;
return TestFail;
}
ret = output_->open();
if (ret) {
std::cout << "Failed to open output device" << std::endl;
return TestFail;
}
V4L2DeviceFormat format = {};
ret = capture_->getFormat(&format);
if (ret) {
std::cout << "Failed to get capture format" << std::endl;
return TestFail;
}
format.size.width = 320;
format.size.height = 180;
ret = capture_->setFormat(&format);
if (ret) {
std::cout << "Failed to set capture format" << std::endl;
return TestFail;
}
ret = output_->setFormat(&format);
if (ret) {
std::cout << "Failed to set output format" << std::endl;
return TestFail;
}
ret = capture_->allocateBuffers(bufferCount, &buffers_);
if (ret < 0) {
std::cout << "Failed to allocate buffers" << std::endl;
return TestFail;
}
ret = output_->importBuffers(bufferCount);
if (ret < 0) {
std::cout << "Failed to import buffers" << std::endl;
return TestFail;
}
return 0;
}
void captureBufferReady(FrameBuffer *buffer)
{
const FrameMetadata &metadata = buffer->metadata();
std::cout << "Received capture buffer" << std::endl;
if (metadata.status != FrameMetadata::FrameSuccess)
return;
output_->queueBuffer(buffer);
framesCaptured_++;
}
void outputBufferReady(FrameBuffer *buffer)
{
const FrameMetadata &metadata = buffer->metadata();
std::cout << "Received output buffer" << std::endl;
if (metadata.status != FrameMetadata::FrameSuccess)
return;
capture_->queueBuffer(buffer);
framesOutput_++;
}
int run()
{
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
Timer timeout;
int ret;
capture_->bufferReady.connect(this, &BufferSharingTest::captureBufferReady);
output_->bufferReady.connect(this, &BufferSharingTest::outputBufferReady);
for (const std::unique_ptr<FrameBuffer> &buffer : buffers_) {
if (capture_->queueBuffer(buffer.get())) {
std::cout << "Failed to queue buffer" << std::endl;
return TestFail;
}
}
ret = capture_->streamOn();
if (ret) {
std::cout << "Failed to start streaming on the capture device" << std::endl;
return TestFail;
}
ret = output_->streamOn();
if (ret) {
std::cout << "Failed to start streaming on the output device" << std::endl;
return TestFail;
}
timeout.start(10000ms);
while (timeout.isRunning()) {
dispatcher->processEvents();
if (framesCaptured_ > 30 && framesOutput_ > 30)
break;
}
if ((framesCaptured_ < 1) || (framesOutput_ < 1)) {
std::cout << "Failed to process any frames within timeout." << std::endl;
return TestFail;
}
if ((framesCaptured_ < 30) || (framesOutput_ < 30)) {
std::cout << "Failed to process 30 frames within timeout." << std::endl;
return TestFail;
}
ret = capture_->streamOff();
if (ret) {
std::cout << "Failed to stop streaming on the capture device" << std::endl;
return TestFail;
}
ret = output_->streamOff();
if (ret) {
std::cout << "Failed to stop streaming on the output device" << std::endl;
return TestFail;
}
return TestPass;
}
void cleanup()
{
std::cout
<< "Captured " << framesCaptured_ << " frames and "
<< "output " << framesOutput_ << " frames"
<< std::endl;
output_->streamOff();
output_->releaseBuffers();
output_->close();
delete output_;
V4L2VideoDeviceTest::cleanup();
}
private:
const unsigned int bufferCount = 4;
V4L2VideoDevice *output_;
unsigned int framesCaptured_;
unsigned int framesOutput_;
};
TEST_REGISTER(BufferSharingTest)

View File

@@ -0,0 +1,97 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* libcamera V4L2 API tests
*/
#include <iostream>
#include <libcamera/framebuffer.h>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include "v4l2_videodevice_test.h"
using namespace libcamera;
using namespace std::chrono_literals;
class CaptureAsyncTest : public V4L2VideoDeviceTest
{
public:
CaptureAsyncTest()
: V4L2VideoDeviceTest("vimc", "Raw Capture 0"), frames(0) {}
void receiveBuffer(FrameBuffer *buffer)
{
std::cout << "Buffer received" << std::endl;
frames++;
/* Requeue the buffer for further use. */
capture_->queueBuffer(buffer);
}
protected:
int run()
{
const unsigned int bufferCount = 8;
EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
Timer timeout;
int ret;
ret = capture_->allocateBuffers(bufferCount, &buffers_);
if (ret < 0) {
std::cout << "Failed to allocate buffers" << std::endl;
return TestFail;
}
capture_->bufferReady.connect(this, &CaptureAsyncTest::receiveBuffer);
for (const std::unique_ptr<FrameBuffer> &buffer : buffers_) {
if (capture_->queueBuffer(buffer.get())) {
std::cout << "Failed to queue buffer" << std::endl;
return TestFail;
}
}
ret = capture_->streamOn();
if (ret)
return TestFail;
const unsigned int nFrames = 30;
timeout.start(500ms * nFrames);
while (timeout.isRunning()) {
dispatcher->processEvents();
if (frames > nFrames)
break;
}
if (frames < 1) {
std::cout << "Failed to capture any frames within timeout." << std::endl;
return TestFail;
}
if (frames < nFrames) {
std::cout << "Failed to capture " << nFrames
<< " frames within timeout." << std::endl;
return TestFail;
}
std::cout << "Processed " << frames << " frames" << std::endl;
ret = capture_->streamOff();
if (ret)
return TestFail;
return TestPass;
}
private:
unsigned int frames;
};
TEST_REGISTER(CaptureAsyncTest)

Some files were not shown because too many files have changed in this diff Show More