diff --git a/test/suite/data_manager.py b/test/suite/data_manager.py index 51b1ff7f012121b0ffe5d682f14f727dfa34d320..455c76d4e935da81fb8133f1a7895ddd18b7f521 100644 --- a/test/suite/data_manager.py +++ b/test/suite/data_manager.py @@ -140,13 +140,13 @@ class FileManager: # @staticmethod # def monitor_test_output(outfile="inotifywait.log"): # # Make sure the output directory exists - # test_output_dir = Path(f"{Config.ROOT_PATH}/{Config.DATA_DIR}") + # test_output_dir = Path(f"{Config.p_baseRootDir}/{Config.p_testDataDir}") # test_output_dir.mkdir(parents=True, exist_ok=True) # # notify_time_fmt = '%Y-%m-%d %H:%M:%S' # notify_log_fmt = "[%T] %e %f" # - # logfile = f"{Config.ROOT_PATH}/{Config.DATA_DIR}/{outfile}" + # logfile = f"{Config.p_baseRootDir}/{Config.p_testDataDir}/{outfile}" # logger.info(f"Monitoring directory {test_output_dir}. Check logs at {logfile}.") # # # # -d daemon background monitoring, -r recursive, -e events, -o outfile diff --git a/test/suite/main.py b/test/suite/main.py index aa82be5e19fd4476333bec2da20b3bc8e445c62a..1df1b5e1e06752fd85ce6e4cef98d2c602e6a181 100644 --- a/test/suite/main.py +++ b/test/suite/main.py @@ -11,34 +11,38 @@ from data_manager import FileManager from utils import * from mock_fm import * -# Create a logger instance -logger = TestLogger(__name__) +logger = TestLogger(__name__) # Create a logger instance class Config: # File names - SCDAQ_TARGET = "scdaq" + s_scdaqTargetName = "scdaq" k_scdaqLogName = 'scdaq' # Base paths - ROOT_PATH = Path().resolve() - BUILD_DIR = Path("build") - DATA_DIR = Path("test/data") - CONFIG_DIR = Path("test/config") - TEST_ARTIFACTS_DIR = Path("test/artifacts") - - # Run paths - v_runNumber = 666505 - v_fmPort = 8000 - SCDAQ_PATH = BUILD_DIR / SCDAQ_TARGET - k_mockFmPythonPath = Path("test/suite/mock_fm.py") - RUN_DATA_DIR = Path(f"test/data/run{v_runNumber:0>6}") + p_baseRootDir = Path().resolve() + p_buildDir = Path("build") + p_testDataDir = Path("test/data") + p_configDir = Path("test/config") + p_testArtifactsDir = Path("test/artifacts") + + # Run info + runNumber = 666505 + fnManagerPort = 8000 + k_defaultRuntimeSecs = 20 + + p_scdaqExecutable = p_buildDir / s_scdaqTargetName + p_runDataDir = Path(f"test/data/run{runNumber:0>6}") + + d_runtimesSecs = defaultdict(lambda: k_defaultRuntimeSecs, { + # Only exceptions to `k_defaultRuntimeSecs` here + "BMTF_STUB": 30, + }) # Hash constants - HASH_DIR = DATA_DIR / ".sha256" + p_hashDir = p_testDataDir / ".sha256" k_referenceCommitTag = "adcd94aa" # Constants - k_defaultRuntimeSecs = 20 k_defaultHashableBytes = 104857600 # Default dictionaries with the constants as default values @@ -47,27 +51,18 @@ class Config: "BMTF_STUB": 1048576, }) - d_runtimesSecs = defaultdict(lambda: k_defaultRuntimeSecs, { - # Only exceptions to `k_defaultRuntimeSecs` here - "BMTF_STUB": 30, - }) class TestController: - def __init__(self, scdaq_command, fm_start_command, fm_stop_command, timeout_secs): - self.dispatcher = CommandDispatcher(logger) + def __init__(self, scdaq_command, timeout_secs): + self.dispatcher = CommandDispatcher() self.scdaq_command = scdaq_command - self.fm_start_command = fm_start_command - self.fm_stop_command = fm_stop_command self.timeout_secs = timeout_secs self.scdaq_proc = None - self.fm_start_proc = None - self.fm_stop_proc = None - # self.monitor_thread = None def run_scdaq(self): # Run SCDAQ and listen for `self.timeout_secs` seconds - with open(f'{Config.DATA_DIR}/{Config.k_scdaqLogName}.out', 'w') as fout, open( - f'{Config.DATA_DIR}/{Config.k_scdaqLogName}.err', 'w') as ferr: + with open(f'{Config.p_testDataDir}/{Config.k_scdaqLogName}.out', 'w') as fout, open( + f'{Config.p_testDataDir}/{Config.k_scdaqLogName}.err', 'w') as ferr: self.scdaq_proc = self.dispatcher.run(self.scdaq_command, out=fout, err=ferr) try: self.scdaq_proc.wait(timeout=self.timeout_secs) @@ -75,14 +70,14 @@ class TestController: logger.warning("SCDAQ process timed out.") self.scdaq_proc.kill() - def mock_fm(self): - fun_manager = FunctionManager(Config.v_fmPort) + def run_function_manager(self): + fn_manager = FunctionManager(Config.fnManagerPort) time.sleep(1) - fun_manager.send_command("start", Config.v_runNumber) + fn_manager.send_command("start", Config.runNumber) # Sleep thread for less time than the maximum timeout scheduled time.sleep(self.timeout_secs * 0.80) # End run - fun_manager.send_command("stop", Config.v_runNumber) + fn_manager.send_command("stop", Config.runNumber) # def monitor_run(self): # # Start monitoring thread as a daemon - main process will be allowed to exit once all non-daemon threads are finished. @@ -91,23 +86,18 @@ class TestController: def orchestrate_run(self): scdaq_thread = threading.Thread(target=self.run_scdaq) - mock_fm_thread = threading.Thread(target=self.mock_fm, daemon=True) + mock_fm_thread = threading.Thread(target=self.run_function_manager, daemon=True) scdaq_thread.start() sleep(2) # Wait a little to ensure scdaq is up and running mock_fm_thread.start() # Start FM process in a detached thread scdaq_thread.join() # Wait for SCDAQ to exit or timeout, then clean up - # Cleanup remaining processes - if self.fm_start_proc and self.fm_start_proc.poll() is None: - self.fm_start_proc.kill() - if self.fm_stop_proc and self.fm_stop_proc.poll() is None: - self.fm_stop_proc.kill() class ScdaqTest: def __init__(self, session_id, test_id, config_file, expects_output=True, validates_contents=True): self.test_id = test_id - self.file_manager = FileManager(Config.DATA_DIR, session_id) + self.file_manager = FileManager(Config.p_testDataDir, session_id) self.config_file = config_file self.expects_output = expects_output @@ -116,9 +106,9 @@ class ScdaqTest: self.output_path, self.output_prefix, self.output_suffix = self.extract_config_values(self.config_file) self.bytes_to_hash = Config.d_hashableBytes.get(test_id, Config.k_defaultHashableBytes) - self.ref_hash_file = Config.HASH_DIR / f"{test_id}_{self.bytes_to_hash}_{Config.k_referenceCommitTag}" + self.ref_hash_file = Config.p_hashDir / f"{test_id}_{self.bytes_to_hash}_{Config.k_referenceCommitTag}" - self.artifact_path = Path(str(Config.TEST_ARTIFACTS_DIR) + "/" + session_id + f"/{test_id}") + self.artifact_path = Path(str(Config.p_testArtifactsDir) + "/" + session_id + f"/{test_id}") @staticmethod def extract_config_values(config_file): @@ -151,7 +141,7 @@ class ScdaqTest: # Check outputs logger.info("Checking existence of output files.") file_pattern = f"{self.output_prefix}*{self.output_suffix}" - output_files = FileManager.recursive_file_search(test_config.RUN_DATA_DIR, file_pattern) + output_files = FileManager.recursive_file_search(test_config.p_runDataDir, file_pattern) if not output_files: logger.error(f"No output file found for {self.test_id}") return False @@ -179,15 +169,11 @@ class ScdaqTest: return True def run_test(self, test_id: str, test_config: Config): - # Run SCDAQ and start_run.py in parallel - scdaq_command = f"./{test_config.SCDAQ_PATH} --config {self.config_file}" - fm_start_command = f"python3 {test_config.k_mockFmPythonPath} start {test_config.v_runNumber} --port {test_config.v_fmPort}" - fm_stop_command = f"python3 {test_config.k_mockFmPythonPath} stop {test_config.v_runNumber} --port {test_config.v_fmPort}" + scdaq_command = f"./{test_config.p_scdaqExecutable} --config {self.config_file}" timeout_secs = test_config.d_runtimesSecs.get(test_id, test_config.k_defaultRuntimeSecs) - controller = TestController(scdaq_command, fm_start_command, fm_stop_command, timeout_secs) + controller = TestController(scdaq_command, timeout_secs) self.file_manager.setup_test(test_id, self.artifact_path) - # controller.monitor_run() controller.orchestrate_run() result = self.check_outputs(test_config) @@ -212,7 +198,7 @@ class CiRegression: @staticmethod def clean_up(): - FileManager.remove(Config.TEST_ARTIFACTS_DIR) + FileManager.remove(Config.p_testArtifactsDir) logger.complete("Cleaned up for regression test.") def run_all_tests(self): @@ -235,7 +221,7 @@ class CiRegression: # } for test_id, test_config_file in tests.items(): - test_runner = ScdaqTest(self.session_id, test_id, Config.CONFIG_DIR / test_config_file) + test_runner = ScdaqTest(self.session_id, test_id, Config.p_configDir / test_config_file) success = test_runner.run_test(test_id, Config()) if not success: logger.error(f"{test_id} test FAILED.") @@ -246,32 +232,23 @@ class CiRegression: def build_scdaq(): - if Config.SCDAQ_PATH.exists(): - logger.info(f"Using pre-built executable at {Config.SCDAQ_PATH}.") + if Config.p_scdaqExecutable.exists(): + logger.info(f"Using pre-built SCDAQ executable at {Config.p_scdaqExecutable}.") return True - logger.info(f"Building SCDAQ from scratch in {Config.BUILD_DIR}...") - Config.BUILD_DIR.mkdir(parents=True, exist_ok=True) + logger.info(f"Building SCDAQ from scratch in {Config.p_buildDir}...") + Config.p_buildDir.mkdir(parents=True, exist_ok=True) - dispatcher = CommandDispatcher(logger) - - # Run cmake - cmake_command = "cmake .." - process = dispatcher.run(cmake_command, cwd=Config.BUILD_DIR, out=subprocess.PIPE, err=subprocess.PIPE) - stdout, stderr = process.communicate() - if process.returncode != 0: - logger.error(f"CMake failed: {stderr.decode('utf-8')}") - return False - - # Run make - make_command = "make" - process = dispatcher.run(make_command, cwd=Config.BUILD_DIR, out=subprocess.PIPE, err=subprocess.PIPE) - stdout, stderr = process.communicate() - if process.returncode != 0: - logger.error(f"Make failed: {stderr.decode('utf-8')}") - return False + dispatcher = CommandDispatcher() + with open(f"{Config.p_testDataDir}/build_scdaq.out", "w+") as fout: + for command in ["cmake ..", "make"]: + process = dispatcher.run(command, cwd=Config.p_buildDir, out=fout) + stdout, stderr = process.communicate() + if process.returncode != 0: + logger.error(f"Command `{command}` failed: {stderr.decode('utf-8')}") + return False - logger.complete("SCDAQ was built successfully.") + logger.complete("SCDAQ was successfully built.") return True diff --git a/test/suite/utils.py b/test/suite/utils.py index ad5c8f822c505c8255b224a52fe426625460e672..70ea051f53a92cb7376c0e9f4e4b3f4bbc25c491 100644 --- a/test/suite/utils.py +++ b/test/suite/utils.py @@ -77,23 +77,23 @@ class TestLogger(logging.Logger): class CommandDispatcher: - def __init__(self, logger : TestLogger): - self.logger = logger + logger = TestLogger("CommandDispatcherLogger") - def run(self, command, cwd=None, out=None, err=None): - self.logger.info(f"Executing command: {command}") - self.logger.debug(f"Command split as: {shlex.split(command)}") + @staticmethod + def run(command, cwd=None, out=None, err=None): + CommandDispatcher.logger.info(f"Executing command: {command}") + CommandDispatcher.logger.debug(f"Command split as: {shlex.split(command)}") try: # Use shlex to safely split commands whose arguments have spaces return subprocess.Popen(shlex.split(command), cwd=cwd, stdout=out, stderr=err or subprocess.STDOUT) except subprocess.CalledProcessError as e: - self.logger.error(f"Command failed: {e}") + CommandDispatcher.logger.error(f"Command failed: {e}") return None, str(e) except subprocess.TimeoutExpired as e: - self.logger.warning(f"Command timed out: {e}") + CommandDispatcher.logger.warning(f"Command timed out: {e}") return None, str(e) except Exception as e: - self.logger.error(f"Unexpected error: {e}") + CommandDispatcher.logger.error(f"Unexpected error: {e}") return None, str(e)