test(listener): cover ListenerQUIC::Port()
ShouldReportListenerPortQUIC: a listener on port 0 reports a nonzero port and a client reaches it there; a second listener on port 0 gets a different port; a fixed port reads back as itself; the port is still there after Stop() and moves with the listener. It binds no fixed port of its own: the fixed-port check reuses the ephemeral port a listener just let go of. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7dace8bb03
commit
3457ff08ce
2 changed files with 108 additions and 0 deletions
107
tests/ShouldReportListenerPortQUIC/main.cpp
Normal file
107
tests/ShouldReportListenerPortQUIC/main.cpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// ListenerQUIC::Port() says which UDP port the listener is bound to, so port
|
||||
// 0 (let msquic pick a free one) is usable. Before, nothing read it back: a
|
||||
// server had to pick a fixed port and hope nothing else held it, and this
|
||||
// suite hands every QUIC test a port of its own by hand.
|
||||
//
|
||||
// Also: two listeners on port 0 get different ports, a fixed port reads back
|
||||
// as itself, and the port is still there after Stop() and gone from a
|
||||
// moved-from listener.
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
import Crafter.Network;
|
||||
import Crafter.Thread;
|
||||
import std;
|
||||
using namespace Crafter;
|
||||
|
||||
namespace {
|
||||
|
||||
int failures = 0;
|
||||
|
||||
void Check(bool ok, std::string_view what) {
|
||||
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||||
if (!ok) ++failures;
|
||||
}
|
||||
|
||||
std::string Text(const std::vector<char>& bytes) {
|
||||
return std::string(bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
ThreadPool::Start();
|
||||
|
||||
// A connect that never completes would otherwise sit out the handshake
|
||||
// timeout and the runner's 60 s; fail well before that instead.
|
||||
std::thread([] {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(30));
|
||||
std::println("FAIL timed out");
|
||||
std::fflush(stdout);
|
||||
std::_Exit(1);
|
||||
}).detach();
|
||||
|
||||
const std::string alpn = "crafter/test-listener-port";
|
||||
QUICServerCredentials serverCreds;
|
||||
serverCreds.selfSigned = true;
|
||||
QUICClientCredentials clientCreds;
|
||||
clientCreds.insecureNoServerValidation = true;
|
||||
|
||||
try {
|
||||
// ── Port 0: msquic picks, Port() says what it picked ─────────────────
|
||||
ListenerQUIC listener(0, alpn, serverCreds, [](ClientQUIC* peer) {
|
||||
peer->OnStream([](QUICStream stream) {
|
||||
try {
|
||||
std::vector<char> got = stream.RecieveUntilCloseSync();
|
||||
stream.SendSync(got.data(), static_cast<std::uint32_t>(got.size()), true);
|
||||
} catch (...) {}
|
||||
});
|
||||
});
|
||||
listener.ListenAsyncAsync();
|
||||
const std::uint16_t port = listener.Port();
|
||||
Check(port != 0, std::format("a listener on port 0 reports the port it got ({})", port));
|
||||
|
||||
{
|
||||
ClientQUIC client("localhost", port, alpn, clientCreds);
|
||||
QUICStream stream = client.OpenStream();
|
||||
stream.SendSync("ping", 4, true);
|
||||
const std::string echoed = Text(stream.RecieveUntilCloseSync());
|
||||
Check(echoed == "ping", std::format("a client reaches it at that port ({})", echoed));
|
||||
}
|
||||
|
||||
// ── Another listener on port 0 gets another port ─────────────────────
|
||||
std::uint16_t freed = 0;
|
||||
{
|
||||
ListenerQUIC other(0, alpn, serverCreds, [](ClientQUIC* peer) { delete peer; });
|
||||
freed = other.Port();
|
||||
Check(freed != 0 && freed != port,
|
||||
std::format("a second listener on port 0 gets a port of its own ({} and {})", port, freed));
|
||||
|
||||
ListenerQUIC moved(std::move(other));
|
||||
Check(moved.Port() == freed && other.Port() == 0,
|
||||
"moving a listener moves its port; the moved-from one reports 0");
|
||||
}
|
||||
|
||||
// ── A fixed port reads back as itself ────────────────────────────────
|
||||
// The port the listener above let go of: free again, and not one any
|
||||
// other test in the suite binds.
|
||||
{
|
||||
ListenerQUIC fixed(freed, alpn, serverCreds, [](ClientQUIC* peer) { delete peer; });
|
||||
Check(fixed.Port() == freed, std::format("a listener on a fixed port reports that port ({})", fixed.Port()));
|
||||
}
|
||||
|
||||
listener.Stop();
|
||||
Check(listener.Port() == port, "Port() still answers after Stop()");
|
||||
} catch (const std::exception& e) {
|
||||
Check(false, std::format("threw: {}", e.what()));
|
||||
}
|
||||
|
||||
std::println("{}", failures == 0 ? "all checks passed" : "check(s) failed");
|
||||
// As the other QUIC tests: skip the static teardown, which waits for the
|
||||
// accepted peer's idle timeout. _Exit doesn't flush.
|
||||
std::fflush(stdout);
|
||||
std::_Exit(failures == 0 ? 0 : 1);
|
||||
}
|
||||
Loading…
Reference in a new issue