OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / test / test_spsc_queue.cpp

185 lines · cpp ref:
#include "../src/dc_spsc_queue.h"

#include <cassert>
#include <cstdio>
#include <string>
#include <thread>
#include <vector>

// ---- Test 1: Single-threaded push/pop ----
static void test_basic_push_pop() {
	SPSCQueue<int, 16> q;

	assert(q.empty());

	assert(q.try_push(42));
	assert(!q.empty());

	int val = 0;
	assert(q.try_pop(val));
	assert(val == 42);
	assert(q.empty());

	// Pop from empty should fail
	assert(!q.try_pop(val));

	// Push several, pop in order
	for (int i = 0; i < 10; i++) {
		assert(q.try_push(i));
	}
	for (int i = 0; i < 10; i++) {
		assert(q.try_pop(val));
		assert(val == i);
	}
	assert(q.empty());

	printf("PASS: basic push/pop\n");
}

// ---- Test 2: Fill to capacity ----
static void test_fill_capacity() {
	// N=8, usable capacity = 7
	SPSCQueue<int, 8> q;

	for (int i = 0; i < 7; i++) {
		assert(q.try_push(i));
	}
	// 8th push should fail (full at N-1)
	assert(!q.try_push(999));

	// Pop one, then push should succeed again
	int val = 0;
	assert(q.try_pop(val));
	assert(val == 0);
	assert(q.try_push(999));

	// Should be full again
	assert(!q.try_push(1000));

	// Drain and verify order
	for (int i = 1; i <= 7; i++) {
		assert(q.try_pop(val));
		int expected = (i < 7) ? i : 999;
		assert(val == expected);
	}
	assert(q.empty());

	printf("PASS: fill to capacity (N-1 usable slots)\n");
}

// ---- Test 3: Multi-threaded producer/consumer ----
static void test_multithreaded() {
	static constexpr int COUNT = 10000;
	SPSCQueue<int, 1024> q;

	std::thread producer([&]() {
		for (int i = 0; i < COUNT; i++) {
			while (!q.try_push(i)) {
				// spin
			}
		}
	});

	std::vector<int> received;
	received.reserve(COUNT);

	std::thread consumer([&]() {
		int val;
		for (int i = 0; i < COUNT; i++) {
			while (!q.try_pop(val)) {
				// spin
			}
			received.push_back(val);
		}
	});

	producer.join();
	consumer.join();

	assert((int)received.size() == COUNT);
	for (int i = 0; i < COUNT; i++) {
		assert(received[i] == i);
	}

	printf("PASS: multi-threaded producer/consumer (%d items, ordering verified)\n", COUNT);
}

// ---- Test 4: Struct payload ----
struct Payload {
	int id;
	float x, y, z;
	std::string name;

	Payload() : id(0), x(0), y(0), z(0) {}
	Payload(int id_, float x_, float y_, float z_, const char *name_)
			: id(id_), x(x_), y(y_), z(z_), name(name_) {}
};

static void test_struct_payload() {
	SPSCQueue<Payload, 32> q;

	assert(q.try_push(Payload(1, 1.0f, 2.0f, 3.0f, "alpha")));
	assert(q.try_push(Payload(2, 4.0f, 5.0f, 6.0f, "beta")));

	Payload p;
	assert(q.try_pop(p));
	assert(p.id == 1);
	assert(p.x == 1.0f && p.y == 2.0f && p.z == 3.0f);
	assert(p.name == "alpha");

	assert(q.try_pop(p));
	assert(p.id == 2);
	assert(p.name == "beta");

	assert(q.empty());

	// Test move semantics with string
	Payload big(3, 0, 0, 0, "a rather long string to avoid SSO");
	assert(q.try_push(std::move(big)));
	assert(q.try_pop(p));
	assert(p.id == 3);
	assert(p.name == "a rather long string to avoid SSO");

	printf("PASS: struct payload (non-trivial type with move)\n");
}

// ---- Test 5: Wrap-around ----
static void test_wraparound() {
	// Small queue (N=4, usable=3) to force frequent wrapping
	SPSCQueue<int, 4> q;

	int val;
	// Push/pop 20 items through a 3-slot queue => many wrap-arounds
	for (int i = 0; i < 20; i++) {
		assert(q.try_push(i));
		assert(q.try_pop(val));
		assert(val == i);
	}

	// Batch: fill, drain, repeat across boundary
	for (int round = 0; round < 10; round++) {
		for (int i = 0; i < 3; i++) {
			assert(q.try_push(round * 3 + i));
		}
		assert(!q.try_push(-1)); // full
		for (int i = 0; i < 3; i++) {
			assert(q.try_pop(val));
			assert(val == round * 3 + i);
		}
		assert(q.empty());
	}

	printf("PASS: wrap-around (push/pop past capacity boundary)\n");
}

int main() {
	test_basic_push_pop();
	test_fill_capacity();
	test_multithreaded();
	test_struct_payload();
	test_wraparound();

	printf("\nAll SPSC queue tests passed.\n");
	return 0;
}