#!/usr/bin/env python3 """Refuse un déclencheur d'arbitrage câblé sur l'HORLOGE MURALE hors garde de simulation. Pourquoi cette vérification existe ---------------------------------- `setPhasePowerLimit()` lançait un cycle d'arbitrage complet avec `QDateTime::currentDateTime()`, sans la garde `ENERGY_SIMULATION` que ses voisins ont. Dans une suite qui injecte un temps SIMULÉ, les deux horloges se mélangent alors dans les verrous : l'armement paresseux (ECS-412) fige `m_lastSwitch` sur l'heure qu'il est, et un scénario daté en dur se déroule « avant » son propre armement. **Le test passait ou échouait selon l'heure de la journée.** Il n'a été trouvé que parce qu'il échouait cinq fois sur cinq — un défaut qui ne bascule qu'une fois sur vingt passerait pour une fragilité de temporisation, et serait relancé jusqu'à ce qu'il passe. C'est un instrument dont le verdict ment selon l'heure, et rien dans la suite ne peut le dire d'elle-même : la vérification doit donc être STATIQUE. """ import re import sys from pathlib import Path RACINE = Path(__file__).resolve().parent.parent # Fonctions qui entraînent un CYCLE complet. En appeler une avec l'horloge murale est un # déclencheur réel, légitime en production et interdit en simulation. DECLENCHEURS = re.compile(r'\b(update|runSurplusPlanning|verifyOverloadProtection)\s*\(\s*QDateTime::currentDateTime\(\)') FICHIERS = list((RACINE / 'energyplugin').rglob('*.cpp')) def analyser(chemin): """Retourne les lignes fautives : un déclencheur hors de tout `#ifndef ENERGY_SIMULATION`.""" fautes = [] # Pile des conditions de préprocesseur : True quand on est dans une branche qui EXCLUT la # simulation. Le suivi est volontairement simple — il ne gère que ce que ce dépôt écrit. pile = [] for numero, ligne in enumerate(chemin.read_text(encoding='utf-8').splitlines(), 1): nu = ligne.strip() if nu.startswith('#ifndef ENERGY_SIMULATION'): pile.append(True) continue if nu.startswith('#ifdef ENERGY_SIMULATION'): pile.append(False) continue if nu.startswith('#if'): pile.append(False) continue if nu.startswith('#else') and pile: pile[-1] = not pile[-1] continue if nu.startswith('#endif') and pile: pile.pop() continue if nu.startswith('//'): continue if DECLENCHEURS.search(ligne) and not any(pile): fautes.append((numero, nu)) return fautes def principal(): total = [] for f in sorted(FICHIERS): for numero, texte in analyser(f): total.append((f.relative_to(RACINE), numero, texte)) if not total: print("✓ aucun déclencheur d'arbitrage à l'horloge murale hors garde de simulation") return 0 print("✗ %d déclencheur(s) d'arbitrage câblé(s) sur l'HORLOGE MURALE sans garde :" % len(total)) for chemin, numero, texte in total: print(" %s:%d %s" % (chemin, numero, texte)) print() print(" Un cycle à l'horloge murale dans une suite à temps simulé mélange les deux horloges") print(" dans les verrous : le test passe ou échoue SELON L'HEURE DE LA JOURNÉE.") print(" → réparer : entourer l'appel de #ifndef ENERGY_SIMULATION / #endif, comme ses voisins.") return 1 if __name__ == '__main__': sys.exit(principal())