test: traces réelles du moteur jugées contre le contrat (refoptimizer)

Les 16 lignes de testTracesReplayWithZeroDivergence : chaque requête, et
chaque réponse de l'optimiseur rejugée sur sa requête (--trace --semantic).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-10-17 21:48:30 +02:00
parent fc41c4ff40
commit 308444f39d
2 changed files with 38 additions and 16 deletions

View File

@ -43,29 +43,16 @@ struct Verdict {
* Juge une session (un message JSON-RPC par élément, dans l'ordre) contre \p schema.
* \p arguments : options en plus (`--requests-only`, `--semantic`).
*/
inline Verdict juger(const QList<QJsonObject> &messages, const QString &schema, const QStringList &arguments = {})
/*! Lance le validateur avec \p arguments (après `--schema`). */
inline Verdict lancer(const QString &schema, const QStringList &arguments)
{
Verdict v;
QTemporaryDir dir;
if (!dir.isValid()) {
v.sortie = QStringLiteral("répertoire temporaire impossible");
return v;
}
QFile f(dir.filePath(QStringLiteral("session.ndjson")));
if (!f.open(QIODevice::WriteOnly)) {
v.sortie = QStringLiteral("écriture de la session impossible");
return v;
}
for (const QJsonObject &m : messages)
f.write(QJsonDocument(m).toJson(QJsonDocument::Compact) + '\n');
f.close();
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
p.start(QStringLiteral("python3"),
QStringList{QStringLiteral("-I"), QStringLiteral("-B"),
racineSources() + QStringLiteral("/tools/etm-powersync-schema-check"),
QStringLiteral("--schema"), schema, QStringLiteral("--ndjson"), f.fileName()}
+ arguments);
QStringLiteral("--schema"), schema} + arguments);
if (!p.waitForFinished(60000)) {
p.kill();
v.sortie = QStringLiteral("validateur sans réponse en 60 s");
@ -79,6 +66,31 @@ inline Verdict juger(const QList<QJsonObject> &messages, const QString &schema,
return v;
}
/*!
* Juge une session (un message JSON-RPC par élément, dans l'ordre) contre \p schema.
* \p arguments : options en plus (`--requests-only`, `--semantic`).
*/
inline Verdict juger(const QList<QJsonObject> &messages, const QString &schema, const QStringList &arguments = {})
{
QTemporaryDir dir;
QFile f(dir.filePath(QStringLiteral("session.ndjson")));
if (!dir.isValid() || !f.open(QIODevice::WriteOnly)) {
Verdict v;
v.sortie = QStringLiteral("écriture de la session impossible");
return v;
}
for (const QJsonObject &m : messages)
f.write(QJsonDocument(m).toJson(QJsonDocument::Compact) + '\n');
f.close();
return lancer(schema, QStringList{QStringLiteral("--ndjson"), f.fileName()} + arguments);
}
/*! Juge un fichier de traces de décision (annexe D) contre \p schema. */
inline Verdict jugerTraces(const QString &trace, const QString &schema, const QStringList &arguments = {})
{
return lancer(schema, QStringList{QStringLiteral("--trace"), trace} + arguments);
}
/*! Écrit \p octets dans \p dir et rend le chemin : le contrat EMBARQUÉ, passé au validateur. */
inline QString ecrireSchema(const QTemporaryDir &dir, const QByteArray &octets)
{

View File

@ -836,6 +836,16 @@ void TestRefOptimizer::testTracesReplayWithZeroDivergence()
const QStringList fichiers = QDir(d).entryList({"etm-powersync-optimizer-trace-*.ndjson"}, QDir::Files);
QCOMPARE(fichiers.count(), 1);
const QString trace = QDir(d).filePath(fichiers.first());
// Lot contrat OpenRPC : les traces RÉELLES du moteur — requêtes des 16 cycles, réponses de
// l'optimiseur rejugées sur leur requête — sont conformes au contrat embarqué.
{
QTemporaryDir dir;
const Contrat::Verdict v = Contrat::jugerTraces(trace, Contrat::ecrireSchema(dir, OptimizerProtocol::schemaDocumentBytes()),
{QStringLiteral("--semantic")});
QVERIFY2(v.code == 0, qUtf8Printable(v.sortie.right(4000)));
QCOMPARE(v.compte("GetPlan.params"), lignes.count());
QCOMPARE(v.compte("GetPlan.result"), surplus.count()); // les lignes à latencyMs : celles de l'optimiseur
}
RefOptimizer neuf;
QString erreur;