400 lines
9.2 KiB
C++
400 lines
9.2 KiB
C++
#define CATCH_CONFIG_MAIN
|
|
|
|
#include "catch2/catch.hpp"
|
|
|
|
#include "Arduino.h"
|
|
|
|
#include "catch_utils.hpp"
|
|
|
|
#include "boost/asynctcp.hpp"
|
|
|
|
WiFiClass WiFi;
|
|
ESPClass ESP;
|
|
|
|
#include "painlessMeshConnection.h"
|
|
|
|
#include "painlessmesh/mesh.hpp"
|
|
|
|
using PMesh = painlessmesh::Mesh<MeshConnection>;
|
|
|
|
using namespace painlessmesh;
|
|
painlessmesh::logger::LogClass Log;
|
|
|
|
class MeshTest : public PMesh {
|
|
public:
|
|
MeshTest(Scheduler *scheduler, size_t id, boost::asio::io_service &io)
|
|
: io_service(io) {
|
|
this->nodeId = id;
|
|
this->init(scheduler, this->nodeId);
|
|
timeOffset = runif(0, 1e09);
|
|
pServer = std::make_shared<AsyncServer>(io_service, this->nodeId);
|
|
painlessmesh::tcp::initServer<MeshConnection, PMesh>(*pServer, (*this));
|
|
}
|
|
|
|
void connect(MeshTest &mesh) {
|
|
auto pClient = new AsyncClient(io_service);
|
|
painlessmesh::tcp::connect<MeshConnection, PMesh>(
|
|
(*pClient), boost::asio::ip::address::from_string("127.0.0.1"),
|
|
mesh.nodeId, (*this));
|
|
}
|
|
|
|
std::shared_ptr<AsyncServer> pServer;
|
|
boost::asio::io_service &io_service;
|
|
};
|
|
|
|
class Nodes {
|
|
public:
|
|
Nodes(Scheduler *scheduler, size_t n, boost::asio::io_service &io)
|
|
: io_service(io) {
|
|
for (size_t i = 0; i < n; ++i) {
|
|
auto m = std::make_shared<MeshTest>(scheduler, i + baseID, io_service);
|
|
if (i > 0) m->connect((*nodes[runif(0, i - 1)]));
|
|
nodes.push_back(m);
|
|
}
|
|
}
|
|
void update() {
|
|
for (auto &&m : nodes) {
|
|
m->update();
|
|
io_service.poll();
|
|
}
|
|
}
|
|
|
|
void stop() {
|
|
for (auto &&m : nodes) m->stop();
|
|
}
|
|
|
|
auto size() { return nodes.size(); }
|
|
|
|
std::shared_ptr<MeshTest> get(size_t nodeId) {
|
|
return nodes[nodeId - baseID];
|
|
}
|
|
|
|
size_t baseID = 6481;
|
|
std::vector<std::shared_ptr<MeshTest>> nodes;
|
|
boost::asio::io_service &io_service;
|
|
};
|
|
|
|
SCENARIO("We can setup and connect two meshes over localport") {
|
|
using namespace logger;
|
|
Scheduler scheduler;
|
|
Log.setLogLevel(ERROR);
|
|
boost::asio::io_service io_service;
|
|
|
|
PMesh mesh1;
|
|
mesh1.init(&scheduler, 6841);
|
|
std::shared_ptr<AsyncServer> pServer;
|
|
pServer = std::make_shared<AsyncServer>(io_service, 6841);
|
|
painlessmesh::tcp::initServer<MeshConnection, PMesh>(*pServer, mesh1);
|
|
|
|
PMesh mesh2;
|
|
mesh2.init(&scheduler, 6842);
|
|
auto pClient = new AsyncClient(io_service);
|
|
painlessmesh::tcp::connect<MeshConnection, PMesh>(
|
|
(*pClient), boost::asio::ip::address::from_string("127.0.0.1"), 6841,
|
|
mesh2);
|
|
|
|
for (auto i = 0; i < 100; ++i) {
|
|
mesh1.update();
|
|
mesh2.update();
|
|
io_service.poll();
|
|
}
|
|
|
|
REQUIRE(layout::size(mesh1.asNodeTree()) == 2);
|
|
REQUIRE(layout::size(mesh2.asNodeTree()) == 2);
|
|
}
|
|
|
|
SCENARIO("The MeshTest class works correctly") {
|
|
using namespace logger;
|
|
Scheduler scheduler;
|
|
Log.setLogLevel(ERROR);
|
|
boost::asio::io_service io_service;
|
|
|
|
MeshTest mesh1(&scheduler, 6841, io_service);
|
|
MeshTest mesh2(&scheduler, 6842, io_service);
|
|
mesh2.connect(mesh1);
|
|
|
|
for (auto i = 0; i < 100; ++i) {
|
|
mesh1.update();
|
|
mesh2.update();
|
|
io_service.poll();
|
|
}
|
|
|
|
REQUIRE(layout::size(mesh1.asNodeTree()) == 2);
|
|
REQUIRE(layout::size(mesh2.asNodeTree()) == 2);
|
|
}
|
|
|
|
SCENARIO("We can send a message using our Nodes class") {
|
|
delay(1000);
|
|
using namespace logger;
|
|
Log.setLogLevel(ERROR);
|
|
|
|
Scheduler scheduler;
|
|
boost::asio::io_service io_service;
|
|
Nodes n(&scheduler, 12, io_service);
|
|
|
|
for (auto i = 0; i < 1000; ++i) {
|
|
n.update();
|
|
delay(10);
|
|
}
|
|
|
|
REQUIRE(layout::size(n.nodes[0]->asNodeTree()) == 12);
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
std::string z;
|
|
n.nodes[0]->onReceive([&x, &y, &z](auto id, auto msg) {
|
|
++x;
|
|
y = id;
|
|
z = msg;
|
|
});
|
|
n.nodes[10]->sendSingle(n.nodes[2]->getNodeId(), "Blaat");
|
|
for (auto i = 0; i < 1000; ++i) n.update();
|
|
REQUIRE(x == 0);
|
|
REQUIRE(y == 0);
|
|
REQUIRE(z == "");
|
|
|
|
n.nodes[10]->sendSingle(n.nodes[0]->getNodeId(), "Blaat");
|
|
for (auto i = 0; i < 1000; ++i) n.update();
|
|
REQUIRE(z == "Blaat");
|
|
REQUIRE(x == 1);
|
|
REQUIRE(y == n.nodes[10]->getNodeId());
|
|
|
|
n.nodes[5]->onReceive([&x, &y, &z](auto id, auto msg) {
|
|
++x;
|
|
y = id;
|
|
z = msg;
|
|
});
|
|
n.nodes[10]->sendBroadcast("Blargh");
|
|
for (auto i = 0; i < 10000; ++i) n.update();
|
|
REQUIRE(z == "Blargh");
|
|
REQUIRE(x == 3);
|
|
REQUIRE(y == n.nodes[10]->getNodeId());
|
|
n.stop();
|
|
}
|
|
|
|
SCENARIO("Time sync works") {
|
|
using namespace logger;
|
|
Log.setLogLevel(ERROR);
|
|
|
|
Scheduler scheduler;
|
|
boost::asio::io_service io_service;
|
|
auto dim = runif(8, 15);
|
|
Nodes n(&scheduler, dim, io_service);
|
|
|
|
int diff = 0;
|
|
for (size_t i = 0; i < n.size() - 1; ++i) {
|
|
diff += std::abs((int)n.nodes[0]->getNodeTime() -
|
|
(int)n.nodes[i + 1]->getNodeTime());
|
|
}
|
|
REQUIRE(diff / n.size() > 10000);
|
|
|
|
for (auto i = 0; i < 10000; ++i) {
|
|
n.update();
|
|
delay(10);
|
|
}
|
|
diff = 0;
|
|
for (size_t i = 0; i < n.size() - 1; ++i) {
|
|
diff += std::abs((int)n.nodes[0]->getNodeTime() -
|
|
(int)n.nodes[i + 1]->getNodeTime());
|
|
}
|
|
REQUIRE(diff / n.size() < 10000);
|
|
n.stop();
|
|
}
|
|
|
|
SCENARIO("Rooting works") {
|
|
using namespace logger;
|
|
Log.setLogLevel(ERROR);
|
|
|
|
Scheduler scheduler;
|
|
boost::asio::io_service io_service;
|
|
auto dim = runif(8, 15);
|
|
Nodes n(&scheduler, dim, io_service);
|
|
|
|
n.nodes[5]->setRoot(true);
|
|
REQUIRE(n.nodes[5]->isRoot());
|
|
REQUIRE(layout::isRooted(n.nodes[5]->asNodeTree()));
|
|
|
|
for (auto i = 0; i < 10000; ++i) {
|
|
n.update();
|
|
delay(10);
|
|
}
|
|
|
|
for (auto &&node : n.nodes) {
|
|
REQUIRE(layout::isRooted(node->asNodeTree()));
|
|
if (n.nodes[5]->getNodeId() == node->getNodeId()) {
|
|
REQUIRE(node->isRoot());
|
|
} else {
|
|
REQUIRE(!node->isRoot());
|
|
}
|
|
}
|
|
|
|
n.stop();
|
|
}
|
|
|
|
SCENARIO("Network loops are detected") {
|
|
using namespace logger;
|
|
Log.setLogLevel(ERROR);
|
|
|
|
Scheduler scheduler;
|
|
boost::asio::io_service io_service;
|
|
MeshTest mesh1(&scheduler, 6841, io_service);
|
|
MeshTest mesh2(&scheduler, 6842, io_service);
|
|
MeshTest mesh3(&scheduler, 6843, io_service);
|
|
MeshTest mesh4(&scheduler, 6844, io_service);
|
|
MeshTest mesh5(&scheduler, 6845, io_service);
|
|
|
|
mesh1.connect(mesh5);
|
|
mesh2.connect(mesh1);
|
|
mesh3.connect(mesh2);
|
|
mesh4.connect(mesh3);
|
|
mesh5.connect(mesh4);
|
|
|
|
for (auto i = 0; i < 10000; ++i) {
|
|
mesh1.update();
|
|
io_service.poll();
|
|
mesh2.update();
|
|
io_service.poll();
|
|
mesh3.update();
|
|
io_service.poll();
|
|
mesh4.update();
|
|
io_service.poll();
|
|
mesh5.update();
|
|
io_service.poll();
|
|
delay(10);
|
|
}
|
|
|
|
// Looped network, should break up so it can reform
|
|
REQUIRE(layout::size(mesh1.asNodeTree()) < 5);
|
|
REQUIRE(layout::size(mesh2.asNodeTree()) < 5);
|
|
REQUIRE(layout::size(mesh3.asNodeTree()) < 5);
|
|
REQUIRE(layout::size(mesh4.asNodeTree()) < 5);
|
|
REQUIRE(layout::size(mesh5.asNodeTree()) < 5);
|
|
|
|
mesh1.stop();
|
|
mesh2.stop();
|
|
mesh3.stop();
|
|
mesh4.stop();
|
|
mesh5.stop();
|
|
}
|
|
|
|
SCENARIO("Disconnects are detected and forwarded") {
|
|
using namespace logger;
|
|
Log.setLogLevel(ERROR);
|
|
|
|
Scheduler scheduler;
|
|
boost::asio::io_service io_service;
|
|
auto dim = runif(10, 15);
|
|
Nodes n(&scheduler, dim, io_service);
|
|
|
|
// Dummy task. This can catch mistaken use of the scheduler
|
|
Task dummyT;
|
|
int y = 0;
|
|
dummyT.set(TASK_MILLISECOND, TASK_FOREVER, [&y]() { ++y; });
|
|
scheduler.addTask(dummyT);
|
|
dummyT.enable();
|
|
|
|
for (auto i = 0; i < 1000; ++i) {
|
|
n.update();
|
|
delay(10);
|
|
}
|
|
|
|
for (auto &&node : n.nodes) {
|
|
REQUIRE(layout::size(node->asNodeTree()) == dim);
|
|
}
|
|
|
|
int x = 0;
|
|
n.nodes[5]->onChangedConnections([&x]() { ++x; });
|
|
|
|
n.nodes[5]->onDroppedConnection([&x](auto nodeId) { ++x; });
|
|
|
|
n.nodes[dim - 1]->onChangedConnections([&x]() { ++x; });
|
|
|
|
auto no = n.nodes[5]->subs.size();
|
|
REQUIRE(no > 0);
|
|
|
|
auto ptr = (*n.nodes[5]->subs.begin());
|
|
|
|
(*n.nodes[5]->subs.begin())->close();
|
|
for (auto i = 0; i < 1000; ++i) {
|
|
n.update();
|
|
delay(10);
|
|
}
|
|
|
|
REQUIRE(n.nodes[5]->subs.size() == no - 1);
|
|
REQUIRE(ptr.use_count() == 1);
|
|
ptr = NULL;
|
|
|
|
REQUIRE(x == 3);
|
|
|
|
for (auto &&node : n.nodes) {
|
|
REQUIRE(layout::size(node->asNodeTree()) < dim);
|
|
}
|
|
|
|
n.stop();
|
|
REQUIRE(y > 0);
|
|
}
|
|
|
|
SCENARIO("Disconnects don't lead to crashes") {
|
|
using namespace logger;
|
|
Log.setLogLevel(ERROR);
|
|
|
|
Scheduler scheduler;
|
|
boost::asio::io_service io_service;
|
|
auto dim = runif(10, 15);
|
|
Nodes n(&scheduler, dim, io_service);
|
|
|
|
// Dummy task. This can catch mistaken use of the scheduler
|
|
Task dummyT;
|
|
int y = 0;
|
|
dummyT.set(TASK_MILLISECOND, TASK_FOREVER, [&y]() { ++y; });
|
|
scheduler.addTask(dummyT);
|
|
dummyT.enable();
|
|
|
|
for (auto i = 0; i < 1000; ++i) {
|
|
n.update();
|
|
delay(10);
|
|
}
|
|
|
|
for (auto &&node : n.nodes) {
|
|
REQUIRE(layout::size(node->asNodeTree()) == dim);
|
|
}
|
|
|
|
int x = 0;
|
|
n.nodes[5]->onChangedConnections([&x]() { ++x; });
|
|
|
|
n.nodes[5]->onDroppedConnection([&x](auto nodeId) { ++x; });
|
|
|
|
n.nodes[dim - 1]->onChangedConnections([&x]() { ++x; });
|
|
|
|
auto no = n.nodes[5]->subs.size();
|
|
REQUIRE(no > 0);
|
|
|
|
auto ptr = (*n.nodes[5]->subs.begin());
|
|
|
|
(*n.nodes[5]->subs.begin())->close();
|
|
for (auto i = 0; i < 10; ++i) {
|
|
io_service.poll();
|
|
}
|
|
n.update();
|
|
for (auto i = 0; i < 10; ++i) {
|
|
io_service.poll();
|
|
}
|
|
for (auto i = 0; i < 1000; ++i) {
|
|
n.update();
|
|
delay(10);
|
|
}
|
|
|
|
REQUIRE(n.nodes[5]->subs.size() == no - 1);
|
|
REQUIRE(ptr.use_count() == 1);
|
|
ptr = NULL;
|
|
|
|
REQUIRE(x == 3);
|
|
|
|
for (auto &&node : n.nodes) {
|
|
REQUIRE(layout::size(node->asNodeTree()) < dim);
|
|
}
|
|
|
|
n.stop();
|
|
REQUIRE(y > 0);
|
|
}
|