mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 20:13:24 -08:00
[Server] Publish the dev server's port through a file
The smoke test parsed the listening port out of the dev server's stdout, but the server announces it from the main thread while the logger thread writes to the same stream. A log line can land inside the announcement — Arch CI caught it — leaving the digits on the next line, so the test read nothing and failed. Add --port-file, written through QSaveFile once the server listens, and read the port from there instead of from the log. The file is either absent or complete, so logging cannot corrupt the handshake.
This commit is contained in:
parent
16e6e8e3ae
commit
5f6b8b02a5
2 changed files with 56 additions and 24 deletions
|
|
@ -15,12 +15,14 @@
|
||||||
#include "smtpclient.h"
|
#include "smtpclient.h"
|
||||||
#include "version_string.h"
|
#include "version_string.h"
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
#include <QCommandLineOption>
|
#include <QCommandLineOption>
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
|
#include <QSaveFile>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
@ -88,6 +90,23 @@ void dumpState(Servatrice &server, const Servatrice_InMemoryDatabaseInterface &d
|
||||||
std::cout << "servatrice-dev: log entries=" << database.getLogEntries().size() << std::endl;
|
std::cout << "servatrice-dev: log entries=" << database.getLogEntries().size() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Publishes the bound port the way a script can read it.
|
||||||
|
*
|
||||||
|
* The stdout announcement is written from this thread while the logger thread writes to the same
|
||||||
|
* stream, so a reader can see a log line inside it. A file written through QSaveFile is either
|
||||||
|
* absent or complete, which makes the port deterministic to consume.
|
||||||
|
*/
|
||||||
|
bool writeListeningPort(const QString &path, int port)
|
||||||
|
{
|
||||||
|
QSaveFile file(path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file.write(QByteArray::number(port) + '\n');
|
||||||
|
return file.commit();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
|
@ -103,9 +122,13 @@ int main(int argc, char *argv[])
|
||||||
parser.addVersionOption();
|
parser.addVersionOption();
|
||||||
|
|
||||||
QCommandLineOption portOption({"p", "port"}, "TCP port to listen on; 0 picks an ephemeral port.", "port", "0");
|
QCommandLineOption portOption({"p", "port"}, "TCP port to listen on; 0 picks an ephemeral port.", "port", "0");
|
||||||
|
QCommandLineOption portFileOption("port-file",
|
||||||
|
"Write the bound listening port to this file once the server listens.",
|
||||||
|
"path");
|
||||||
QCommandLineOption seedOption("seed", "Seed for the game RNG; random when omitted.", "seed");
|
QCommandLineOption seedOption("seed", "Seed for the game RNG; random when omitted.", "seed");
|
||||||
QCommandLineOption dumpStateOption("dump-state", "Print the server state after startup and keep serving.");
|
QCommandLineOption dumpStateOption("dump-state", "Print the server state after startup and keep serving.");
|
||||||
parser.addOption(portOption);
|
parser.addOption(portOption);
|
||||||
|
parser.addOption(portFileOption);
|
||||||
parser.addOption(seedOption);
|
parser.addOption(seedOption);
|
||||||
parser.addOption(dumpStateOption);
|
parser.addOption(dumpStateOption);
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
|
|
@ -169,11 +192,18 @@ int main(int argc, char *argv[])
|
||||||
std::cerr << "servatrice-dev: the server is not listening" << std::endl;
|
std::cerr << "servatrice-dev: the server is not listening" << std::endl;
|
||||||
retval = EXIT_FAILURE;
|
retval = EXIT_FAILURE;
|
||||||
} else {
|
} else {
|
||||||
std::cout << "servatrice-dev: listening on port " << listeningPort << std::endl;
|
const QString portFilePath = parser.value(portFileOption);
|
||||||
if (parser.isSet(dumpStateOption)) {
|
if (!portFilePath.isEmpty() && !writeListeningPort(portFilePath, listeningPort)) {
|
||||||
dumpState(*server, *server->getInMemoryDatabase());
|
std::cerr << "servatrice-dev: cannot write the listening port to " << portFilePath.toStdString()
|
||||||
|
<< std::endl;
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
} else {
|
||||||
|
std::cout << "servatrice-dev: listening on port " << listeningPort << std::endl;
|
||||||
|
if (parser.isSet(dumpStateOption)) {
|
||||||
|
dumpState(*server, *server->getInMemoryDatabase());
|
||||||
|
}
|
||||||
|
retval = QCoreApplication::exec();
|
||||||
}
|
}
|
||||||
retval = QCoreApplication::exec();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
|
#include <QFile>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
|
@ -92,6 +93,7 @@ protected:
|
||||||
void SetUp() override
|
void SetUp() override
|
||||||
{
|
{
|
||||||
ASSERT_TRUE(tempDir.isValid());
|
ASSERT_TRUE(tempDir.isValid());
|
||||||
|
portFilePath = tempDir.filePath(QStringLiteral("listening-port"));
|
||||||
|
|
||||||
// servatrice-dev writes its throwaway settings under QDir::tempPath(); pointing that at
|
// servatrice-dev writes its throwaway settings under QDir::tempPath(); pointing that at
|
||||||
// a directory the test owns means a terminated server cannot leave an ini behind.
|
// a directory the test owns means a terminated server cannot leave an ini behind.
|
||||||
|
|
@ -102,9 +104,10 @@ protected:
|
||||||
process.setProcessEnvironment(environment);
|
process.setProcessEnvironment(environment);
|
||||||
process.setProcessChannelMode(QProcess::SeparateChannels);
|
process.setProcessChannelMode(QProcess::SeparateChannels);
|
||||||
process.start(QStringLiteral(SERVATRICE_DEV_BINARY),
|
process.start(QStringLiteral(SERVATRICE_DEV_BINARY),
|
||||||
{QStringLiteral("--port"), QStringLiteral("0"), QStringLiteral("--seed"), QStringLiteral("42")});
|
{QStringLiteral("--port"), QStringLiteral("0"), QStringLiteral("--seed"), QStringLiteral("42"),
|
||||||
|
QStringLiteral("--port-file"), portFilePath});
|
||||||
ASSERT_TRUE(process.waitForStarted(kStartupTimeoutMs)) << process.errorString().toStdString();
|
ASSERT_TRUE(process.waitForStarted(kStartupTimeoutMs)) << process.errorString().toStdString();
|
||||||
ASSERT_TRUE(readListeningPort()) << "servatrice-dev did not announce a listening port.\nstdout:\n"
|
ASSERT_TRUE(readListeningPort()) << "servatrice-dev did not publish its listening port.\nstdout:\n"
|
||||||
<< stdoutText.toStdString() << "\nstderr:\n"
|
<< stdoutText.toStdString() << "\nstderr:\n"
|
||||||
<< stderrText.toStdString();
|
<< stderrText.toStdString();
|
||||||
|
|
||||||
|
|
@ -128,34 +131,32 @@ protected:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Reads the child's output until it announces the ephemeral port it listens on.
|
/// @brief Waits for the child to publish the ephemeral port it listens on.
|
||||||
bool readListeningPort()
|
bool readListeningPort()
|
||||||
{
|
{
|
||||||
const QString prefix = QStringLiteral("servatrice-dev: listening on port ");
|
|
||||||
QElapsedTimer elapsed;
|
QElapsedTimer elapsed;
|
||||||
elapsed.start();
|
elapsed.start();
|
||||||
while (elapsed.elapsed() < kStartupTimeoutMs) {
|
while (elapsed.elapsed() < kStartupTimeoutMs) {
|
||||||
if (!process.waitForReadyRead(100)) {
|
// The child logs heavily during startup; keep its pipes drained so it cannot block.
|
||||||
if (process.state() == QProcess::NotRunning) {
|
|
||||||
drainProcessOutput();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
drainProcessOutput();
|
drainProcessOutput();
|
||||||
|
|
||||||
const int at = stdoutText.indexOf(prefix);
|
QFile portFile(portFilePath);
|
||||||
if (at < 0) {
|
if (portFile.open(QIODevice::ReadOnly)) {
|
||||||
continue;
|
bool ok = false;
|
||||||
|
const int port = QString::fromLatin1(portFile.readAll()).trimmed().toInt(&ok);
|
||||||
|
if (ok && port > 0) {
|
||||||
|
listeningPort = port;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const int end = stdoutText.indexOf(QLatin1Char('\n'), at);
|
|
||||||
if (end < 0) {
|
if (process.state() == QProcess::NotRunning) {
|
||||||
continue;
|
drainProcessOutput();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
bool ok = false;
|
QThread::msleep(20);
|
||||||
listeningPort = stdoutText.mid(at + prefix.size(), end - at - prefix.size()).trimmed().toInt(&ok);
|
|
||||||
return ok && listeningPort > 0;
|
|
||||||
}
|
}
|
||||||
|
drainProcessOutput();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,6 +229,7 @@ protected:
|
||||||
|
|
||||||
QProcess process;
|
QProcess process;
|
||||||
QTemporaryDir tempDir;
|
QTemporaryDir tempDir;
|
||||||
|
QString portFilePath;
|
||||||
StubNetworkSettingsProvider provider;
|
StubNetworkSettingsProvider provider;
|
||||||
std::unique_ptr<RemoteClient> client;
|
std::unique_ptr<RemoteClient> client;
|
||||||
int listeningPort = 0;
|
int listeningPort = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue