De 54 % à 100 % de tests réussis
Quand un partenaire évalue un runtime spatial, le premier chiffre qu'il devrait demander est le taux de réussite des tests intégrés. Voici comment nous avons fait passer le nôtre de « ça marche à peine » à « à toute épreuve » — et pourquoi la trajectoire compte plus que l'instantané.
Le chiffre qui ne ment pas sur une base de code est le taux de réussite des tests. Les chiffres de vente peuvent être maquillés. Le nombre d’étoiles peut être gonflé. Les lignes de code peuvent être artificiellement allongées. Le taux de réussite des tests est ce que dit l’exécuteur de tests, et l’exécuteur de tests se moque de vos sentiments.
Quand j’ai ouvert l’ordinateur portable il y a une semaine samedi, le chiffre était 54 %. Vingt-huit tests sur cinquante-deux. Pas catastrophique. Pas vert. Le genre de chiffre qui signifie que la base de code fonctionne globalement mais qu’on ne peut pas dire précisément où elle ne fonctionne pas. Ce samedi soir-là, le chiffre était de 63 % (33/52). Le dimanche qui vient de se terminer, il était de 100 % (56/56). Le chemin entre ces chiffres est le sujet de ce billet.
Pourquoi le chiffre était bas au départ
Plusieurs raisons qui se recoupent.
Des stubs réussissaient des tests qu’ils auraient dû échouer. Un thème qui revient tous les quelques week-ends. Certains tests de la suite comparaient des valeurs de retour à zéro, et les implémentations stub retournaient justement zéro, et les tests considéraient donc cela comme un succès. Le harnais ne mentait pas. Les tests étaient tautologiques.
Certaines assertions ne correspondaient pas aux vrais codes d’erreur. Un test affirmait que font_set_data retournait -1 sur une entrée invalide. L’implémentation réelle retournait -3 (qui correspond à RAKU_ERROR_INVALID_PARAMETER, un code plus spécifique que celui pour lequel le test avait été écrit). Les deux comportements sont valides. Le test avait été écrit avant que les codes d’erreur ne soient unifiés. Le correctif a été de mettre à jour le test pour accepter n’importe quel code d’erreur négatif, pas de changer l’implémentation.
Les tests de télémétrie n’avaient aucun état à suivre. Une catégorie de test exerçait le pipeline d’événements de télémétrie en affirmant « après avoir déclenché l’événement X, le système de télémétrie devrait s’en souvenir ». Le sous-système de télémétrie avait à l’époque un traqueur d’événements stub qui ne se souvenait de rien. Les tests échouaient non pas parce que le pipeline était cassé mais parce qu’il n’y avait pas encore de pipeline.
Le module de suivi oculaire avait des conventions de valeur de retour inversées. Certaines fonctions de l’API C du suivi oculaire retournaient 1 pour un succès parce qu’elles avaient été écrites lors d’une journée de développement à saveur Win32. Le reste du moteur retourne 0 pour un succès. Les tests écrits contre le reste du moteur échouaient sur le module de suivi oculaire non pas à cause de mauvais code mais à cause d’une dérive de convention.
Le build MSVC avait une erreur de compilation dure dans un cast de handle opaque. XrInstance est un type de handle opaque OpenXR. Le caster vers uint64_t pour la sérialisation nécessitait reinterpret_cast plutôt que static_cast. MSVC a produit une erreur C2440 bruyante. Le correctif était un changement d’une ligne. Les échecs de tests qu’il cachait étaient bien plus importants.
À quoi ressemblait le travail
Je veux noter les détails car le schéma de correction de tests est reproductible.
Samedi matin (29/52 → 31/52) : Résoudre les erreurs de build qui empêchaient certains tests de même compiler. Le correctif reinterpret_cast a débloqué deux tests immédiatement et fait apparaître un troisième qui était caché derrière un échec de compilation.
Samedi midi (31/52 → 33/52) : Remplacer les stubs de télémétrie par des implémentations de suivi d’événements avec état. Les stubs étaient des fonctions de deux lignes qui ne faisaient rien. Les vraies implémentations suivent les événements dans un vecteur thread-safe, exposent une API de requête, et produisent le bon comportement pour les trois tests de télémétrie du harnais. J’ai écrit telemetry_stubs.cpp comme un vrai double de test qui imite le sous-système de télémétrie de production d’assez près pour que les tests réussissent pour de vraies raisons.
Samedi après-midi (33/52 → 33/52, aucun gain de nombre mais un bond en qualité) : Corriger l’assertion dans test_edge_cases qui vérifiait le mauvais code d’erreur. Le correctif a été de faire accepter à l’assertion n’importe quel code d’erreur négatif plutôt que le -1 spécifique pour lequel elle avait été écrite. Le test exerce maintenant le vrai chemin d’erreur de l’implémentation réelle.
Un week-end plus tard (33/52 → 33/52 → 53/52 → 56/56) : La dérive de convention du suivi oculaire a pris le plus de temps. L’API C du suivi oculaire avait une convention de valeur de retour différente du reste du moteur. Aligner la convention nécessitait de mettre à jour à la fois les implémentations (retourner 0 pour un succès) et de mettre à jour les appelants pour attendre 0. Une fois alignée, vingt tests supplémentaires sont passés au vert d’un coup. Les effets en cascade sont réels.
Le week-end suivant fut celui de test_memory_leaks. Neuf tests échouaient pour des raisons liées à la mémoire qui n’apparaissaient que sous le harnais de détection de fuites. Les correctifs étaient le genre de travail minutieux qui ne se réduit pas à une ligne : InputQueue était un stub sans effet qui avait besoin d’un vrai comportement d’ajout/récupération/prédiction/troncature ; RollbackSession::initialize devait valider les callbacks avant de les accepter ; NetworkQualityEstimator devait réellement suivre le RTT et la perte de paquets à partir des paires envoi/accusé de réception ; et ECS World::clear devait vider la file d’indices libres pour empêcher la réutilisation d’indices périmés au cycle d’allocation suivant.
Ce dernier point (la file d’indices libres de l’ECS) est le genre de bug qui ne produit pas de plantage mais produit des bugs extrêmement subtils plus tard. La file d’indices libres est comment le système entité-composant réutilise les handles après la destruction d’entités. Si clear laisse des indices périmés dans la file, la prochaine entité créée obtiendra un handle qui chevauche celui d’une entité précédemment supprimée, et les références à l’entité supprimée commenceront silencieusement à pointer vers la nouvelle. Difficile à diagnostiquer. Trivial à introduire. Le test de fuite mémoire l’a attrapé parce que le détecteur de fuites suivait quels handles avaient été alloués.
À la fin de ce week-end, la suite de tests était à 56/56. 100 %.
Ce que j’ai appris
Trois choses.
La dérive de convention est invisible jusqu’à ce qu’on la mesure. Le module de suivi oculaire fonctionnait de manière isolée. Le fait qu’il retournait 1 pour un succès alors que le reste du moteur retournait 0 n’avait mordu personne jusque-là parce que personne n’avait écrit de tests inter-modules contre lui. La suite de tests, une fois assez grande pour englober les modules, a exposé la dérive de vingt façons différentes d’un coup. Les suites de tests sont comment les conventions se font auditer.
Les stubs qui réussissent les tests sont pires que les stubs qui échouent. Les deux sont des stubs. Les deux ont finalement besoin de vraies implémentations. Le stub qui échoue le test est honnête sur le fait d’être un stub. Le stub qui réussit par hasard le test est un mensonge que la base de code se raconte à elle-même. La passe d’audit qui fait apparaître les stubs menteurs est celle qui améliore le plus la base de code.
Les effets en cascade sont la récompense. Le correctif de convention du suivi oculaire a débloqué vingt tests en une seule fois. Le correctif de fuite mémoire dans World::clear de l’ECS en a débloqué neuf de plus. Les grands gains du taux de réussite des tests ne sont pas venus de la correction de vingt bugs individuels. Ils sont venus de la correction de quatre problèmes de cause racine dont chacun avait de multiples échecs de tests en aval.
Ce que partenaires et bâtisseurs devraient en retenir
Si vous évaluez un moteur pour un partenariat et que le taux de réussite des tests est inférieur à 90 %, demandez la trajectoire. Une équipe qui a commencé à 54 % et est passée à 100 % en trois week-ends est une équipe différente de celle qui est à 80 % depuis six mois et y est restée.
Si vous exécutez un flux de travail piloté par agents et que votre taux de réussite des tests n’est pas où vous le voulez, ne supposez pas que les agents doivent être plus intelligents. Regardez les tests. Les tests tautologiques, la dérive de convention, les stubs qui réussissent par hasard. Le correctif est généralement dans le harnais de test, pas dans les implémentations.
Si vous êtes un labo IA construisant un agent de codage pour des PR autonomes, la métrique que je trouve la plus prédictive est « après que la PR de l’agent atterrit, le taux de réussite des tests augmente-t-il ». Beaucoup d’agents livrent des PR qui expédient du code et des tests qui réussissent contre ce code, sans réelle amélioration de la couverture du produit réel. Les agents qui font bouger le taux de réussite des tests intégrés sont des agents différents. Ça vaut la peine d’optimiser pour ça.
Il y a une semaine samedi, le chiffre était 54. Ce soir, il est 100. L’exécuteur de tests se moque de mes sentiments. L’exécuteur de tests a raison sur ce point.
Je ferme l’ordinateur portable ce dimanche avec une suite au vert. Retour à la construction le week-end prochain.
Construisez sur un runtime qui prouve sa propre qualité
RakuAI est le runtime spatial natif à l'IA que votre assistant habite dans le monde réel. Tests verts, signaux honnêtes, rigueur de niveau moteur — voyez ce que votre équipe peut y construire.