glove/tests/mcp/stdio_transport_test.cpp
2026-07-19 21:06:11 -04:00

72 lines
2.5 KiB
C++

// Spawns /bin/cat -u as an echo "server" and round-trips a few frames through
// the stdio transport. The point is to exercise the spawn/pipe/cleanup path
// under sanitizers; the codec is irrelevant here.
//
// macOS-only for now: BSD cat supports `-u` (unbuffered). When Linux CI lands
// we will switch to `stdbuf -o0 cat` or equivalent.
#include "glove/mcp/stdio_transport.hpp"
#include "glove/mcp/transport.hpp"
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <string>
namespace {
#define REQUIRE(cond) \
do { \
if (!(cond)) { \
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
return 1; \
} \
} while (0)
auto run() -> int {
glove::mcp::stdio_child_options opts{
.program = "/bin/cat",
.args = {"cat", "-u"},
};
auto t_or = glove::mcp::make_stdio_transport(opts);
REQUIRE(t_or.has_value());
std::unique_ptr<glove::mcp::transport> t = std::move(*t_or);
for (int i = 0; i < 8; ++i) {
std::string frame = "hello-";
frame.append(std::to_string(i));
auto sent = t->send(frame);
REQUIRE(sent.has_value());
auto got = t->recv();
REQUIRE(got.has_value());
REQUIRE(*got == frame);
}
REQUIRE(!t->send("two\nframes").has_value());
REQUIRE(::setenv("GLOVE_TEST_HOST_SECRET", "must-not-cross", 1) == 0);
glove::mcp::stdio_child_options environment_probe{
.program = "/bin/sh",
.args = {"sh", "-c", "env | grep GLOVE_TEST_HOST_SECRET || echo scrubbed"},
};
auto probe_or = glove::mcp::make_stdio_transport(environment_probe);
REQUIRE(probe_or.has_value());
auto probe = std::move(*probe_or);
auto environment_result = probe->recv();
REQUIRE(environment_result.has_value());
REQUIRE(*environment_result == "scrubbed");
REQUIRE(::unsetenv("GLOVE_TEST_HOST_SECRET") == 0);
// Destruction signals the child and reaps it; sanitizers will catch leaks
// or use-after-close on the pipes.
return 0;
}
} // namespace
auto main() -> int {
return run();
}