2121import shutil
2222import subprocess
2323import sys
24+ from datetime import datetime
2425from pathlib import Path
2526
2627# --- Configuration ---
@@ -170,27 +171,95 @@ def task_build(args):
170171def task_test (args ):
171172 """Run tests."""
172173 target = args .target
174+ artifacts_dir = PROJECT_ROOT / "docs" / "traceability" / "artifacts" / "latest"
175+
176+ def run_logged (cmd : list [str ], log_path : Path , cwd : Path , title : str , check : bool ):
177+ info (title )
178+ log_path .parent .mkdir (parents = True , exist_ok = True )
179+ with open (log_path , "w" ) as log_file :
180+ start = datetime .utcnow ().isoformat () + "Z"
181+ log_file .write (f"=== START { start } ===\n " )
182+ try :
183+ proc = subprocess .Popen (
184+ cmd ,
185+ cwd = cwd ,
186+ stdout = subprocess .PIPE ,
187+ stderr = subprocess .STDOUT ,
188+ text = True ,
189+ )
190+ assert proc .stdout is not None
191+ for line in proc .stdout :
192+ ts = datetime .utcnow ().isoformat () + "Z"
193+ log_file .write (f"[{ ts } ] { line } " )
194+ proc .wait ()
195+ end = datetime .utcnow ().isoformat () + "Z"
196+ log_file .write (f"=== END { end } (exit { proc .returncode } ) ===\n " )
197+ if check and proc .returncode != 0 :
198+ error (f"Command failed: { ' ' .join (cmd )} " )
199+ return proc .returncode == 0
200+ except subprocess .CalledProcessError as e :
201+ if check :
202+ error (f"Command failed: { ' ' .join (cmd )} \n { e } " )
203+ return False
204+ except FileNotFoundError :
205+ warn (f"Command not found: { cmd [0 ]} " )
206+ return False
207+ except KeyboardInterrupt :
208+ print ("\n Interrupted." )
209+ sys .exit (130 )
173210
174211 # Unit Tests
175212 if target == "unit" or target == "all" :
176213 log ("=== Unit Tests ===" )
214+ artifacts_dir .mkdir (parents = True , exist_ok = True )
177215
178216 # 1. C++
179217 ctest = shutil .which ("ctest" )
180218 if ctest :
181- run ([ctest , "--output-on-failure" ], cwd = BUILD_DIR , title = "C++ Core Tests" )
219+ run_logged (
220+ [ctest , "--output-on-failure" ],
221+ artifacts_dir / "ctest.log" ,
222+ BUILD_DIR ,
223+ "C++ Core Tests" ,
224+ True ,
225+ )
182226 else :
183227 warn ("ctest not found" )
184228
185229 # 2. Python
186230 log ("Python Tests..." )
187- uv_run (["python" , "-m" , "pytest" ], cwd = PYTHON_BINDING_DIR )
231+ pytest_log = artifacts_dir / "pytest.log"
232+ pytest_junit = artifacts_dir / "pytest-junit.xml"
233+ uv_run (
234+ [
235+ "python" ,
236+ "-m" ,
237+ "pytest" ,
238+ "--log-file" ,
239+ str (pytest_log ),
240+ "--log-file-level" ,
241+ "INFO" ,
242+ "--log-format" ,
243+ "%(asctime)s %(levelname)s %(name)s %(message)s" ,
244+ "--log-date-format" ,
245+ "%Y-%m-%dT%H:%M:%S" ,
246+ "--junitxml" ,
247+ str (pytest_junit ),
248+ ],
249+ cwd = PYTHON_BINDING_DIR ,
250+ )
188251
189252 # 3. Node.js
190253 if NODE_DIR .exists ():
191254 log ("Node.js Tests..." )
192255 npm = "npm.cmd" if sys .platform == "win32" else "npm"
193- run ([npm , "test" ], cwd = NODE_DIR , check = False , title = "Node.js Tests" )
256+ run_logged (
257+ [npm , "test" ],
258+ artifacts_dir / "node-test.log" ,
259+ NODE_DIR ,
260+ "Node.js Tests" ,
261+ False ,
262+ )
194263
195264 # Reliability/System Tests
196265 if target == "verify" or target == "all" :
@@ -202,6 +271,23 @@ def task_test(args):
202271 uv_cmd = ["--with" , "matplotlib" , "python" , str (script )]
203272 uv_run (uv_cmd , cwd = PROJECT_ROOT )
204273
274+ viz_report = (
275+ PROJECT_ROOT
276+ / "docs"
277+ / "traceability"
278+ / "artifacts"
279+ / "latest"
280+ / "verify_reliability_report.md"
281+ )
282+ viz = SCRIPTS_DIR / "visualize_results.py"
283+ uv_run (
284+ ["--with" , "matplotlib" , "python" , str (viz ), str (viz_report )],
285+ cwd = PROJECT_ROOT ,
286+ )
287+
288+ diagram = SCRIPTS_DIR / "generate_diagram.py"
289+ uv_run (["--with" , "matplotlib" , "python" , str (diagram )], cwd = PROJECT_ROOT )
290+
205291 linker = SCRIPTS_DIR / "link_test_ids.py"
206292 run (
207293 [sys .executable , str (linker )],
@@ -244,10 +330,19 @@ def task_viz(args):
244330 """Generate reports/charts."""
245331 log ("Running Visualization..." )
246332 script = SCRIPTS_DIR / "visualize_results.py"
247- report_file = PROJECT_ROOT / "docs" / "test_report.md"
333+ report_file = (
334+ PROJECT_ROOT
335+ / "docs"
336+ / "traceability"
337+ / "artifacts"
338+ / "latest"
339+ / "verify_reliability_report.md"
340+ )
248341
249342 if not report_file .exists ():
250- warn ("No test_report.md found. Run 'bridge.py test verify' first." )
343+ warn (
344+ "No verify_reliability_report.md found. Run 'bridge.py test verify' first."
345+ )
251346
252347 # We pass the log file path if needed, but the script defaults to test_report.md logic
253348 # Actually the script takes the report file as arg 1
0 commit comments