193 lines
8.0 KiB
C++
193 lines
8.0 KiB
C++
// Exercise the production Camera with simulated capture and no GPU or camera.
|
|
#include "camera.hpp"
|
|
#include "uyvy_converter.hpp"
|
|
#include <linux/videodev2.h>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
#ifdef TEST_V4L2_CAPTURE
|
|
#include "v4l2_capture.h"
|
|
#else
|
|
#include <cstdarg>
|
|
#include <cerrno>
|
|
#include <poll.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
|
|
namespace {
|
|
std::string events;
|
|
std::size_t cursor{};
|
|
char current{};
|
|
unsigned sequence{};
|
|
int timeoutMs{};
|
|
bool realWait{};
|
|
std::function<void()> onStop;
|
|
unsigned char pixels[8]{};
|
|
void check(bool condition,char const* message) {
|
|
if(!condition)throw std::runtime_error(message);
|
|
}
|
|
void script(std::string value) {events=std::move(value);cursor=0;}
|
|
char next() {
|
|
check(cursor<events.size(),"Camera requested an unexpected extra capture attempt");
|
|
return current=events[cursor++];
|
|
}
|
|
void expectTimeout(cms::Camera& camera) {
|
|
try {camera.read();} catch(std::runtime_error const& e) {
|
|
check(std::string(e.what()).find("6 consecutive times")!=std::string::npos,"Wrong timeout failure");
|
|
return;
|
|
}
|
|
throw std::runtime_error("Six consecutive timeouts did not fail");
|
|
}
|
|
}
|
|
namespace cms {
|
|
struct UyvyConverter::Impl {int width,height;};
|
|
UyvyConverter::UyvyConverter(int w,int h):impl_(std::make_unique<Impl>(Impl{w,h})){}
|
|
UyvyConverter::~UyvyConverter()=default;
|
|
std::shared_ptr<const BgrImage> UyvyConverter::convert(void const*,std::size_t) {
|
|
auto image=std::make_shared<BgrImage>();image->width=impl_->width;image->height=impl_->height;
|
|
image->stride=image->width*3;image->pixels.resize(image->stride*image->height);return image;
|
|
}
|
|
}
|
|
#ifdef TEST_V4L2_CAPTURE
|
|
V4l2Capture::~V4l2Capture()=default;
|
|
bool V4l2Capture::open(std::string const&){reconnect_needed_=false;return true;}
|
|
void V4l2Capture::close(){}
|
|
bool V4l2Capture::setFormat(uint32_t w,uint32_t h,uint32_t p){width_=w;height_=h;pixelformat_=p;return true;}
|
|
bool V4l2Capture::setFps(uint32_t){return true;}
|
|
bool V4l2Capture::initMmap(uint32_t){return true;}
|
|
bool V4l2Capture::startStreaming(){return true;}
|
|
void V4l2Capture::releaseFrame(Frame const&){}
|
|
bool V4l2Capture::getFrame(Frame& f,int waitMs) {
|
|
check(waitMs<=100,"Stop polling should remain at most 100 ms");
|
|
if(next()=='s'){onStop();return false;}
|
|
if(current=='d'){reconnect_needed_=true;return false;}
|
|
if(current=='e'||current=='x'){return false;}
|
|
if(current=='t') {
|
|
// Each scripted timeout represents a full logical capture window.
|
|
// The separate stop test exercises the short-wait cancellation path.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(timeoutMs));return false;
|
|
}
|
|
check(current=='f',"Unsupported mock V4L2 event");
|
|
f.data=pixels;f.size=sizeof(pixels);f.width=2;f.height=2;f.bytes_per_line=4;f.sequence=sequence++;
|
|
return true;
|
|
}
|
|
#else
|
|
extern "C" {
|
|
int __real_poll(pollfd*,nfds_t,int);
|
|
int __real_open(char const*,int,...);
|
|
int __real_close(int);
|
|
int __real_ioctl(int,unsigned long,...);
|
|
void* __real_mmap(void*,size_t,int,int,int,off_t);
|
|
int __real_munmap(void*,size_t);
|
|
int __wrap_open(char const* path,int flags,...) {
|
|
if(std::strcmp(path,"/mock/camera")==0)return 100000;
|
|
return __real_open(path,flags);
|
|
}
|
|
int __wrap_close(int fd){return fd==100000?0:__real_close(fd);}
|
|
void* __wrap_mmap(void* a,size_t n,int p,int f,int fd,off_t o) {
|
|
return fd==100000?pixels:__real_mmap(a,n,p,f,fd,o);
|
|
}
|
|
int __wrap_munmap(void* a,size_t n){return a==pixels?0:__real_munmap(a,n);}
|
|
int __wrap_ioctl(int fd,unsigned long request,...) {
|
|
va_list args;va_start(args,request);void* p=va_arg(args,void*);va_end(args);
|
|
if(fd!=100000)return __real_ioctl(fd,request,p);
|
|
switch(request) {
|
|
case VIDIOC_QUERYCAP:static_cast<v4l2_capability*>(p)->capabilities=V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_STREAMING;break;
|
|
case VIDIOC_S_FMT:static_cast<v4l2_format*>(p)->fmt.pix.bytesperline=4;break;
|
|
case VIDIOC_G_PARM:break;
|
|
case VIDIOC_REQBUFS:static_cast<v4l2_requestbuffers*>(p)->count=2;break;
|
|
case VIDIOC_QUERYBUF:static_cast<v4l2_buffer*>(p)->length=sizeof(pixels);break;
|
|
case VIDIOC_DQBUF: {
|
|
if(current=='x'){errno=EIO;return -1;}
|
|
auto& b=*static_cast<v4l2_buffer*>(p);b.index=0;b.bytesused=sizeof(pixels);b.sequence=sequence++;
|
|
b.flags=current=='e'?V4L2_BUF_FLAG_ERROR:0;break;
|
|
}
|
|
case VIDIOC_QBUF:case VIDIOC_STREAMON:case VIDIOC_STREAMOFF:break;
|
|
default:throw std::runtime_error("Unexpected camera ioctl");
|
|
}
|
|
return 0;
|
|
}
|
|
int __wrap_poll(pollfd* fds,nfds_t count,int waitMs) {
|
|
if(count==1)return __real_poll(fds,count,waitMs);
|
|
check(count==2&&waitMs>0&&waitMs<=timeoutMs,"Camera must respect the configured timeout window");
|
|
switch(next()) {
|
|
case 't':if(realWait)std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));return 0;
|
|
case 'i':errno=EINTR;return -1;
|
|
case 's':onStop();return 0;
|
|
case 'd':fds[0].revents=POLLHUP;return 1;
|
|
case 'f':case 'e':case 'x':fds[0].revents=POLLIN;return 1;
|
|
default:throw std::runtime_error("Unexpected poll event");
|
|
}
|
|
}
|
|
int __wrap___poll_chk(pollfd* fds,nfds_t count,int waitMs,size_t size) {
|
|
check(size>=count*sizeof(pollfd),"Invalid fortified poll buffer size");
|
|
return __wrap_poll(fds,count,waitMs);
|
|
}
|
|
}
|
|
#endif
|
|
int main(int argc,char** argv)try {
|
|
cms::CameraConfig config;config.device="/mock/camera";config.width=config.height=2;config.buffers=2;
|
|
check(config.timeout_ms==2000,"Default per-attempt timeout must be 2 seconds");
|
|
realWait=argc>1&&std::string(argv[1])=="--wall-clock";
|
|
if(!realWait)config.timeout_ms=100;
|
|
timeoutMs=config.timeout_ms;
|
|
if(realWait) {
|
|
cms::Camera camera(config);script("tttttt");
|
|
auto before=std::chrono::steady_clock::now();expectTimeout(camera);
|
|
auto elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-before).count();
|
|
check(cursor==6&&elapsed>=12.0&&elapsed<15.0,"Default timeout should fail after six attempts, about 12 seconds");
|
|
std::cout<<"PASS wall-clock six-timeout duration="<<elapsed<<" s\n";
|
|
return 0;
|
|
}
|
|
{
|
|
cms::Camera camera(config);
|
|
script("tttttf");check(bool(camera.read()),"Five timeouts should recover on a valid frame");
|
|
script("tttttf");check(bool(camera.read()),"A valid frame must reset the timeout count");
|
|
script("tttttt");
|
|
expectTimeout(camera);
|
|
check(cursor==6,"Failure must occur on the sixth timeout");
|
|
}
|
|
{
|
|
cms::Camera camera(config);onStop=[&]{camera.requestStop();};
|
|
script("tts");check(!camera.read(),"Stop must cancel retries without throwing");
|
|
check(cursor==3,"Stop should not wait for all six timeouts");onStop={};
|
|
}
|
|
{
|
|
cms::Camera camera(config);onStop=[&]{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(timeoutMs));
|
|
camera.requestStop();
|
|
};
|
|
script("ttttts");check(!camera.read(),"Stop on the final timeout must take precedence over failure");
|
|
onStop={};
|
|
}
|
|
#ifndef TEST_V4L2_CAPTURE
|
|
{
|
|
cms::Camera camera(config);script("tttttif");
|
|
check(bool(camera.read()),"EINTR must not count as a timeout");
|
|
script("tttttet");expectTimeout(camera);
|
|
check(cursor==7,"A corrupt frame must not reset the timeout count");
|
|
}
|
|
{
|
|
cms::Camera camera(config);script("df");
|
|
check(bool(camera.read()),"Capture must reopen after a transient disconnection");
|
|
}
|
|
{
|
|
cms::Camera camera(config);script("xxf");
|
|
check(bool(camera.read()),"Transient EIO must recover without restarting the pipeline");
|
|
}
|
|
{
|
|
auto slow=config;slow.timeout_ms=2000;timeoutMs=slow.timeout_ms;
|
|
cms::Camera camera(slow);script(std::string(35,'e')+"f");
|
|
check(bool(camera.read()),"More than one second's frame count of corrupt frames must remain recoverable");
|
|
timeoutMs=config.timeout_ms;
|
|
}
|
|
#endif
|
|
std::cout<<"PASS camera timeout retries, recovery reset, sixth-timeout failure and cancellation\n";
|
|
return 0;
|
|
}catch(std::exception const& e){std::cerr<<"FAIL: "<<e.what()<<'\n';return 1;}
|