22import os
33import pathlib
44import shutil
5+ import datetime
6+ import traceback
57
68import shortuuid
9+ from _pytest .runner import CallInfo
10+ from _pytest .reports import TestReport
711from pytest import (
812 Parser ,
913 Session ,
1014 FixtureRequest ,
1115 FixtureDef ,
1216 Item ,
17+ Collector ,
1318 Config ,
1419 CollectReport ,
1520)
1823from pytest_testconfig import config as py_config
1924
2025from utilities .constants import KServeDeploymentType
26+ from utilities .database import Database
2127from utilities .logger import separator , setup_logging
22-
28+ from utilities .must_gather_collector import (
29+ get_must_gather_base_dir ,
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_must_gather_collector_base_directory ,
35+ )
2336
2437LOGGER = logging .getLogger (name = __name__ )
2538BASIC_LOGGER = logging .getLogger (name = "basic" )
@@ -31,7 +44,7 @@ def pytest_addoption(parser: Parser) -> None:
3144 runtime_group = parser .getgroup (name = "Runtime details" )
3245 upgrade_group = parser .getgroup (name = "Upgrade options" )
3346 platform_group = parser .getgroup (name = "Platform" )
34-
47+ must_gather_group = parser . getgroup ( name = "MustGather" )
3548 # AWS config and credentials options
3649 aws_group .addoption (
3750 "--aws-secret-access-key" ,
@@ -117,6 +130,11 @@ def pytest_addoption(parser: Parser) -> None:
117130 "--applications-namespace" ,
118131 help = "RHOAI/ODH applications namespace" ,
119132 )
133+ must_gather_group .addoption (
134+ "--collect-must-gather" ,
135+ help = "Indicate if must-gather should be collected on failure." ,
136+ action = "store_false" ,
137+ )
120138
121139
122140def pytest_cmdline_main (config : Any ) -> None :
@@ -200,6 +218,11 @@ def pytest_sessionstart(session: Session) -> None:
200218 log_file = tests_log_file ,
201219 log_level = session .config .getoption ("log_cli_level" ) or logging .INFO ,
202220 )
221+ set_must_gather_collector_values ()
222+ shutil .rmtree (
223+ get_must_gather_collector_base_directory (),
224+ ignore_errors = True ,
225+ )
203226
204227
205228def pytest_fixture_setup (fixturedef : FixtureDef [Any ], request : FixtureRequest ) -> None :
@@ -213,9 +236,23 @@ def pytest_runtest_setup(item: Item) -> None:
213236 2. Adds skip fixture for kserve if serverless or authorino operators are not installed.
214237 3. Adds skip fixture for serverless if authorino/serverless/service mesh are not deployed.
215238 """
216-
217239 BASIC_LOGGER .info (f"\n { separator (symbol_ = '-' , val = item .name )} " )
218240 BASIC_LOGGER .info (f"{ separator (symbol_ = '-' , val = 'SETUP' )} " )
241+ if item .config .getoption ("--collect-must-gather" ):
242+ # set must-gather collection directory:
243+ set_must_gather_collector_directory (item = item , directory_path = get_must_gather_collector_dir ())
244+
245+ # At the begining of setup work, insert current epoch time into the database to indicate test
246+ # start time
247+
248+ try :
249+ db = Database ()
250+ db .insert_test_start_time (
251+ test_name = f"{ item .fspath } ::{ item .name } " ,
252+ start_time = int (datetime .datetime .now ().strftime ("%s" )),
253+ )
254+ except Exception as db_exception :
255+ LOGGER .error (f"Database error: { db_exception } . Must-gather collection may not be accurate" )
219256
220257 if KServeDeploymentType .SERVERLESS .lower () in item .keywords :
221258 item .fixturenames .insert (0 , "skip_if_no_deployed_redhat_authorino_operator" )
@@ -239,6 +276,10 @@ def pytest_runtest_call(item: Item) -> None:
239276
240277def pytest_runtest_teardown (item : Item ) -> None :
241278 BASIC_LOGGER .info (f"{ separator (symbol_ = '-' , val = 'TEARDOWN' )} " )
279+ # reset must-gather collector after each tests
280+ py_config ["must_gather_collector" ]["collector_directory" ] = py_config ["must_gather_collector" ][
281+ "must_gather_base_directory"
282+ ]
242283
243284
244285def pytest_report_teststatus (report : CollectReport , config : Config ) -> None :
@@ -262,11 +303,54 @@ def pytest_report_teststatus(report: CollectReport, config: Config) -> None:
262303def pytest_sessionfinish (session : Session , exitstatus : int ) -> None :
263304 if session .config .option .setupplan or session .config .option .collectonly :
264305 return
265-
266- base_dir = py_config ["tmp_base_dir" ]
267- LOGGER .info (f"Deleting pytest base dir { base_dir } " )
268- shutil .rmtree (path = base_dir , ignore_errors = True )
306+ if session .config .getoption ("--collect-must-gather" ):
307+ db = Database ()
308+ file_path = db .database_file_path
309+ LOGGER .info (f"Removing database file path { file_path } " )
310+ os .remove (file_path )
311+ # clean up the empty folders
312+ collector_directory = py_config ["must_gather_collector" ]["must_gather_base_directory" ]
313+ if os .path .exists (collector_directory ):
314+ for root , dirs , files in os .walk (collector_directory , topdown = False ):
315+ for _dir in dirs :
316+ dir_path = os .path .join (root , _dir )
317+ if not os .listdir (dir_path ):
318+ shutil .rmtree (dir_path , ignore_errors = True )
319+ LOGGER .info (f"Deleting pytest base dir { session .config .option .basetemp } " )
320+ shutil .rmtree (path = session .config .option .basetemp , ignore_errors = True )
269321
270322 reporter : Optional [TerminalReporter ] = session .config .pluginmanager .get_plugin ("terminalreporter" )
271323 if reporter :
272324 reporter .summary_stats ()
325+
326+
327+ def calculate_must_gather_timer (test_start_time : int ) -> int :
328+ default_duration = 300
329+ if test_start_time > 0 :
330+ return int (datetime .datetime .now ().strftime ("%s" )) - test_start_time
331+ else :
332+ LOGGER .warning (f"Could not get start time of test. Collecting must-gather for last { default_duration } s" )
333+ return default_duration
334+
335+
336+ def pytest_exception_interact (node : Item | Collector , call : CallInfo [Any ], report : TestReport | CollectReport ) -> None :
337+ BASIC_LOGGER .error (report .longreprtext )
338+ if node .config .getoption ("--collect-must-gather" ):
339+ test_name = f"{ node .fspath } ::{ node .name } "
340+ LOGGER .info (f"Must-gather collection is enabled for { test_name } ." )
341+
342+ try :
343+ db = Database ()
344+ test_start_time = db .get_test_start_time (test_name = test_name )
345+ except Exception as db_exception :
346+ test_start_time = 0
347+ LOGGER .warning (f"Error: { db_exception } in accessing database." )
348+
349+ try :
350+ collection_dir = os .path .join (get_must_gather_base_dir (), "pytest_exception_interact" )
351+ collect_rhoai_must_gather (
352+ since = calculate_must_gather_timer (test_start_time = test_start_time ), target_dir = collection_dir
353+ )
354+
355+ except Exception as current_exception :
356+ LOGGER .warning (f"Failed to collect logs: { test_name } : { current_exception } { traceback .format_exc ()} " )
0 commit comments