44import os
55import pathlib
66import shutil
7+ import datetime
8+ import traceback
79
810import shortuuid
11+ from _pytest .runner import CallInfo
12+ from _pytest .reports import TestReport
913from pytest import (
1014 Parser ,
1115 Session ,
1216 FixtureRequest ,
1317 FixtureDef ,
1418 Item ,
19+ Collector ,
1520 Config ,
1621 CollectReport ,
1722)
1823from _pytest .terminal import TerminalReporter
1924from typing import Optional , Any
2025from pytest_testconfig import config as py_config
21-
26+ from utilities . database import Database
2227from utilities .constants import KServeDeploymentType
2328from utilities .logger import separator , setup_logging
24-
29+ from utilities .must_gather_collector import (
30+ set_must_gather_collector_directory ,
31+ set_must_gather_collector_values ,
32+ get_must_gather_collector_dir ,
33+ collect_rhoai_must_gather ,
34+ get_base_dir ,
35+ )
2536
2637LOGGER = logging .getLogger (name = __name__ )
2738BASIC_LOGGER = logging .getLogger (name = "basic" )
@@ -194,14 +205,26 @@ def _add_upgrade_test(_item: Item, _upgrade_deployment_modes: list[str]) -> bool
194205
195206
196207def pytest_sessionstart (session : Session ) -> None :
197- tests_log_file = session .config .getoption ("log_file" ) or "pytest-tests.log"
208+ log_file = session .config .getoption ("log_file" ) or "pytest-tests.log"
209+ tests_log_file = os .path .join (get_base_dir (), log_file )
210+ LOGGER .info (f"Writing tests log to { tests_log_file } " )
198211 if os .path .exists (tests_log_file ):
199212 pathlib .Path (tests_log_file ).unlink ()
200-
213+ if session .config .getoption ("--collect-must-gather" ):
214+ session .config .option .must_gather_db = Database ()
201215 session .config .option .log_listener = setup_logging (
202216 log_file = tests_log_file ,
203217 log_level = session .config .getoption ("log_cli_level" ) or logging .INFO ,
204218 )
219+ must_gather_dict = set_must_gather_collector_values ()
220+ shutil .rmtree (
221+ path = must_gather_dict ["must_gather_base_directory" ],
222+ ignore_errors = True ,
223+ )
224+ config = session .config
225+ if config .getoption ("--collect-only" ) or config .getoption ("--setup-plan" ):
226+ LOGGER .info ("Skipping global config update for collect-only or setup-plan" )
227+ return
205228
206229 if py_config .get ("distribution" ) == "upstream" :
207230 py_config ["applications_namespace" ] = "opendatahub"
@@ -220,6 +243,21 @@ def pytest_runtest_setup(item: Item) -> None:
220243
221244 BASIC_LOGGER .info (f"\n { separator (symbol_ = '-' , val = item .name )} " )
222245 BASIC_LOGGER .info (f"{ separator (symbol_ = '-' , val = 'SETUP' )} " )
246+ if item .config .getoption ("--collect-must-gather" ):
247+ # set must-gather collection directory:
248+ set_must_gather_collector_directory (item = item , directory_path = get_must_gather_collector_dir ())
249+
250+ # At the begining of setup work, insert current epoch time into the database to indicate test
251+ # start time
252+
253+ try :
254+ db = item .config .option .must_gather_db
255+ db .insert_test_start_time (
256+ test_name = f"{ item .fspath } ::{ item .name } " ,
257+ start_time = int (datetime .datetime .now ().timestamp ()),
258+ )
259+ except Exception as db_exception :
260+ LOGGER .error (f"Database error: { db_exception } . Must-gather collection may not be accurate" )
223261
224262 if KServeDeploymentType .SERVERLESS .lower () in item .keywords :
225263 item .fixturenames .insert (0 , "skip_if_no_deployed_redhat_authorino_operator" )
@@ -240,6 +278,10 @@ def pytest_runtest_call(item: Item) -> None:
240278
241279def pytest_runtest_teardown (item : Item ) -> None :
242280 BASIC_LOGGER .info (f"{ separator (symbol_ = '-' , val = 'TEARDOWN' )} " )
281+ # reset must-gather collector after each tests
282+ py_config ["must_gather_collector" ]["collector_directory" ] = py_config ["must_gather_collector" ][
283+ "must_gather_base_directory"
284+ ]
243285
244286
245287def pytest_report_teststatus (report : CollectReport , config : Config ) -> None :
@@ -261,13 +303,59 @@ def pytest_report_teststatus(report: CollectReport, config: Config) -> None:
261303
262304
263305def pytest_sessionfinish (session : Session , exitstatus : int ) -> None :
306+ session .config .option .log_listener .stop ()
264307 if session .config .option .setupplan or session .config .option .collectonly :
265308 return
266-
267- base_dir = py_config ["tmp_base_dir" ]
268- LOGGER .info (f"Deleting pytest base dir { base_dir } " )
269- shutil .rmtree (path = base_dir , ignore_errors = True )
309+ if session .config .getoption ("--collect-must-gather" ):
310+ db = session .config .option .must_gather_db
311+ file_path = db .database_file_path
312+ LOGGER .info (f"Removing database file path { file_path } " )
313+ if os .path .exists (file_path ):
314+ os .remove (file_path )
315+ # clean up the empty folders
316+ collector_directory = py_config ["must_gather_collector" ]["must_gather_base_directory" ]
317+ if os .path .exists (collector_directory ):
318+ for root , dirs , files in os .walk (collector_directory , topdown = False ):
319+ for _dir in dirs :
320+ dir_path = os .path .join (root , _dir )
321+ if not os .listdir (dir_path ):
322+ shutil .rmtree (path = dir_path , ignore_errors = True )
323+ LOGGER .info (f"Deleting pytest base dir { session .config .option .basetemp } " )
324+ shutil .rmtree (path = session .config .option .basetemp , ignore_errors = True )
270325
271326 reporter : Optional [TerminalReporter ] = session .config .pluginmanager .get_plugin ("terminalreporter" )
272327 if reporter :
273328 reporter .summary_stats ()
329+
330+
331+ def calculate_must_gather_timer (test_start_time : int ) -> int :
332+ default_duration = 300
333+ if test_start_time > 0 :
334+ duration = int (datetime .datetime .now ().timestamp ()) - test_start_time
335+ return duration if duration > 60 else default_duration
336+ else :
337+ LOGGER .warning (f"Could not get start time of test. Collecting must-gather for last { default_duration } s" )
338+ return default_duration
339+
340+
341+ def pytest_exception_interact (node : Item | Collector , call : CallInfo [Any ], report : TestReport | CollectReport ) -> None :
342+ LOGGER .error (report .longreprtext )
343+ if node .config .getoption ("--collect-must-gather" ):
344+ test_name = f"{ node .fspath } ::{ node .name } "
345+ LOGGER .info (f"Must-gather collection is enabled for { test_name } ." )
346+
347+ try :
348+ db = node .config .option .must_gather_db
349+ test_start_time = db .get_test_start_time (test_name = test_name )
350+ except Exception as db_exception :
351+ test_start_time = 0
352+ LOGGER .warning (f"Error: { db_exception } in accessing database." )
353+
354+ try :
355+ collect_rhoai_must_gather (
356+ since = calculate_must_gather_timer (test_start_time = test_start_time ),
357+ target_dir = os .path .join (get_must_gather_collector_dir (), "pytest_exception_interact" ),
358+ )
359+
360+ except Exception as current_exception :
361+ LOGGER .warning (f"Failed to collect logs: { test_name } : { current_exception } { traceback .format_exc ()} " )
0 commit comments