From b6a556e0079fde206aee0751119201fc70f6777c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Wed, 16 Jul 2025 19:41:50 +0200 Subject: [PATCH 1/8] Define error log file in resource_dict --- .github/workflows/pipeline.yml | 2 +- executorlib/backend/cache_parallel.py | 5 +++++ executorlib/backend/interactive_parallel.py | 5 +++++ executorlib/backend/interactive_serial.py | 5 +++++ executorlib/standalone/error.py | 22 +++++++++++++++++++ executorlib/task_scheduler/file/backend.py | 5 +++++ executorlib/task_scheduler/file/hdf.py | 4 ++++ executorlib/task_scheduler/file/shared.py | 2 ++ .../task_scheduler/interactive/shared.py | 3 +++ pyproject.toml | 1 - tests/test_cache_fileexecutor_serial.py | 15 +++++++++++++ tests/test_singlenodeexecutor_cache.py | 12 ++++++++++ 12 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 executorlib/standalone/error.py diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 5b1e02a7..c51853d0 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -402,7 +402,7 @@ jobs: run: echo -e "channels:\n - conda-forge\n" > .condarc - uses: conda-incubator/setup-miniconda@v3 with: - python-version: '3.9' + python-version: '3.10' miniforge-version: latest condarc-file: .condarc environment-file: .ci_support/environment-old.yml diff --git a/executorlib/backend/cache_parallel.py b/executorlib/backend/cache_parallel.py index 9b1d25d8..49ad6d6e 100644 --- a/executorlib/backend/cache_parallel.py +++ b/executorlib/backend/cache_parallel.py @@ -4,6 +4,7 @@ import cloudpickle +from executorlib.standalone.error import backend_write_error_file from executorlib.task_scheduler.file.backend import ( backend_load_file, backend_write_file, @@ -53,6 +54,10 @@ def main() -> None: output={"error": error}, runtime=time.time() - time_start, ) + backend_write_error_file( + error=error, + apply_dict=apply_dict, + ) else: if mpi_rank_zero: backend_write_file( diff --git a/executorlib/backend/interactive_parallel.py b/executorlib/backend/interactive_parallel.py index 5ae2c320..3d5aadcc 100644 --- a/executorlib/backend/interactive_parallel.py +++ b/executorlib/backend/interactive_parallel.py @@ -6,6 +6,7 @@ import cloudpickle import zmq +from executorlib.standalone.error import backend_write_error_file from executorlib.standalone.interactive.backend import call_funct, parse_arguments from executorlib.standalone.interactive.communication import ( interface_connect, @@ -82,6 +83,10 @@ def main() -> None: socket=socket, result_dict={"error": error}, ) + backend_write_error_file( + error=error, + apply_dict=input_dict, + ) else: # Send output if mpi_rank_zero: diff --git a/executorlib/backend/interactive_serial.py b/executorlib/backend/interactive_serial.py index 34fb2288..c72d95a0 100644 --- a/executorlib/backend/interactive_serial.py +++ b/executorlib/backend/interactive_serial.py @@ -2,6 +2,7 @@ from os.path import abspath from typing import Optional +from executorlib.standalone.error import backend_write_error_file from executorlib.standalone.interactive.backend import call_funct, parse_arguments from executorlib.standalone.interactive.communication import ( interface_connect, @@ -58,6 +59,10 @@ def main(argument_lst: Optional[list[str]] = None): socket=socket, result_dict={"error": error}, ) + backend_write_error_file( + error=error, + apply_dict=input_dict, + ) else: # Send output interface_send(socket=socket, result_dict={"result": output}) diff --git a/executorlib/standalone/error.py b/executorlib/standalone/error.py new file mode 100644 index 00000000..387e6374 --- /dev/null +++ b/executorlib/standalone/error.py @@ -0,0 +1,22 @@ + +import traceback + + +def backend_write_error_file(error: Exception, apply_dict: dict) -> None: + """ + Write an error to a file if specified in the apply_dict. + + Args: + error (Exception): The error to be written. + apply_dict (dict): Dictionary containing additional parameters. + + Returns: + None + """ + error_log_file = apply_dict.get("error_log_file", None) + if error_log_file is not None: + with open(error_log_file, "a") as f: + f.write("function: " + str(apply_dict["fn"]) + "\n") + f.write("args: " + str(apply_dict["args"]) + "\n") + f.write("kwargs: " + str(apply_dict["kwargs"]) + "\n") + traceback.print_exception(error, file=f) \ No newline at end of file diff --git a/executorlib/task_scheduler/file/backend.py b/executorlib/task_scheduler/file/backend.py index 4ea27c17..cbe869cc 100644 --- a/executorlib/task_scheduler/file/backend.py +++ b/executorlib/task_scheduler/file/backend.py @@ -2,6 +2,7 @@ import time from typing import Any +from executorlib.standalone.error import backend_write_error_file from executorlib.task_scheduler.file.hdf import dump, load from executorlib.task_scheduler.file.shared import FutureItem @@ -77,6 +78,10 @@ def backend_execute_task_in_file(file_name: str) -> None: } except Exception as error: result = {"error": error} + backend_write_error_file( + error=error, + apply_dict=apply_dict, + ) backend_write_file( file_name=file_name, diff --git a/executorlib/task_scheduler/file/hdf.py b/executorlib/task_scheduler/file/hdf.py index d8cde507..812198c9 100644 --- a/executorlib/task_scheduler/file/hdf.py +++ b/executorlib/task_scheduler/file/hdf.py @@ -52,6 +52,10 @@ def load(file_name: str) -> dict: data_dict["kwargs"] = cloudpickle.loads(np.void(hdf["/input_kwargs"])) else: data_dict["kwargs"] = {} + if "error_log_file" in hdf: + data_dict["error_log_file"] = cloudpickle.loads( + np.void(hdf["/error_log_file"]) + ) return data_dict diff --git a/executorlib/task_scheduler/file/shared.py b/executorlib/task_scheduler/file/shared.py index 8aaa76bc..1944321f 100644 --- a/executorlib/task_scheduler/file/shared.py +++ b/executorlib/task_scheduler/file/shared.py @@ -126,6 +126,7 @@ def execute_tasks_h5( ) cache_key = task_resource_dict.pop("cache_key", None) cache_directory = os.path.abspath(task_resource_dict.pop("cache_directory")) + error_log_file = task_resource_dict.pop("error_log_file", None) task_key, data_dict = serialize_funct_h5( fn=task_dict["fn"], fn_args=task_args, @@ -133,6 +134,7 @@ def execute_tasks_h5( resource_dict=task_resource_dict, cache_key=cache_key, ) + data_dict["error_log_file"] = error_log_file if task_key not in memory_dict: if os.path.join( cache_directory, task_key + "_o.h5" diff --git a/executorlib/task_scheduler/interactive/shared.py b/executorlib/task_scheduler/interactive/shared.py index 7c27e043..cc22ca2c 100644 --- a/executorlib/task_scheduler/interactive/shared.py +++ b/executorlib/task_scheduler/interactive/shared.py @@ -26,6 +26,7 @@ def execute_tasks( cache_key: Optional[str] = None, queue_join_on_shutdown: bool = True, log_obj_size: bool = False, + error_log_file: Optional[str] = None, **kwargs, ) -> None: """ @@ -70,6 +71,8 @@ def execute_tasks( future_queue.join() break elif "fn" in task_dict and "future" in task_dict: + if error_log_file is not None: + task_dict["error_log_file"] = error_log_file if cache_directory is None: _execute_task_without_cache( interface=interface, task_dict=task_dict, future_queue=future_queue diff --git a/pyproject.toml b/pyproject.toml index d51a8076..ee8dd253 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Intended Audience :: Science/Research", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/tests/test_cache_fileexecutor_serial.py b/tests/test_cache_fileexecutor_serial.py index a9eb97a5..9f8211a1 100644 --- a/tests/test_cache_fileexecutor_serial.py +++ b/tests/test_cache_fileexecutor_serial.py @@ -88,6 +88,21 @@ def test_executor_error(self): fs1 = exe.submit(get_error, a=1) with self.assertRaises(ValueError): fs1.result() + self.assertEqual(len(os.listdir(cwd)), 1) + + def test_executor_error(self): + cwd = os.path.join(os.path.dirname(__file__), "executables") + with FileTaskScheduler( + resource_dict={"cwd": cwd, "error_log_file": "error.out"}, + execute_function=execute_in_subprocess + ) as exe: + fs1 = exe.submit(get_error, a=1) + with self.assertRaises(ValueError): + fs1.result() + working_directory_file_lst = os.listdir(cwd) + self.assertEqual(len(working_directory_file_lst), 2) + self.assertTrue("error.out" in working_directory_file_lst) + os.remove(os.path.join(cwd, "error.out")) def test_executor_function(self): fs1 = Future() diff --git a/tests/test_singlenodeexecutor_cache.py b/tests/test_singlenodeexecutor_cache.py index 531d369d..650c0044 100644 --- a/tests/test_singlenodeexecutor_cache.py +++ b/tests/test_singlenodeexecutor_cache.py @@ -58,6 +58,18 @@ def test_cache_error(self): with self.assertRaises(ValueError): print(f.result()) + def test_cache_error_file(self): + cache_directory = os.path.abspath("cache_error") + error_out = "error.out" + with SingleNodeExecutor(cache_directory=cache_directory) as exe: + self.assertTrue(exe) + cloudpickle_register(ind=1) + f = exe.submit(get_error, a=1, resource_dict={"error_log_file": error_out}) + with self.assertRaises(ValueError): + print(f.result()) + self.assertTrue(os.path.exists(error_out)) + os.remove(error_out) + def tearDown(self): shutil.rmtree("executorlib_cache", ignore_errors=True) shutil.rmtree("cache_error", ignore_errors=True) From 925a62905e985a717d95b7898e9c777df505ae7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Wed, 16 Jul 2025 19:49:57 +0200 Subject: [PATCH 2/8] fix tests --- executorlib/standalone/cache.py | 1 + tests/test_cache_fileexecutor_serial.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/executorlib/standalone/cache.py b/executorlib/standalone/cache.py index c3d89483..92e1ae68 100644 --- a/executorlib/standalone/cache.py +++ b/executorlib/standalone/cache.py @@ -10,6 +10,7 @@ "error": "error", "runtime": "runtime", "queue_id": "queue_id", + "error_log_file": "error_log_file", } diff --git a/tests/test_cache_fileexecutor_serial.py b/tests/test_cache_fileexecutor_serial.py index 9f8211a1..fcbebc1a 100644 --- a/tests/test_cache_fileexecutor_serial.py +++ b/tests/test_cache_fileexecutor_serial.py @@ -90,7 +90,7 @@ def test_executor_error(self): fs1.result() self.assertEqual(len(os.listdir(cwd)), 1) - def test_executor_error(self): + def test_executor_error_file(self): cwd = os.path.join(os.path.dirname(__file__), "executables") with FileTaskScheduler( resource_dict={"cwd": cwd, "error_log_file": "error.out"}, From 297e0cae93cb7f61ba31afd618d19da1da43e498 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 16 Jul 2025 17:50:59 +0000 Subject: [PATCH 3/8] Format black --- executorlib/standalone/error.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/executorlib/standalone/error.py b/executorlib/standalone/error.py index 387e6374..5b0cbabd 100644 --- a/executorlib/standalone/error.py +++ b/executorlib/standalone/error.py @@ -1,4 +1,3 @@ - import traceback @@ -19,4 +18,4 @@ def backend_write_error_file(error: Exception, apply_dict: dict) -> None: f.write("function: " + str(apply_dict["fn"]) + "\n") f.write("args: " + str(apply_dict["args"]) + "\n") f.write("kwargs: " + str(apply_dict["kwargs"]) + "\n") - traceback.print_exception(error, file=f) \ No newline at end of file + traceback.print_exception(error, file=f) From 6d141367760c827ba7f853db2ecd10b3fde492ef Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 17:52:21 +0000 Subject: [PATCH 4/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- executorlib/standalone/error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executorlib/standalone/error.py b/executorlib/standalone/error.py index 5b0cbabd..410a9c22 100644 --- a/executorlib/standalone/error.py +++ b/executorlib/standalone/error.py @@ -12,7 +12,7 @@ def backend_write_error_file(error: Exception, apply_dict: dict) -> None: Returns: None """ - error_log_file = apply_dict.get("error_log_file", None) + error_log_file = apply_dict.get("error_log_file") if error_log_file is not None: with open(error_log_file, "a") as f: f.write("function: " + str(apply_dict["fn"]) + "\n") From ceca9ca83bda3ce2d4078d4b2e99f556084182c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Wed, 16 Jul 2025 19:57:33 +0200 Subject: [PATCH 5/8] Add docstrings --- executorlib/executor/flux.py | 10 ++++++++++ executorlib/executor/single.py | 10 ++++++++++ executorlib/executor/slurm.py | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/executorlib/executor/flux.py b/executorlib/executor/flux.py index 145ccc73..33b45306 100644 --- a/executorlib/executor/flux.py +++ b/executorlib/executor/flux.py @@ -41,6 +41,8 @@ class FluxJobExecutor(BaseExecutor): Defaults to None. - exclusive (bool): Whether to exclusively reserve the compute nodes, or allow sharing compute notes. Defaults to False. + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. flux_executor (flux.job.FluxExecutor): Flux Python interface to submit the workers to flux flux_executor_pmi_mode (str): PMI interface to use (OpenMPI v5 requires pmix) default is None (Flux only) flux_executor_nesting (bool): Provide hierarchically nested Flux job scheduler inside the submitted function. @@ -126,6 +128,8 @@ def __init__( Defaults to None. - exclusive (bool): Whether to exclusively reserve the compute nodes, or allow sharing compute notes. Defaults to False. + - error_log_file (str): Name of the error log file to use for storing exceptions + raised by the Python functions submitted to the Executor. flux_executor (flux.job.FluxExecutor): Flux Python interface to submit the workers to flux flux_executor_pmi_mode (str): PMI interface to use (OpenMPI v5 requires pmix) default is None (Flux only) flux_executor_nesting (bool): Provide hierarchically nested Flux job scheduler inside the submitted function. @@ -229,6 +233,8 @@ class FluxClusterExecutor(BaseExecutor): - openmpi_oversubscribe (bool): adds the `--oversubscribe` command line flag (OpenMPI and SLURM only) - default False - slurm_cmd_args (list): Additional command line arguments for the srun call (SLURM only) + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. pysqa_config_directory (str, optional): path to the pysqa config directory (only for pysqa based backend). hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an @@ -308,6 +314,8 @@ def __init__( and SLURM only) - default False - slurm_cmd_args (list): Additional command line arguments for the srun call (SLURM only) + - error_log_file (str): Name of the error log file to use for storing exceptions + raised by the Python functions submitted to the Executor. pysqa_config_directory (str, optional): path to the pysqa config directory (only for pysqa based backend). hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an @@ -424,6 +432,8 @@ def create_flux_executor( Defaults to None. - exclusive (bool): Whether to exclusively reserve the compute nodes, or allow sharing compute notes. Defaults to False. + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. flux_executor (flux.job.FluxExecutor): Flux Python interface to submit the workers to flux flux_executor_pmi_mode (str): PMI interface to use (OpenMPI v5 requires pmix) default is None (Flux only) flux_executor_nesting (bool): Provide hierarchically nested Flux job scheduler inside the submitted function. diff --git a/executorlib/executor/single.py b/executorlib/executor/single.py index 30037505..9ad40c13 100644 --- a/executorlib/executor/single.py +++ b/executorlib/executor/single.py @@ -39,6 +39,8 @@ class SingleNodeExecutor(BaseExecutor): - openmpi_oversubscribe (bool): adds the `--oversubscribe` command line flag (OpenMPI and SLURM only) - default False - slurm_cmd_args (list): Additional command line arguments for the srun call (SLURM only) + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And @@ -116,6 +118,8 @@ def __init__( and SLURM only) - default False - slurm_cmd_args (list): Additional command line arguments for the srun call (SLURM only) + - error_log_file (str): Name of the error log file to use for storing exceptions + raised by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And @@ -202,6 +206,8 @@ class TestClusterExecutor(BaseExecutor): - threads_per_core (int): number of OpenMP threads to be used for each function call - gpus_per_core (int): number of GPUs per worker - defaults to 0 - cwd (str/None): current working directory where the parallel python task is executed + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And @@ -273,6 +279,8 @@ def __init__( - threads_per_core (int): number of OpenMP threads to be used for each function call - gpus_per_core (int): number of GPUs per worker - defaults to 0 - cwd (str/None): current working directory where the parallel python task is executed + - error_log_file (str): Name of the error log file to use for storing exceptions + raised by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And @@ -381,6 +389,8 @@ def create_single_node_executor( and SLURM only) - default False - slurm_cmd_args (list): Additional command line arguments for the srun call (SLURM only) + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And diff --git a/executorlib/executor/slurm.py b/executorlib/executor/slurm.py index bf3e48c7..c0353a97 100644 --- a/executorlib/executor/slurm.py +++ b/executorlib/executor/slurm.py @@ -41,6 +41,8 @@ class SlurmClusterExecutor(BaseExecutor): - openmpi_oversubscribe (bool): adds the `--oversubscribe` command line flag (OpenMPI and SLURM only) - default False - slurm_cmd_args (list): Additional command line arguments for the srun call (SLURM only) + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. pysqa_config_directory (str, optional): path to the pysqa config directory (only for pysqa based backend). hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an @@ -120,6 +122,8 @@ def __init__( and SLURM only) - default False - slurm_cmd_args (list): Additional command line arguments for the srun call (SLURM only) + - error_log_file (str): Name of the error log file to use for storing exceptions + raised by the Python functions submitted to the Executor. pysqa_config_directory (str, optional): path to the pysqa config directory (only for pysqa based backend). hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an @@ -226,6 +230,8 @@ class SlurmJobExecutor(BaseExecutor): Defaults to None. - exclusive (bool): Whether to exclusively reserve the compute nodes, or allow sharing compute notes. Defaults to False. + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And @@ -307,6 +313,8 @@ def __init__( Defaults to None. - exclusive (bool): Whether to exclusively reserve the compute nodes, or allow sharing compute notes. Defaults to False. + - error_log_file (str): Name of the error log file to use for storing exceptions + raised by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And @@ -408,6 +416,8 @@ def create_slurm_executor( Defaults to None. - exclusive (bool): Whether to exclusively reserve the compute nodes, or allow sharing compute notes. Defaults to False. + - error_log_file (str): Name of the error log file to use for storing exceptions raised + by the Python functions submitted to the Executor. hostname_localhost (boolean): use localhost instead of the hostname to establish the zmq connection. In the context of an HPC cluster this essential to be able to communicate to an Executor running on a different compute node within the same allocation. And From 80d001bc6ea16c9cc8f143e3dfe0d84a835ff236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Wed, 16 Jul 2025 20:00:58 +0200 Subject: [PATCH 6/8] Add HDF test --- tests/test_standalone_hdf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_standalone_hdf.py b/tests/test_standalone_hdf.py index addcce55..ed25c1a9 100644 --- a/tests/test_standalone_hdf.py +++ b/tests/test_standalone_hdf.py @@ -75,6 +75,7 @@ def test_hdf_kwargs(self): "args": (), "kwargs": {"a": a, "b": b}, "queue_id": 123, + "error_log_file": "error.out", }, ) data_dict = load(file_name=file_name) From 4a1be415ac3171def197a0394f414f4a6ef7acb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Wed, 16 Jul 2025 20:12:42 +0200 Subject: [PATCH 7/8] test error function --- tests/test_standalone_error.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/test_standalone_error.py diff --git a/tests/test_standalone_error.py b/tests/test_standalone_error.py new file mode 100644 index 00000000..a0074cc2 --- /dev/null +++ b/tests/test_standalone_error.py @@ -0,0 +1,29 @@ +import os +import unittest +from executorlib.standalone.error import backend_write_error_file + + +class TestErrorWriter(unittest.TestCase): + def test_backend_write_error_file(self): + backend_write_error_file( + error=ValueError(), + apply_dict={ + "error_log_file": "error.out", + "fn": 1, + "args": (1, 2, 3), + "kwargs": {"a": 1, "b": 2, "c": 3}, + } + ) + error_file_content = [ + 'function: 1\n', + 'args: (1, 2, 3)\n', + "kwargs: {'a': 1, 'b': 2, 'c': 3}\n", + 'ValueError\n' + ] + with open("error.out", "r") as f: + content = f.readlines() + self.assertEqual(error_file_content, content) + + def tearDown(self): + if os.path.exists("error.out"): + os.remove("error.out") \ No newline at end of file From dc149191ef81e28ca4c92680d819f1786d9463f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Wed, 16 Jul 2025 20:20:45 +0200 Subject: [PATCH 8/8] new test --- tests/test_cache_backend_execute.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_cache_backend_execute.py b/tests/test_cache_backend_execute.py index bb634c33..bc0131bd 100644 --- a/tests/test_cache_backend_execute.py +++ b/tests/test_cache_backend_execute.py @@ -121,6 +121,7 @@ def test_execute_function_error(self): ) file_name = os.path.join(cache_directory, task_key + "_i.h5") os.makedirs(cache_directory, exist_ok=True) + data_dict["error_log_file"] = os.path.join(cache_directory, "error.out") dump(file_name=file_name, data_dict=data_dict) backend_execute_task_in_file(file_name=file_name) future_obj = Future() @@ -130,6 +131,11 @@ def test_execute_function_error(self): self.assertTrue(future_obj.done()) with self.assertRaises(ValueError): future_obj.result() + with open(os.path.join(cache_directory, "error.out"), "r") as f: + content = f.readlines() + self.assertEqual(content[1], 'args: []\n') + self.assertEqual(content[2], "kwargs: {'a': 1}\n") + self.assertEqual(content[-1], 'ValueError: 1\n') self.assertTrue( get_runtime(file_name=os.path.join(cache_directory, task_key + "_o.h5")) > 0.0