An attempt at getting image data back
This commit is contained in:
211
spider-cam/libcamera/test/bayer-format.cpp
Normal file
211
spider-cam/libcamera/test/bayer-format.cpp
Normal 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)
|
||||
178
spider-cam/libcamera/test/byte-stream-buffer.cpp
Normal file
178
spider-cam/libcamera/test/byte-stream-buffer.cpp
Normal 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)
|
||||
131
spider-cam/libcamera/test/camera-sensor.cpp
Normal file
131
spider-cam/libcamera/test/camera-sensor.cpp
Normal 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)
|
||||
182
spider-cam/libcamera/test/camera/buffer_import.cpp
Normal file
182
spider-cam/libcamera/test/camera/buffer_import.cpp
Normal 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)
|
||||
264
spider-cam/libcamera/test/camera/camera_reconfigure.cpp
Normal file
264
spider-cam/libcamera/test/camera/camera_reconfigure.cpp
Normal 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)
|
||||
181
spider-cam/libcamera/test/camera/capture.cpp
Normal file
181
spider-cam/libcamera/test/camera/capture.cpp
Normal 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)
|
||||
60
spider-cam/libcamera/test/camera/configuration_default.cpp
Normal file
60
spider-cam/libcamera/test/camera/configuration_default.cpp
Normal 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)
|
||||
106
spider-cam/libcamera/test/camera/configuration_set.cpp
Normal file
106
spider-cam/libcamera/test/camera/configuration_set.cpp
Normal 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)
|
||||
20
spider-cam/libcamera/test/camera/meson.build
Normal file
20
spider-cam/libcamera/test/camera/meson.build
Normal 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
|
||||
216
spider-cam/libcamera/test/camera/statemachine.cpp
Normal file
216
spider-cam/libcamera/test/camera/statemachine.cpp
Normal 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)
|
||||
105
spider-cam/libcamera/test/color-space.cpp
Normal file
105
spider-cam/libcamera/test/color-space.cpp
Normal 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)
|
||||
86
spider-cam/libcamera/test/controls/control_info.cpp
Normal file
86
spider-cam/libcamera/test/controls/control_info.cpp
Normal 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)
|
||||
89
spider-cam/libcamera/test/controls/control_info_map.cpp
Normal file
89
spider-cam/libcamera/test/controls/control_info_map.cpp
Normal 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)
|
||||
253
spider-cam/libcamera/test/controls/control_list.cpp
Normal file
253
spider-cam/libcamera/test/controls/control_list.cpp
Normal 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)
|
||||
264
spider-cam/libcamera/test/controls/control_value.cpp
Normal file
264
spider-cam/libcamera/test/controls/control_value.cpp
Normal 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)
|
||||
16
spider-cam/libcamera/test/controls/meson.build
Normal file
16
spider-cam/libcamera/test/controls/meson.build
Normal 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
|
||||
303
spider-cam/libcamera/test/delayed_controls.cpp
Normal file
303
spider-cam/libcamera/test/delayed_controls.cpp
Normal 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)
|
||||
103
spider-cam/libcamera/test/event-dispatcher.cpp
Normal file
103
spider-cam/libcamera/test/event-dispatcher.cpp
Normal 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)
|
||||
137
spider-cam/libcamera/test/event-thread.cpp
Normal file
137
spider-cam/libcamera/test/event-thread.cpp
Normal 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)
|
||||
132
spider-cam/libcamera/test/event.cpp
Normal file
132
spider-cam/libcamera/test/event.cpp
Normal 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)
|
||||
361
spider-cam/libcamera/test/fence.cpp
Normal file
361
spider-cam/libcamera/test/fence.cpp
Normal 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)
|
||||
383
spider-cam/libcamera/test/file.cpp
Normal file
383
spider-cam/libcamera/test/file.cpp
Normal 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)
|
||||
193
spider-cam/libcamera/test/flags.cpp
Normal file
193
spider-cam/libcamera/test/flags.cpp
Normal 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)
|
||||
488
spider-cam/libcamera/test/geometry.cpp
Normal file
488
spider-cam/libcamera/test/geometry.cpp
Normal 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
174
spider-cam/libcamera/test/gstreamer/gstreamer_test.cpp
Normal file
174
spider-cam/libcamera/test/gstreamer/gstreamer_test.cpp
Normal 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");
|
||||
}
|
||||
34
spider-cam/libcamera/test/gstreamer/gstreamer_test.h
Normal file
34
spider-cam/libcamera/test/gstreamer/gstreamer_test.h
Normal 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);
|
||||
};
|
||||
28
spider-cam/libcamera/test/gstreamer/meson.build
Normal file
28
spider-cam/libcamera/test/gstreamer/meson.build
Normal 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
|
||||
129
spider-cam/libcamera/test/hotplug-cameras.cpp
Normal file
129
spider-cam/libcamera/test/hotplug-cameras.cpp
Normal 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)
|
||||
184
spider-cam/libcamera/test/ipa/ipa_interface_test.cpp
Normal file
184
spider-cam/libcamera/test/ipa/ipa_interface_test.cpp
Normal 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)
|
||||
73
spider-cam/libcamera/test/ipa/ipa_module_test.cpp
Normal file
73
spider-cam/libcamera/test/ipa/ipa_module_test.cpp
Normal 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)
|
||||
17
spider-cam/libcamera/test/ipa/meson.build
Normal file
17
spider-cam/libcamera/test/ipa/meson.build
Normal 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
|
||||
15
spider-cam/libcamera/test/ipa/rkisp1/meson.build
Normal file
15
spider-cam/libcamera/test/ipa/rkisp1/meson.build
Normal 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
|
||||
108
spider-cam/libcamera/test/ipa/rkisp1/rkisp1-utils.cpp
Normal file
108
spider-cam/libcamera/test/ipa/rkisp1/rkisp1-utils.cpp
Normal 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)
|
||||
15
spider-cam/libcamera/test/ipc/meson.build
Normal file
15
spider-cam/libcamera/test/ipc/meson.build
Normal 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
|
||||
511
spider-cam/libcamera/test/ipc/unixsocket.cpp
Normal file
511
spider-cam/libcamera/test/ipc/unixsocket.cpp
Normal 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();
|
||||
}
|
||||
233
spider-cam/libcamera/test/ipc/unixsocket_ipc.cpp
Normal file
233
spider-cam/libcamera/test/ipc/unixsocket_ipc.cpp
Normal 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();
|
||||
}
|
||||
94
spider-cam/libcamera/test/libtest/buffer_source.cpp
Normal file
94
spider-cam/libcamera/test/libtest/buffer_source.cpp
Normal 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_;
|
||||
}
|
||||
27
spider-cam/libcamera/test/libtest/buffer_source.h
Normal file
27
spider-cam/libcamera/test/libtest/buffer_source.h
Normal 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_;
|
||||
};
|
||||
55
spider-cam/libcamera/test/libtest/camera_test.cpp
Normal file
55
spider-cam/libcamera/test/libtest/camera_test.cpp
Normal 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_;
|
||||
}
|
||||
25
spider-cam/libcamera/test/libtest/camera_test.h
Normal file
25
spider-cam/libcamera/test/libtest/camera_test.h
Normal 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_;
|
||||
};
|
||||
24
spider-cam/libcamera/test/libtest/meson.build
Normal file
24
spider-cam/libcamera/test/libtest/meson.build
Normal 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]
|
||||
38
spider-cam/libcamera/test/libtest/test.cpp
Normal file
38
spider-cam/libcamera/test/libtest/test.cpp
Normal 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;
|
||||
}
|
||||
45
spider-cam/libcamera/test/libtest/test.h
Normal file
45
spider-cam/libcamera/test/libtest/test.h
Normal 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(); \
|
||||
}
|
||||
154
spider-cam/libcamera/test/log/log_api.cpp
Normal file
154
spider-cam/libcamera/test/log/log_api.cpp
Normal 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)
|
||||
165
spider-cam/libcamera/test/log/log_process.cpp
Normal file
165
spider-cam/libcamera/test/log/log_process.cpp
Normal 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();
|
||||
}
|
||||
15
spider-cam/libcamera/test/log/meson.build
Normal file
15
spider-cam/libcamera/test/log/meson.build
Normal 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
|
||||
117
spider-cam/libcamera/test/mapped-buffer.cpp
Normal file
117
spider-cam/libcamera/test/mapped-buffer.cpp
Normal 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
36
spider-cam/libcamera/test/media_device/media_device_test.cpp
Normal file
36
spider-cam/libcamera/test/media_device/media_device_test.cpp
Normal 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;
|
||||
}
|
||||
30
spider-cam/libcamera/test/media_device/media_device_test.h
Normal file
30
spider-cam/libcamera/test/media_device/media_device_test.h
Normal 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_;
|
||||
};
|
||||
24
spider-cam/libcamera/test/media_device/meson.build
Normal file
24
spider-cam/libcamera/test/media_device/meson.build
Normal 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
|
||||
129
spider-cam/libcamera/test/meson.build
Normal file
129
spider-cam/libcamera/test/meson.build
Normal 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
|
||||
170
spider-cam/libcamera/test/message.cpp
Normal file
170
spider-cam/libcamera/test/message.cpp
Normal 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)
|
||||
116
spider-cam/libcamera/test/object-delete.cpp
Normal file
116
spider-cam/libcamera/test/object-delete.cpp
Normal 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)
|
||||
203
spider-cam/libcamera/test/object-invoke.cpp
Normal file
203
spider-cam/libcamera/test/object-invoke.cpp
Normal 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)
|
||||
157
spider-cam/libcamera/test/object.cpp
Normal file
157
spider-cam/libcamera/test/object.cpp
Normal 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)
|
||||
51
spider-cam/libcamera/test/pixel-format.cpp
Normal file
51
spider-cam/libcamera/test/pixel-format.cpp
Normal 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)
|
||||
14
spider-cam/libcamera/test/process/meson.build
Normal file
14
spider-cam/libcamera/test/process/meson.build
Normal 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
|
||||
112
spider-cam/libcamera/test/process/process_test.cpp
Normal file
112
spider-cam/libcamera/test/process/process_test.cpp
Normal 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();
|
||||
}
|
||||
25
spider-cam/libcamera/test/public-api.cpp
Normal file
25
spider-cam/libcamera/test/public-api.cpp
Normal 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)
|
||||
32
spider-cam/libcamera/test/py/meson.build
Normal file
32
spider-cam/libcamera/test/py/meson.build
Normal 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)
|
||||
365
spider-cam/libcamera/test/py/unittests.py
Executable file
365
spider-cam/libcamera/test/py/unittests.py
Executable 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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@'
|
||||
])
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
16
spider-cam/libcamera/test/serialization/meson.build
Normal file
16
spider-cam/libcamera/test/serialization/meson.build
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
29
spider-cam/libcamera/test/serialization/serialization_test.h
Normal file
29
spider-cam/libcamera/test/serialization/serialization_test.h
Normal 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);
|
||||
};
|
||||
244
spider-cam/libcamera/test/shared-fd.cpp
Normal file
244
spider-cam/libcamera/test/shared-fd.cpp
Normal 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)
|
||||
134
spider-cam/libcamera/test/signal-threads.cpp
Normal file
134
spider-cam/libcamera/test/signal-threads.cpp
Normal 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)
|
||||
361
spider-cam/libcamera/test/signal.cpp
Normal file
361
spider-cam/libcamera/test/signal.cpp
Normal 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)
|
||||
205
spider-cam/libcamera/test/span.cpp
Normal file
205
spider-cam/libcamera/test/span.cpp
Normal 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)
|
||||
14
spider-cam/libcamera/test/stream/meson.build
Normal file
14
spider-cam/libcamera/test/stream/meson.build
Normal 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
|
||||
96
spider-cam/libcamera/test/stream/stream_colorspace.cpp
Normal file
96
spider-cam/libcamera/test/stream/stream_colorspace.cpp
Normal 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)
|
||||
101
spider-cam/libcamera/test/stream/stream_formats.cpp
Normal file
101
spider-cam/libcamera/test/stream/stream_formats.cpp
Normal 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)
|
||||
176
spider-cam/libcamera/test/threads.cpp
Normal file
176
spider-cam/libcamera/test/threads.cpp
Normal 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)
|
||||
109
spider-cam/libcamera/test/timer-fail.cpp
Normal file
109
spider-cam/libcamera/test/timer-fail.cpp
Normal 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)
|
||||
92
spider-cam/libcamera/test/timer-thread.cpp
Normal file
92
spider-cam/libcamera/test/timer-thread.cpp
Normal 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)
|
||||
212
spider-cam/libcamera/test/timer.cpp
Normal file
212
spider-cam/libcamera/test/timer.cpp
Normal 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)
|
||||
329
spider-cam/libcamera/test/transform.cpp
Normal file
329
spider-cam/libcamera/test/transform.cpp
Normal 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)
|
||||
220
spider-cam/libcamera/test/unique-fd.cpp
Normal file
220
spider-cam/libcamera/test/unique-fd.cpp
Normal 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)
|
||||
314
spider-cam/libcamera/test/utils.cpp
Normal file
314
spider-cam/libcamera/test/utils.cpp
Normal 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)
|
||||
29
spider-cam/libcamera/test/v4l2_compat/meson.build
Normal file
29
spider-cam/libcamera/test/v4l2_compat/meson.build
Normal 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)
|
||||
180
spider-cam/libcamera/test/v4l2_compat/v4l2_compat_test.py
Executable file
180
spider-cam/libcamera/test/v4l2_compat/v4l2_compat_test.py
Executable 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))
|
||||
83
spider-cam/libcamera/test/v4l2_subdevice/list_formats.cpp
Normal file
83
spider-cam/libcamera/test/v4l2_subdevice/list_formats.cpp
Normal 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)
|
||||
14
spider-cam/libcamera/test/v4l2_subdevice/meson.build
Normal file
14
spider-cam/libcamera/test/v4l2_subdevice/meson.build
Normal 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
|
||||
78
spider-cam/libcamera/test/v4l2_subdevice/test_formats.cpp
Normal file
78
spider-cam/libcamera/test/v4l2_subdevice/test_formats.cpp
Normal 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)
|
||||
@@ -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_;
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
252
spider-cam/libcamera/test/v4l2_videodevice/buffer_cache.cpp
Normal file
252
spider-cam/libcamera/test/v4l2_videodevice/buffer_cache.cpp
Normal 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)
|
||||
206
spider-cam/libcamera/test/v4l2_videodevice/buffer_sharing.cpp
Normal file
206
spider-cam/libcamera/test/v4l2_videodevice/buffer_sharing.cpp
Normal 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)
|
||||
97
spider-cam/libcamera/test/v4l2_videodevice/capture_async.cpp
Normal file
97
spider-cam/libcamera/test/v4l2_videodevice/capture_async.cpp
Normal 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
Reference in New Issue
Block a user