quest'n'click 4.5.0 · r1

Construite pour quest'n'click 4.5.0, révision r1. Si elle est publiée, et laquelle est la courante, est dit par publication.json.

Tests de mutation

English · Type : procédure + explication · Public : contributeur · Contexte : dépôt du moteur · Statut : prête · S'applique depuis : 4.3.1

Des lignes exécutées par un test ne sont pas une preuve : les tests de mutation demandent si les tests distinguent un bon résultat d'un mauvais. Cette page dit comment quest'n'click s'y prend, et que faire quand un mutant survit à votre changement.

Ce que vous obtenez

Un ensemble de mutation qui repasse après votre changement : chaque mutant de ses fichiers est tué par un test, ou nommé dans docs/dev/mutants.json avec une catégorie, une raison, une preuve, un responsable et une version de relecture.

Pourquoi des tests de mutation, et pourquoi cet outil

Chaque mutant change une seule chose dans un fichier source : un opérateur de comparaison ou logique échangé (< pour <=, && pour ||), une négation retirée, une condition forcée à true ou false, un booléen inversé. Les tests qui jugent l'ensemble de ce fichier tournent contre lui, et l'un d'eux doit échouer. Un mutant qui survit est un test qui manque, ou un mutant équivalent qu'aucune entrée ne distingue de l'original.

L'outil est tools/mutate.ts, écrit pour le projet en 4.1.0 « Clarity » : Stryker 10 n'activait pas ses mutants sous Vitest ici (739 sur 749 survivaient, un bloc vidé compris), alors chaque mutant tourne en source ordinaire. Le fichier est restauré après chaque mutant, et à la sortie (Ctrl-C compris).

Un survivant non nommé fait échouer l'exécution, quel que soit le score. Il n'y a pas de seuil : un ensemble passe quand chaque survivant est expliqué, et une explication est une exemption avec ses conditions (plus bas), jamais « le score est assez bon ».

Contexte et versions

Le dépôt du moteur. L'outil depuis la 4.1.0, les ensembles et la barrière par identité depuis la 4.1.2, le découpage en morceaux et les conditions d'exemption depuis la 4.1.20 ; treize ensembles barrières en 4.4.0.

EnsembleMuteBarrière depuis
coresrc/engine/core/{cond,save,session-runtime,migrate}.ts4.1.2
realityla réception du moteur, le protocole, le client, le port HTTP ; le Bridge, son stockage de journal, son verrou et sa politique4.1.2
runsla file des speedruns : bridge/src/{runs,runs-store,runs-limiter}.ts4.1.17
remixla graine d'un monde, sa compilation, son application, ses catégories ; les jetons Daily et Mystery4.1.17
speedrunl'enveloppe d'un run, l'enregistreur, le vérificateur4.1.17
connectors-pure (connectors jusqu'à la 4.2)les parties pures des connecteurs : SDK, lecteur MIME, éditeur de ligne, disque virtuel, cryptographie et récupération des badges4.1.18
reality-storeles stockages du Bridge en mémoire, SQL et SQLite, migrations, flux, signature (pas l'adaptateur Postgres)4.1.18
connectors-edgeconfiguration et démarrage, le connecteur e-mail, la vérification des badges, garde et shell du terminal, SSH, Telnet (4 morceaux)4.1.20
reality-store-postgresbridge/src/store-postgres.ts, jugé sur un vrai Postgres (BRIDGE_PG_URL)4.1.20
studio-opsle journal du Studio Server, ses écritures durables et les pans des fichiers d'un jeu4.2.1
studio-authla barrière du Studio Server, le fournisseur OpenID, les routes et le stockage d'authentification, les baux des pans, l'origine du jeu4.2.1
studio-workshopl'annulation personnelle, la santé par révision, les aperçus, les builds, les assets préparés, la sauvegarde, le journal expédié et la collecte4.2.3
voicesles voix par valeur : les clés des clips d'une réplique, la table d'enregistrement et son CSV, la vérification du validateur ; les phrases du solveur4.3.1

Les ensembles et les fichiers de test qui jugent chacun sont dans tools/mutation-sets.ts (SETS, TESTS, GATED) ; MEASURED (les ensembles qui rapportent sans barrière) est vide depuis la 4.2.1. Où chacun tourne, c'est Quels tests tournent quand : une pull request étiquetée full-ci, chaque nuit (de zéro), le candidat et la release.

Prérequis

Où lancer les commandes

Dans le dépôt du moteur, sur votre machine ; ou sur un runner avec le workflow mutation, qui mesure un ensemble sur une référence et garde le rapport :

# depuis n'importe quel clone avec la CLI GitHub
gh workflow run mutation --ref main -f set=remix -f ref=<branche ou SHA> [-f file=<chemin>]

L'outil réécrit le fichier source qu'il mute : ne le modifiez pas pendant une exécution.

Étapes

  1. Mutez le fichier que vous avez changé, jugé par les tests de son ensemble :

    # dans le dépôt du moteur
    npm run test:mutation:core -- --file=src/engine/core/cond.ts
    

    Il affiche chaque mutant survivant sous la forme survived <fichier>:<ligne> <opérateur>: <avant> → <après>, puis le score de chaque fichier, puis ✖ not in docs/dev/mutants.json: … pour chaque survivant que personne n'a nommé.

  1. Ou lancez l'ensemble entier, comme la CI :

    # dans le dépôt du moteur
    npm run test:mutation:core -- --set=core          # l'ensemble par défaut
    npm run test:mutation:reality                     # équivaut à --set=reality
    npm run test:mutation:core -- --set=all           # chaque ensemble barrière, l'un après l'autre
    

    Un rapport dont les entrées n'ont pas changé (inputHash : les sources et les tests de l'ensemble, tout ce qu'ils importent, les deux configurations, l'outil, les survivants nommés, le fichier de verrouillage sans ses champs de version) est réutilisé : l'exécution le dit et s'arrête. --fresh relance quand même. Les rapports sont écrits dans .cache/mutation/report-<ensemble>.json.

  1. Tuez le survivant avec un test, la réponse habituelle : écrivez l'entrée qui distingue le mutant de l'original, dans un fichier de test que TESTS liste déjà pour l'ensemble (ou ajoutez-y le fichier). Relancez l'étape 1 : le mutant est tué.
  1. Ou nommez-le, quand aucune entrée ne peut le distinguer. Ajoutez une entrée à docs/dev/mutants.json (en anglais, comme toute la documentation de développement) :

    {
      "file": "src/engine/core/session-runtime.ts",
      "operator": "condition true",
      "from": "this.open.length",
      "to": "true",
      "context": "return this.open.length ? this.open[this.open.length - 1] : undefined;",
      "why": "`cur`: with no open entry, `this.open[-1]` is undefined, which is what the condition returned",
      "category": "equivalent",
      "proof": "argument",
      "owner": "@wanoo",
      "revisit": "4.6.0"
    }
    
    • category : equivalent, unreachable, instrumentation ou out-of-contract.
    • why : plus de 20 caractères qui le disent.
    • proof : argument (la raison est la preuve ; un mutant équivalent seulement), ou un fichier de test sous tests/ qui existe et fixe ce qui le rend tel.
    • owner, et revisit : la version à partir de laquelle l'entrée est refusée tant qu'elle n'a pas été relue.
    • context : la ligne source sans ses espaces, pour que l'entrée ne nomme que le mutant de cette ligne.

    Puis régénérez la table de docs/dev/MUTANTS.md et relancez l'ensemble :

    # dans le dépôt du moteur
    npm run test:mutation:core -- --doc
    npm run test:mutation:core -- --set=<ensemble> --fresh
    

    docs/dev/mutants.json est un fichier que relit le mainteneur (CODEOWNERS) : dites dans la pull request pourquoi chaque entrée est équivalente.

Limite · revisit-due Une entrée dont la version revisit est atteinte arrête chaque ensemble, quel que soit son fichier : « docs/dev/mutants.json: n entries do not hold … to be read again at x.y.z ». En 4.4.0, les 132 entrées disent toutes revisit: 4.5.0 : dès que package.json dit 4.5.0 (ses pré-versions comprises), chaque exécution de mutation refuse tant que chaque entrée n'a pas été relue et son revisit déplacé.

Résultat attendu, et comment le vérifier

✔ n/m mutants killed, k survivors explained, 0 not; report in .cache/mutation/…, et le code de sortie 0. Dans la CI, les jobs mutation (<ensemble>) de l'ensemble et mutation-merge sont verts.

Erreurs fréquentes

SymptômeCauseCorrection
✖ not in docs/dev/mutants.json: <fichier>:<ligne> …Un survivant que personne n'a nomméTuez-le avec un test (étape 3), ou nommez-le (étape 4)
⚠ named in docs/dev/mutants.json but killed or goneUne entrée dont le mutant ne survit plusRetirez l'entrée
✖ docs/dev/mutants.json: n entries do not holdUne entrée sans ses conditions, ou dont le revisit est passéComplétez-la, ou relisez-la et déplacez revisit
✖ --set=… mutates reality-store-postgres, which needs BRIDGE_PG_URLAucun Postgres donnéDéfinissez BRIDGE_PG_URL, ou lancez un autre ensemble
ℹ --set=connectors is --set=connectors-pure since 4.1.19L'ancien nomUtilisez le nouveau (l'alias marche jusqu'à la 4.2)
✖ <ensemble>: … nothing merged, the set is not judged (CI)Un morceau manquant, en double, annulé ou d'autres entréesRelancez les jobs de morceaux en échec, puis mutation-merge

Fichiers ou données modifiés

L'outil réécrit le fichier source muté pendant qu'il tourne et le restaure après chaque mutant et à la sortie ; il écrit ses rapports sous .cache/mutation/. Vous modifiez des fichiers de test, docs/dev/mutants.json et, par --doc, docs/dev/MUTANTS.md.

Revenir en arrière

Si une exécution a été tuée sans son gestionnaire de sortie (un kill -9, un plantage), regardez git status : un fichier source muté apparaît modifié ; git restore <fichier> le remet en place.

Références


Modifier la source de cette page · Signaler une erreur