test(conformance) : sonde des règles 1.2 que le moteur ne provoque pas
Quatre fonctions, une connexion de SONDE sur l'optimiseur (sans le moteur), pour ce que le client du moteur n'envoie jamais : - testGetPlanBeforeTheHandshakeIsRefused (Q3) : -32600 à son id, puis le handshake sur la même connexion est accepté (connexion gardée) ; - testARefusedHandshakeIsClosedByTheOptimizer (Q3, Q4) : autre majeure → -32602 puis l'optimiseur ferme ; avec un token attendu, token faux → -32001, fermé pareil ; - testABatchIsRefused (Q8) : un lot → une erreur -32600 à id null, son contenu non traité, connexion gardée ; - testALineOverTheLimitIsRefused (Q7) : maxLineBytes + 1 → -32600 à id null, une ligne d'exactement maxLineBytes est lue. Le traitement d'un message se constate par une réponse normée sans contexte : UpdateLearned avec id → -32601 (Q6). Réponses lues brutes : un lot renvoyé se verrait. README : tableau, boucle de lancement, ce qui n'est pas vérifié. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4cbbb81802
commit
7bb2b4a194
@ -11,9 +11,18 @@ adaptateurs, sur les mocks de la suite `simulation` — et vérifie, une fonctio
|
||||
| `testTheSessionMatchesTheContract` | la session entière (les deux sens) est conforme au contrat OpenRPC **embarqué** dans le moteur, règles sémantiques comprises (`tools/etm-powersync-schema-check --semantic`) |
|
||||
| `testEveryPlanIsAccepted` | à chaque cycle, le plan est **appliqué** : l'optimiseur commande, `GetPlan` sans `fallbackCode` |
|
||||
| `testTheSafetyLoopStaysOnTop` | la boucle de sécurité reste au-dessus : une consigne hors bornes (réécrite en 100 kW par le relais de test) est **écrêtée** par l'adaptateur (`clamped`), une surcharge de phase est **délestée** par L4 |
|
||||
| `testGetPlanBeforeTheHandshakeIsRefused` | protocole 1.2, §3 : `GetPlan` avant un handshake réussi → erreur `-32600` à son `id`, connexion **gardée** (le handshake qui suit est accepté) |
|
||||
| `testARefusedHandshakeIsClosedByTheOptimizer` | protocole 1.2, §3 : handshake d'une autre majeure → `-32602`, puis l'optimiseur **ferme** ; si un token est attendu, token faux → `-32001`, fermé pareil |
|
||||
| `testABatchIsRefused` | protocole 1.2, §2 : un lot `[…]` → **une** erreur `-32600` à `id: null`, rien du lot traité, connexion gardée |
|
||||
| `testALineOverTheLimitIsRefused` | protocole 1.2, §2 : une ligne de `maxLineBytes` + 1 octets → `-32600` à `id: null`, connexion gardée ; une ligne d'exactement `maxLineBytes` est lue |
|
||||
|
||||
Ces deux dernières lignes valent pour le mode `plan` (défaut). Voir **Optimiseur qui s'abstient**.
|
||||
|
||||
Les fonctions 6 à 9 ne branchent pas le moteur : elles envoient, par une **connexion de sonde**, ce
|
||||
que son client n'envoie jamais, et lisent la réponse brute (un lot renvoyé se verrait). Elles
|
||||
utilisent des requêtes dont la réponse est normée sans contexte — `UpdateLearned` avec `id` →
|
||||
`-32601` (§8) — pour constater qu'un message a été, ou n'a pas été, traité.
|
||||
|
||||
Les fonctions 2 à 5 passent par un relais transparent (une ligne reçue = une ligne renvoyée) qui
|
||||
capture la session et mesure les délais ; la 5 est la seule où il réécrit une réponse. Banc : deux
|
||||
charges `etmvariableload` (0–2 000 W ; plancher 800, 3 000 W) et une PAC SG-Ready (P3 1 500 W,
|
||||
@ -30,7 +39,8 @@ cd <build>/tests/auto/conformance
|
||||
export NYMEA_PLUGINS_PATH=<build>/tests/mocks/plugins/energymocks:/usr/lib/x86_64-linux-gnu/nymea/plugins
|
||||
export ETM_POWERSYNC_OPTIMIZER_CMD='exec /chemin/de/mon-optimiseur --listen {endpoint}'
|
||||
for fn in testHandshakeIsAccepted testGetPlanIsAnsweredInTime testTheSessionMatchesTheContract \
|
||||
testEveryPlanIsAccepted testTheSafetyLoopStaysOnTop; do
|
||||
testEveryPlanIsAccepted testTheSafetyLoopStaysOnTop testGetPlanBeforeTheHandshakeIsRefused \
|
||||
testARefusedHandshakeIsClosedByTheOptimizer testABatchIsRefused testALineOverTheLimitIsRefused; do
|
||||
systemd-run --user --scope -q -p MemoryMax=2G \
|
||||
bwrap --dev-bind / / --unshare-net --tmpfs /tmp --bind "$PWD/../../.." "$PWD/../../.." \
|
||||
--bind <sources> <sources> --chdir "$PWD" ./target_wrapper.sh ./etm-powersync-optimizer-conformance "$fn"
|
||||
@ -65,7 +75,7 @@ toujours est conforme. Avec `ETM_POWERSYNC_CONFORMANCE_EXPECT=abstain` :
|
||||
- `testTheSafetyLoopStaysOnTop` vérifie toujours L4 sous le rule-based, sans consigne de
|
||||
l'optimiseur : la trace doit porter `shed` (la seule baisse des consignes ne suffit pas, l'import
|
||||
la produit déjà). Puis la fonction se termine en **SKIP** : aucune consigne à écrêter ;
|
||||
- les trois autres fonctions sont inchangées.
|
||||
- les autres fonctions sont inchangées.
|
||||
|
||||
À l'inverse, un optimiseur qui s'abstient en mode `plan` échoue à `testEveryPlanIsAccepted` et
|
||||
`testTheSafetyLoopStaysOnTop`. Chaque fonction affiche en tête le mode retenu (`[Conformité] mode
|
||||
@ -81,6 +91,9 @@ fondée sur ces champs jugerait faux dans les deux cas : le mode se donne, il ne
|
||||
## Ce qu'elle ne vérifie pas
|
||||
|
||||
La **qualité** des plans (aucune comparaison au rule-based : c'est `refoptimizer`, pour la référence
|
||||
seulement), les bornes VE (aucune borne au banc), `UpdateLearned` (ignoré par le moteur 1.1), le
|
||||
comportement sous défaut de transport. Le contrat : `docs/etm-powersync-optimizer-protocol.openrpc.json`
|
||||
seulement), les bornes VE (aucune borne au banc), `UpdateLearned` émis par l'optimiseur (ignoré par le moteur ; sa
|
||||
forme n'est pas normée en 1.2), le token envoyé à un optimiseur qui n'en exige pas (§3, 1.2 : accepté ;
|
||||
le moteur n'envoie de token que si l'installateur en a posé un), la réponse à un contexte `GetPlan`
|
||||
invalide (§7, 1.2 : `-32602` ; la forme d'`error.data` n'est pas normée), le comportement sous défaut
|
||||
de transport. Le contrat : `docs/etm-powersync-optimizer-protocol.openrpc.json`
|
||||
(prose : `docs/OPTIMIZER_PROTOCOL.md`, annexe G).
|
||||
|
||||
@ -16,7 +16,11 @@
|
||||
// sémantiques comprises — jugée par le validateur publié ;
|
||||
// 4. chaque plan ACCEPTÉ : l'optimiseur commande, `GetPlan` sans `fallbackCode` ;
|
||||
// 5. la boucle de sécurité au-dessus : une consigne hors bornes est écrêtée par l'adaptateur,
|
||||
// une surcharge de phase est délestée par L4, quoi que dise l'optimiseur.
|
||||
// une surcharge de phase est délestée par L4, quoi que dise l'optimiseur ;
|
||||
// 6 à 9 (protocole 1.2). Ce que le moteur n'envoie JAMAIS, envoyé par une connexion de SONDE
|
||||
// (sans le moteur) : GetPlan avant le handshake (-32600, connexion gardée), handshake d'une
|
||||
// autre majeure (-32602 puis fermeture par l'optimiseur), lot (-32600 à id null, rien du lot
|
||||
// traité), ligne de plus de 1 Mio (-32600 à id null, connexion gardée).
|
||||
//
|
||||
// Mode attendu ($ETM_POWERSYNC_CONFORMANCE_EXPECT, défaut `plan`) : `abstain` juge un optimiseur
|
||||
// qui s'abstient toujours (§7, réponse permise) — le point 4 exige alors une abstention à chaque
|
||||
@ -52,6 +56,7 @@
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include <csignal>
|
||||
#include <memory>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace nymeaserver;
|
||||
@ -319,6 +324,69 @@ private:
|
||||
QElapsedTimer m_chrono;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Connexion de SONDE (protocole 1.2) : des lignes écrites à la main, les réponses lues une à une,
|
||||
* sans le moteur — pour ce que son client n'envoie jamais. Une réponse est gardée BRUTE aussi : un
|
||||
* lot renvoyé (`[…]`) n'est pas un objet, et QJsonDocument::object() le rendrait vide.
|
||||
*/
|
||||
class Sonde
|
||||
{
|
||||
public:
|
||||
explicit Sonde(const QString &endpoint) : m_s(socketVers(endpoint, &m_proprietaire)) {}
|
||||
|
||||
bool ouverte() { return m_s && waitFor([this] { return connecte(m_s); }, 5000); }
|
||||
|
||||
void envoyer(const QByteArray &ligne)
|
||||
{
|
||||
m_s->write(ligne + '\n');
|
||||
if (auto *l = qobject_cast<QLocalSocket *>(m_s))
|
||||
l->flush();
|
||||
else if (auto *t = qobject_cast<QTcpSocket *>(m_s))
|
||||
t->flush();
|
||||
}
|
||||
|
||||
//! La ligne suivante (sans son '\n') ; faux si rien n'arrive en \p ms, ou si la connexion se ferme avant.
|
||||
bool recevoir(QByteArray *ligne, int ms = 5000)
|
||||
{
|
||||
waitFor([this] { lire(); return m_tampon.contains('\n') || !connecte(m_s); }, ms);
|
||||
lire();
|
||||
const int nl = m_tampon.indexOf('\n');
|
||||
if (nl < 0)
|
||||
return false;
|
||||
*ligne = m_tampon.left(nl);
|
||||
m_tampon.remove(0, nl + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Vrai si l'AUTRE côté ferme en \p ms : la sonde ne ferme jamais d'elle-même.
|
||||
bool fermeeParLautre(int ms) { return waitFor([this] { lire(); return !connecte(m_s); }, ms); }
|
||||
|
||||
private:
|
||||
void lire() { m_tampon += m_s->readAll(); }
|
||||
|
||||
QObject m_proprietaire;
|
||||
QIODevice *m_s;
|
||||
QByteArray m_tampon;
|
||||
};
|
||||
|
||||
//! Une réponse error de la sonde : `{"jsonrpc":"2.0","id":<id>,"error":{"code":<code>,…}}`, objet seul.
|
||||
QString verifierErreur(const QByteArray &ligne, const QJsonValue &id, int code)
|
||||
{
|
||||
QJsonParseError e;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(ligne, &e);
|
||||
const QString vu = QString::fromUtf8(ligne.left(300));
|
||||
if (e.error != QJsonParseError::NoError || !doc.isObject())
|
||||
return QStringLiteral("réponse qui n'est pas UN objet JSON : ") + vu;
|
||||
const QJsonObject m = doc.object();
|
||||
if (!m.contains("error") || m.value("error").toObject().value("code").toInt() != code)
|
||||
return QStringLiteral("erreur %1 attendue : ").arg(code) + vu;
|
||||
const QJsonValue recu = m.value("id");
|
||||
const bool bonId = id.isNull() ? m.contains("id") && recu.isNull() : recu.isDouble() && recu.toDouble() == id.toDouble();
|
||||
if (!bonId)
|
||||
return QStringLiteral("id %1 attendu : ").arg(id.isNull() ? QStringLiteral("null") : QString::number(id.toInt())) + vu;
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString cle(const QVariantMap &v)
|
||||
{
|
||||
return QString::fromUtf8(QJsonDocument::fromVariant(v).toJson(QJsonDocument::Compact));
|
||||
@ -336,9 +404,23 @@ private slots:
|
||||
void testTheSessionMatchesTheContract();
|
||||
void testEveryPlanIsAccepted();
|
||||
void testTheSafetyLoopStaysOnTop();
|
||||
void testGetPlanBeforeTheHandshakeIsRefused();
|
||||
void testARefusedHandshakeIsClosedByTheOptimizer();
|
||||
void testABatchIsRefused();
|
||||
void testALineOverTheLimitIsRefused();
|
||||
|
||||
private:
|
||||
void monter();
|
||||
//! Où l'optimiseur écoute ($ETM_POWERSYNC_OPTIMIZER_ENDPOINT, sinon un socket du test).
|
||||
QString endpoint() const;
|
||||
//! Le token que l'optimiseur attend ($ETM_POWERSYNC_OPTIMIZER_TOKEN_FILE), vide sinon.
|
||||
QString lireToken();
|
||||
//! Points 6 à 9 : l'optimiseur seul, sans moteur ; \p sonde ouverte dessus.
|
||||
void sonder(std::unique_ptr<Sonde> &sonde);
|
||||
//! Un Handshake de sonde en \p version, avec \p token s'il n'est pas vide.
|
||||
QByteArray handshake(int id, const QString &version, const QString &token) const;
|
||||
//! Handshake accepté sur \p sonde, ou échec du test.
|
||||
void ouvrirSession(Sonde &sonde, int id);
|
||||
//! Lance l'optimiseur et y branche le moteur (directement, ou par le relais).
|
||||
void brancher(bool parLeRelais);
|
||||
void cycle(const QDateTime &t);
|
||||
@ -437,20 +519,32 @@ void TestOptimizerConformance::monter()
|
||||
m_arb->setOptimizerTraceDirectory(d);
|
||||
QCOMPARE(rpc("SetOptimizerTrace", {{"enabled", true}}).value("energyError").toString(), QString("EnergyErrorNoError"));
|
||||
// Token : celui que l'optimiseur attend, s'il en attend un.
|
||||
const QString fichier = variable("ETM_POWERSYNC_OPTIMIZER_TOKEN_FILE");
|
||||
if (!fichier.isEmpty()) {
|
||||
QFile f(fichier);
|
||||
QVERIFY2(f.open(QIODevice::ReadOnly), qUtf8Printable("ETM_POWERSYNC_OPTIMIZER_TOKEN_FILE illisible : " + fichier));
|
||||
m_token = QString::fromUtf8(f.readAll().trimmed());
|
||||
m_token = lireToken();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
if (!m_token.isEmpty())
|
||||
QCOMPARE(rpc("SetOptimizerToken", {{"token", m_token}}).value("energyError").toString(), QString("EnergyErrorNoError"));
|
||||
}
|
||||
m_surplusW = 0;
|
||||
}
|
||||
|
||||
QString TestOptimizerConformance::endpoint() const
|
||||
{
|
||||
return variable("ETM_POWERSYNC_OPTIMIZER_ENDPOINT", QStringLiteral("unix://%1/optimiseur.sock").arg(m_dir.path()));
|
||||
}
|
||||
|
||||
QString TestOptimizerConformance::lireToken()
|
||||
{
|
||||
const QString fichier = variable("ETM_POWERSYNC_OPTIMIZER_TOKEN_FILE");
|
||||
if (fichier.isEmpty())
|
||||
return QString();
|
||||
QFile f(fichier);
|
||||
[&] { QVERIFY2(f.open(QIODevice::ReadOnly), qUtf8Printable("ETM_POWERSYNC_OPTIMIZER_TOKEN_FILE illisible : " + fichier)); }();
|
||||
return QString::fromUtf8(f.readAll().trimmed());
|
||||
}
|
||||
|
||||
void TestOptimizerConformance::brancher(bool parLeRelais)
|
||||
{
|
||||
const QString endpoint = variable("ETM_POWERSYNC_OPTIMIZER_ENDPOINT",
|
||||
QStringLiteral("unix://%1/optimiseur.sock").arg(m_dir.path()));
|
||||
const QString endpoint = this->endpoint();
|
||||
QString erreur;
|
||||
QVERIFY2(m_optimiseur.demarrer(endpoint, &erreur), qUtf8Printable(erreur));
|
||||
QString cible = endpoint;
|
||||
@ -710,5 +804,136 @@ void TestOptimizerConformance::testTheSafetyLoopStaysOnTop()
|
||||
}
|
||||
}
|
||||
|
||||
// --- 6 à 9 : protocole 1.2, par une connexion de sonde ----------------------------------------------
|
||||
|
||||
void TestOptimizerConformance::sonder(std::unique_ptr<Sonde> &sonde)
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
m_token = lireToken();
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
QString erreur;
|
||||
QVERIFY2(m_optimiseur.demarrer(endpoint(), &erreur), qUtf8Printable(erreur));
|
||||
sonde.reset(new Sonde(endpoint()));
|
||||
QVERIFY2(sonde->ouverte(), qUtf8Printable("la sonde ne se connecte pas à " + endpoint()));
|
||||
}
|
||||
|
||||
QByteArray TestOptimizerConformance::handshake(int id, const QString &version, const QString &token) const
|
||||
{
|
||||
QJsonObject params{{"protocolVersion", version}, {"engine", "etm-powersync-energy-plugin-etm/conformance"}};
|
||||
if (!token.isEmpty())
|
||||
params.insert("token", token);
|
||||
return QJsonDocument(QJsonObject{{"jsonrpc", "2.0"}, {"id", id}, {"method", "Handshake"}, {"params", params}})
|
||||
.toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
void TestOptimizerConformance::ouvrirSession(Sonde &sonde, int id)
|
||||
{
|
||||
sonde.envoyer(handshake(id, QString::fromLatin1(OptimizerProtocol::ProtocolVersion), m_token));
|
||||
QByteArray ligne;
|
||||
QVERIFY2(sonde.recevoir(&ligne), "handshake de sonde sans réponse");
|
||||
const QJsonObject m = QJsonDocument::fromJson(ligne).object();
|
||||
QVERIFY2(m.value("id").toInt() == id && m.value("result").toObject().value("protocolVersion").isString(),
|
||||
qUtf8Printable("handshake de sonde refusé : " + QString::fromUtf8(ligne.left(300))));
|
||||
}
|
||||
|
||||
void TestOptimizerConformance::testGetPlanBeforeTheHandshakeIsRefused()
|
||||
{
|
||||
// §3 (1.2) : GetPlan avant un handshake réussi → -32600, et la connexion est GARDÉE — le
|
||||
// handshake qui suit, sur la même connexion, est accepté.
|
||||
std::unique_ptr<Sonde> sonde;
|
||||
sonder(sonde);
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
sonde->envoyer(R"({"jsonrpc":"2.0","id":1,"method":"GetPlan","params":{}})");
|
||||
QByteArray ligne;
|
||||
QVERIFY2(sonde->recevoir(&ligne), "GetPlan avant le handshake : aucune réponse (ou connexion fermée)");
|
||||
const QString e = verifierErreur(ligne, 1, -32600);
|
||||
QVERIFY2(e.isEmpty(), qUtf8Printable(e));
|
||||
ouvrirSession(*sonde, 2);
|
||||
}
|
||||
|
||||
void TestOptimizerConformance::testARefusedHandshakeIsClosedByTheOptimizer()
|
||||
{
|
||||
// §3 (1.2) : un handshake refusé est fermé des deux côtés. Ici, la moitié de l'OPTIMISEUR : il
|
||||
// ferme après avoir envoyé l'erreur (la sonde, elle, ne ferme pas). Majeure incompatible →
|
||||
// -32602 ; si l'optimiseur attend un token, un token faux → -32001, fermé pareil. La moitié du
|
||||
// moteur est éprouvée par la suite `optimizerclient` (testARefusedHandshakeIsClosedByTheEngine).
|
||||
std::unique_ptr<Sonde> sonde;
|
||||
sonder(sonde);
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
const QString autreMajeure = QString::number(QString::fromLatin1(OptimizerProtocol::ProtocolVersion).section('.', 0, 0).toInt() + 1) + ".0";
|
||||
sonde->envoyer(handshake(1, autreMajeure, m_token));
|
||||
QByteArray ligne;
|
||||
QVERIFY2(sonde->recevoir(&ligne), qUtf8Printable("handshake en " + autreMajeure + " : aucune réponse avant la fermeture"));
|
||||
QString e = verifierErreur(ligne, 1, -32602);
|
||||
QVERIFY2(e.isEmpty(), qUtf8Printable(e));
|
||||
QVERIFY2(sonde->fermeeParLautre(5000), "handshake refusé (-32602) : l'optimiseur n'a pas fermé la connexion en 5 s");
|
||||
|
||||
if (m_token.isEmpty()) {
|
||||
qInfo().noquote() << "[Conformité] aucun token attendu : refus -32001 non éprouvé";
|
||||
return;
|
||||
}
|
||||
Sonde faux(endpoint());
|
||||
QVERIFY(faux.ouverte());
|
||||
faux.envoyer(handshake(1, QString::fromLatin1(OptimizerProtocol::ProtocolVersion), m_token + QStringLiteral("-faux")));
|
||||
QVERIFY2(faux.recevoir(&ligne), "token faux : aucune réponse avant la fermeture");
|
||||
e = verifierErreur(ligne, 1, -32001);
|
||||
QVERIFY2(e.isEmpty(), qUtf8Printable(e));
|
||||
QVERIFY2(faux.fermeeParLautre(5000), "handshake refusé (-32001) : l'optimiseur n'a pas fermé la connexion en 5 s");
|
||||
}
|
||||
|
||||
void TestOptimizerConformance::testABatchIsRefused()
|
||||
{
|
||||
// §2 (1.2) : lots interdits. Un lot reçu → UNE réponse -32600 à id null, aucun de ses messages
|
||||
// traité, connexion gardée. Le lot porte une requête dont la réponse est normée (UpdateLearned
|
||||
// avec id → -32601, §8) : la traiter se verrait.
|
||||
std::unique_ptr<Sonde> sonde;
|
||||
sonder(sonde);
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
ouvrirSession(*sonde, 1);
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
sonde->envoyer(R"([{"jsonrpc":"2.0","id":2,"method":"UpdateLearned","params":{}}])");
|
||||
QByteArray ligne;
|
||||
QVERIFY2(sonde->recevoir(&ligne), "lot : aucune réponse (ou connexion fermée)");
|
||||
QString e = verifierErreur(ligne, QJsonValue::Null, -32600);
|
||||
QVERIFY2(e.isEmpty(), qUtf8Printable(e));
|
||||
// Connexion gardée, et la ligne suivante répond à la requête suivante — pas au contenu du lot.
|
||||
sonde->envoyer(R"({"jsonrpc":"2.0","id":3,"method":"UpdateLearned","params":{}})");
|
||||
QVERIFY2(sonde->recevoir(&ligne), "après le lot : aucune réponse (ou connexion fermée)");
|
||||
e = verifierErreur(ligne, 3, -32601);
|
||||
QVERIFY2(e.isEmpty(), qUtf8Printable(e));
|
||||
}
|
||||
|
||||
void TestOptimizerConformance::testALineOverTheLimitIsRefused()
|
||||
{
|
||||
// §2 (1.2) : maxLineBytes dans les deux sens, '\n' non compté. Un octet de trop → -32600 à id
|
||||
// null, la ligne n'est pas traitée, connexion gardée ; une ligne d'exactement maxLineBytes est
|
||||
// lue normalement.
|
||||
std::unique_ptr<Sonde> sonde;
|
||||
sonder(sonde);
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
ouvrirSession(*sonde, 1);
|
||||
if (QTest::currentTestFailed())
|
||||
return;
|
||||
auto ligneDe = [](int id, int taille) {
|
||||
QByteArray l = QStringLiteral(R"({"jsonrpc":"2.0","id":%1,"method":"UpdateLearned","params":{}})").arg(id).toUtf8();
|
||||
return l + QByteArray(taille - l.size(), ' ');
|
||||
};
|
||||
sonde->envoyer(ligneDe(2, OptimizerClient::kMaxLineBytes + 1));
|
||||
QByteArray ligne;
|
||||
QVERIFY2(sonde->recevoir(&ligne, 10000), "ligne trop longue : aucune réponse (ou connexion fermée)");
|
||||
QString e = verifierErreur(ligne, QJsonValue::Null, -32600);
|
||||
QVERIFY2(e.isEmpty(), qUtf8Printable(e));
|
||||
sonde->envoyer(ligneDe(3, OptimizerClient::kMaxLineBytes));
|
||||
QVERIFY2(sonde->recevoir(&ligne, 10000), "ligne d'exactement maxLineBytes : aucune réponse (ou connexion fermée)");
|
||||
e = verifierErreur(ligne, 3, -32601);
|
||||
QVERIFY2(e.isEmpty(), qUtf8Printable(e));
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestOptimizerConformance)
|
||||
#include "testconformance.moc"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user