@@ -32,7 +32,9 @@ def total(self):
3232
3333
3434global_counts = Counter ()
35- global_logs = []
35+
36+ # file for storing global logs across reboots (only for TMT and plain reporting)
37+ GLOBAL_LOGS_FILE = '_global_logs.json'
3638
3739
3840def have_atex_api ():
@@ -72,6 +74,43 @@ def _count_yaml_results(path):
7274 return counter
7375
7476
77+ def _get_global_logs_path ():
78+ """Get the path for the logs persistence file. Only for TMT and plain reporting."""
79+ if have_tmt_api ():
80+ return Path (os .environ ['TMT_TEST_DATA' ]) / GLOBAL_LOGS_FILE
81+ else :
82+ # plain mode: use current working directory
83+ return Path (GLOBAL_LOGS_FILE )
84+
85+
86+ def _store_to_global_logs (new_logs ):
87+ """Append logs to the persistence file (survives reboots). Only for TMT and plain reporting."""
88+ if have_atex_api ():
89+ return # ATEX handles logs itself via partial results
90+ logs_file = _get_global_logs_path ()
91+ # read existing logs
92+ if logs_file .exists ():
93+ with open (logs_file ) as f :
94+ existing = json .load (f )
95+ else :
96+ existing = []
97+ # append new logs and write back
98+ existing .extend (new_logs )
99+ with open (logs_file , 'w' ) as f :
100+ json .dump (existing , f )
101+
102+
103+ def _read_from_global_logs ():
104+ """Read all logs from the persistence file. Only for TMT and plain reporting."""
105+ if have_atex_api ():
106+ return [] # ATEX handles logs itself via partial results
107+ logs_file = _get_global_logs_path ()
108+ if logs_file .exists ():
109+ with open (logs_file ) as f :
110+ return json .load (f )
111+ return []
112+
113+
75114def report_atex (status , name = None , note = None , logs = None , * , partial = False ):
76115 report_plain (status , name , note , logs )
77116
@@ -159,7 +198,9 @@ def report_tmt(status, name=None, note=None, logs=None, *, add_output=True):
159198 for log in logs :
160199 log = Path (log )
161200 dstfile = dst / log .name
162- shutil .copyfile (log , dstfile )
201+ # Only copy if not already present (add_log() may have already copied it)
202+ if not dstfile .exists ():
203+ shutil .copyfile (log , dstfile )
163204 log_entries .append (str (dstfile .relative_to (test_data )))
164205 # add an empty log if none are present, to work around Testing Farm
165206 # and its Oculus result viewer expecting at least something
@@ -231,8 +272,8 @@ def add_log(*logs):
231272
232273 The log file(s) will be processed immediately:
233274 - For ATEX: uploaded incrementally using report_atex() with partial=True
234- - For TMT: copied to the TMT data directory and stored in global_logs for later upload
235- - For plain: stored in global_logs for later upload
275+ - For TMT: copied to the TMT data directory and stored in global logs file to survive reboots
276+ - For plain: stored in global logs file to survive reboots
236277
237278 This allows logs to be added incrementally throughout the test,
238279 and ensures they're available even if the test later fails with a traceback.
@@ -249,13 +290,15 @@ def add_log(*logs):
249290 report_atex (status = 'error' , logs = logs , partial = True )
250291 elif have_tmt_api ():
251292 test_data = Path (os .environ ['TMT_TEST_DATA' ])
293+ new_logs = []
252294 for log in logs :
253295 log = Path (log )
254296 dstfile = test_data / log .name
255297 shutil .copyfile (log , dstfile )
256- global_logs .append (str (dstfile .relative_to (test_data )))
298+ new_logs .append (str (dstfile .relative_to (test_data )))
299+ _store_to_global_logs (new_logs )
257300 else :
258- global_logs . extend ( str (log ) for log in logs )
301+ _store_to_global_logs ([ str (log ) for log in logs ] )
259302
260303
261304def report_and_exit (status = None , note = None , logs = None ):
@@ -279,8 +322,9 @@ def report_and_exit(status=None, note=None, logs=None):
279322 else :
280323 status = 'pass'
281324
282- # combine accumulated logs with any directly passed logs
283- all_logs = (global_logs + logs ) if logs else global_logs
325+ # read logs from global logs file (survives reboots) and combine with directly passed logs
326+ stored_logs = _read_from_global_logs ()
327+ all_logs = (stored_logs + logs ) if logs else stored_logs
284328
285329 # report and pass the status through the waiving logic, use combined logs or None if empty
286330 status = report (status = status , note = note , logs = (all_logs or None ))
0 commit comments