KPN/tests/test_static_network.cpp
Duncan Tourolle 2bca2a7554
All checks were successful
🧪 Test / test (push) Successful in 6m4s
Add static network
2026-05-08 20:00:15 +02:00

210 lines
7.1 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <kpn/kpn.hpp>
#include <chrono>
#include <thread>
#include <atomic>
using namespace kpn;
static int increment(int x) { return x + 1; }
static int multiply2(int x) { return x * 2; }
static int multiply3(int x) { return x * 3; }
static int add10(int x) { return x + 10; }
static int negate_val(int x) { return -x; }
static int square(int x) { return x * x; }
// ── Linear pipeline ───────────────────────────────────────────────────────────
TEST_CASE("static_network: linear pipeline produces correct result", "[static_network]") {
auto src = make_node<increment>(5);
auto dst = make_node<multiply2>(5);
Channel<int> final_out(5);
dst.set_output_channel<0>(&final_out);
auto net = make_network(
edge(src.output<0>(), dst.input<0>())
);
net.start();
src.input_channel<0>().push(5); // 5 → increment → 6 → multiply2 → 12
int result = final_out.pop();
net.stop();
REQUIRE(result == 12);
}
TEST_CASE("static_network: three-node pipeline", "[static_network]") {
auto a = make_node<increment>(5);
auto b = make_node<multiply2>(5);
auto c = make_node<add10>(5);
Channel<int> out(5);
c.set_output_channel<0>(&out);
auto net = make_network(
edge(a.output<0>(), b.input<0>()),
edge(b.output<0>(), c.input<0>())
);
net.start();
a.input_channel<0>().push(3); // 3 → +1=4 → *2=8 → +10=18
int result = out.pop();
net.stop();
REQUIRE(result == 18);
}
// ── Auto fan-out ──────────────────────────────────────────────────────────────
TEST_CASE("static_network: auto fanout delivers to both consumers", "[static_network]") {
// Use distinct functions so each node has a distinct type in the graph
auto src = make_node<increment>(8);
auto dstA = make_node<multiply2>(8);
auto dstB = make_node<multiply3>(8);
Channel<int> outA(8), outB(8);
dstA.set_output_channel<0>(&outA);
dstB.set_output_channel<0>(&outB);
// Two edges from the same output port — FanoutNode<int,2> is auto-inserted
auto net = make_network(
edge(src.output<0>(), dstA.input<0>()),
edge(src.output<0>(), dstB.input<0>())
);
net.start();
src.input_channel<0>().push(3); // 3 → +1=4 → *2=8 and *3=12
int a = outA.pop();
int b = outB.pop();
net.stop();
REQUIRE(a == 8);
REQUIRE(b == 12);
}
TEST_CASE("static_network: auto fanout preserves ordering across multiple items", "[static_network]") {
auto src = make_node<increment>(16);
auto dstA = make_node<multiply2>(16);
auto dstB = make_node<negate_val>(16);
Channel<int> outA(16), outB(16);
dstA.set_output_channel<0>(&outA);
dstB.set_output_channel<0>(&outB);
auto net = make_network(
edge(src.output<0>(), dstA.input<0>()),
edge(src.output<0>(), dstB.input<0>())
);
net.start();
for (int i = 0; i < 5; ++i)
src.input_channel<0>().push(i); // 0..4 → +1 → *2 or negate
for (int i = 0; i < 5; ++i) {
REQUIRE(outA.pop() == (i + 1) * 2);
REQUIRE(outB.pop() == -(i + 1));
}
net.stop();
}
// ── Stop/start lifecycle ──────────────────────────────────────────────────────
TEST_CASE("static_network: stop disables input channel", "[static_network]") {
auto src = make_node<increment>(5);
auto dst = make_node<multiply2>(5);
auto net = make_network(
edge(src.output<0>(), dst.input<0>())
);
net.start();
net.stop();
// After stop, input channel disabled — push must not throw
src.input_channel<0>().push(99);
REQUIRE(src.input_channel<0>().size() == 0);
}
// ── Compile-time cycle detection ──────────────────────────────────────────────
// Cycles fire a static_assert in make_network(), so we can only test the
// no-cycle path at runtime.
//
// To manually verify a cycle error: add
// auto bad = make_network(edge(a.output<0>(), b.input<0>()),
// edge(b.output<0>(), a.input<0>()));
// and confirm: "make_network: graph contains a directed cycle"
//
// To manually verify a duplicate-tag error: add
// auto x = make_node<increment>(5);
// auto y = make_node<increment>(5); // same type as x — UniqueTag=0 for both
// auto bad = make_network(edge(x.output<0>(), y.input<0>()));
// and confirm: "make_network: two nodes have the same (Func, UniqueTag)"
TEST_CASE("static_network: acyclic graph does not trigger static_assert", "[static_network]") {
auto a = make_node<increment>(5);
auto b = make_node<add10>(5);
Channel<int> out(5);
b.set_output_channel<0>(&out);
auto net = make_network(edge(a.output<0>(), b.input<0>()));
net.start();
a.input_channel<0>().push(5);
REQUIRE(out.pop() == 16); // 5 → +1=6 → +10=16
net.stop();
}
// ── Label and UniqueTag ───────────────────────────────────────────────────────
TEST_CASE("static_network: same function distinguished by UniqueTag", "[static_network]") {
// Two nodes wrapping the same function — only possible with distinct UniqueTag
auto a = make_node<increment, "stage1", 0>(8);
auto b = make_node<increment, "stage2", 1>(8); // same func, tag=1 → distinct type
Channel<int> out(8);
b.set_output_channel<0>(&out);
auto net = make_network(edge(a.output<0>(), b.input<0>()));
net.start();
a.input_channel<0>().push(10);
REQUIRE(out.pop() == 12); // 10 → +1=11 → +1=12
net.stop();
}
TEST_CASE("static_network: label is accessible as static member", "[static_network]") {
using MyNode = decltype(make_node<increment, "my_node">(5));
REQUIRE(MyNode::label() == "my_node");
REQUIRE(MyNode::unique_tag == 0);
using TaggedNode = decltype(make_node<increment, "tagged", 42>(5));
REQUIRE(TaggedNode::label() == "tagged");
REQUIRE(TaggedNode::unique_tag == 42);
}
TEST_CASE("static_network: fanout with labelled same-function consumers", "[static_network]") {
auto src = make_node<increment, "src" >(8);
auto dstA = make_node<increment, "consumer_a", 1>(8);
auto dstB = make_node<increment, "consumer_b", 2>(8);
Channel<int> outA(8), outB(8);
dstA.set_output_channel<0>(&outA);
dstB.set_output_channel<0>(&outB);
// Fan-out from src to two increment nodes — only possible because tags differ
auto net = make_network(
edge(src.output<0>(), dstA.input<0>()),
edge(src.output<0>(), dstB.input<0>())
);
net.start();
src.input_channel<0>().push(5); // 5 → +1=6 → both +1=7
REQUIRE(outA.pop() == 7);
REQUIRE(outB.pop() == 7);
net.stop();
}