210 lines
7.1 KiB
C++
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();
|
|
}
|