Initial Release

This commit is contained in:
CJ van den Berg 2024-02-08 22:23:40 +01:00
commit 5a00e06cb9
81 changed files with 12670 additions and 0 deletions

315
test/cbor_match.cpp Normal file
View file

@ -0,0 +1,315 @@
#include "tests.hpp"
#include <thespian/instance.hpp>
#include <thespian/trace.hpp>
#include <limits>
#include <string>
using cbor::A;
using cbor::array;
using cbor::buffer;
using cbor::extract;
using cbor::M;
using cbor::map;
using cbor::type;
using std::move;
using std::string;
using std::string_view;
using std::stringstream;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::link;
using thespian::ok;
using thespian::result;
namespace {
auto test() -> result {
auto verbose = env().is("verbose");
auto log = env().proc("log");
result _;
buffer m = array("five", 5, "four", 4, array("three", 3));
if (verbose) {
_ = log.send("message:", m.to_json());
_ = log.send("buffer:", m.hexdump());
}
check(m.hexdump() ==
"21: 85 64 66 69 76 65 05 64 66 6f 75 72 04 82 65 74 68 72 65 65 03");
check(m.to_json() == R"(["five",5,"four",4,["three",3]])");
check(m(type::string, type::number, type::string, type::number, type::array));
check(!m(type::string, type::number, type::string, type::number));
check(!m(type::number, type::string, type::number, type::array, type::any));
check(m(type::any, type::number, type::string, type::number, type::any));
check(m("five", type::number, type::string, 4, type::any));
check(m("five", 5, "four", 4, type::array));
check(m("five", 5, type::more));
check(m("five", 5, "four", 4, type::array, type::more));
check(!m("five", 5, "four", 4, type::array, type::any));
check(array().is_null());
check(!array(1).is_null());
{
buffer b;
b.array_header(5)
.push("five")
.push(5)
.push("four")
.push(4)
.array_header(2)
.push("three")
.push(3);
check(m == b);
}
{
buffer b = array(false, true, 5, "five");
check(b == array(false, true, 5, "five"));
check(b(false, true, 5, "five"));
check(!b(true, false, 5, "five"));
bool t = false;
check(b(extract(t), true, 5, "five"));
check(!t);
check(b(false, extract(t), 5, "five"));
check(t);
}
{
buffer::range r;
check(
m(type::string, type::number, type::string, type::number, extract(r)));
check(r(type::string, type::number));
check(r("three", 3));
}
{
int64_t i{};
check(m(type::string, type::number, type::string, type::number,
A(type::string, extract(i))));
check(i == 3);
}
{
buffer ref = array(array(124), array("blip"));
check(ref(A(type::more), A(type::more)));
}
{
int i{0};
auto dump = [&](string_view val) {
++i;
if (verbose)
_ = log.send("value", i, ":", val);
};
buffer a = array("five", "four", "three", "two", "one");
for (const auto val : a)
dump(val);
check(i == 5);
}
{
int i{0};
auto dump = [&](auto val) {
++i;
if (verbose)
_ = log.send("value", i, ":", val);
};
for (const auto val : m)
val.visit(dump);
check(i == 5);
buffer::range r;
check(m(type::any, type::any, type::any, type::any, extract(r)));
for (const auto val : r)
val.visit(dump);
check(i == 7);
}
{
buffer a = array(array(2, 3, 4, 5), array(6, 7, 8, 9));
check(a(A(2, 3, 4, 5), A(6, 7, 8, 9)));
buffer::range a1;
buffer::range a2;
check(a(extract(a1), extract(a2)));
int i{0};
auto dump = [&](uint32_t val) {
++i;
if (verbose)
_ = log.send("value", i, ":", val);
};
for (const auto val : a1)
dump(val);
}
{
buffer a = array(array(2, 3, 4, 5), array(6, 7, 8, 9));
buffer::range a1;
buffer::range a2;
check(a(extract(a1), extract(a2)));
buffer b = array(a1);
check(b(A(2, 3, 4, 5)));
buffer b2;
b2.push(a2);
check(b2(6, 7, 8, 9));
}
{
buffer a = array(1, 2, map(3, array(3)));
if (verbose) {
_ = log.send("map buffer:", a.hexdump());
_ = log.send("map json:", a.to_json());
}
check(a(1, 2, M(3, A(3))));
buffer::range a1;
check(a(1, 2, extract(a1)));
if (verbose)
_ = log.send("map range json:", a1.to_json());
check(a1.to_json() == "{3:[3]}");
}
{
buffer a;
a.push_json(R"(["five", 5, "four", 4, ["three", 3]])");
if (verbose) {
_ = log.send("buffer:", a.hexdump());
_ = log.send("message:", a.to_json());
}
check(a.hexdump() == "21: 85 64 66 69 76 65 05 64 66 6f 75 72 04 82 "
"65 74 68 72 65 65 03");
}
{
buffer a;
a.push_json(R"({"five": 5, "four": 4, "three": [{3:3}]})");
string json = a.to_json();
if (verbose) {
_ = log.send("buffer:", a.hexdump());
_ = log.send("json:", json);
}
check(a.hexdump() == "23: a3 64 66 69 76 65 05 64 66 6f 75 72 04 65 "
"74 68 72 65 65 81 a1 03 03");
check(json == R"({"five":5,"four":4,"three":[{3:3}]})");
}
{
buffer a = array("string\r\n", "\tstring\t", "\r\nstring");
string json = a.to_json();
if (verbose) {
_ = log.send("escaped buffer:", a.hexdump());
_ = log.send("escaped json:", json);
}
check(a.hexdump() == "28: 83 68 73 74 72 69 6e 67 0d 0a 68 09 73 74 "
"72 69 6e 67 09 68 0d 0a 73 74 72 69 6e 67");
check(json == R"(["string\r\n","\tstring\t","\r\nstring"])");
}
{
buffer a;
a.push_json(R"(["string\r\n", "\tstring\t", "\r\nstring"])");
string json = a.to_json();
if (verbose) {
_ = log.send("escaped2 buffer:", a.hexdump());
_ = log.send("escaped2 json:", json);
}
check(a.hexdump() == "28: 83 68 73 74 72 69 6e 67 0d 0a 68 09 73 74 "
"72 69 6e 67 09 68 0d 0a 73 74 72 69 6e 67");
check(json == R"(["string\r\n","\tstring\t","\r\nstring"])");
}
{
buffer a = array(array());
buffer::range r;
check(a(extract(r)));
int i{0};
auto dump = [&](uint32_t /*val*/) { ++i; };
for (const auto v1 : r)
dump(v1);
check(i == 0);
check(r.is_null());
check(r.to_json() == "null");
}
{
int64_t i{-1};
uint64_t sz{0};
buffer a = array(i, sz);
check(a(extract(i), extract(sz)));
check(i == -1);
check(sz == 0);
}
{
int64_t i{-1};
uint64_t sz{0};
buffer a = array(i);
check(a(extract(i)));
check(not a(extract(sz)));
}
{
int32_t i{-1};
uint32_t sz{0};
buffer a = array(i);
check(a(extract(i)));
check(not a(extract(sz)));
}
{
int16_t i{-1};
uint16_t sz{0};
buffer a = array(i);
check(a(extract(i)));
check(not a(extract(sz)));
}
{
int8_t i{-1};
uint8_t sz{0};
buffer a = array(i);
check(a(extract(i)));
check(not a(extract(sz)));
}
{
uint16_t ui{1000};
uint8_t sz{0};
buffer a = array(ui);
check(a(extract(ui)));
check(not a(extract(sz)));
}
{
uint64_t ui{18446744073709551615ULL};
int64_t i{0};
buffer a = array(ui);
check(a(extract(ui)));
check(not a(extract(i)));
}
{
buffer a = array(array(1, 2));
buffer b = array(array(1, 2));
buffer::range ra;
check(a(extract(ra)));
buffer::range rb;
check(b(extract(rb)));
check(ra == rb);
}
{
buffer a = array(array(1));
buffer b = array(array(1, 2));
buffer::range ra;
check(a(extract(ra)));
buffer::range rb;
check(b(extract(rb)));
check(not(ra == rb));
}
if (verbose)
_ = log.send("done");
return ok();
}
} // namespace
auto cbor_match(context &ctx, bool &result, env_t env_) -> ::result {
return to_result(ctx.spawn_link(
[=]() {
link(env().proc("log"));
return test();
},
[&](auto s) {
check(s == "noreceive");
result = true;
},
"cbor_match", move(env_)));
};

196
test/debug.cpp Normal file
View file

@ -0,0 +1,196 @@
#include "tests.hpp"
#include "thespian/handle.hpp"
#include <thespian/debug.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/tcp.hpp>
#include <thespian/trace.hpp>
#include <array>
#include <cstring>
#include <memory>
using cbor::buffer;
using cbor::extract;
using std::make_shared;
using std::make_unique;
using std::move;
using std::string;
using std::string_view;
using std::stringstream;
using std::unique_ptr;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::error;
using thespian::exit;
using thespian::expected;
using thespian::handle;
using thespian::link;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::self;
using thespian::socket;
using thespian::spawn_link;
using thespian::trap;
using thespian::unexpected;
using thespian::tcp::connector;
namespace {
struct debuggee {
auto receive(const handle &from, const buffer &m) {
if (m("ping")) {
return from.send("pong");
}
if (m("shutdown"))
return exit("debuggee_shutdown");
return unexpected(m);
return ok();
}
static auto start() -> expected<handle, error> {
return spawn_link(
[=]() {
::receive([p{make_shared<debuggee>()}](auto from, auto m) {
return p->receive(move(from), move(m));
});
return ok();
},
"debuggee");
}
};
struct controller {
static constexpr string_view tag{"debug_test_controller"};
handle debug_tcp;
handle debuggee_;
connector c;
unique_ptr<thespian::socket> s;
size_t pong_count{};
bool success_{false};
string prev_buf;
explicit controller(handle d, handle debuggee)
: debug_tcp{std::move(d)}, debuggee_{std::move(debuggee)},
c{connector::create(tag)} {
trap(true);
}
auto receive(const handle & /*from*/, const buffer &m) {
int fd{};
string buf;
int written{};
int err{};
string_view err_msg{};
if (m("pong")) {
pong_count++;
if (pong_count == 2)
c.connect(in6addr_loopback, 4242);
} else if (m("connector", tag, "connected", extract(fd))) {
s = make_unique<thespian::socket>(socket::create(tag, fd));
s->read();
s->write("\n");
s->write("debuggee [\"ping\"]\n");
} else if (m("socket", tag, "read_error", extract(err), extract(err_msg))) {
return exit("read_error", err_msg);
} else if (m("socket", tag, "read_complete", extract(buf))) {
if (buf.empty()) {
s->close();
return ok();
}
s->read();
buf.swap(prev_buf);
buf.append(prev_buf);
string::size_type pos{};
while (not buf.empty() and pos != string::npos) {
pos = buf.find_first_of('\n');
if (pos != string::npos) {
auto ret = self().send("dispatch", string(buf.data(), pos));
if (not ret)
return ret;
buf.erase(0, pos + 1);
}
}
prev_buf = buf;
} else if (m("dispatch", extract(buf))) {
if (buf == "debuggee [\"pong\"]") {
s->write("debuggee [\"shutdown\"]\n");
}
} else if (m("socket", tag, "write_error", extract(err),
extract(err_msg))) {
return exit("write_error", err_msg);
} else if (m("socket", tag, "write_complete", extract(written))) {
;
} else if (m("socket", tag, "closed")) {
if (success_)
return exit("success");
auto ret = debuggee_.send("shutdown");
if (not ret)
return ret;
return exit("closed");
} else if (m("exit", "debuggee_shutdown")) {
success_ = true;
s->close();
} else if (m("connector", tag, "cancelled"))
return exit("cancelled");
else if (m("connector", tag, "error", extract(buf)))
return exit("connect_error", buf);
else
return unexpected(m);
return ok();
}
};
} // namespace
auto debug(context &ctx, bool &result, env_t env_) -> ::result {
thespian::debug::enable(ctx);
return to_result(ctx.spawn_link(
[&ctx]() {
link(env().proc("log"));
auto ret = thespian::debug::tcp::create(ctx, 4242, "");
if (not ret)
return to_result(ret);
auto debug_tcp = ret.value();
ret = spawn_link(
[&ctx, debug_tcp]() {
trap(true);
::receive([&ctx, debug_tcp](auto, auto /*m*/) -> ::result {
auto ret = debug_tcp.send("shutdown");
if (not ret)
return ret;
thespian::debug::disable(ctx);
return exit();
});
return ok();
},
"controller_guard");
if (not ret)
return to_result(ret);
auto ret2 = debug_tcp.send("ping");
if (not ret2)
return ret2;
ret = debuggee::start();
if (not ret)
return to_result(ret);
auto debuggee = ret.value();
ret2 = debuggee.send("ping");
if (not ret2)
return ret2;
receive([p{make_shared<controller>(debug_tcp, debuggee)}](auto from,
auto m) {
return p->receive(move(from), move(m));
});
return ok();
},
[&](auto s) {
if (s == "success")
result = true;
},
"debug", move(env_)));
}

93
test/endpoint_tcp.cpp Normal file
View file

@ -0,0 +1,93 @@
#include "tests.hpp"
#include "cbor/cbor.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/endpoint.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/tcp.hpp>
#include <cstring>
#include <map>
#include <utility>
using cbor::any;
using cbor::buffer;
using cbor::extract;
using std::make_shared;
using std::move;
using std::string;
using std::string_view;
using std::stringstream;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::handle;
using thespian::link;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::unexpected;
using thespian::endpoint::tcp::listen;
namespace {
struct controller {
handle ep_listen;
handle ep_connect;
size_t ping_count{};
size_t pong_count{};
bool success_{false};
explicit controller(handle ep_l) : ep_listen{move(ep_l)} {}
auto receive(const handle &from, const buffer &m) {
port_t port{0};
if (m("port", extract(port))) {
ep_connect =
thespian::endpoint::tcp::connect(in6addr_loopback, port).value();
return ok();
}
if (m("connected")) {
ping_count++;
return ep_connect.send("ping", ping_count);
}
if (m("ping", any)) {
pong_count++;
return from.send("pong", pong_count);
}
if (m("pong", any)) {
if (ping_count > 10) {
return exit(ping_count == pong_count ? "success" : "closed");
}
ping_count++;
return from.send("ping", ping_count);
}
return unexpected(m);
}
}; // namespace
} // namespace
auto endpoint_tcp(context &ctx, bool &result, env_t env_) -> ::result {
return to_result(ctx.spawn_link(
[=]() {
link(env().proc("log"));
handle ep_listen = listen(in6addr_loopback, 0).value();
auto ret = ep_listen.send("get", "port");
if (not ret)
return ret;
receive([p{make_shared<controller>(ep_listen)}](auto from, auto m) {
return p->receive(move(from), move(m));
});
return ok();
},
[&](auto s) {
if (s == "success")
result = true;
},
"endpoint_tcp", move(env_)));
}

104
test/endpoint_unx.cpp Normal file
View file

@ -0,0 +1,104 @@
#include "tests.hpp"
#include <thespian/endpoint.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/timeout.hpp>
#include <thespian/unx.hpp>
#include <cstring>
#include <map>
#include <sstream>
#include <unistd.h>
#include <utility>
using cbor::array;
using cbor::buffer;
using cbor::extract;
using std::make_shared;
using std::move;
using std::string;
using std::string_view;
using std::stringstream;
using thespian::context;
using thespian::create_timeout;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::handle;
using thespian::link;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::timeout;
using thespian::unexpected;
using thespian::endpoint::unx::connect;
using thespian::endpoint::unx::listen;
using thespian::unx::mode;
using namespace std::chrono_literals;
namespace {
struct controller {
handle ep_listen;
handle ep_connect;
size_t ping_count{};
size_t pong_count{};
bool success_{false};
timeout t{create_timeout(100ms, array("connect"))};
explicit controller(handle ep_l) : ep_listen{move(ep_l)} {}
auto receive(const handle &from, const buffer &m) {
string_view path;
if (m("connect")) {
auto ret = ep_listen.send("get", "path");
if (not ret)
return ret;
return ok();
}
if (m("path", extract(path))) {
ep_connect = connect(path).value();
return ok();
}
if (m("connected")) {
ping_count++;
return ep_connect.send("ping");
}
if (m("ping")) {
pong_count++;
return from.send("pong");
}
if (m("pong")) {
if (ping_count > 10) {
return exit(ping_count == pong_count ? "success" : "closed");
}
ping_count++;
return ep_connect.send("ping");
}
return unexpected(m);
}
}; // namespace
} // namespace
auto endpoint_unx(context &ctx, bool &result, env_t env_) -> ::result {
stringstream ss;
ss << "/net/vdbonline/thespian/endpoint_t_" << getpid();
string path = ss.str();
return to_result(ctx.spawn_link(
[path]() {
link(env().proc("log"));
handle ep_listen = listen(path).value();
receive([p{make_shared<controller>(ep_listen)}](auto from, auto m) {
return p->receive(move(from), move(m));
});
return ok();
},
[&](auto s) {
if (s == "success")
result = true;
},
"endpoint_unx", move(env_)));
}

127
test/hub_filter.cpp Normal file
View file

@ -0,0 +1,127 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/hub.hpp>
#include <thespian/instance.hpp>
#include <map>
using cbor::extract;
using cbor::type;
using std::map;
using std::move;
using std::string;
using std::string_view;
using std::stringstream;
using thespian::context;
using thespian::env_t;
using thespian::handle;
using thespian::hub;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::self;
using thespian::spawn_link;
namespace {
auto sub_plain(const hub &h, const handle &controller, string name) -> result {
auto ret = h.subscribe();
if (not ret)
return ret;
ret = controller.send("ready", name);
if (not ret)
return ret;
receive([=](auto /*from*/, auto m) {
string_view cmd;
check(m("parmA", "parmB", "parmC", extract(cmd)));
check(cmd == "continue" || cmd == "done");
if (cmd == "done") {
auto ret = controller.send(cmd, name);
if (not ret)
return ret;
}
return ok();
});
return ok();
}
auto sub_filtered(const hub &h, const handle &controller, string name)
-> result {
auto ret = h.subscribe(
[](auto m) { return m(type::any, type::any, type::any, "done"); });
if (not ret)
return ret;
ret = controller.send("ready", name);
if (not ret)
return ret;
receive([=](auto /*from*/, auto m) {
check(m("parmA", "parmB", "parmC", "done"));
auto ret = controller.send("done", name);
if (not ret)
return ret;
return ok();
});
return ok();
}
map<string, auto(*)(const hub &, const handle &, string)->result>
subscription_defs // NOLINT
= {
{"sub_plain", sub_plain},
{"sub_filtered", sub_filtered},
};
using submap = map<string, handle>;
auto controller() -> result {
thespian::link(thespian::env().proc("log"));
auto h = hub::create("hub").value();
handle c = self();
auto ret = h.subscribe();
if (not ret)
return ret;
submap subs;
for (const auto &s : subscription_defs) {
subs.insert({s.first, spawn_link(
[=]() {
s.second(h, c, s.first);
return ok();
},
s.first)
.value()});
}
submap not_ready = subs;
submap done = subs;
receive([=](auto, auto m) mutable {
string name;
if (m("ready", extract(name))) {
not_ready.erase(name);
if (not_ready.empty()) {
h.broadcast("parmA", "parmB", "parmC", "continue");
h.broadcast("parmA", "parmB", "parmC", "continue");
h.broadcast("parmA", "parmB", "parmC", "done");
}
} else if (m("done", extract(name))) {
done.erase(name);
if (done.empty())
return h.shutdown();
}
return ok();
});
return ok();
}
} // namespace
auto hub_filter(context &ctx, bool &result, env_t env) -> ::result {
return to_result(ctx.spawn_link(
controller,
[&](auto s) {
if (s == "shutdown")
result = true;
},
"hub_filter", move(env)));
}

View file

@ -0,0 +1,225 @@
#include "tests.hpp"
#include "thespian/handle.hpp"
#include <cbor/cbor_in.hpp>
#include <thespian/debug.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/tcp.hpp>
#include <netinet/in.h>
using cbor::buffer;
using cbor::extract;
using std::make_shared;
using std::move;
using std::string;
using std::string_view;
using std::stringstream;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::handle;
using thespian::link;
using thespian::ok;
using thespian::result;
using thespian::self;
using thespian::socket;
using thespian::spawn_link;
using thespian::unexpected;
using thespian::tcp::acceptor;
using thespian::tcp::connector;
namespace {
struct client_connection {
thespian::socket socket;
handle connector;
explicit client_connection(int fd, handle connector)
: socket{socket::create("client_connection", fd)},
connector(move(connector)) {
socket.read();
}
auto receive(const buffer &m) {
int written{};
int err{};
string_view buf{};
if (m("socket", "client_connection", "read_complete", extract(buf))) {
check(buf == "ping");
check(err == 0);
socket.write("pong");
} else if (m("socket", "client_connection", "write_complete",
extract(written))) {
check(written == 4);
check(err == 0);
// socket.close(); // let server close
} else if (m("socket", "client_connection", "closed")) {
auto ret = connector.send("client_connection", "done");
if (not ret)
return ret;
return exit("success");
} else {
return unexpected(m);
}
return ok();
}
[[nodiscard]] static auto init(int fd, const handle &connector) {
return spawn_link(
[fd, connector]() {
::thespian::receive(
[p{make_shared<client_connection>(fd, connector)}](
auto /*from*/, auto m) { return p->receive(move(m)); });
return ok();
},
"client_connection");
}
};
struct client {
connector connector;
handle server;
port_t server_port;
explicit client(handle server, port_t server_port)
: connector{connector::create("client")}, server(move(server)),
server_port(server_port) {
connector.connect(in6addr_loopback, server_port);
}
auto receive(const buffer &m) -> thespian::result {
int fd{};
if (m("connector", "client", "connected", extract(fd))) {
return to_result(client_connection::init(fd, self()));
}
if (m("connector_connection", "done")) {
return server.send("client", "done");
}
return unexpected(m);
}
static auto init(handle server, port_t server_port) -> result {
::thespian::receive(
[p{make_shared<client>(server, server_port)}](auto /*from*/, auto m) {
return p->receive(move(m));
});
return ok();
}
};
struct server_connection {
thespian::socket socket;
handle server;
explicit server_connection(int fd, handle server)
: socket{socket::create("server_connection", fd)}, server(move(server)) {
socket.write("ping");
}
auto receive(const buffer &m) {
int written{};
int err{};
string_view buf{};
if (m("socket", "server_connection", "write_complete", extract(written))) {
check(written == 4);
check(err == 0);
socket.read();
} else if (m("socket", "server_connection", "read_complete",
extract(buf))) {
check(buf == "pong");
check(err == 0);
socket.close();
} else if (m("socket", "server_connection", "closed")) {
auto ret = server.send("server_connection", "done");
if (not ret)
return ret;
return exit("success");
} else {
return unexpected(m);
}
return ok();
}
[[nodiscard]] static auto init(int fd, const handle &server) {
return spawn_link(
[fd, server]() {
::thespian::receive(
[p{make_shared<server_connection>(fd, server)}](
auto /*from*/, auto m) { return p->receive(move(m)); });
return ok();
},
"server_connection");
}
};
struct server {
acceptor acceptor;
bool client_done{false};
bool server_connection_done{false};
bool acceptor_closed{false};
port_t server_port;
explicit server()
: acceptor{acceptor::create("server")},
server_port(acceptor.listen(in6addr_loopback, 0)) {}
auto receive(const buffer &m) {
int fd{};
if (m("acceptor", "server", "accept", extract(fd))) {
auto ret = server_connection::init(fd, self());
if (not ret)
return to_result(ret);
acceptor.close();
} else if (m("acceptor", "server", "closed")) {
acceptor_closed = true;
} else if (m("client", "done")) {
client_done = true;
} else if (m("server_connection", "done")) {
server_connection_done = true;
} else {
return unexpected(m);
}
if (acceptor_closed and client_done and server_connection_done)
return exit("success");
return ok();
}
[[nodiscard]] static auto init() {
link(env().proc("log"));
auto log = env().proc("log");
auto _ = log.send("server starting");
auto p{make_shared<server>()};
_ = log.send("server listening on port", p->server_port);
auto ret = spawn_link(
[server{self()}, server_port{p->server_port}]() {
return client::init(server, server_port);
},
"ip_tcp_client");
if (not ret)
return to_result(ret);
thespian::receive(
[p](auto /*from*/, auto m) { return p->receive(move(m)); });
return ok();
}
};
} // namespace
auto ip_tcp_client_server(context &ctx, bool &result, env_t env) -> ::result {
thespian::debug::enable(ctx);
return to_result(ctx.spawn_link(
server::init,
[&](auto s) {
if (s == "success") {
result = true;
} else {
auto _ = env.proc("log").send("failed:", s);
}
},
"ip_tcp_client_server", move(env)));
}

122
test/ip_udp_echo.cpp Normal file
View file

@ -0,0 +1,122 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
#include <thespian/timeout.hpp>
#include <thespian/udp.hpp>
#include <netinet/in.h>
using cbor::array;
using cbor::buffer;
using cbor::extract;
using cbor::more;
using std::make_shared;
using std::move;
using std::string;
using std::string_view;
using std::stringstream;
using thespian::context;
using thespian::create_timeout;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::link;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::udp;
using thespian::unexpected;
using namespace std::chrono_literals;
namespace {
struct controller {
port_t server_port{0};
port_t client_port{0};
udp client;
udp server;
size_t ping_count{};
size_t pong_count{};
bool success_{false};
thespian::timeout server_receive_timeout{
create_timeout(500s, array("udp", "server", "timeout"))};
explicit controller()
: client{udp::create("client")}, server{udp::create("server")} {
server_port = server.open(in6addr_loopback, server_port);
client.open(in6addr_loopback, client_port);
auto sent = client.sendto("ping", in6addr_loopback, server_port);
if (sent != 4)
abort();
}
controller(controller &&) = delete;
controller(const controller &) = delete;
~controller() { server_receive_timeout.cancel(); }
auto operator=(controller &&) -> controller & = delete;
auto operator=(const controller &) -> controller & = delete;
auto client_receive(const buffer &m) {
in6_addr remote_ip{};
port_t remote_port{};
if (m("udp", "client", "receive", "pong", extract(remote_ip),
extract(remote_port))) {
check(remote_port == server_port);
client.close();
} else if (m("udp", "client", "closed")) {
return exit("success");
} else {
return unexpected(m);
}
return ok();
}
auto server_receive(const buffer &m) {
in6_addr remote_ip{};
port_t remote_port{};
if (m("udp", "server", "receive", "ping", extract(remote_ip),
extract(remote_port))) {
auto sent = server.sendto("pong", remote_ip, remote_port);
if (sent != 4)
return exit("unexpected_sendto_size", sent);
server.close();
} else if (m("udp", "server", "timeout")) {
return exit("timeout");
} else if (m("udp", "server", "closed")) {
;
} else {
return unexpected(m);
}
return ok();
}
auto receive(const buffer &m) {
if (m("udp", "client", more))
return client_receive(m);
if (m("udp", "server", more))
return server_receive(m);
return unexpected(m);
}
};
} // namespace
auto ip_udp_echo(context &ctx, bool &result, env_t env_) -> ::result {
return to_result(ctx.spawn_link(
[=]() {
link(env().proc("log"));
receive([p{make_shared<controller>()}](auto /*from*/, auto m) {
return p->receive(move(m));
});
return ok();
},
[&](auto s) {
if (s == "success")
result = true;
},
"ip_udp_echo", move(env_)));
}

81
test/metronome_test.cpp Normal file
View file

@ -0,0 +1,81 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include <thespian/instance.hpp>
#include <thespian/metronome.hpp>
#include <chrono>
#include <memory>
#include <string>
using cbor::buffer;
using std::move;
using std::shared_ptr;
using std::chrono::microseconds;
using std::chrono::system_clock;
using thespian::context;
using thespian::create_metronome;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::exit_handler;
using thespian::handle;
using thespian::link;
using thespian::metronome;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::unexpected;
using clk = std::chrono::system_clock;
using namespace std::chrono_literals;
namespace {
auto initA() {
link(env().proc("log"));
struct state_t {
int i{0};
system_clock::time_point start{clk::now()};
metronome met{create_metronome(10ms)};
state_t() { met.start(); }
handle log{env().proc("log")};
auto receive(const buffer &m) {
if (m("tick", i))
++i;
else
return unexpected(m);
{
auto end = clk::now();
auto us = duration_cast<microseconds>(end - start).count();
auto ret = log.send("tick@", us, "µs");
if (not ret)
return ret;
}
if (i == 5) {
auto end = clk::now();
auto us = duration_cast<microseconds>(end - start);
met.stop();
return exit(us > 50000us ? "done" : "failed");
}
return ok();
}
};
shared_ptr<state_t> state{new state_t};
receive([state](auto, auto m) mutable { return state->receive(m); });
return ok();
}
} // namespace
auto metronome_test(context &ctx, bool &result, env_t env) -> ::result {
return to_result(ctx.spawn_link(
initA,
[&](auto s) {
if (s == "done")
result = true;
},
"metronome", move(env)));
}

140
test/perf_cbor.cpp Normal file
View file

@ -0,0 +1,140 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
#include <chrono>
#include <string>
using cbor::A;
using cbor::array;
using cbor::buffer;
using cbor::extract;
using cbor::type;
using std::move;
using std::string_view;
using std::to_string;
using std::chrono::duration_cast;
using std::chrono::microseconds;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::link;
using thespian::ok;
using thespian::result;
using clk = std::chrono::system_clock;
namespace {
auto count{10000UL}; // NOLINT
auto time(const char *name, void (*f)()) -> void {
auto start = clk::now();
for (uint64_t i{0}; i < count; ++i)
f();
auto end = clk::now();
auto us = duration_cast<microseconds>(end - start).count();
auto _ = env().proc("log").send(to_string((count * 1.0L) / us), name, "/µs");
}
const auto ma = array("five", 5, "four", 4, array("three", 3));
auto test() -> result {
link(env().proc("log"));
auto _ = env().proc("log").send("buffer size:", ma.size());
time("noop", []() {});
time("encode_template", []() {
buffer m{array("five", 5, "four", 4, array("three", 3))};
check(m.size() == 21);
});
time("encode_chain", []() {
buffer b;
b.array_header(5)
.push("five")
.push(5)
.push("four")
.push(4)
.array_header(2)
.push("three")
.push(3);
check(b.size() == 21);
});
time("encode_chain_reserve", []() {
buffer b;
b.reserve(21);
b.array_header(5)
.push("five")
.push(5)
.push("four")
.push(4)
.array_header(2)
.push("three")
.push(3);
check(b.size() == 21);
});
time("match", []() {
check(ma(type::string, type::number, type::string, type::number,
type::array));
});
time("match2", []() {
check(ma(type::string, type::number, type::string, type::number,
A(type::string, type::number)));
});
time("extract_int", []() {
int64_t i{};
check(ma(type::string, extract(i), type::more));
check(i == 5);
});
time("extract_string", []() {
string_view s;
check(ma(extract(s), type::more));
});
time("extract_int2", []() {
int64_t i{};
check(ma(type::string, type::number, type::string, type::number,
A(type::string, extract(i))));
check(i == 3);
});
time("extract_string2", []() {
string_view s;
check(ma(type::string, type::number, type::string, type::number,
A(extract(s), type::number)));
});
time("extract_array", []() {
buffer::range r;
check(
ma(type::string, type::number, type::string, type::number, extract(r)));
check(r(type::string, type::number));
});
return ok();
}
} // namespace
auto perf_cbor(context &ctx, bool &result, env_t env) -> ::result {
if (env.is("long_run"))
count = 10000000;
return to_result(ctx.spawn_link(
test,
[&](auto s) {
check(s == "noreceive");
result = true;
},
"perf_cbor", move(env)));
}

153
test/perf_hub.cpp Normal file
View file

@ -0,0 +1,153 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/hub.hpp>
#include <thespian/instance.hpp>
#include <thespian/trace.hpp>
#include <chrono>
#include <set>
#include <sstream>
using cbor::extract;
using std::move;
using std::set;
using std::string;
using std::stringstream;
using std::underlying_type_t;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::handle;
using thespian::hub;
using thespian::link;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::self;
using thespian::spawn_link;
using thespian::unexpected;
using clk = std::chrono::system_clock;
namespace {
enum class tag {
ready = 1,
done = 2,
};
auto tag_to_int(tag e) noexcept -> int {
return static_cast<underlying_type_t<tag>>(e);
}
auto tag_from_int(int i) -> tag {
return static_cast<tag>(static_cast<int>(i));
}
auto subscriber(const hub &h, const handle &controller, const int num,
const int messages) -> result {
auto ret = h.subscribe();
if (not ret)
return ret;
ret = controller.send(tag_to_int(tag::ready), num);
if (not ret)
return ret;
int count{1};
receive([=](auto /*from*/, auto m) mutable {
if (!m("parmA", "parmB", "parmC", count))
std::abort();
if (count == messages) {
auto ret = controller.send(tag_to_int(tag::done), num);
if (not ret)
return ret;
}
++count;
return ok();
});
return ok();
}
auto controller(const int subscriptions, const int messages) -> result {
const auto log = env().proc("log");
const auto verbose = env().is("verbose");
auto ret = hub::create("hub");
if (not ret)
return to_result(ret);
auto h = ret.value();
handle c = self();
auto _ = log.send("subscribing", subscriptions);
set<int> subs;
for (auto i{0}; i < subscriptions; ++i) {
auto ret =
spawn_link([=]() -> result { return subscriber(h, c, i, messages); },
"subscriber");
if (not ret)
return to_result(ret);
subs.insert(i);
}
_ = log.send("all", "spawned");
set<int> not_ready = subs;
set<int> done = subs;
clk::time_point start;
receive([=](auto, auto m) mutable {
int tag_{}, num{};
if (!m(extract(tag_), extract(num)))
return unexpected(m);
tag tag = tag_from_int(tag_);
switch (tag) {
case tag::ready:
not_ready.erase(num);
if (not_ready.empty()) {
if (verbose)
_ = log.send("all", "ready");
for (int i = 1; i <= messages; ++i)
h.broadcast("parmA", "parmB", "parmC", i);
start = clk::now();
if (verbose)
_ = log.send("broadcasting", messages);
}
break;
case tag::done:
done.erase(num);
if (done.empty()) {
if (verbose) {
clk::time_point end = clk::now();
auto ms = duration_cast<milliseconds>(end - start).count();
double msgs = subscriptions * messages + subscriptions;
_ = log.send("all", "done");
_ = log.send("time:", ms, "ms");
_ = log.send(
static_cast<int>((msgs / static_cast<double>(ms)) * 1000),
"msg/s");
}
return h.shutdown();
}
}
return ok();
});
return ok();
}
} // namespace
auto perf_hub(context &ctx, bool &result, env_t env_) -> ::result {
const auto long_run = env_.is("long_run");
const auto subscriptions = long_run ? 1000 : 100;
const auto messages = long_run ? 10000 : 100;
return to_result(ctx.spawn_link(
[=]() {
link(env().proc("log"));
return controller(subscriptions, messages);
},
[&](auto s) {
if (s == "shutdown")
result = true;
},
"controller", move(env_)));
}

79
test/perf_ring.cpp Normal file
View file

@ -0,0 +1,79 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include <thespian/instance.hpp>
#include <string>
using cbor::extract;
using std::move;
using std::string;
using std::string_view;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::handle;
using thespian::link;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::self;
using thespian::spawn_link;
namespace {
auto slave(const handle &last, int n) -> result {
handle next;
if (n)
next = spawn_link([=]() { return slave(last, n - 1); }, "slave").value();
receive([n, next, last](auto, auto m) {
return n ? next.send_raw(move(m)) : last.send_raw(move(m));
});
return ok();
}
auto controller(const int slaves) -> result {
auto verbose = env().is("long_run");
int loop = 10;
handle last = self();
auto ret = spawn_link([=]() { return slave(last, slaves); }, "slave");
if (not ret)
return to_result(ret);
handle first = ret.value();
auto ret2 = first.send("forward");
if (not ret2)
return ret2;
receive([loop, first, verbose](auto, auto m) mutable {
if (loop) {
if (verbose)
auto _ = env().proc("log").send(loop);
--loop;
return first.send_raw(move(m));
}
string_view s;
check(m(extract(s)));
return exit(s);
});
return ok();
}
} // namespace
auto perf_ring(context &ctx, bool &result, env_t env_) -> ::result {
auto long_run = env_.is("long_run");
auto slaves{long_run ? 100000 : 1000};
return to_result(ctx.spawn_link(
[=]() {
link(env().proc("log"));
return controller(slaves);
},
[&](auto s) {
if (s == "forward") {
result = true;
} else {
auto _ = env_.proc("log").send("failed:", s);
}
},
"perf_ring", move(env_)));
}

73
test/perf_spawn.cpp Normal file
View file

@ -0,0 +1,73 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include <thespian/instance.hpp>
#include <string>
using cbor::extract;
using std::move;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::link;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::spawn_link;
using thespian::unexpected;
namespace {
auto slave(const int slaves, int spawned) -> result {
receive([slaves, spawned](auto, auto m) mutable {
int n{0};
if (!m(extract(n)))
return unexpected(m);
if (n) {
++spawned;
return thespian::to_result(
spawn_link([=]() { return slave(slaves, spawned); }, "slave")
.value()
.send(n - 1));
}
if (spawned != slaves) {
auto _ = env().proc("log").send("spawned", spawned, "slaves !=", slaves);
return exit("failed");
}
if (env().is("verbose"))
auto _ = env().proc("log").send("spawned", slaves, "slaves");
return exit("done");
});
return ok();
}
auto controller(const int slaves) -> result {
auto ret = spawn_link([=]() { return slave(slaves, 1); }, "slave");
if (not ret)
return to_result(ret);
auto ret2 = ret.value().send(slaves - 1);
if (not ret2)
return ret2;
receive([](auto, auto) { return exit(); });
return ok();
}
} // namespace
auto perf_spawn(context &ctx, bool &result, env_t env_) -> ::result {
auto slaves{env_.is("long_run") ? 1000000 : 1000};
return to_result((ctx.spawn_link(
[=]() {
link(env().proc("log"));
return controller(slaves);
},
[&](auto s) {
if (s == "done") {
result = true;
} else {
auto _ = env_.proc("log").send(s);
}
},
"perf_spawn", move(env_))));
}

120
test/spawn_exit.cpp Normal file
View file

@ -0,0 +1,120 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
using std::move;
using thespian::behaviour;
using thespian::context;
using thespian::env;
using thespian::env_t;
using thespian::exit;
using thespian::exit_handler;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::self;
using thespian::spawn;
using thespian::spawn_link;
using thespian::to_result;
using thespian::trap;
using thespian::unexpected;
namespace {
auto initE() {
check(env().str("initAsays") == "yes");
check(env().str("initBsays") == "");
check(env().str("initCsays") == "no");
check(env().num("intval") == 42);
check(!env().proc("A").expired());
link(env().proc("A"));
auto ret = env().proc("A").send("shutdown");
if (not ret)
return ret;
receive([](auto, auto) { return ok(); });
return ok();
}
auto initD() {
auto ret = spawn(initE, "E");
if (not ret)
return to_result(ret);
receive([](auto, auto m) {
if (m("die"))
return exit("died");
return unexpected(m);
});
return ok();
}
auto initC() {
env().str("initCsays") = "no";
auto ret = spawn_link(initD, "D").value().send("die");
if (not ret)
return to_result(ret);
trap(true);
receive([](auto /*from*/, auto m) {
if (m("exit", "died"))
return exit();
return unexpected(m);
});
return ok();
}
auto initB() {
env().str("initBsays") = "noyoudont";
receive([](auto from, auto m) {
if (m("shutdown")) {
auto ret = from.send("done");
if (not ret)
return ret;
return exit();
}
return unexpected(m);
});
return ok();
}
auto initA() {
link(env().proc("log"));
env().str("initAsays") = "yes";
env().num("intval") = 42;
env().proc("A") = self();
auto ret = spawn_link(initB, "B").value().send("shutdown");
if (not ret)
return ret;
spawn_link(initC, "C").value();
receive([i{0}](auto, auto m) mutable {
if (m("shutdown") || m("done"))
++i;
else
return unexpected(m);
if (i == 2)
return exit("done");
return ok();
});
return ok();
}
} // namespace
auto spawn_exit(context &ctx, bool &result, env_t env) -> ::result {
behaviour b;
check(!b);
b = []() { return ok(); };
check(!!b);
b = behaviour{};
check(!b);
return to_result(ctx.spawn_link(
initA,
[&](auto s) {
if (s == "done")
result = true;
},
"spawn_exit", move(env)));
}

161
test/tests.cpp Normal file
View file

@ -0,0 +1,161 @@
#include "tests.hpp"
#include <cstring>
#include <thespian/backtrace.h>
#include <thespian/context.hpp>
#include <thespian/debug.hpp>
#include <thespian/env.hpp>
#include <thespian/instance.hpp>
#include <thespian/timeout.hpp>
#include <thespian/trace.hpp>
#include <csignal>
#include <cstdlib>
#include <iostream>
#include <map>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
using cbor::buffer;
using std::cerr;
using std::cout;
using std::lock_guard;
using std::map;
using std::move;
using std::mutex;
using std::ostream;
using std::string;
using thespian::env_t;
using thespian::handle;
using thespian::ok;
namespace {
using namespace std::chrono_literals;
auto operator<<(ostream &s, const cbor::buffer::value_accessor::unvisitable_type
& /*unused*/) -> ostream & {
return s << "unvisitable_type";
}
struct logger {
thespian::timeout t{thespian::create_timeout(60s, cbor::array("timeout"))};
const char *name;
mutex &trace_m;
bool verbose;
explicit logger(const char *name, mutex &trace_m, bool verbose)
: name(name), trace_m(trace_m), verbose(verbose) {}
auto receive(const buffer &m) {
if (verbose) {
std::lock_guard<mutex> lock(trace_m);
cout << name << ": ";
auto dump = [&](auto val) { cout << val << " "; };
for (const auto val : m)
val.visit(dump);
cout << '\n';
}
return ok();
}
static auto start(thespian::context &ctx, const char *name, mutex &trace_m,
bool verbose, env_t env) -> handle {
return ctx
.spawn(
[name, &trace_m, verbose]() {
thespian::receive(
[p{make_shared<logger>(name, trace_m, verbose)}](
auto, auto m) { return p->receive(move(m)); });
return ok();
},
string("logger_") + name, move(env))
.value();
}
}; // namespace
} // namespace
extern "C" auto runtestcase(const char *name) -> int {
mutex trace_m;
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) // NOLINT
abort();
auto gdb = getenv("JITDEBUG"); // NOLINT
if (gdb) {
if (strcmp(gdb, "on") != 0)
install_debugger();
else if (strcmp(gdb, "backtrace") != 0)
install_backtrace();
}
auto ctx = thespian::context::create();
map<string, testcase *> tests;
tests["cbor_match"] = cbor_match;
tests["debug"] = debug;
tests["endpoint_unx"] = endpoint_unx;
tests["endpoint_tcp"] = endpoint_tcp;
tests["hub_filter"] = hub_filter;
tests["ip_tcp_client_server"] = ip_tcp_client_server;
tests["ip_udp_echo"] = ip_udp_echo;
tests["metronome_test"] = metronome_test;
tests["perf_cbor"] = perf_cbor;
tests["perf_hub"] = perf_hub;
tests["perf_ring"] = perf_ring;
tests["perf_spawn"] = perf_spawn;
tests["spawn_exit"] = spawn_exit;
tests["timeout_test"] = timeout_test;
env_t env{};
env_t log_env{};
auto trace = [&](const buffer &buf) {
lock_guard<mutex> lock(trace_m);
cout << buf.to_json() << '\n';
};
log_env.on_trace(trace);
env.on_trace(trace);
if (getenv("TRACE")) { // NOLINT
thespian::debug::enable(*ctx);
env.enable_all_channels();
log_env.enable_all_channels();
}
if (getenv("TRACE_LIFETIME")) { // NOLINT
thespian::debug::enable(*ctx);
env.enable(thespian::channel::lifetime);
log_env.enable(thespian::channel::lifetime);
}
if (getenv("TRACE_EXECUTE")) { // NOLINT
env.enable(thespian::channel::execute);
}
if (getenv("TRACE_SEND")) { // NOLINT
env.enable(thespian::channel::send);
}
if (getenv("TRACE_RECEIVE")) { // NOLINT
env.enable(thespian::channel::receive);
}
bool long_run = getenv("LONGRUN"); // NOLINT
bool verbose = getenv("VERBOSE"); // NOLINT
env.is("long_run") = long_run;
env.is("verbose") = verbose;
if (verbose)
cout << '\n';
env.proc("log") = logger::start(*ctx, name, trace_m, verbose, move(log_env));
auto test = tests.find(name);
if (test == tests.end()) {
cerr << "invalid testcase: " << name << '\n';
return 1;
}
bool result{false};
test->second(*ctx, result, move(env));
ctx->run();
return result ? 0 : 1;
}

2
test/tests.h Normal file
View file

@ -0,0 +1,2 @@
int runtestcase(const char *name); // NOLINT

30
test/tests.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <thespian/context.hpp>
#include <thespian/env.hpp>
#include <cstdlib>
#include <string_view>
constexpr auto check(bool expression) -> void {
if (!expression)
std::abort();
}
using testcase = auto(thespian::context &ctx, bool &result, thespian::env_t env)
-> thespian::result;
testcase cbor_match;
testcase debug;
testcase endpoint_tcp;
testcase endpoint_unx;
testcase hub_filter;
testcase ip_tcp_client_server;
testcase ip_udp_echo;
testcase metronome_test;
testcase perf_cbor;
testcase perf_hub;
testcase perf_ring;
testcase perf_spawn;
testcase spawn_exit;
testcase timeout_test;

8
test/tests.zig Normal file
View file

@ -0,0 +1,8 @@
const std = @import("std");
pub const cpp = @import("tests_cpp.zig");
pub const cbor = @import("tests_cbor.zig");
pub const thespian = @import("tests_thespian.zig");
test {
std.testing.refAllDecls(@This());
}

435
test/tests_cbor.zig Normal file
View file

@ -0,0 +1,435 @@
const std = @import("std");
const cbor_mod = @import("cbor");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualDeep = std.testing.expectEqualDeep;
const expectError = std.testing.expectError;
const fmt = cbor_mod.fmt;
const toJson = cbor_mod.toJson;
const toJsonPretty = cbor_mod.toJsonPretty;
const fromJson = cbor_mod.fromJson;
const decodeType = cbor_mod.decodeType;
const matchInt = cbor_mod.matchInt;
const matchIntValue = cbor_mod.matchIntValue;
const matchValue = cbor_mod.matchValue;
const match = cbor_mod.match;
const isNull = cbor_mod.isNull;
const writeArrayHeader = cbor_mod.writeArrayHeader;
const writeMapHeader = cbor_mod.writeMapHeader;
const writeValue = cbor_mod.writeValue;
const extract = cbor_mod.extract;
const extract_cbor = cbor_mod.extract_cbor;
const more = cbor_mod.more;
const any = cbor_mod.any;
const string = cbor_mod.string;
const number = cbor_mod.number;
const array = cbor_mod.array;
const map = cbor_mod.map;
test "cbor simple" {
var buf: [128]u8 = undefined;
try expectEqualDeep(
fmt(&buf, .{ "five", 5, "four", 4, .{ "three", 3 } }),
&[_]u8{ 0x85, 0x64, 0x66, 0x69, 0x76, 0x65, 0x05, 0x64, 0x66, 0x6f, 0x75, 0x72, 0x04, 0x82, 0x65, 0x74, 0x68, 0x72, 0x65, 0x65, 0x03 },
);
}
test "cbor exit message" {
var buf: [128]u8 = undefined;
try expectEqualDeep(
fmt(&buf, .{ "exit", "normal" }),
&[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x66, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C },
);
}
test "cbor error.OutOfMemory" {
var buf: [128]u8 = undefined;
try expectEqualDeep(
fmt(&buf, .{ "exit", error.OutOfMemory }),
&[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x71, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x2E, 0x4F, 0x75, 0x74, 0x4F, 0x66, 0x4D, 0x65, 0x6D, 0x6F, 0x72, 0x79 },
);
}
test "cbor error.OutOfMemory json" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const cbor = fmt(&buf, .{ "exit", error.OutOfMemory });
try expectEqualDeep(toJson(cbor, &json_buf),
\\["exit","error.OutOfMemory"]
);
}
test "cbor.decodeType2" {
var buf = [_]u8{ 0x20, 0xDF };
var iter: []const u8 = &buf;
const t = try decodeType(&iter);
try expectEqual(t.major, 1);
try expectEqual(t.minor, 0);
try expectEqual(iter[0], 0xDF);
}
test "cbor.decodeType3" {
var buf = [_]u8{ 0x03, 0xDF };
var iter: []const u8 = &buf;
const t = try decodeType(&iter);
try expectEqual(t.major, 0);
try expectEqual(t.minor, 3);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 small" {
var buf = [_]u8{ 1, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 1);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 1byte" {
var buf = [_]u8{ 0x18, 0x1A, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 26);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 2byte" {
var buf = [_]u8{ 0x19, 0x01, 0x07, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 263);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 8byte" {
var buf = [_]u8{ 0x1B, 0x00, 0x00, 0xEF, 0x6F, 0xC1, 0x4A, 0x0A, 0x1F, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 263263263263263);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 error.CborIntegerTooLarge" {
var buf = [_]u8{ 0x1B, 0xA9, 0x0A, 0xDE, 0x0D, 0x4E, 0x2B, 0x8A, 0x1F, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
const result = matchInt(i64, &iter, &val);
try expectError(error.CborIntegerTooLarge, result);
}
test "cbor.matchI64 error.CborTooShort" {
var buf = [_]u8{ 0x19, 0x01 };
var iter: []const u8 = &buf;
var val: i64 = 0;
const result = matchInt(i64, &iter, &val);
try expectError(error.CborTooShort, result);
}
test "cbor.matchI64Value" {
var buf = [_]u8{ 7, 0xDF };
var iter: []const u8 = &buf;
try expect(try matchIntValue(i64, &iter, 7));
}
test "cbor.matchValue(i64)" {
var buf: [128]u8 = undefined;
var iter = fmt(&buf, 7);
try expect(try matchValue(&iter, 7));
}
test "cbor.matchValue(i64) multi" {
var buf: [128]u8 = undefined;
const iter = fmt(&buf, 7);
const iter2 = fmt(buf[iter.len..], 8);
var iter3 = buf[0 .. iter.len + iter2.len];
try expect(try matchValue(&iter3, 7));
try expect(try matchValue(&iter3, 8));
}
test "cbor.match(.{i64...})" {
var buf: [128]u8 = undefined;
const v = .{ 5, 4, 3, 123456, 234567890 };
const m = fmt(&buf, v);
try expect(try match(m, v));
}
test "cbor.match(.{i64... more})" {
var buf: [128]u8 = undefined;
const v = .{ 5, 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 };
const m = fmt(&buf, v);
try expect(try match(m, .{ 5, 4, 3, more }));
}
test "cbor.match(.{any, i64... more})" {
var buf: [128]u8 = undefined;
const v = .{ "cbor", 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 };
const m = fmt(&buf, v);
try expect(try match(m, .{ any, 4, 3, more }));
}
test "cbor.match(.{types...})" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ string, number, string, number, array }));
try expect(!try match(m, .{ string, number, string, number }));
try expect(!try match(m, .{ number, string, number, array, any }));
try expect(try match(m, .{ any, number, string, number, any }));
try expect(try match(m, .{ "five", number, string, 4, any }));
try expect(try match(m, .{ "five", 5, "four", 4, array }));
try expect(try match(m, .{ "five", 5, more }));
try expect(try match(m, .{ "five", 5, "four", 4, array, more }));
try expect(!try match(m, .{ "five", 5, "four", 4, array, any }));
try expect(!try match(m, .{ "four", 5, "five", 4, array, any }));
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } }));
}
test "cbor.nulls" {
var buf: [128]u8 = undefined;
try expect(isNull(fmt(&buf, .{})));
try expect(!isNull(fmt(&buf, .{1})));
}
test "cbor.stream_writer" {
var buf: [128]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer();
try writeArrayHeader(writer, 5);
try writeValue(writer, "five");
try writeValue(writer, 5);
try writeValue(writer, "four");
try writeValue(writer, 4);
try writeArrayHeader(writer, 2);
try writeValue(writer, "three");
try writeValue(writer, 3);
try expect(try match(stream.getWritten(), .{ "five", 5, "four", 4, .{ "three", 3 } }));
}
test "cbor.stream_object_writer" {
var buf: [128]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer();
try writeMapHeader(writer, 3);
try writeValue(writer, "five");
try writeValue(writer, 5);
try writeValue(writer, "four");
try writeValue(writer, 4);
try writeValue(writer, "three");
try writeValue(writer, 3);
const obj = stream.getWritten();
var json_buf: [128]u8 = undefined;
const json = try toJson(obj, &json_buf);
try expectEqualDeep(json,
\\{"five":5,"four":4,"three":3}
);
}
test "cbor.match_bool" {
var buf: [128]u8 = undefined;
const m = fmt(&buf, .{ false, true, 5, "five" });
try expect(try match(m, .{ false, true, 5, "five" }));
try expect(!try match(m, .{ true, false, 5, "five" }));
}
test "cbor.extract_match" {
var buf: [128]u8 = undefined;
var t: bool = false;
const extractor = extract(&t);
var iter = fmt(&buf, true);
try expect(try extractor.extract(&iter));
try expect(t);
iter = fmt(&buf, false);
try expect(try extractor.extract(&iter));
try expect(!t);
const m = fmt(&buf, .{ false, true, 5, "five" });
try expect(try match(m, .{ extract(&t), true, 5, "five" }));
try expect(!t);
try expect(try match(m, .{ false, extract(&t), 5, "five" }));
try expect(t);
var i: i64 = undefined;
try expect(try match(m, .{ false, true, extract(&i), "five" }));
try expect(i == 5);
var u: u64 = undefined;
try expect(try match(m, .{ false, true, extract(&u), "five" }));
try expect(u == 5);
var s: []const u8 = undefined;
try expect(try match(m, .{ false, true, 5, extract(&s) }));
try expect(std.mem.eql(u8, "five", s));
}
test "cbor.extract_cbor" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } }));
var sub: []const u8 = undefined;
try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&sub) }));
try expect(try match(sub, .{ "three", 3 }));
}
test "cbor.extract_nested" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
const m = fmt(&buf, v);
var u: u64 = undefined;
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", extract(&u) } }));
try expect(u == 3);
}
test "cbor.match_map" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ "five", 5, "four", 4, map }));
}
test "cbor.extract_map_cbor" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
var map_cbor: []const u8 = undefined;
try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&map_cbor) }));
var json_buf: [256]u8 = undefined;
const json = try toJson(map_cbor, &json_buf);
try expectEqualDeep(json,
\\{"three":3}
);
}
test "cbor.extract_map" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
var obj = std.json.ObjectMap.init(std.testing.allocator);
defer obj.deinit();
try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) }));
try expect(obj.contains("three"));
try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 });
}
test "cbor.extract_map_map" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3, .child = .{ .two = 2, .one = .{ 1, 2, 3, true, false, null } } } };
const m = fmt(&buf, v);
var a = std.heap.ArenaAllocator.init(std.testing.allocator);
defer a.deinit();
var obj = std.json.ObjectMap.init(a.allocator());
defer obj.deinit();
try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) }));
try expect(obj.contains("three"));
try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 });
var child = obj.get("child").?.object;
try expectEqual(child.get("two"), std.json.Value{ .integer = 2 });
var json_buf: [256]u8 = undefined;
const json = try toJson(m, &json_buf);
try expectEqualDeep(json,
\\["five",5,"four",4,{"three":3,"child":{"two":2,"one":[1,2,3,true,false,null]}}]
);
}
test "cbor.extract_value" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
var value = std.json.Value{ .null = {} };
var value_int = std.json.Value{ .null = {} };
try expect(try match(m, .{ "five", 5, extract(&value), extract(&value_int), any }));
try expectEqualDeep(value.string, "four");
try expectEqualDeep(value_int.integer, 4);
}
test "cbor.match_more_nested" {
var buf: [128]u8 = undefined;
const v = .{ .{124}, .{ "three", 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ .{more}, .{more} }));
}
test "cbor.extract_number_limits" {
var buf: [128]u8 = undefined;
const bigint: u64 = 18446744073709551615;
var u: u64 = undefined;
var i: i64 = undefined;
const m = fmt(&buf, bigint);
try expect(try match(m, extract(&u)));
try expectError(error.CborIntegerTooLarge, match(m, extract(&i)));
}
test "cbor.toJson" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const a = fmt(&buf, .{ "string\r\n", "\tstring\t", "\r\nstring" });
const json = try toJson(a, &json_buf);
try expect(try match(a, .{ "string\r\n", "\tstring\t", "\r\nstring" }));
try expectEqualDeep(json,
\\["string\r\n","\tstring\t","\r\nstring"]
);
}
test "cbor.toJson_object" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 });
const json = try toJson(a, &json_buf);
try expectEqualDeep(json,
\\{"five":5,"four":4,"three":3}
);
}
test "cbor.toJsonPretty_object" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 });
const json = try toJsonPretty(a, &json_buf);
try expectEqualDeep(json,
\\{
\\ "five": 5,
\\ "four": 4,
\\ "three": 3
\\}
);
}
test "cbor.fromJson_small" {
var cbor_buf: [128]u8 = undefined;
const json_buf: []const u8 =
\\[12345]
;
const cbor = try fromJson(json_buf, &cbor_buf);
try expect(try match(cbor, .{12345}));
}
test "cbor.fromJson" {
var cbor_buf: [128]u8 = undefined;
const json_buf: []const u8 =
\\["string\r\n","\tstring\t","\r\nstring",12345]
;
const cbor = try fromJson(json_buf, &cbor_buf);
try expect(try match(cbor, .{ "string\r\n", "\tstring\t", "\r\nstring", 12345 }));
}
test "cbor.fromJson_object" {
var cbor_buf: [128]u8 = undefined;
const json_buf: []const u8 =
\\{"five":5,"four":4,"three":3}
;
const cbor = try fromJson(json_buf, &cbor_buf);
try expect(try match(cbor, map));
}

65
test/tests_cpp.zig Normal file
View file

@ -0,0 +1,65 @@
const std = @import("std");
const c = @cImport({
@cInclude("tests.h");
});
fn testcase(name: [*c]const u8) !void {
const result = c.runtestcase(name);
try std.testing.expectEqual(@as(c_int, 0), result);
}
test "cbor_match" {
try testcase("cbor_match");
}
test "debug" {
try testcase("debug");
}
test "endpoint_unx" {
try testcase("endpoint_unx");
}
test "endpoint_tcp" {
try testcase("endpoint_tcp");
}
test "hub_filter" {
try testcase("hub_filter");
}
test "ip_tcp_client_server" {
try testcase("ip_tcp_client_server");
}
test "ip_udp_echo" {
try testcase("ip_udp_echo");
}
test "metronome_test" {
try testcase("metronome_test");
}
test "perf_cbor" {
try testcase("perf_cbor");
}
test "perf_hub" {
try testcase("perf_hub");
}
test "perf_ring" {
try testcase("perf_ring");
}
test "perf_spawn" {
try testcase("perf_spawn");
}
test "spawn_exit" {
try testcase("spawn_exit");
}
test "timeout_test" {
try testcase("timeout_test");
}

14
test/tests_thespian.zig Normal file
View file

@ -0,0 +1,14 @@
const std = @import("std");
const thespian = @import("thespian");
const cbor = @import("cbor");
pub const unexpected = thespian.unexpected;
const message = thespian.message;
const error_message = thespian.error_message;
test "thespian.unexpected" {
var buf: [512]u8 = undefined;
try std.testing.expectEqual(error.Exit, unexpected(message.fmt(.{"TEST"})));
const json = try cbor.toJson(error_message(), &buf);
try std.testing.expectEqualStrings("[\"exit\",\"UNEXPECTED_MESSAGE: [\\\"TEST\\\"]\"]", json);
}

64
test/timeout_test.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
#include <thespian/timeout.hpp>
#include <chrono>
#include <memory>
#include <string>
using cbor::array;
using cbor::buffer;
using std::move;
using std::shared_ptr;
using std::chrono::microseconds;
using std::chrono::system_clock;
using thespian::context;
using thespian::create_timeout;
using thespian::env_t;
using thespian::exit;
using thespian::exit_handler;
using thespian::ok;
using thespian::receive;
using thespian::result;
using thespian::timeout;
using thespian::unexpected;
using clk = std::chrono::system_clock;
using namespace std::chrono_literals;
namespace {
auto initA() -> result {
thespian::link(thespian::env().proc("log"));
struct state_t {
system_clock::time_point start{clk::now()};
timeout t{create_timeout(200ms, array("timeout"))};
auto receive(const buffer &m) {
if (!m("timeout"))
return unexpected(m);
auto end = clk::now();
auto us = duration_cast<microseconds>(end - start);
return exit(us > 200ms ? "done" : "failed");
}
};
shared_ptr<state_t> state{new state_t};
receive([state](auto, auto m) mutable { return state->receive(m); });
return ok();
}
} // namespace
auto timeout_test(context &ctx, bool &result, env_t env) -> ::result {
return to_result(ctx.spawn_link(
initA,
[&](auto s) {
if (s == "done")
result = true;
},
"timeout", move(env)));
}