// Тесты платформенного слоя (POSIX-путь; на ESP-IDF тесты не запускаются — // верификация IDF-слоя сборкой и on-device приёмкой). #include "doctest/doctest.h" #include #include #include #include "ayla/platform/platform.hpp" TEST_CASE("now_ms монотонен") { uint64_t a = fgl::plat::now_ms(); fgl::plat::sleep_ms(30); uint64_t b = fgl::plat::now_ms(); CHECK(b >= a + 25); CHECK(b - a < 1000); } TEST_CASE("random заполняет и не повторяется тривиально") { uint8_t a[16] = {}, b[16] = {}; REQUIRE(fgl::plat::random(a, sizeof(a))); REQUIRE(fgl::plat::random(b, sizeof(b))); CHECK_FALSE(memcmp(a, b, sizeof(a)) == 0); bool all_zero = true; for (uint8_t x : a) all_zero = all_zero && (x == 0); CHECK_FALSE(all_zero); } TEST_CASE("thread_create + thread_join: поток завершён после join") { struct Ctx { std::atomic ran{false}; std::atomic finished{false}; }; Ctx c; fgl::plat::ThreadId tid = nullptr; REQUIRE(fgl::plat::thread_create( [](void* p) { auto* x = static_cast(p); x->ran.store(true); fgl::plat::sleep_ms(100); x->finished.store(true); }, &c, "t_join", 0, &tid)); REQUIRE(tid != nullptr); CHECK_FALSE(c.finished.load()); fgl::plat::thread_join(tid); // вернётся после завершения потока CHECK(c.ran.load()); CHECK(c.finished.load()); } TEST_CASE("tcp loopback: ephemeral-порт, connect/send/recv/echo, shutdown") { int listen_fd = fgl::plat::tcp_listen(0); REQUIRE(listen_fd >= 0); const uint16_t port = fgl::plat::tcp_local_port(listen_fd); REQUIRE(port != 0); struct SrvCtx { int lfd; int cfd = -1; std::atomic accepted{false}; std::atomic echoed{false}; std::atomic peer_ip{0}; }; SrvCtx srv; srv.lfd = listen_fd; fgl::plat::ThreadId tid = nullptr; REQUIRE(fgl::plat::thread_create( [](void* p) { auto* s = static_cast(p); uint32_t ip = 0; uint16_t peer_port = 0; int cfd = fgl::plat::tcp_accept(s->lfd, &ip, &peer_port); if (cfd < 0) return; s->cfd = cfd; s->accepted.store(true); s->peer_ip.store(ip); fgl::plat::tcp_set_timeout(cfd, 5000, 5000); char buf[16]; long n = fgl::plat::tcp_recv(cfd, buf, sizeof(buf)); if (n > 0) { fgl::plat::tcp_send(cfd, buf, static_cast(n)); s->echoed.store(true); // ждём возможного второго запроса (для проверки shutdown ниже) char buf2[16]; fgl::plat::tcp_recv(cfd, buf2, sizeof(buf2)); } fgl::plat::tcp_close(cfd); }, &srv, "t_srv", 0, &tid)); int cfd = fgl::plat::tcp_connect("127.0.0.1", port, 2000); REQUIRE(cfd >= 0); REQUIRE(fgl::plat::tcp_send(cfd, "ping", 4) == 4); fgl::plat::tcp_set_timeout(cfd, 2000, 2000); char buf[16] = {}; REQUIRE(fgl::plat::tcp_recv(cfd, buf, sizeof(buf)) == 4); CHECK(std::string(buf, 4) == "ping"); fgl::plat::tcp_close(cfd); REQUIRE(srv.accepted.load()); CHECK(srv.peer_ip.load() == 0x7f000001); // 127.0.0.1 в host byte order REQUIRE(srv.echoed.load()); // shutdown прерывает блокированный recv сервера → поток завершается fgl::plat::tcp_shutdown(srv.cfd); fgl::plat::thread_join(tid); fgl::plat::tcp_close(listen_fd); }