diff options
-rw-r--r-- | test/v4l2_videodevice/buffer_cache.cpp | 215 | ||||
-rw-r--r-- | test/v4l2_videodevice/meson.build | 1 |
2 files changed, 216 insertions, 0 deletions
diff --git a/test/v4l2_videodevice/buffer_cache.cpp b/test/v4l2_videodevice/buffer_cache.cpp new file mode 100644 index 00000000..0a8cb0d2 --- /dev/null +++ b/test/v4l2_videodevice/buffer_cache.cpp @@ -0,0 +1,215 @@ +/* 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/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 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 = V4L2_PIX_FMT_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; + + return TestPass; + } + +private: + std::mt19937 generator_; +}; + +} /* namespace */ + +TEST_REGISTER(BufferCacheTest); diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build index 5c52da72..685fcf6d 100644 --- a/test/v4l2_videodevice/meson.build +++ b/test/v4l2_videodevice/meson.build @@ -5,6 +5,7 @@ v4l2_videodevice_tests = [ [ 'controls', 'controls.cpp' ], [ 'formats', 'formats.cpp' ], [ 'request_buffers', 'request_buffers.cpp' ], + [ 'buffer_cache', 'buffer_cache.cpp' ], [ 'stream_on_off', 'stream_on_off.cpp' ], [ 'capture_async', 'capture_async.cpp' ], [ 'buffer_sharing', 'buffer_sharing.cpp' ], |