diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp
index d7d6f0626e..f256ec3033 100644
--- a/src/apps/lc-compliance/tests/capture_test.cpp
+++ b/src/apps/lc-compliance/tests/capture_test.cpp
@@ -8,8 +8,10 @@
 
 #include "capture.h"
 
+#include <semaphore>
 #include <sstream>
 #include <string>
+#include <thread>
 #include <tuple>
 #include <vector>
 
@@ -142,4 +144,263 @@ INSTANTIATE_TEST_SUITE_P(MultiStream,
 					  testing::ValuesIn(NUMREQUESTS)),
 			 SimpleCapture::nameParameters);
 
+class TestWithCamera : public testing::Test, public CameraHolder
+{
+protected:
+	void SetUp() override { acquireCamera(); }
+	void TearDown() override { releaseCamera(); }
+};
+
+class BufferPool : public TestWithCamera
+{
+protected:
+	void SetUp() override
+	{
+		TestWithCamera::SetUp();
+
+		config_ = camera_->generateConfiguration({ StreamRole::Viewfinder });
+		ASSERT_TRUE(config_);
+		ASSERT_FALSE(config_->empty());
+
+		ASSERT_EQ(camera_->configure(config_.get()), 0);
+	}
+
+	void TearDown() override
+	{
+		config_.reset();
+		TestWithCamera::TearDown();
+	}
+
+	std::vector<std::unique_ptr<Request>>
+	createRequests(std::size_t count)
+	{
+		std::vector<std::unique_ptr<Request>> requests;
+
+		for (std::size_t i = 0; i < count; i++) {
+			auto request = camera_->createRequest(i);
+			[&] { ASSERT_TRUE(request); }();
+
+			for (const auto &cfg : *config_)
+				request->enableStream(cfg.stream(), true);
+
+			requests.push_back(std::move(request));
+		}
+
+		return requests;
+	}
+
+	std::unique_ptr<CameraConfiguration> config_;
+};
+
+template<typename T, typename... Args>
+struct ScopedSignalReceiver {
+	Signal<Args...> &signal;
+	T *object = nullptr;
+
+	template<typename Func>
+	ScopedSignalReceiver(Signal<Args...> &s, T *o, Func f)
+		: signal(s), object(o)
+	{
+		signal.connect(o, std::move(f));
+	}
+
+	~ScopedSignalReceiver()
+	{
+		signal.disconnect(object);
+	}
+};
+
+template<typename T, typename... Args>
+ScopedSignalReceiver(Signal<Args...> &, T *) -> ScopedSignalReceiver<T, Args...>;
+
+struct ScopedCameraStart {
+	Camera &camera;
+
+	ScopedCameraStart(Camera &c)
+		: camera(c)
+	{
+		[&]() { ASSERT_EQ(camera.start(), 0); }();
+	}
+
+	~ScopedCameraStart()
+	{
+		EXPECT_EQ(camera.stop(), 0);
+	}
+};
+
+TEST_F(BufferPool, BufferBeforeRequest)
+{
+	FrameBufferAllocator fba(camera_);
+
+	std::size_t buffers = -1;
+	for (const auto &cfg : *config_) {
+		ASSERT_GT(fba.allocate(cfg.stream()), 0);
+		buffers = std::min(buffers, fba.buffers(cfg.stream()).size());
+	}
+
+	auto requests = createRequests(buffers);
+
+	std::counting_semaphore<> requestCompletedSemaphore(0);
+	bool requestCompletedOk = true;
+	unsigned int requestCompleted = 0;
+	ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) {
+		if (requestCompleted < requests.size())
+			requestCompletedOk &= request == requests[requestCompleted].get();
+
+		requestCompletedSemaphore.release(1);
+		requestCompleted += 1;
+	});
+
+	{
+		ScopedCameraStart scs(*camera_);
+
+		for (const auto &cfg : *config_) {
+			Stream *stream = cfg.stream();
+			for (const auto &buffer : fba.buffers(stream))
+				EXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0);
+		}
+
+		for (const auto &request : requests)
+			ASSERT_EQ(camera_->queueRequest(request.get()), 0);
+
+		auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(buffers);
+
+		for (size_t i = 0; i < buffers; i++) {
+			if (!requestCompletedSemaphore.try_acquire_until(deadline))
+				break;
+		}
+	}
+
+	EXPECT_TRUE(requestCompletedOk);
+	ASSERT_FALSE(requestCompletedSemaphore.try_acquire());
+	EXPECT_EQ(requestCompleted, buffers);
+}
+
+TEST_F(BufferPool, RequestBeforeBuffer)
+{
+	FrameBufferAllocator fba(camera_);
+
+	std::size_t buffers = -1;
+	for (const auto &cfg : *config_) {
+		ASSERT_GT(fba.allocate(cfg.stream()), 0);
+		buffers = std::min(buffers, fba.buffers(cfg.stream()).size());
+	}
+
+	auto requests = createRequests(buffers);
+
+	std::counting_semaphore<> requestCompletedSemaphore(0);
+	bool requestCompletedOk = true;
+	unsigned int requestCompleted = 0;
+	ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) {
+		if (requestCompleted < requests.size())
+			requestCompletedOk &= request == requests[requestCompleted].get();
+
+		requestCompletedSemaphore.release(1);
+		requestCompleted += 1;
+	});
+
+	{
+		ScopedCameraStart scs(*camera_);
+
+		for (const auto &request : requests)
+			ASSERT_EQ(camera_->queueRequest(request.get()), 0);
+
+		std::this_thread::sleep_for(std::chrono::seconds(2));
+		ASSERT_FALSE(requestCompletedSemaphore.try_acquire());
+		ASSERT_EQ(requestCompleted, 0);
+
+		auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(buffers);
+
+		for (const auto &cfg : *config_) {
+			Stream *stream = cfg.stream();
+			for (const auto &buffer : fba.buffers(stream))
+				EXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0);
+		}
+
+		for (size_t i = 0; i < buffers; i++) {
+			if (!requestCompletedSemaphore.try_acquire_until(deadline))
+				break;
+		}
+	}
+
+	EXPECT_TRUE(requestCompletedOk);
+	ASSERT_FALSE(requestCompletedSemaphore.try_acquire());
+	EXPECT_EQ(requestCompleted, buffers);
+}
+
+TEST_F(BufferPool, BufferWithoutRequest)
+{
+	FrameBufferAllocator fba(camera_);
+	std::set<std::pair<const Stream *, FrameBuffer *>> buffers;
+
+	for (const auto &cfg : *config_) {
+		Stream *stream = cfg.stream();
+		ASSERT_GT(fba.allocate(stream), 0);
+
+		for (const auto &buffer : fba.buffers(stream))
+			buffers.insert({ stream, buffer.get() });
+	}
+
+	unsigned int requestCompleted = 0;
+	ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *) {
+		requestCompleted += 1;
+	});
+
+	bool bufferCompletedOk = true;
+	ScopedSignalReceiver bcr(camera_->bufferCompleted, this, [&](Request *request, const Stream *stream, FrameBuffer *buffer) {
+		bufferCompletedOk &= !request;
+		bufferCompletedOk &= buffer->metadata().status == libcamera::FrameMetadata::FrameCancelled;
+		bufferCompletedOk &= buffers.erase({ stream, buffer });
+	});
+
+	{
+		ScopedCameraStart scs(*camera_);
+
+		for (const auto &cfg : *config_) {
+			Stream *stream = cfg.stream();
+			for (const auto &buffer : fba.buffers(stream))
+				EXPECT_EQ(camera_->addBuffer(stream, buffer.get()), 0);
+		}
+	}
+
+	EXPECT_EQ(requestCompleted, 0);
+	EXPECT_EQ(buffers.size(), 0);
+	EXPECT_TRUE(bufferCompletedOk);
+}
+
+TEST_F(BufferPool, RequestWithoutBuffers)
+{
+	constexpr std::size_t kRequestCount = 128;
+	auto requests = createRequests(kRequestCount);
+
+	unsigned int requestCompleted = 0;
+	bool requestCompletedOk = true;
+	ScopedSignalReceiver rcr(camera_->requestCompleted, this, [&](Request *request) {
+		if (requestCompleted < requests.size()) {
+			requestCompletedOk &= request == requests[requestCompleted].get();
+			requestCompletedOk &= request->status() == Request::Status::RequestCancelled;
+		}
+
+		requestCompleted += 1;
+	});
+
+	unsigned int bufferCompleted = 0;
+	ScopedSignalReceiver bcr(camera_->bufferCompleted, this, [&](Request *, const Stream *, FrameBuffer *) {
+		bufferCompleted += 1;
+	});
+
+	{
+		ScopedCameraStart scs(*camera_);
+
+		for (const auto &request : requests)
+			ASSERT_EQ(camera_->queueRequest(request.get()), 0);
+
+		std::this_thread::sleep_for(std::chrono::seconds(2));
+	}
+
+	EXPECT_EQ(requestCompleted, requests.size());
+	EXPECT_TRUE(requestCompletedOk);
+	EXPECT_EQ(bufferCompleted, 0);
+}
+
 } /* namespace */
