@@ -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,46 @@ def _count_yaml_results(path):
7274 return counter
7375
7476
77+ def _get_global_logs_path ():
78+ """Get the path of the global log storage 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+ """
88+ Append logs to the global log storage file (survives reboots).
89+ Only for TMT and plain reporting.
90+ """
91+ if have_atex_api ():
92+ return # ATEX handles logs itself via partial results
93+ logs_file = _get_global_logs_path ()
94+ # read existing logs
95+ if logs_file .exists ():
96+ with open (logs_file ) as f :
97+ existing = json .load (f )
98+ else :
99+ existing = []
100+ # append new logs and write back
101+ existing .extend (new_logs )
102+ with open (logs_file , 'w' ) as f :
103+ json .dump (existing , f )
104+
105+
106+ def _read_from_global_logs ():
107+ """Read all logs from the global log storage file. Only for TMT and plain reporting."""
108+ if have_atex_api ():
109+ return [] # ATEX handles logs itself via partial results
110+ logs_file = _get_global_logs_path ()
111+ if logs_file .exists ():
112+ with open (logs_file ) as f :
113+ return json .load (f )
114+ return []
115+
116+
75117def report_atex (status , name = None , note = None , logs = None , * , partial = False ):
76118 if not partial :
77119 report_plain (status , name , note , logs )
@@ -160,7 +202,9 @@ def report_tmt(status, name=None, note=None, logs=None, *, add_output=True):
160202 for log in logs :
161203 log = Path (log )
162204 dstfile = dst / log .name
163- shutil .copyfile (log , dstfile )
205+ # Only copy if not already present (add_log() may have already copied it)
206+ if not dstfile .exists ():
207+ shutil .copyfile (log , dstfile )
164208 log_entries .append (str (dstfile .relative_to (test_data )))
165209 # add an empty log if none are present, to work around Testing Farm
166210 # and its Oculus result viewer expecting at least something
@@ -232,8 +276,9 @@ def add_log(*logs):
232276
233277 The log file(s) will be processed immediately:
234278 - For ATEX: uploaded incrementally using report_atex() with partial=True
235- - For TMT: copied to the TMT data directory and stored in global_logs for later upload
236- - For plain: stored in global_logs for later upload
279+ - For TMT: copied to the TMT data directory and stored in global log storage
280+ file to survive reboots
281+ - For plain: stored in global log storage file to survive reboots
237282
238283 This allows logs to be added incrementally throughout the test,
239284 and ensures they're available even if the test later fails with a traceback.
@@ -250,13 +295,15 @@ def add_log(*logs):
250295 report_atex (status = 'error' , logs = logs , partial = True )
251296 elif have_tmt_api ():
252297 test_data = Path (os .environ ['TMT_TEST_DATA' ])
298+ new_logs = []
253299 for log in logs :
254300 log = Path (log )
255301 dstfile = test_data / log .name
256302 shutil .copyfile (log , dstfile )
257- global_logs .append (str (dstfile .relative_to (test_data )))
303+ new_logs .append (str (dstfile .relative_to (test_data )))
304+ _store_to_global_logs (new_logs )
258305 else :
259- global_logs . extend ( str (log ) for log in logs )
306+ _store_to_global_logs ([ str (log ) for log in logs ] )
260307
261308
262309def report_and_exit (status = None , note = None , logs = None ):
@@ -280,8 +327,9 @@ def report_and_exit(status=None, note=None, logs=None):
280327 else :
281328 status = 'pass'
282329
283- # combine accumulated logs with any directly passed logs
284- all_logs = (global_logs + logs ) if logs else global_logs
330+ # read logs from global log storage file and combine with directly passed logs
331+ stored_logs = _read_from_global_logs ()
332+ all_logs = (stored_logs + logs ) if logs else stored_logs
285333
286334 # report and pass the status through the waiving logic, use combined logs or None if empty
287335 status = report (status = status , note = note , logs = (all_logs or None ))
0 commit comments