From 1da8b5c37251cb506d0b0c4d09f7df89385b493b Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Wed, 20 May 2026 10:08:21 -0500 Subject: [PATCH 01/10] Modified a few attribute names in the CreateConfigResult data_class to help make their meaning more clear. --- src/integrationtest/data_classes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/integrationtest/data_classes.py b/src/integrationtest/data_classes.py index bf2bef1..7a5bd18 100755 --- a/src/integrationtest/data_classes.py +++ b/src/integrationtest/data_classes.py @@ -107,9 +107,9 @@ class integtest_params_for_predefined_dunedaq_config(integtest_param_base_class) @dataclass class CreateConfigResult: - config: integtest_param_base_class - config_dir: str - config_file: str + integtest_params: integtest_param_base_class + dunedaq_config_dir: str + dunedaq_config_file: str log_file: str data_dirs: list[str] tpstream_data_dirs: list[str] From 3db6558eb83371f21e07299188be773b8d7c76f9 Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Wed, 20 May 2026 10:09:43 -0500 Subject: [PATCH 02/10] Added the --remove-hdf5-files option in integrationtest_commandline.py to allow users to request that the HDF5 files created in integtests get removed at the end of the test. --- src/integrationtest/integrationtest_commandline.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/integrationtest/integrationtest_commandline.py b/src/integrationtest/integrationtest_commandline.py index ac9de62..bee1846 100755 --- a/src/integrationtest/integrationtest_commandline.py +++ b/src/integrationtest/integrationtest_commandline.py @@ -56,6 +56,13 @@ def pytest_addoption(parser): help="A phrase that, if found in run control messages, will trigger the printout of all RC messages", required=False ) + parser.addoption( + "--remove-hdf5-files", + action="store_true", + default=False, + help="Whether to remove HDF5 (data) files when the testing has finished", + required=False + ) def pytest_configure(config): for opt in ("--dunerc-path",): From 45412aeb89a6382719c6b6731df8c7a90a140fdb Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Wed, 20 May 2026 10:12:50 -0500 Subject: [PATCH 03/10] Added utility_functions.py as a place to provide functions that can be used by many/all integtests to accomplish common functionality. It will take over basic_checks.py --- src/integrationtest/utility_functions.py | 75 ++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/integrationtest/utility_functions.py diff --git a/src/integrationtest/utility_functions.py b/src/integrationtest/utility_functions.py new file mode 100644 index 0000000..9ad85cf --- /dev/null +++ b/src/integrationtest/utility_functions.py @@ -0,0 +1,75 @@ +import pytest +import os +import re +from integrationtest.verbosity_helper import IntegtestVerbosityLevels + +import functools +print = functools.partial(print, flush=True) # always flush print() output + +def basic_checks(run_dunerc, caplog, print_test_name: bool = True): + + # print out the name of the current test, if requested + if print_test_name and run_dunerc.verbosity_helper.compare_level(IntegtestVerbosityLevels.drunc_transitions): + # print the name of the current test + current_test = os.environ.get("PYTEST_CURRENT_TEST") + match_obj = re.search(r".*\[(.+)-run_.*rc.*\d].*", current_test) + if match_obj: + current_test = match_obj.group(1) + banner_line = re.sub(".", "=", current_test) + print(banner_line) + print(current_test) + print(banner_line) + + # Check that dunerc completed correctly + if run_dunerc.completed_process.returncode != 0: + fail_msg = f"The run control session returned a non-zero status code ({run_dunerc.completed_process.returncode})." + pytest.fail(fail_msg, pytrace=False) + + # Check that there weren't any warnings or errors during setup + setup_logs = caplog.get_records("setup") + if len(setup_logs) > 0: + fail_msg = f"One or more problems were encountered during the setup of the pytest: {setup_logs}" + pytest.fail(fail_msg, pytrace=False) + + +def remove_hdf5_files_if_requested(run_dunerc, integtest_requests_hdf5_file_removal: bool = False): + + # if either the integtest writer or the user running the test requested that the HDF5 files + # be removed at the end of the test, do that. + if run_dunerc.user_requests_hdf5_file_removal or integtest_requests_hdf5_file_removal: + pathlist_string = "" + filelist_string = "" + for data_file in run_dunerc.data_files: + filelist_string += " " + str(data_file) + if str(data_file.parent) not in pathlist_string: + pathlist_string += " " + str(data_file.parent) + for data_file in run_dunerc.tpset_files: + filelist_string += " " + str(data_file) + if str(data_file.parent) not in pathlist_string: + pathlist_string += " " + str(data_file.parent) + for data_file in run_dunerc.trmon_files: + filelist_string += " " + str(data_file) + if str(data_file.parent) not in pathlist_string: + pathlist_string += " " + str(data_file.parent) + + if pathlist_string and filelist_string: + if run_dunerc.verbosity_helper.compare_level(IntegtestVerbosityLevels.integtest_debug): + print("============================================") + print("Listing the hdf5 files before deleting them:") + print("============================================") + + os.system(f"df -h {pathlist_string}") + print("--------------------") + os.system(f"ls -alF {filelist_string}") + + for data_file in run_dunerc.data_files: + data_file.unlink() + for data_file in run_dunerc.tpset_files: + data_file.unlink() + for data_file in run_dunerc.trmon_files: + data_file.unlink() + + if run_dunerc.verbosity_helper.compare_level(IntegtestVerbosityLevels.integtest_debug): + print("--------------------") + os.system(f"df -h {pathlist_string}") + print("============================================") From 9f1696b056b7bfc9e58fda0f7fbebd4adb966c32 Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Wed, 20 May 2026 10:15:24 -0500 Subject: [PATCH 04/10] Modified integrationtest_drunc.py to start using the latest CreateConfigResult attribute names and store the value of the --remove-hdf5-files command-line option in the run_dunerc result so that it can be used by integtests and utility_functions::remove_hdf5_files_if_requested() --- src/integrationtest/integrationtest_drunc.py | 63 ++++++++++---------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/integrationtest/integrationtest_drunc.py b/src/integrationtest/integrationtest_drunc.py index ec7a09a..455eb11 100755 --- a/src/integrationtest/integrationtest_drunc.py +++ b/src/integrationtest/integrationtest_drunc.py @@ -423,9 +423,9 @@ def apply_update(obj, substitution): pass result = CreateConfigResult( - config=integtest_params, - config_dir=config_dir, - config_file=config_db, + integtest_params=integtest_params, + dunedaq_config_dir=config_dir, + dunedaq_config_file=config_db, log_file=logfile, data_dirs=rawdata_dirs, tpstream_data_dirs=tpstream_dirs, @@ -461,8 +461,8 @@ def run_dunerc(request, create_config_files, process_manager_type, tmp_path_fact integtest_verbosity_level = int(request.config.getoption("--integtest-verbosity")) if no_integtest_connsvc and \ - isinstance(create_config_files.config, integtest_params_for_generated_dunedaq_config): - create_config_files.config.connsvc_control = ConnSvcControl.NONE + isinstance(create_config_files.integtest_params, integtest_params_for_generated_dunedaq_config): + create_config_files.integtest_params.connsvc_control = ConnSvcControl.NONE run_dir = tmp_path_factory.mktemp("run") @@ -516,36 +516,36 @@ def run_dunerc(request, create_config_files, process_manager_type, tmp_path_fact # start the Connectivity Service, if requested (only supported for generated dune-daq configs, for now) connsvc_obj = None if ( - isinstance(create_config_files.config, integtest_params_for_generated_dunedaq_config) - and create_config_files.config.connsvc_control == ConnSvcControl.INTEGRATIONTEST + isinstance(create_config_files.integtest_params, integtest_params_for_generated_dunedaq_config) + and create_config_files.integtest_params.connsvc_control == ConnSvcControl.INTEGRATIONTEST ): # start connsvc if integtest_verbosity_level >= IntegtestVerbosityLevels.full_output: print( - f"Starting Connectivity Service on port {create_config_files.config.connsvc_port}" + f"Starting Connectivity Service on port {create_config_files.integtest_params.connsvc_port}" ) connsvc_env = os.environ.copy() - if create_config_files.config.connsvc_debug_level is not None: + if create_config_files.integtest_params.connsvc_debug_level is not None: connsvc_env["CONNECTION_FLASK_DEBUG"] = str( - create_config_files.config.connsvc_debug_level + create_config_files.integtest_params.connsvc_debug_level ) connsvc_log = open( run_dir - / f"log_{getpass.getuser()}_{create_config_files.config.daq_session_name}_connectivity-service.txt", + / f"log_{getpass.getuser()}_{create_config_files.integtest_params.daq_session_name}_connectivity-service.txt", "w", ) connsvc_obj = subprocess.Popen( - f"gunicorn -b 0.0.0.0:{create_config_files.config.connsvc_port} --workers=1 --worker-class=gthread --threads=2 --timeout 5000000000 --log-level=info connectivityserver.connectionflask:app".split(), + f"gunicorn -b 0.0.0.0:{create_config_files.integtest_params.connsvc_port} --workers=1 --worker-class=gthread --threads=2 --timeout 5000000000 --log-level=info connectivityserver.connectionflask:app".split(), stdout=connsvc_log, stderr=connsvc_log, env=connsvc_env, ) - elif create_config_files.config.connsvc_debug_level is not None: - set_session_env_var(str(create_config_files.config_file), create_config_files.config.config_session_name, - "CONNECTION_FLASK_DEBUG", create_config_files.config.connsvc_debug_level, overwrite=True) + elif create_config_files.integtest_params.connsvc_debug_level is not None: + set_session_env_var(str(create_config_files.dunedaq_config_file), create_config_files.integtest_params.config_session_name, + "CONNECTION_FLASK_DEBUG", create_config_files.integtest_params.connsvc_debug_level, overwrite=True) dunerc = request.config.getoption("--dunerc-path") if dunerc is None: @@ -588,13 +588,13 @@ class RunResult: temp_suffix = ".temp_saved" now = time.time() for file_obj in rawdata_dir.glob( - f"{create_config_files.config.op_env}_raw*.hdf5" + f"{create_config_files.integtest_params.op_env}_raw*.hdf5" ): print(f"Renaming raw data file from earlier test: {str(file_obj)}") new_name = str(file_obj) + temp_suffix file_obj.rename(new_name) for file_obj in rawdata_dir.glob( - f"{create_config_files.config.op_env}_raw*.hdf5{temp_suffix}" + f"{create_config_files.integtest_params.op_env}_raw*.hdf5{temp_suffix}" ): modified_time = file_obj.stat().st_mtime if (now - modified_time) > 3600: @@ -608,13 +608,13 @@ class RunResult: temp_suffix = ".temp_saved" now = time.time() for file_obj in tpset_dir.glob( - f"{create_config_files.config.op_env}_tp*.hdf5" + f"{create_config_files.integtest_params.op_env}_tp*.hdf5" ): print(f"Renaming TP data file from earlier test: {str(file_obj)}") new_name = str(file_obj) + temp_suffix file_obj.rename(new_name) for file_obj in tpset_dir.glob( - f"{create_config_files.config.op_env}_tp*.hdf5{temp_suffix}" + f"{create_config_files.integtest_params.op_env}_tp*.hdf5{temp_suffix}" ): modified_time = file_obj.stat().st_mtime if (now - modified_time) > 3600: @@ -628,13 +628,13 @@ class RunResult: temp_suffix = ".temp_saved" now = time.time() for file_obj in trmon_dir.glob( - f"{create_config_files.config.op_env}_trmon*.hdf5" + f"{create_config_files.integtest_params.op_env}_trmon*.hdf5" ): print(f"Renaming TRMon data file from earlier test: {str(file_obj)}") new_name = str(file_obj) + temp_suffix file_obj.rename(new_name) for file_obj in trmon_dir.glob( - f"{create_config_files.config.op_env}_trmon*.hdf5{temp_suffix}" + f"{create_config_files.integtest_params.op_env}_trmon*.hdf5{temp_suffix}" ): modified_time = file_obj.stat().st_mtime if (now - modified_time) > 3600: @@ -650,8 +650,8 @@ class RunResult: # 25-Mar-2026, KAB: use subprocess.Popen to manage the run control session so that we can # capture the console output and pass it back to the user for inspection and validation. popen_command_list = [dunerc] + dunerc_option_strings + [process_manager_type] \ - + [str(create_config_files.config_file)] + [str(create_config_files.config.config_session_name)] \ - + [str(create_config_files.config.daq_session_name)] + run_control_commands + + [str(create_config_files.dunedaq_config_file)] + [str(create_config_files.integtest_params.config_session_name)] \ + + [str(create_config_files.integtest_params.daq_session_name)] + run_control_commands rc_process = subprocess.Popen( popen_command_list, stdout=subprocess.PIPE, @@ -740,35 +740,35 @@ class RunResult: pass connsvc_obj.kill() - if create_config_files.config.attempt_cleanup: + if create_config_files.integtest_params.attempt_cleanup: print( "Checking for remaining gunicorn and drunc-controller processes", flush=True ) subprocess.run(["killall", "gunicorn", "drunc-controller"]) - result.confgen_config = create_config_files.config - result.config_session_name = create_config_files.config.config_session_name - result.daq_session_name = create_config_files.config.daq_session_name + result.confgen_config = create_config_files.integtest_params + result.config_session_name = create_config_files.integtest_params.config_session_name + result.daq_session_name = create_config_files.integtest_params.daq_session_name # 27-Feb-2026, KAB: the nanorc_commands return value can be removed once # all integtests have been changed to use "dunerc". result.nanorc_commands = run_control_commands result.dunerc_commands = run_control_commands result.run_dir = run_dir - result.config_dir = create_config_files.config_dir + result.dunedaq_config_dir = create_config_files.dunedaq_config_dir result.data_files = [] for rawdata_dir in rawdata_dirs: result.data_files += list( - rawdata_dir.glob(f"{create_config_files.config.op_env}_raw_*.hdf5") + rawdata_dir.glob(f"{create_config_files.integtest_params.op_env}_raw_*.hdf5") ) result.tpset_files = [] for tpset_dir in tpset_dirs: result.tpset_files += list( - tpset_dir.glob(f"{create_config_files.config.op_env}_tp_*.hdf5") + tpset_dir.glob(f"{create_config_files.integtest_params.op_env}_tp_*.hdf5") ) result.trmon_files = [] for trmon_dir in trmon_dirs: result.trmon_files += list( - trmon_dir.glob(f"{create_config_files.config.op_env}_trmon_*.hdf5") + trmon_dir.glob(f"{create_config_files.integtest_params.op_env}_trmon_*.hdf5") ) result.log_files = list(run_dir.glob("log_*.txt")) + list(run_dir.glob("log_*.log")) result.opmon_files = list(run_dir.glob(f"info*{result.daq_session_name}*.json")) @@ -776,6 +776,7 @@ class RunResult: # information in fine-tuning the allowed ranges in time-based checking of test results. result.daq_session_overall_time = time_after - time_before result.verbosity_helper = VerbosityHelper(integtest_verbosity_level) + result.user_requests_hdf5_file_removal = request.config.getoption("--remove-hdf5-files") if number_of_lines_printed_to_the_console > 0: print("---------- DRUNC Session END ----------", flush=True) print("", flush=True) From 54a78fa30f2ff5f6117c90ed27cfde09d7f3ae47 Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Wed, 20 May 2026 14:40:50 -0500 Subject: [PATCH 05/10] Changed the name of a function argument in utility_functions::remove_hdf5_files_if_requested() to try to make its meaning more clear --- src/integrationtest/utility_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/integrationtest/utility_functions.py b/src/integrationtest/utility_functions.py index 9ad85cf..32a85ca 100644 --- a/src/integrationtest/utility_functions.py +++ b/src/integrationtest/utility_functions.py @@ -32,11 +32,11 @@ def basic_checks(run_dunerc, caplog, print_test_name: bool = True): pytest.fail(fail_msg, pytrace=False) -def remove_hdf5_files_if_requested(run_dunerc, integtest_requests_hdf5_file_removal: bool = False): +def remove_hdf5_files_if_requested(run_dunerc, this_test_requests_hdf5_file_removal: bool = False): # if either the integtest writer or the user running the test requested that the HDF5 files # be removed at the end of the test, do that. - if run_dunerc.user_requests_hdf5_file_removal or integtest_requests_hdf5_file_removal: + if run_dunerc.user_requests_hdf5_file_removal or this_test_requests_hdf5_file_removal: pathlist_string = "" filelist_string = "" for data_file in run_dunerc.data_files: From c9f0c0f9e315826d5b253f170f4f3b1bd7e2c4d8 Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Tue, 26 May 2026 14:04:45 -0500 Subject: [PATCH 06/10] modified the --remove-hdf5-files pytest option to allow users to specify true, nothing, or false. In this way, HDF5 files can be kept in special situations. --- src/integrationtest/integrationtest_commandline.py | 6 +++--- src/integrationtest/utility_functions.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/integrationtest/integrationtest_commandline.py b/src/integrationtest/integrationtest_commandline.py index bee1846..cd90d70 100755 --- a/src/integrationtest/integrationtest_commandline.py +++ b/src/integrationtest/integrationtest_commandline.py @@ -58,9 +58,9 @@ def pytest_addoption(parser): ) parser.addoption( "--remove-hdf5-files", - action="store_true", - default=False, - help="Whether to remove HDF5 (data) files when the testing has finished", + action="store", + default=None, + help="Whether to remove HDF5 (data) files when the testing has finished (over-rides value in integtest)", required=False ) diff --git a/src/integrationtest/utility_functions.py b/src/integrationtest/utility_functions.py index 32a85ca..87653a2 100644 --- a/src/integrationtest/utility_functions.py +++ b/src/integrationtest/utility_functions.py @@ -34,9 +34,16 @@ def basic_checks(run_dunerc, caplog, print_test_name: bool = True): def remove_hdf5_files_if_requested(run_dunerc, this_test_requests_hdf5_file_removal: bool = False): - # if either the integtest writer or the user running the test requested that the HDF5 files - # be removed at the end of the test, do that. - if run_dunerc.user_requests_hdf5_file_removal or this_test_requests_hdf5_file_removal: + # if the user requested that the files should be kept, we can simply exit early + if (run_dunerc.user_requests_hdf5_file_removal is not None and + "false" in run_dunerc.user_requests_hdf5_file_removal.lower()): + return + + # if either of the integtest writer or the user running the test requested that the HDF5 files + # be deleted at the end of the test, do that. + if ((run_dunerc.user_requests_hdf5_file_removal is not None and + "true" in run_dunerc.user_requests_hdf5_file_removal.lower()) or + this_test_requests_hdf5_file_removal): pathlist_string = "" filelist_string = "" for data_file in run_dunerc.data_files: From ca4c635eb4cd259d3d8aec99907f07eed5cc8286 Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Tue, 26 May 2026 14:24:29 -0500 Subject: [PATCH 07/10] Added more help text to a couple of our custom pytest options --- src/integrationtest/integrationtest_commandline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/integrationtest/integrationtest_commandline.py b/src/integrationtest/integrationtest_commandline.py index cd90d70..cd7e6b7 100755 --- a/src/integrationtest/integrationtest_commandline.py +++ b/src/integrationtest/integrationtest_commandline.py @@ -46,7 +46,7 @@ def pytest_addoption(parser): "--integtest-verbosity", action="store", default=3, - help="The volume of messages that are printed out by the integration test infrastructure", + help="This controls the volume of messages that are printed out by the integration test infrastructure (1 is lowest, 6 is highest)", required=False ) parser.addoption( @@ -60,7 +60,7 @@ def pytest_addoption(parser): "--remove-hdf5-files", action="store", default=None, - help="Whether to remove HDF5 (data) files when the testing has finished (over-rides value in integtest)", + help="Whether to remove HDF5 (data) files when the testing has finished (this over-rides the choice in the integtest). 'True' forces files to be removed, 'False' forces files to be kept.", required=False ) From 4f7846ec3d693683480a714e2c47a378b2279c25 Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Tue, 2 Jun 2026 16:01:09 -0500 Subject: [PATCH 08/10] Added 'always' and 'never' choices to the --remove-hdf5-files pytest option --- src/integrationtest/integrationtest_commandline.py | 2 +- src/integrationtest/utility_functions.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/integrationtest/integrationtest_commandline.py b/src/integrationtest/integrationtest_commandline.py index cd7e6b7..1421e2b 100755 --- a/src/integrationtest/integrationtest_commandline.py +++ b/src/integrationtest/integrationtest_commandline.py @@ -60,7 +60,7 @@ def pytest_addoption(parser): "--remove-hdf5-files", action="store", default=None, - help="Whether to remove HDF5 (data) files when the testing has finished (this over-rides the choice in the integtest). 'True' forces files to be removed, 'False' forces files to be kept.", + help="Whether to remove HDF5 (data) files when the testing has finished (this over-rides the choice in the integtest). 'always' forces files to be removed, 'never' forces files to be kept.", required=False ) diff --git a/src/integrationtest/utility_functions.py b/src/integrationtest/utility_functions.py index 87653a2..7cfdef1 100644 --- a/src/integrationtest/utility_functions.py +++ b/src/integrationtest/utility_functions.py @@ -36,13 +36,15 @@ def remove_hdf5_files_if_requested(run_dunerc, this_test_requests_hdf5_file_remo # if the user requested that the files should be kept, we can simply exit early if (run_dunerc.user_requests_hdf5_file_removal is not None and - "false" in run_dunerc.user_requests_hdf5_file_removal.lower()): + ("false" in run_dunerc.user_requests_hdf5_file_removal.lower() or + "never" in run_dunerc.user_requests_hdf5_file_removal.lower())): return # if either of the integtest writer or the user running the test requested that the HDF5 files # be deleted at the end of the test, do that. if ((run_dunerc.user_requests_hdf5_file_removal is not None and - "true" in run_dunerc.user_requests_hdf5_file_removal.lower()) or + ("true" in run_dunerc.user_requests_hdf5_file_removal.lower() or + "always" in run_dunerc.user_requests_hdf5_file_removal.lower())) or this_test_requests_hdf5_file_removal): pathlist_string = "" filelist_string = "" From 66b6aad3c4e96608a58492cdbf2465bbfaed9d7e Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Wed, 17 Jun 2026 13:09:31 -0500 Subject: [PATCH 09/10] Re-worked the functionality that removes HDF5 files at the end of an integtest so that it is in a fixture. --- src/integrationtest/data_classes.py | 3 + src/integrationtest/integrationtest_drunc.py | 193 +++++++++++++------ src/integrationtest/utility_functions.py | 52 ----- 3 files changed, 140 insertions(+), 108 deletions(-) diff --git a/src/integrationtest/data_classes.py b/src/integrationtest/data_classes.py index 7a5bd18..1b3a73f 100755 --- a/src/integrationtest/data_classes.py +++ b/src/integrationtest/data_classes.py @@ -67,6 +67,9 @@ class integtest_param_base_class: connsvc_port: int = 0 connsvc_debug_level: int = None + # whether the HDF5 files should be deleted at the end of the test + remove_hdf5_files: bool = False + @dataclass class integtest_params_for_generated_dunedaq_config(integtest_param_base_class): # *** Parameters that are needed for both generated and predefined configs, diff --git a/src/integrationtest/integrationtest_drunc.py b/src/integrationtest/integrationtest_drunc.py index cc4ee11..e2cbe7f 100755 --- a/src/integrationtest/integrationtest_drunc.py +++ b/src/integrationtest/integrationtest_drunc.py @@ -129,63 +129,19 @@ def pytest_generate_tests(metafunc): total_paramtrization_combinations *= len(metafunc.module.dunerc_command_list) @pytest.fixture(scope="module") -def process_manager_type(request, tmp_path_factory): - yield request.param - -@pytest.fixture(scope="module") -def check_system_resources(request): - """Check that the system resources (CPU, Memory) are sufficient for the test - The required and recommended resources are taken from the - `resource_validator` variable in the global scope of the test - module, which should be an instance of ResourceValidator. If the - required resources are not present, then the test is skipped. If - the recommended resources are not present, then a warning is printed +def process_manager_type(request): + """Simply return the process manager type that was requested """ - skip_resource_checks = request.config.getoption("--skip-resource-checks") - integtest_verbosity_level = int(request.config.getoption("--integtest-verbosity")) - - # print out a couple of blank lines to help with formatting - if integtest_verbosity_level > IntegtestVerbosityLevels.just_errors_and_warnings: - print("", flush=True) - print("", flush=True) - - resval = getattr(request.module, "resource_validator", ResourceValidator()) - - if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: - resval_debug_string = resval.get_debug_string() - print(resval_debug_string) - - if not resval.required_resources_are_present: - resval_report_string = resval.get_required_resources_report() - print(f"\n\N{LARGE YELLOW CIRCLE} {resval_report_string}") - if not skip_resource_checks: - # 16-Feb-2026, KAB: discard all of the test items except the - # first one so that we only get one "skip" output message. - del request.session.items[1:] - pytest.skip(f"\n\N{LARGE YELLOW CIRCLE} {resval_report_string}") - if not resval.recommended_resources_are_present: - if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: - resval_report_string = resval.get_recommended_resources_report() - print(f"\n*** Note: {resval_report_string}") + yield request.param - yield True - - # 16-Feb-2026, KAB: added a printout for recommended resources after the "yield" - # statement so that it gets printed out at the end of the output that the user sees. - if not resval.recommended_resources_are_present: - if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: - resval_report_string = resval.get_recommended_resources_report() - print(f"\n*** Note: {resval_report_string}") @pytest.fixture(scope="module") def create_config_files(request, tmp_path_factory, check_system_resources): """Run the confgen to produce the configuration json files - The name of the module to use is taken (indirectly) from the - `base_oks_config` variable in the global scope of the test module, - and the arguments for the confgen are taken from the - `confgen_arguments` variable in the same place. These variables - are converted into parameters for this fixture by the + The parameters for the DUNE-DAQ configuration are taken from the + `confgen_arguments` variable in the global scope of the test module. + This variable is converted into parameters for this fixture by the pytest_generate_tests function, to allow multiple confgens to be produced by one pytest module @@ -441,7 +397,7 @@ def apply_update(obj, substitution): @pytest.fixture(scope="module") -def run_dunerc(request, create_config_files, process_manager_type, tmp_path_factory): +def run_dunerc(request, create_config_files, process_manager_type, cleanup_hdf5_files, tmp_path_factory): """Run drunc with the OKS DB files created by `create_config_files`. The commands specified by the `dunerc_command_list` variable in the test module are executed. If `dunerc_command_list`'s items are @@ -740,6 +696,12 @@ class RunResult: ) subprocess.run(["killall", "gunicorn", "drunc-controller"]) + if number_of_lines_printed_to_the_console > 0: + print("---------- DRUNC Session END ----------", flush=True) + print("", flush=True) + elif integtest_verbosity_level >= IntegtestVerbosityLevels.drunc_boot_terminate: + print("", flush=True) + result.confgen_config = create_config_files.integtest_params result.config_session_name = create_config_files.integtest_params.config_session_name result.daq_session_name = create_config_files.integtest_params.daq_session_name @@ -767,10 +729,129 @@ class RunResult: # information in fine-tuning the allowed ranges in time-based checking of test results. result.daq_session_overall_time = time_after - time_before result.verbosity_helper = VerbosityHelper(integtest_verbosity_level) - result.user_requests_hdf5_file_removal = request.config.getoption("--remove-hdf5-files") - if number_of_lines_printed_to_the_console > 0: - print("---------- DRUNC Session END ----------", flush=True) + + # pass the names of the HDF5 files to the 'cleanup' fixture + cleanup_hdf5_files["raw"] = result.data_files + cleanup_hdf5_files["tpset"] = result.tpset_files + cleanup_hdf5_files["trmon"] = result.trmon_files + + yield result + + +import functools +print = functools.partial(print, flush=True) # always flush print() output + +@pytest.fixture(scope="module") +def check_system_resources(request): + """Check that the system resources (CPU, Memory) are sufficient for the test + The required and recommended resources are taken from the + `resource_validator` variable in the global scope of the test + module, which should be an instance of ResourceValidator. If the + required resources are not present, then the test is skipped. If + the recommended resources are not present, then a warning is printed + """ + skip_resource_checks = request.config.getoption("--skip-resource-checks") + integtest_verbosity_level = int(request.config.getoption("--integtest-verbosity")) + + # print out a couple of blank lines to help with formatting + if integtest_verbosity_level > IntegtestVerbosityLevels.just_errors_and_warnings: print("", flush=True) - elif integtest_verbosity_level >= IntegtestVerbosityLevels.drunc_boot_terminate: print("", flush=True) - yield result + + resval = getattr(request.module, "resource_validator", ResourceValidator()) + + if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: + resval_debug_string = resval.get_debug_string() + print(resval_debug_string) + + if not resval.required_resources_are_present: + resval_report_string = resval.get_required_resources_report() + print(f"\n\N{LARGE YELLOW CIRCLE} {resval_report_string}") + if not skip_resource_checks: + # 16-Feb-2026, KAB: discard all of the test items except the + # first one so that we only get one "skip" output message. + del request.session.items[1:] + pytest.skip(f"\n\N{LARGE YELLOW CIRCLE} {resval_report_string}") + if not resval.recommended_resources_are_present: + if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: + resval_report_string = resval.get_recommended_resources_report() + print(f"\n*** Note: {resval_report_string}") + + yield True + + # 16-Feb-2026, KAB: added a printout for recommended resources after the "yield" + # statement so that it gets printed out at the end of the output that the user sees. + if not resval.recommended_resources_are_present: + if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: + resval_report_string = resval.get_recommended_resources_report() + print(f"\n*** Note: {resval_report_string}") + + +@pytest.fixture(scope="module") +def cleanup_hdf5_files(request, create_config_files): + """Delete the HDF5 files that are produced by the test, if requested + """ + + # nothing to be done during setup; all of the work happens during teardown + # so, we simply return here + # we return a dictionary that run_dunerc can fill with the names of the files + file_lists = {} + yield file_lists + + # here is where the work gets done... + integtest_verbosity_level = int(request.config.getoption("--integtest-verbosity")) + user_requests_hdf5_file_removal = request.config.getoption("--remove-hdf5-files") + the_test_requests_hdf5_file_removal = create_config_files.integtest_params.remove_hdf5_files + #print(f"AFTER {user_requests_hdf5_file_removal} {the_test_requests_hdf5_file_removal} {len(file_lists)}", flush=True) + + # if the user requested that the files should be kept, we can exit early + if (user_requests_hdf5_file_removal is not None and + ("false" in user_requests_hdf5_file_removal.lower() or + "never" in user_requests_hdf5_file_removal.lower())): + return + + # if either of the integtest writer or the user running the test requested that the HDF5 files + # be deleted at the end of the test, do that. + if ((user_requests_hdf5_file_removal is not None and + ("true" in user_requests_hdf5_file_removal.lower() or + "always" in user_requests_hdf5_file_removal.lower())) or + the_test_requests_hdf5_file_removal): + pathlist_string = "" + filelist_string = "" + for data_file in file_lists["raw"]: + filelist_string += " " + str(data_file) + if str(data_file.parent) not in pathlist_string: + pathlist_string += " " + str(data_file.parent) + for data_file in file_lists["tpset"]: + filelist_string += " " + str(data_file) + if str(data_file.parent) not in pathlist_string: + pathlist_string += " " + str(data_file.parent) + for data_file in file_lists["trmon"]: + filelist_string += " " + str(data_file) + if str(data_file.parent) not in pathlist_string: + pathlist_string += " " + str(data_file.parent) + + if pathlist_string and filelist_string: + if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: + print("============================================") + print("Listing the hdf5 files before deleting them:") + print("============================================") + + os.system(f"df -h {pathlist_string}") + print("--------------------") + os.system(f"ls -alF {filelist_string}") + + for data_file in file_lists["raw"]: + data_file.unlink() + for data_file in file_lists["tpset"]: + data_file.unlink() + for data_file in file_lists["trmon"]: + data_file.unlink() + + if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: + print("--------------------") + os.system(f"df -h {pathlist_string}") + print("============================================") + +# somewhere, write down the difference between what is specified in global vars and +# what is specified in the contents of the integtest config params diff --git a/src/integrationtest/utility_functions.py b/src/integrationtest/utility_functions.py index 7cfdef1..c21fcda 100644 --- a/src/integrationtest/utility_functions.py +++ b/src/integrationtest/utility_functions.py @@ -30,55 +30,3 @@ def basic_checks(run_dunerc, caplog, print_test_name: bool = True): if len(setup_logs) > 0: fail_msg = f"One or more problems were encountered during the setup of the pytest: {setup_logs}" pytest.fail(fail_msg, pytrace=False) - - -def remove_hdf5_files_if_requested(run_dunerc, this_test_requests_hdf5_file_removal: bool = False): - - # if the user requested that the files should be kept, we can simply exit early - if (run_dunerc.user_requests_hdf5_file_removal is not None and - ("false" in run_dunerc.user_requests_hdf5_file_removal.lower() or - "never" in run_dunerc.user_requests_hdf5_file_removal.lower())): - return - - # if either of the integtest writer or the user running the test requested that the HDF5 files - # be deleted at the end of the test, do that. - if ((run_dunerc.user_requests_hdf5_file_removal is not None and - ("true" in run_dunerc.user_requests_hdf5_file_removal.lower() or - "always" in run_dunerc.user_requests_hdf5_file_removal.lower())) or - this_test_requests_hdf5_file_removal): - pathlist_string = "" - filelist_string = "" - for data_file in run_dunerc.data_files: - filelist_string += " " + str(data_file) - if str(data_file.parent) not in pathlist_string: - pathlist_string += " " + str(data_file.parent) - for data_file in run_dunerc.tpset_files: - filelist_string += " " + str(data_file) - if str(data_file.parent) not in pathlist_string: - pathlist_string += " " + str(data_file.parent) - for data_file in run_dunerc.trmon_files: - filelist_string += " " + str(data_file) - if str(data_file.parent) not in pathlist_string: - pathlist_string += " " + str(data_file.parent) - - if pathlist_string and filelist_string: - if run_dunerc.verbosity_helper.compare_level(IntegtestVerbosityLevels.integtest_debug): - print("============================================") - print("Listing the hdf5 files before deleting them:") - print("============================================") - - os.system(f"df -h {pathlist_string}") - print("--------------------") - os.system(f"ls -alF {filelist_string}") - - for data_file in run_dunerc.data_files: - data_file.unlink() - for data_file in run_dunerc.tpset_files: - data_file.unlink() - for data_file in run_dunerc.trmon_files: - data_file.unlink() - - if run_dunerc.verbosity_helper.compare_level(IntegtestVerbosityLevels.integtest_debug): - print("--------------------") - os.system(f"df -h {pathlist_string}") - print("============================================") From 377b56a7e6bf5a087fd6fe7c33b63ee42f87884c Mon Sep 17 00:00:00 2001 From: Kurt Biery Date: Wed, 17 Jun 2026 16:37:31 -0500 Subject: [PATCH 10/10] Created a function to delete a file and catch exceptions while doing that, and made use of that function in the logic that removes HDF5 files at the end of integtests. --- src/integrationtest/integrationtest_drunc.py | 11 ++++------- src/integrationtest/utility_functions.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/integrationtest/integrationtest_drunc.py b/src/integrationtest/integrationtest_drunc.py index e2cbe7f..f6fe498 100755 --- a/src/integrationtest/integrationtest_drunc.py +++ b/src/integrationtest/integrationtest_drunc.py @@ -24,6 +24,7 @@ integtest_params_for_generated_dunedaq_config, integtest_params_for_predefined_dunedaq_config, ) +from integrationtest.utility_functions import delete_file from daqconf.generate_hwmap import generate_hwmap from daqconf.generate import ( generate_readout, @@ -802,7 +803,6 @@ def cleanup_hdf5_files(request, create_config_files): integtest_verbosity_level = int(request.config.getoption("--integtest-verbosity")) user_requests_hdf5_file_removal = request.config.getoption("--remove-hdf5-files") the_test_requests_hdf5_file_removal = create_config_files.integtest_params.remove_hdf5_files - #print(f"AFTER {user_requests_hdf5_file_removal} {the_test_requests_hdf5_file_removal} {len(file_lists)}", flush=True) # if the user requested that the files should be kept, we can exit early if (user_requests_hdf5_file_removal is not None and @@ -842,16 +842,13 @@ def cleanup_hdf5_files(request, create_config_files): os.system(f"ls -alF {filelist_string}") for data_file in file_lists["raw"]: - data_file.unlink() + delete_file(data_file) for data_file in file_lists["tpset"]: - data_file.unlink() + delete_file(data_file) for data_file in file_lists["trmon"]: - data_file.unlink() + delete_file(data_file) if integtest_verbosity_level >= IntegtestVerbosityLevels.integtest_debug: print("--------------------") os.system(f"df -h {pathlist_string}") print("============================================") - -# somewhere, write down the difference between what is specified in global vars and -# what is specified in the contents of the integtest config params diff --git a/src/integrationtest/utility_functions.py b/src/integrationtest/utility_functions.py index c21fcda..e72c2db 100644 --- a/src/integrationtest/utility_functions.py +++ b/src/integrationtest/utility_functions.py @@ -1,6 +1,7 @@ import pytest import os import re +import pathlib from integrationtest.verbosity_helper import IntegtestVerbosityLevels import functools @@ -30,3 +31,14 @@ def basic_checks(run_dunerc, caplog, print_test_name: bool = True): if len(setup_logs) > 0: fail_msg = f"One or more problems were encountered during the setup of the pytest: {setup_logs}" pytest.fail(fail_msg, pytrace=False) + + +def delete_file(file: pathlib.PosixPath, verbose: bool = False): + try: + file.unlink() + if verbose: + print(f"File {file} was successfully deleted.") + except FileNotFoundError: + print(f"File {file} could not be deleted because it was not found.") + except IsADirectoryError: + print(f"File {file} could not be deleted because it is actually a directory.")