Files
Kill_LIFE/test/test_auto_check_ci_cd.py
rootandClaude Opus 4.6 e67fb754c2 feat: KiCad 10 native, QEMU simulation, compliance EU, firmware fixes
Session kxkm-ai 25-26 mars 2026 — 98/98 tâches TODO complétées.

Hardware:
- KiCad 10.0.0 native avec sym/fp-lib-table locales
- Nouveau block SPI (gen_spi_header.py), ERC clean
- Module partagé hardware/lib/kicad_gen.py (5 générateurs refactorisés)
- Pipeline export: tools/hw/hw_export.sh (ERC + SVG + PDF + netlist)
- KiBot 1.8.5 installé, .kibot.yaml configuré

Firmware:
- Fix I2S driver conflict: migration I2sMic vers nouvelle API i2s_channel_*
- Fix WDT risk: yield() dans CompletePushToTalk (voice_controller.cpp)
- Fix XSS wifi_manager.cpp: innerHTML → createElement/textContent
- Fix null check FwIsValidWavHeader
- Dead code supprimé: i2s_audio.cpp.bak, i2s_audio.h

Simulation MCU:
- QEMU ESP32-S3 v9.2.2 installé (tools/sim/)
- Script run_qemu_esp32s3.sh — boot OK vérifié
- [env:esp32s3_qemu] dans platformio.ini
- Wokwi CI: wokwi.toml + diagram.json + scenario.yaml
- SPICE bridge POC: tools/sim/spice_bridge.py (ngspice → ADC/brownout)

Compliance:
- Profil iot_wifi_eu validé (16 standards, 8 evidence)
- 4 evidence remplis: risk_assessment, security_architecture,
  test_plan_radio_emc, supply_chain_declarations
- plan.yaml complété avec données produit réelles

CI:
- Job hardware-export (KiCad 10 ERC + SVG + PDF + netlist)
- Job firmware-sim (Wokwi, conditionnel WOKWI_CLI_TOKEN)
- evidence_pack.yml enrichi avec exports hardware

Docs & RAG:
- specs/02_arch.md complet (481 lignes, 4 ADR, diagrammes)
- docs/SIMULATION.md (3 niveaux: native, QEMU, Wokwi)
- 6 chunks ingérés dans kb-kicad RAG
- Dataset HF: tools/generate_hf_dataset.py + datasets/kb_kicad_qa.jsonl
- Rapport analyse: docs/plans/ANALYSIS_REPORT_2026-03-25.md
- Recherche OSS: docs/research/oss_similar_projects.md

Infra:
- ZeroClaw 0.1.7 installé (cargo install)
- APIFY_API_KEY configurée, smoke OK
- Tests: 39/39 firmware + 26/26 Python stable

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-26 09:52:00 +01:00

266 lines
11 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from tools import auto_check_ci_cd
class AutoCheckCiCdTests(unittest.TestCase):
def sample_evidence_summary(self, lane: str) -> dict | None:
summaries = {
"esp": {
"required_files": [
"build.result.json",
"build.stdout.txt",
"build.stderr.txt",
],
"missing": [],
},
"linux": {
"required_files": [
"test.result.json",
"test.stdout.txt",
"test.stderr.txt",
],
"missing": ["test.stderr.txt", "artifacts"],
},
}
return summaries.get(lane)
def sample_drift_evidence_summary(self, lane: str) -> dict | None:
summaries = {
"esp": self.sample_evidence_summary("esp"),
"linux": {
"required_files": [
"test.result.json",
"test.stdout.txt",
"test.stderr.txt",
],
"artifacts": [
"firmware/.pio/build/native/program",
"docs/evidence/linux/test.result.json",
"docs/evidence/linux/test.stdout.txt",
],
"missing": [],
"status": "ok",
},
}
return summaries.get(lane)
def sample_drift_report(self) -> dict:
report = self.sample_report()
report["targets"]["linux"] = [
{
"command": ["python", "tools/test_firmware.py", "linux"],
"returncode": 0,
"stdout": "Tests terminés pour linux",
"stderr": "",
},
{
"command": ["python", "tools/collect_evidence.py", "linux"],
"returncode": 0,
"stdout": f"Evidence pack généré pour linux: {auto_check_ci_cd.ROOT}/docs/evidence/linux",
"stderr": "",
},
{
"command": ["python", "tools/verify_evidence.py", "linux"],
"returncode": 1,
"stdout": "Artifacts manquants pour linux: ['firmware/.pio/build/native/program']",
"stderr": "",
},
]
return report
def sample_report(self) -> dict:
return {
"compliance": {
"command": ["python", "tools/compliance/validate.py", "--strict"],
"returncode": 0,
"stdout": "OK: compliance profile validated.",
"stderr": "",
},
"targets": {
"esp": [
{
"command": ["python", "tools/build_firmware.py", "esp"],
"returncode": 0,
"stdout": "Build terminé pour esp",
"stderr": "",
},
{
"command": ["python", "tools/collect_evidence.py", "esp"],
"returncode": 0,
"stdout": f"Evidence pack généré pour esp: {auto_check_ci_cd.ROOT}/docs/evidence/esp",
"stderr": "",
},
{
"command": ["python", "tools/verify_evidence.py", "esp"],
"returncode": 0,
"stdout": "Evidence pack trouvé pour esp: ['firmware/.pio/build/esp32s3_arduino/firmware.bin', 'firmware/.pio/build/esp32s3_arduino/firmware.elf', 'firmware/.pio/build/esp32s3_arduino/firmware.map', 'firmware/.pio/build/esp32s3_arduino']",
"stderr": "",
},
],
"linux": [
{
"command": ["python", "tools/test_firmware.py", "linux"],
"returncode": 1,
"stdout": "",
"stderr": "native test failed",
}
],
},
}
def test_render_markdown_summary_contains_lane_table_and_step_details(self):
with patch.object(
auto_check_ci_cd,
"load_evidence_summary",
side_effect=self.sample_evidence_summary,
):
summary = auto_check_ci_cd.render_markdown_summary(self.sample_report())
self.assertIn("# Kill_LIFE Evidence Pack Summary", summary)
self.assertIn("| compliance | `0` | ok |", summary)
self.assertIn("| esp | `0` | ok |", summary)
self.assertIn("| linux | `1` | failed (1) |", summary)
self.assertIn("## Focus failures", summary)
self.assertIn("| linux | `1` | `test_firmware` | native test failed |", summary)
self.assertIn("## Artifact summary", summary)
self.assertIn(
"| esp | ok | `4` | `firmware.bin`, `firmware.elf`, `+2` | `3` | `build.result.json`, `build.stdout.txt`, `build.stderr.txt` | - | - |",
summary,
)
self.assertIn(
"| linux | failed (1) | `0` | - | `3` | `test.result.json`, `test.stdout.txt`, `test.stderr.txt` | `test.stderr.txt`, `artifacts` | - |",
summary,
)
self.assertIn("## esp", summary)
self.assertIn("`build_firmware`", summary)
self.assertIn("Build terminé pour esp", summary)
self.assertIn("Evidence pack généré pour esp: docs/evidence/esp", summary)
self.assertIn("Evidence pack trouvé pour esp", summary)
self.assertNotIn(f"{auto_check_ci_cd.ROOT}/docs/evidence/esp", summary)
self.assertNotIn("firmware.elf', 'firmware.map'", summary)
def test_render_markdown_summary_omits_focus_failures_when_all_green(self):
report = self.sample_report()
report["targets"]["linux"][0]["returncode"] = 0
report["targets"]["linux"][0]["stderr"] = ""
report["targets"]["linux"][0]["stdout"] = "Tests terminés pour linux"
with patch.object(
auto_check_ci_cd,
"load_evidence_summary",
side_effect=self.sample_evidence_summary,
):
summary = auto_check_ci_cd.render_markdown_summary(report)
self.assertNotIn("## Focus failures", summary)
self.assertIn("| linux | `0` | ok |", summary)
def test_render_markdown_summary_marks_verify_drift_when_summary_still_ok(self):
with patch.object(
auto_check_ci_cd,
"load_evidence_summary",
side_effect=self.sample_drift_evidence_summary,
):
summary = auto_check_ci_cd.render_markdown_summary(self.sample_drift_report())
self.assertIn(
"| linux | failed (1) | `2` | `test.result.json`, `test.stdout.txt` | `3` | `test.result.json`, `test.stdout.txt`, `test.stderr.txt` | `program` | summary ok |",
summary,
)
def test_write_step_summary_writes_markdown_when_env_is_set(self):
report = self.sample_report()
with tempfile.TemporaryDirectory() as tmp:
summary_path = Path(tmp) / "summary.md"
markdown_path = Path(tmp) / "ci_cd_audit_summary.md"
with patch.object(auto_check_ci_cd, "MARKDOWN_REPORT_PATH", markdown_path), patch.dict(
os.environ, {auto_check_ci_cd.STEP_SUMMARY_ENV: str(summary_path)}, clear=False
), patch.object(
auto_check_ci_cd,
"load_evidence_summary",
side_effect=self.sample_evidence_summary,
):
auto_check_ci_cd.write_markdown_report(report)
written = auto_check_ci_cd.write_step_summary(report)
self.assertEqual(written, summary_path)
content = summary_path.read_text(encoding="utf-8")
self.assertIn("Kill_LIFE Evidence Pack Summary", content)
self.assertIn("Artifact snapshot: `docs/evidence/`", content)
self.assertEqual(content, markdown_path.read_text(encoding="utf-8"))
def test_write_markdown_report_writes_sidecar_in_docs_evidence(self):
report = self.sample_report()
with tempfile.TemporaryDirectory() as tmp:
markdown_path = Path(tmp) / "ci_cd_audit_summary.md"
with patch.object(auto_check_ci_cd, "MARKDOWN_REPORT_PATH", markdown_path), patch.object(
auto_check_ci_cd,
"load_evidence_summary",
side_effect=self.sample_evidence_summary,
):
written = auto_check_ci_cd.write_markdown_report(report)
self.assertEqual(written, markdown_path)
content = markdown_path.read_text(encoding="utf-8")
self.assertIn("# Kill_LIFE Evidence Pack Summary", content)
self.assertIn("| linux | `1` | failed (1) |", content)
self.assertIn("## Focus failures", content)
def test_compact_repo_paths_keeps_markdown_repo_relative(self):
signal = f"Evidence pack généré: {auto_check_ci_cd.ROOT}/docs/evidence/linux | prêt"
compacted = auto_check_ci_cd.markdown_signal(signal)
self.assertEqual(compacted, "Evidence pack généré: docs/evidence/linux \\| prêt")
def test_markdown_signal_compacts_artifact_lists(self):
signal = (
"Evidence pack trouvé pour linux: "
"['firmware/.pio/build/native']"
)
compacted = auto_check_ci_cd.markdown_signal(signal)
self.assertEqual(compacted, "Evidence pack trouvé pour linux")
def test_artifact_summary_rows_extract_counts_and_samples(self):
with patch.object(
auto_check_ci_cd,
"load_evidence_summary",
side_effect=self.sample_evidence_summary,
):
rows = auto_check_ci_cd.artifact_summary_rows(self.sample_report())
self.assertEqual(rows[0]["lane"], "esp")
self.assertEqual(rows[0]["status"], "ok")
self.assertEqual(len(rows[0]["source_artifacts"]), 4)
self.assertEqual(rows[0]["evidence_files"], ["build.result.json", "build.stdout.txt", "build.stderr.txt"])
self.assertEqual(rows[1]["missing"], ["test.stderr.txt", "artifacts"])
self.assertEqual(
auto_check_ci_cd.artifact_summary_sample(rows[0]["source_artifacts"]),
"`firmware.bin`, `firmware.elf`, `+2`",
)
def test_artifact_summary_rows_marks_drift_when_verify_fails_after_ok_summary(self):
with patch.object(
auto_check_ci_cd,
"load_evidence_summary",
side_effect=self.sample_drift_evidence_summary,
):
rows = auto_check_ci_cd.artifact_summary_rows(self.sample_drift_report())
self.assertEqual(rows[1]["status"], "failed (1)")
self.assertEqual(
rows[1]["source_artifacts"],
[
"docs/evidence/linux/test.result.json",
"docs/evidence/linux/test.stdout.txt",
],
)
self.assertEqual(rows[1]["evidence_files"], ["test.result.json", "test.stdout.txt", "test.stderr.txt"])
self.assertEqual(rows[1]["missing"], ["firmware/.pio/build/native/program"])
self.assertEqual(rows[1]["drift"], "summary ok")
if __name__ == "__main__":
unittest.main()