mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Add ganl_connection_tests with fakes for NetworkEngine/Protocol/Session/TLS: close-with-drain then Error aborts teardown (#1855), and a StickyTls socketpair flood trips the 256 KiB ingress high-water (#1856). Wire the new binary into make -C mux/ganl/tests check. On Windows, run-msvc.bat builds and runs ganl_tests.vcxproj (wselect/iocp) plus ganl_connection_tests.vcxproj and propagates nonzero status.
This commit is contained in:
parent
e4f73dcb1d
commit
4ab12e3092
5 changed files with 669 additions and 15 deletions
7
Makefile
7
Makefile
|
|
@ -291,10 +291,11 @@ test-lua-ecall: install
|
|||
@echo "==> Running Lua JIT differential harness (survive/agree/exec)"
|
||||
bash tests/luajit/run.sh
|
||||
|
||||
# GANL engine regression harness (epoll/select on Linux, kqueue/select on
|
||||
# macOS/BSD). Scripted engine scenarios locking in the 2026-07 fixes.
|
||||
# GANL engine + ConnectionBase harness (epoll/select on Linux, kqueue/select
|
||||
# on macOS/BSD). Windows: mux/ganl/tests/run-msvc.bat (wselect/iocp + same
|
||||
# ConnectionBase fakes; #1857/#1858).
|
||||
test-ganl:
|
||||
@echo "==> Running GANL engine tests"
|
||||
@echo "==> Running GANL engine + ConnectionBase tests"
|
||||
$(MAKE) -C mux/ganl/tests check
|
||||
|
||||
# netaddr unit tests: mux_subnet::compare_to (subnet/address, #799/#800) and
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# GANL engine regression harness (POSIX side).
|
||||
# GANL engine + ConnectionBase regression harness (POSIX side).
|
||||
#
|
||||
# Linux: epoll + select engines
|
||||
# macOS/BSD: kqueue + select engines
|
||||
# Windows: no make — build ganl_tests.vcxproj with MSBuild instead
|
||||
# (covers the wselect + iocp engines via the accept path).
|
||||
# Windows: no make — run mux/ganl/tests/run-msvc.bat (builds ganl_tests +
|
||||
# ganl_connection_tests via MSBuild; covers wselect/iocp + fakes).
|
||||
#
|
||||
# Invoked as `make test-ganl` (part of `make test`) from the repo root,
|
||||
# or directly: `make -C mux/ganl/tests check`.
|
||||
#
|
||||
# Engines print debug logging to stderr in non-NDEBUG builds; `check`
|
||||
# redirects stderr to ganl_tests.err and shows the TAP stdout only.
|
||||
# redirects stderr to ganl_tests.err / ganl_connection_tests.err.
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
|
||||
|
|
@ -17,29 +17,42 @@ CXX ?= g++
|
|||
CXXFLAGS ?= -std=c++17 -g -O1
|
||||
CPPFLAGS += -I../include
|
||||
|
||||
SRCS = ganl_engine_tests.cpp \
|
||||
ENGINE_SRCS = ganl_engine_tests.cpp \
|
||||
../src/select_network_engine.cpp \
|
||||
../src/io_buffer.cpp \
|
||||
../src/network_address.cpp \
|
||||
../src/slave_spawn_posix.cpp
|
||||
|
||||
CONN_SRCS = ganl_connection_tests.cpp \
|
||||
../src/connection.cpp \
|
||||
../src/select_network_engine.cpp \
|
||||
../src/io_buffer.cpp \
|
||||
../src/network_address.cpp \
|
||||
../src/slave_spawn_posix.cpp
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
SRCS += ../src/epoll_network_engine.cpp
|
||||
ENGINE_SRCS += ../src/epoll_network_engine.cpp
|
||||
else
|
||||
SRCS += ../src/kqueue_network_engine.cpp
|
||||
ENGINE_SRCS += ../src/kqueue_network_engine.cpp
|
||||
endif
|
||||
|
||||
HDRS = $(wildcard ../include/*.h)
|
||||
|
||||
.PHONY: all check clean
|
||||
|
||||
all: ganl_engine_tests
|
||||
all: ganl_engine_tests ganl_connection_tests
|
||||
|
||||
ganl_engine_tests: $(SRCS) $(HDRS)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(SRCS) -o $@
|
||||
ganl_engine_tests: $(ENGINE_SRCS) $(HDRS)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ENGINE_SRCS) -o $@
|
||||
|
||||
check: ganl_engine_tests
|
||||
ganl_connection_tests: $(CONN_SRCS) $(HDRS)
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(CONN_SRCS) -o $@
|
||||
|
||||
check: ganl_engine_tests ganl_connection_tests
|
||||
./ganl_engine_tests 2> ganl_tests.err
|
||||
./ganl_connection_tests 2> ganl_connection_tests.err
|
||||
|
||||
clean:
|
||||
rm -rf ganl_engine_tests ganl_engine_tests.dSYM ganl_tests.err
|
||||
rm -rf ganl_engine_tests ganl_connection_tests \
|
||||
ganl_engine_tests.dSYM ganl_connection_tests.dSYM \
|
||||
ganl_tests.err ganl_connection_tests.err
|
||||
|
|
|
|||
492
mux/ganl/tests/ganl_connection_tests.cpp
Normal file
492
mux/ganl/tests/ganl_connection_tests.cpp
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
// GANL ConnectionBase regression harness (#1858).
|
||||
//
|
||||
// Dependency-injected fakes for NetworkEngine / SecureTransport /
|
||||
// ProtocolHandler / SessionManager drive ReadinessConnection without a
|
||||
// real epoll loop for the close-drain path, and with a socketpair +
|
||||
// SelectNetworkEngine for the ingress high-water path.
|
||||
//
|
||||
// #1855 write/error during close-with-drain must abort teardown
|
||||
// #1856 encrypted ingress high-water closes a non-consuming TLS peer
|
||||
//
|
||||
// Build/run: POSIX `make -C mux/ganl/tests check` (runs after engine tests);
|
||||
// Windows: `run-msvc.bat` builds ganl_connection_tests.vcxproj too.
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#include <connection.h>
|
||||
#include <network_engine.h>
|
||||
#include <protocol_handler.h>
|
||||
#include <secure_transport.h>
|
||||
#include <session_manager.h>
|
||||
#include <io_buffer.h>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <select_network_engine.h>
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace ganl;
|
||||
|
||||
namespace {
|
||||
|
||||
enum class Outcome { Pass, Fail, Skip };
|
||||
struct Result {
|
||||
Outcome outcome;
|
||||
std::string detail;
|
||||
};
|
||||
Result pass(const std::string& d = "") { return {Outcome::Pass, d}; }
|
||||
Result fail(const std::string& d) { return {Outcome::Fail, d}; }
|
||||
Result skip(const std::string& d) { return {Outcome::Skip, d}; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fakes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class FakeEngine : public NetworkEngine {
|
||||
public:
|
||||
IoModel getIoModelType() const override { return IoModel::Readiness; }
|
||||
bool initialize() override { return true; }
|
||||
void shutdown() override {}
|
||||
|
||||
ListenerHandle createListener(const std::string&, uint16_t,
|
||||
ErrorCode& error) override {
|
||||
error = ENOTSUP;
|
||||
return InvalidListenerHandle;
|
||||
}
|
||||
bool startListening(ListenerHandle, void*, ErrorCode& error) override {
|
||||
error = ENOTSUP;
|
||||
return false;
|
||||
}
|
||||
void closeListener(ListenerHandle) override {}
|
||||
|
||||
bool associateContext(ConnectionHandle conn, void* context,
|
||||
ErrorCode& error) override {
|
||||
(void)conn;
|
||||
context_ = context;
|
||||
error = 0;
|
||||
associateCalls_++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void closeConnection(ConnectionHandle conn) override {
|
||||
(void)conn;
|
||||
closeCalls_++;
|
||||
}
|
||||
|
||||
bool postRead(ConnectionHandle, IoBuffer&, ErrorCode& error) override {
|
||||
error = 0;
|
||||
postReadCalls_++;
|
||||
return postReadOk_;
|
||||
}
|
||||
|
||||
bool postWrite(ConnectionHandle, const char*, size_t, void*,
|
||||
ErrorCode& error) override {
|
||||
error = 0;
|
||||
postWriteCalls_++;
|
||||
return postWriteOk_;
|
||||
}
|
||||
|
||||
int processEvents(int, IoEvent*, int) override { return 0; }
|
||||
|
||||
std::string getRemoteAddress(ConnectionHandle) override {
|
||||
return "127.0.0.1:0";
|
||||
}
|
||||
NetworkAddress getRemoteNetworkAddress(ConnectionHandle) override {
|
||||
return NetworkAddress();
|
||||
}
|
||||
std::string getErrorString(ErrorCode e) override {
|
||||
return "fake-error-" + std::to_string(e);
|
||||
}
|
||||
|
||||
void* context_{nullptr};
|
||||
int associateCalls_{0};
|
||||
int closeCalls_{0};
|
||||
int postReadCalls_{0};
|
||||
int postWriteCalls_{0};
|
||||
bool postReadOk_{true};
|
||||
bool postWriteOk_{true};
|
||||
};
|
||||
|
||||
class FakeProtocol : public ProtocolHandler {
|
||||
public:
|
||||
bool createProtocolContext(ConnectionHandle) override { return true; }
|
||||
void destroyProtocolContext(ConnectionHandle) override { destroyCalls_++; }
|
||||
void startNegotiation(ConnectionHandle, IoBuffer&) override {}
|
||||
bool processInput(ConnectionHandle, IoBuffer& decrypted_in,
|
||||
IoBuffer& app_data_out, IoBuffer& /*telnet*/,
|
||||
bool consumeInput) override {
|
||||
size_t n = decrypted_in.readableBytes();
|
||||
if (n > 0) {
|
||||
app_data_out.append(decrypted_in.readPtr(), n);
|
||||
bytesToApp_ += n;
|
||||
if (consumeInput) {
|
||||
decrypted_in.consumeRead(n);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool formatOutput(ConnectionHandle, IoBuffer& app_data_in,
|
||||
IoBuffer& formatted_out, bool consumeInput) override {
|
||||
size_t n = app_data_in.readableBytes();
|
||||
if (n > 0) {
|
||||
formatted_out.append(app_data_in.readPtr(), n);
|
||||
if (consumeInput) {
|
||||
app_data_in.consumeRead(n);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
NegotiationStatus getNegotiationStatus(ConnectionHandle) override {
|
||||
return NegotiationStatus::Completed;
|
||||
}
|
||||
bool consumeStateChanges(ConnectionHandle, ProtocolState&,
|
||||
ProtocolStateChangeFlags&) override {
|
||||
return false;
|
||||
}
|
||||
bool setEncoding(ConnectionHandle, EncodingType) override { return true; }
|
||||
EncodingType getEncoding(ConnectionHandle) override {
|
||||
return EncodingType::Utf8;
|
||||
}
|
||||
ProtocolState getProtocolState(ConnectionHandle) override {
|
||||
return ProtocolState{};
|
||||
}
|
||||
void updateWidth(ConnectionHandle, uint16_t) override {}
|
||||
void updateHeight(ConnectionHandle, uint16_t) override {}
|
||||
std::string getLastProtocolErrorString(ConnectionHandle) override {
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t bytesToApp_{0};
|
||||
int destroyCalls_{0};
|
||||
};
|
||||
|
||||
class FakeSession : public SessionManager {
|
||||
public:
|
||||
bool initialize() override { return true; }
|
||||
void shutdown() override {}
|
||||
SessionId onConnectionOpen(ConnectionHandle, const std::string&) override {
|
||||
openCalls_++;
|
||||
return nextId_++;
|
||||
}
|
||||
void onDataReceived(SessionId, const std::string& data) override {
|
||||
received_ += data;
|
||||
}
|
||||
void onConnectionClose(SessionId, DisconnectReason reason) override {
|
||||
closeCalls_++;
|
||||
lastReason_ = reason;
|
||||
}
|
||||
bool sendToSession(SessionId, const std::string&) override { return true; }
|
||||
bool broadcastMessage(const std::string&, SessionId) override { return true; }
|
||||
bool disconnectSession(SessionId, DisconnectReason) override { return true; }
|
||||
bool authenticateSession(SessionId, ConnectionHandle, const std::string&,
|
||||
const std::string&) override {
|
||||
return false;
|
||||
}
|
||||
void onAuthenticationSuccess(SessionId, int) override {}
|
||||
int getPlayerId(SessionId) override { return -1; }
|
||||
SessionState getSessionState(SessionId) override {
|
||||
return SessionState::Connected;
|
||||
}
|
||||
SessionStats getSessionStats(SessionId) override { return SessionStats{}; }
|
||||
ConnectionHandle getConnectionHandle(SessionId) override {
|
||||
return InvalidConnectionHandle;
|
||||
}
|
||||
bool isAddressAllowed(const std::string&) override { return true; }
|
||||
bool isAddressRegistered(const std::string&) override { return false; }
|
||||
bool isAddressForbidden(const std::string&) override { return false; }
|
||||
bool isAddressSuspect(const std::string&) override { return false; }
|
||||
std::string getLastSessionErrorString(SessionId) override { return {}; }
|
||||
|
||||
SessionId nextId_{1};
|
||||
int openCalls_{0};
|
||||
int closeCalls_{0};
|
||||
DisconnectReason lastReason_{DisconnectReason::Unknown};
|
||||
std::string received_;
|
||||
};
|
||||
|
||||
// TLS that never establishes and never consumes ciphertext — forces
|
||||
// encryptedInput_ to grow under a sustained writer (#1856).
|
||||
//
|
||||
class StickyTls : public SecureTransport {
|
||||
public:
|
||||
bool initialize(const TlsConfig&) override { return true; }
|
||||
void shutdown() override {}
|
||||
bool createSessionContext(ConnectionHandle, bool) override { return true; }
|
||||
void destroySessionContext(ConnectionHandle) override { destroyCalls_++; }
|
||||
TlsResult processIncoming(ConnectionHandle, IoBuffer& /*encrypted_in*/,
|
||||
IoBuffer&, IoBuffer&, bool) override {
|
||||
// Leave encrypted_in intact so the ingress high-water can trip.
|
||||
//
|
||||
return TlsResult::WantRead;
|
||||
}
|
||||
TlsResult processOutgoing(ConnectionHandle, IoBuffer&, IoBuffer&,
|
||||
bool) override {
|
||||
return TlsResult::WantWrite;
|
||||
}
|
||||
TlsResult shutdownSession(ConnectionHandle, IoBuffer& out) override {
|
||||
// Produce a close_notify-shaped byte so close() defers for drain.
|
||||
//
|
||||
const char token = 'X';
|
||||
out.append(&token, 1);
|
||||
return TlsResult::WantWrite;
|
||||
}
|
||||
bool isEstablished(ConnectionHandle) override { return established_; }
|
||||
bool needsNetworkRead(ConnectionHandle) override { return true; }
|
||||
bool needsNetworkWrite(ConnectionHandle) override { return false; }
|
||||
std::string getLastTlsErrorString(ConnectionHandle) override {
|
||||
return "sticky-tls";
|
||||
}
|
||||
|
||||
bool established_{false};
|
||||
int destroyCalls_{0};
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #1855 — forceCloseAfterIoFailure while Closing + drain pending
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Result scenarioCloseDrainWriteFailure() {
|
||||
FakeEngine eng;
|
||||
FakeProtocol proto;
|
||||
FakeSession sess;
|
||||
|
||||
const ConnectionHandle h = static_cast<ConnectionHandle>(42);
|
||||
auto conn = std::make_shared<ReadinessConnection>(
|
||||
h, eng, nullptr, proto, sess);
|
||||
if (!conn->initialize(/*useTls=*/false)) {
|
||||
return fail("plaintext initialize failed");
|
||||
}
|
||||
if (conn->getState() == ConnectionState::Closed) {
|
||||
return fail("connection closed after init");
|
||||
}
|
||||
|
||||
conn->sendDataToClient("queued-output-for-drain");
|
||||
if (conn->pendingOutputBytes() == 0) {
|
||||
return fail("sendDataToClient left no pending output");
|
||||
}
|
||||
if (eng.postWriteCalls_ < 1) {
|
||||
return fail("expected postWrite when queuing output");
|
||||
}
|
||||
|
||||
const int closesBefore = eng.closeCalls_;
|
||||
conn->close(DisconnectReason::UserQuit);
|
||||
|
||||
if (conn->getState() != ConnectionState::Closing) {
|
||||
return fail("expected Closing after close with queued output, got "
|
||||
+ std::to_string(static_cast<int>(conn->getState())));
|
||||
}
|
||||
if (conn->pendingOutputBytes() == 0) {
|
||||
return fail("output vanished before drain failure injection");
|
||||
}
|
||||
|
||||
// Simulate a terminal write/error while still Closing (#1855).
|
||||
//
|
||||
IoEvent errEv{};
|
||||
errEv.type = IoEventType::Error;
|
||||
errEv.connection = h;
|
||||
errEv.context = conn.get();
|
||||
errEv.error = ECONNRESET;
|
||||
conn->handleNetworkEvent(errEv);
|
||||
|
||||
if (eng.closeCalls_ <= closesBefore) {
|
||||
return fail("forceCloseAfterIoFailure did not call closeConnection "
|
||||
"(#1855 sticky Closing)");
|
||||
}
|
||||
if (proto.destroyCalls_ < 1) {
|
||||
return fail("protocol context not destroyed after abort-drain");
|
||||
}
|
||||
if (sess.closeCalls_ < 1) {
|
||||
return fail("session onConnectionClose not invoked after abort-drain");
|
||||
}
|
||||
if (sess.lastReason_ != DisconnectReason::UserQuit) {
|
||||
return fail("disconnect reason not preserved (got "
|
||||
+ std::to_string(static_cast<int>(sess.lastReason_)) + ")");
|
||||
}
|
||||
|
||||
return pass("drain abort + teardown");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #1856 — TLS incomplete-record flood hits encrypted ingress high-water
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#if !defined(_WIN32)
|
||||
Result scenarioIngressHighWaterTls() {
|
||||
SelectNetworkEngine eng;
|
||||
if (!eng.initialize()) {
|
||||
return fail("select engine init failed");
|
||||
}
|
||||
|
||||
int sv[2];
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) {
|
||||
eng.shutdown();
|
||||
return fail("socketpair failed");
|
||||
}
|
||||
// Non-blocking peer side not required for the writer flood.
|
||||
//
|
||||
ErrorCode err = 0;
|
||||
ConnectionHandle ch = eng.adoptConnection(sv[0], nullptr, err);
|
||||
if (ch == InvalidConnectionHandle) {
|
||||
::close(sv[0]);
|
||||
::close(sv[1]);
|
||||
eng.shutdown();
|
||||
return fail("adoptConnection failed errno " + std::to_string(err));
|
||||
}
|
||||
|
||||
StickyTls tls;
|
||||
FakeProtocol proto;
|
||||
FakeSession sess;
|
||||
auto conn = std::make_shared<ReadinessConnection>(
|
||||
ch, eng, &tls, proto, sess);
|
||||
if (!conn->initialize(/*useTls=*/true)) {
|
||||
::close(sv[1]);
|
||||
eng.shutdown();
|
||||
return fail("TLS connection initialize failed");
|
||||
}
|
||||
|
||||
// Non-blocking writer so we cannot deadlock the socketpair when the
|
||||
// peer has not yet drained (blocking write would hang the test).
|
||||
//
|
||||
int fl = fcntl(sv[1], F_GETFL, 0);
|
||||
fcntl(sv[1], F_SETFL, fl | O_NONBLOCK);
|
||||
|
||||
std::vector<char> chunk(16 * 1024, 'Z');
|
||||
size_t written = 0;
|
||||
const size_t target = 512 * 1024;
|
||||
bool closed = false;
|
||||
|
||||
// Interleave flood writes with processEvents so the connection
|
||||
// actually sees the data and the high-water can trip.
|
||||
//
|
||||
for (int round = 0; round < 200 && !closed && written < target; ++round) {
|
||||
while (written < target) {
|
||||
ssize_t n = ::write(sv[1], chunk.data(), chunk.size());
|
||||
if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
written += static_cast<size_t>(n);
|
||||
}
|
||||
|
||||
IoEvent events[16];
|
||||
int n = eng.processEvents(20, events, 16);
|
||||
if (n < 0) {
|
||||
::close(sv[1]);
|
||||
eng.shutdown();
|
||||
return fail("processEvents error");
|
||||
}
|
||||
for (int j = 0; j < n; ++j) {
|
||||
// adoptConnection stored the Connection* via initialize's
|
||||
// associateContext; use event context if set, else our conn.
|
||||
//
|
||||
if (events[j].context == nullptr) {
|
||||
events[j].context = conn.get();
|
||||
}
|
||||
if (events[j].connection == ch
|
||||
|| events[j].context == conn.get()) {
|
||||
conn->handleNetworkEvent(events[j]);
|
||||
}
|
||||
}
|
||||
if (conn->getState() == ConnectionState::Closing
|
||||
|| conn->getState() == ConnectionState::Closed
|
||||
|| conn->isTearingDown()
|
||||
|| sess.closeCalls_ > 0) {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
::close(sv[1]);
|
||||
eng.shutdown();
|
||||
|
||||
if (!closed) {
|
||||
return fail("TLS incomplete-record flood did not trip ingress high-water "
|
||||
"(#1856); wrote " + std::to_string(written) + " bytes, state="
|
||||
+ std::to_string(static_cast<int>(conn->getState())));
|
||||
}
|
||||
return pass("ingress high-water closed after flood ("
|
||||
+ std::to_string(written) + " bytes written)");
|
||||
}
|
||||
#else
|
||||
Result scenarioIngressHighWaterTls() {
|
||||
return skip("socketpair readiness flood covered on POSIX");
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runner
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct Scenario {
|
||||
const char* name;
|
||||
Result (*run)();
|
||||
};
|
||||
|
||||
const Scenario kScenarios[] = {
|
||||
{"close-drain-write-failure", scenarioCloseDrainWriteFailure},
|
||||
{"ingress-high-water-tls", scenarioIngressHighWaterTls},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
#if defined(_WIN32)
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
printf("Bail out! WSAStartup failed\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int total = static_cast<int>(sizeof(kScenarios) / sizeof(kScenarios[0]));
|
||||
int failed = 0;
|
||||
int skipped = 0;
|
||||
int passed = 0;
|
||||
int n = 0;
|
||||
|
||||
printf("# GANL ConnectionBase harness (#1858)\n");
|
||||
for (const auto& s : kScenarios) {
|
||||
++n;
|
||||
Result r = s.run();
|
||||
if (r.outcome == Outcome::Pass) {
|
||||
++passed;
|
||||
printf("ok %d - %s", n, s.name);
|
||||
if (!r.detail.empty()) {
|
||||
printf(" # %s", r.detail.c_str());
|
||||
}
|
||||
printf("\n");
|
||||
} else if (r.outcome == Outcome::Skip) {
|
||||
++skipped;
|
||||
printf("ok %d - %s # SKIP %s\n", n, s.name, r.detail.c_str());
|
||||
} else {
|
||||
++failed;
|
||||
printf("not ok %d - %s # %s\n", n, s.name, r.detail.c_str());
|
||||
}
|
||||
}
|
||||
printf("1..%d\n", total);
|
||||
printf("# %d passed, %d failed, %d skipped\n", passed, failed, skipped);
|
||||
|
||||
#if defined(_WIN32)
|
||||
WSACleanup();
|
||||
#endif
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
85
mux/ganl/tests/ganl_connection_tests.vcxproj
Normal file
85
mux/ganl/tests/ganl_connection_tests.vcxproj
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C8F3D2B1-4E65-5A63-0D2F-0B8C1E6F5A21}</ProjectGuid>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<RootNamespace>ganl_connection_tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings" />
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>..\..\bin_debug\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>..\..\bin_release\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>..\include;..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader />
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalDependencies>ws2_32.lib;mswsock.lib;secur32.lib;crypt32.lib;ncrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>..\include;..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<Optimization>Full</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader />
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalDependencies>ws2_32.lib;mswsock.lib;secur32.lib;crypt32.lib;ncrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ganl_connection_tests.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ganl.vcxproj">
|
||||
<Project>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
||||
63
mux/ganl/tests/run-msvc.bat
Normal file
63
mux/ganl/tests/run-msvc.bat
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
@echo off
|
||||
REM run-msvc.bat — build and run the GANL harness on Windows (#1857).
|
||||
REM
|
||||
REM Builds ganl_tests.vcxproj (wselect + iocp engine matrix) and
|
||||
REM ganl_connection_tests.vcxproj (ConnectionBase fake-driven scenarios).
|
||||
REM Propagates a nonzero exit if either executable fails TAP.
|
||||
REM
|
||||
REM Usage (from a VS developer prompt, or any shell with MSBuild on PATH):
|
||||
REM mux\ganl\tests\run-msvc.bat
|
||||
REM mux\ganl\tests\run-msvc.bat Release
|
||||
REM
|
||||
REM Default configuration is Release|x64.
|
||||
|
||||
setlocal EnableExtensions
|
||||
set CONFIG=%~1
|
||||
if "%CONFIG%"=="" set CONFIG=Release
|
||||
|
||||
set SCRIPT_DIR=%~dp0
|
||||
set ROOT=%SCRIPT_DIR%..\..\..
|
||||
set TESTS=%SCRIPT_DIR%
|
||||
set OUTDIR=%ROOT%\mux\bin_%CONFIG%
|
||||
if /I "%CONFIG%"=="Debug" set OUTDIR=%ROOT%\mux\bin_debug
|
||||
if /I "%CONFIG%"=="Release" set OUTDIR=%ROOT%\mux\bin_release
|
||||
|
||||
where msbuild >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ERROR: msbuild not on PATH. Open a "x64 Native Tools" VS prompt, or
|
||||
echo run from a shell where vswhere has seeded the environment.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ==^> MSBuild ganl_tests.vcxproj /p:Configuration=%CONFIG% Platform=x64
|
||||
msbuild "%TESTS%ganl_tests.vcxproj" /m /p:Configuration=%CONFIG% /p:Platform=x64 /v:minimal
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo ==^> MSBuild ganl_connection_tests.vcxproj /p:Configuration=%CONFIG% Platform=x64
|
||||
msbuild "%TESTS%ganl_connection_tests.vcxproj" /m /p:Configuration=%CONFIG% /p:Platform=x64 /v:minimal
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
set FAIL=0
|
||||
|
||||
echo ==^> Run ganl_tests.exe (wselect + iocp engines)
|
||||
if not exist "%OUTDIR%\ganl_tests.exe" (
|
||||
echo ERROR: missing %OUTDIR%\ganl_tests.exe
|
||||
exit /b 1
|
||||
)
|
||||
"%OUTDIR%\ganl_tests.exe"
|
||||
if errorlevel 1 set FAIL=1
|
||||
|
||||
echo ==^> Run ganl_connection_tests.exe (ConnectionBase harness)
|
||||
if not exist "%OUTDIR%\ganl_connection_tests.exe" (
|
||||
echo ERROR: missing %OUTDIR%\ganl_connection_tests.exe
|
||||
exit /b 1
|
||||
)
|
||||
"%OUTDIR%\ganl_connection_tests.exe"
|
||||
if errorlevel 1 set FAIL=1
|
||||
|
||||
if %FAIL% NEQ 0 (
|
||||
echo FAIL: one or more GANL Windows harness legs failed
|
||||
exit /b 1
|
||||
)
|
||||
echo PASS: GANL Windows harness
|
||||
exit /b 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue