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
|
|
@ -203,6 +203,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
||||||
cfg.AddTest("ShouldSendRecieveQUICDatagram").Dependencies({ &cfg });
|
cfg.AddTest("ShouldSendRecieveQUICDatagram").Dependencies({ &cfg });
|
||||||
cfg.AddTest("ShouldSendRecieveQUICStream").Dependencies({ &cfg });
|
cfg.AddTest("ShouldSendRecieveQUICStream").Dependencies({ &cfg });
|
||||||
cfg.AddTest("ShouldReportClosedQUICStream").Dependencies({ &cfg });
|
cfg.AddTest("ShouldReportClosedQUICStream").Dependencies({ &cfg });
|
||||||
|
cfg.AddTest("ShouldReportListenerPortQUIC").Dependencies({ &cfg });
|
||||||
cfg.AddTest("ShouldSurviveConnectionChurn").Dependencies({ &cfg });
|
cfg.AddTest("ShouldSurviveConnectionChurn").Dependencies({ &cfg });
|
||||||
} else {
|
} else {
|
||||||
// Only a --no-quic build can show that msquic stayed out.
|
// Only a --no-quic build can show that msquic stayed out.
|
||||||
|
|
|
||||||
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