diff --git a/.github/ci-config.yml b/.github/ci-config.yml index 921a22ea9..e7cebd497 100644 --- a/.github/ci-config.yml +++ b/.github/ci-config.yml @@ -20,3 +20,9 @@ dependencies: | dependency_branch: develop parallelism_factor: 8 + +python_requirements: libs/pyext/test/requirements-test.txt + +overrides: + rocky-8.6: + python_version: "3.11" diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 716ae94a5..861cd60a5 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -12,6 +12,7 @@ build: - -DENABLE_ALL_TESTS=ON ctest_options: - -L nightly + requirements: libs/pyext/test/requirements-test.txt gnu-15.2.0: build: @@ -37,10 +38,12 @@ gnu-15.2.0: -DPython3_LIBRARIES=/usr/local/apps/python3/3.13.13-01/lib64/libpython3.13.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.13.13-01/lib64 -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.13.13-01/lib/python3.13/site-packages/pybind11/share/cmake/pybind11 + -DECFLOW_PYEXT_TEST_LD_LIBRARY_PATH=/usr/local/apps/gcc/15.2.0/lib64 # Note: Python 3.13.13-01, provides Pybind11 3.0.4. ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' + requirements: libs/pyext/test/requirements-test.txt gnu-14.2.0: build: @@ -65,6 +68,7 @@ gnu-14.2.0: -DPython3_LIBRARIES=/usr/local/apps/python3/3.12.9-01/lib64/libpython3.12.so -DPython3_LIBRARY_DIRS=/usr/local/apps/python3/3.12.9-01/lib64 -DCMAKE_PREFIX_PATH=/usr/local/apps/python3/3.13.13-01/lib/python3.13/site-packages/pybind11/share/cmake/pybind11 + -DECFLOW_PYEXT_TEST_LD_LIBRARY_PATH=/usr/local/apps/gcc/14.2.0/lib64 # Note: Python 3.13.13-01, provides Pybind11 3.0.4. # # Important: @@ -73,6 +77,7 @@ gnu-14.2.0: ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' + requirements: libs/pyext/test/requirements-test.txt gnu-12.2.0: build: @@ -101,6 +106,7 @@ gnu-12.2.0: ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' + requirements: libs/pyext/test/requirements-test.txt gnu-8.5.0: build: @@ -129,6 +135,7 @@ gnu-8.5.0: ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' + requirements: libs/pyext/test/requirements-test.txt intel-2025.3.1: build: @@ -159,6 +166,7 @@ intel-2025.3.1: ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' + requirements: libs/pyext/test/requirements-test.txt intel-2025.0.1: build: @@ -192,6 +200,7 @@ intel-2025.0.1: ctest_options: # disable s_http* tests - -L nightly -E '(s_http)' + requirements: libs/pyext/test/requirements-test.txt intel-2021.4.0: build: @@ -220,6 +229,7 @@ intel-2021.4.0: # Note: Python 3.10.10-01, provides Pybind11 2.10.3. ctest_options: - -L nightly -E '(py3_|s_http)' + requirements: libs/pyext/test/requirements-test.txt nvidia-24.11: build: @@ -252,6 +262,7 @@ nvidia-24.11: ctest_options: # disable all tests - --version + requirements: libs/pyext/test/requirements-test.txt nvidia-22.11: build: @@ -280,6 +291,7 @@ nvidia-22.11: ctest_options: # disable all tests - --version + requirements: libs/pyext/test/requirements-test.txt aocc-4.0.0: build: @@ -308,3 +320,4 @@ aocc-4.0.0: ctest_options: # disable all tests - --version + requirements: libs/pyext/test/requirements-test.txt diff --git a/libs/pyext/python3/CMakeLists.txt b/libs/pyext/python3/CMakeLists.txt index 0510ae7b8..358fec2ec 100644 --- a/libs/pyext/python3/CMakeLists.txt +++ b/libs/pyext/python3/CMakeLists.txt @@ -62,6 +62,48 @@ set_target_properties(ecflow3 # ===================================================================== # tests +# The python tests are executed via 'python -m pytest' (see below), so +# pytest must be available for the selected interpreter. Detect it at configure +# time and warn early, rather than having every py3_u_* / py3_s_* test fail at run time +# with 'No module named pytest'. +execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import pytest" + RESULT_VARIABLE _ECFLOW_HAVE_PYTEST + OUTPUT_QUIET ERROR_QUIET) +if( NOT _ECFLOW_HAVE_PYTEST EQUAL 0 ) + message( WARNING + "pytest was not found for ${Python3_EXECUTABLE}; the python tests " + "(py3_u_* and py3_s_*) will fail to run. Install it with:\n" + " ${Python3_EXECUTABLE} -m pip install -r " + "${CMAKE_CURRENT_SOURCE_DIR}/../test/requirements-test.txt" ) +endif() + +# +# When using some HPC toolchains, the dynamic linker for ecflow resolves and loads the platform default libstdc++ first, +# instead of the one that ecflow.so was built against. This leads to symbol version mismatches, and the python tests +# fail with errors like: +# +# /lib64/libstdc++.so.6: error: version lookup error: version `GLIBCXX_3.4.32' not found +# (required by /path/to/ecflow/build/libs/pyext/python3/ecflow.so) (fatal) +# +# ECFLOW_PYEXT_TEST_LD_LIBRARY_PATH allows a runtime library directory to be prepended to LD_LIBRARY_PATH, so that +# the correct libstdc++.so.6 is used by the python tests. +# +set(ECFLOW_PYEXT_TEST_LD_LIBRARY_PATH "" CACHE STRING + "Colon-separated directories prepended to LD_LIBRARY_PATH when running the python tests") + +set( _ECFLOW_PYEXT_ENVIRONMENT + "PATH=${Python3_EXECUTABLE_DIR}:$ENV{PATH}" + "PYTHONPATH=${ECFLOW_PYTHONPATH}" +) +if( ECFLOW_PYEXT_TEST_LD_LIBRARY_PATH ) + if( DEFINED ENV{LD_LIBRARY_PATH} AND NOT "$ENV{LD_LIBRARY_PATH}" STREQUAL "" ) + list( APPEND _ECFLOW_PYEXT_ENVIRONMENT "LD_LIBRARY_PATH=${ECFLOW_PYEXT_TEST_LD_LIBRARY_PATH}:$ENV{LD_LIBRARY_PATH}" ) + else() + list( APPEND _ECFLOW_PYEXT_ENVIRONMENT "LD_LIBRARY_PATH=${ECFLOW_PYEXT_TEST_LD_LIBRARY_PATH}" ) + endif() +endif() + foreach( test ${u_tests} ) ecbuild_add_test( TARGET @@ -70,9 +112,9 @@ foreach( test ${u_tests} ) unit python nightly fast TYPE PYTHON ARGS - ${CMAKE_CURRENT_SOURCE_DIR}/../test/py_${test}.py + -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/../test/py_${test}.py -v ENVIRONMENT - "PATH=${Python3_EXECUTABLE_DIR}:$ENV{PATH};PYTHONPATH=${ECFLOW_PYTHONPATH}" + ${_ECFLOW_PYEXT_ENVIRONMENT} TEST_DEPENDS u_base ) @@ -81,6 +123,11 @@ endforeach() if ( ENABLE_ALL_TESTS AND ENABLE_SERVER) + # + # Python integration tests run the ecflow server, and thus use ECF_SSL_DIR to find the generated SSL certificates. + # + list( APPEND _ECFLOW_PYEXT_ENVIRONMENT "ECF_SSL_DIR=${CMAKE_CURRENT_BINARY_DIR}/" ) + foreach( test ${s_tests} ) ecbuild_add_test( TARGET @@ -89,9 +136,9 @@ if ( ENABLE_ALL_TESTS AND ENABLE_SERVER) integration python nightly slow TYPE PYTHON ARGS - ${CMAKE_CURRENT_SOURCE_DIR}/../test/py_${test}.py + -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/../test/py_${test}.py -v ENVIRONMENT - "PATH=${Python3_EXECUTABLE_DIR}:$ENV{PATH};PYTHONPATH=${ECFLOW_PYTHONPATH};ECF_SSL_DIR=${CMAKE_CURRENT_BINARY_DIR}/" + ${_ECFLOW_PYEXT_ENVIRONMENT} TEST_DEPENDS u_base ) diff --git a/libs/pyext/test/conftest.py b/libs/pyext/test/conftest.py new file mode 100644 index 000000000..17e940b2d --- /dev/null +++ b/libs/pyext/test/conftest.py @@ -0,0 +1,29 @@ +# +# Copyright 2009- ECMWF. +# +# This software is licensed under the terms of the Apache Licence version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +""" +Shared pytest configuration for the ecFlow Python binding tests. + +This file is intentionally minimal. Most unit tests only need a working +``import ecflow`` and use bare ``assert`` statements. Global fixtures or +hooks should only be added here when they are genuinely shared across many +files. +""" + +import pytest # to ensure pytest is available + +def pytest_configure(config): + """Ensure ecflow is importable before any test is collected.""" + try: + import ecflow # noqa: F401 + except ImportError as exc: + raise RuntimeError( + "Could not import ecflow. Make sure the binding is on PYTHONPATH." + ) from exc diff --git a/libs/pyext/test/ecflow_test_util.py b/libs/pyext/test/ecflow_test_util.py index 21b2fc449..c106f12d9 100644 --- a/libs/pyext/test/ecflow_test_util.py +++ b/libs/pyext/test/ecflow_test_util.py @@ -8,7 +8,6 @@ # nor does it submit to any jurisdiction. # -from platform import python_version from socket import gethostname import os, sys, fnmatch import time @@ -23,9 +22,9 @@ import ecflow as ecf -def all_files(root, patterns='*', single_level=False, yield_folders=False): +def all_files(root, patterns="*", single_level=False, yield_folders=False): """Expand patterns from semi-colon separated string to list""" - patterns = patterns.split(';') + patterns = patterns.split(";") for path, subdirs, files in os.walk(root): if yield_folders: files.extend(subdirs) @@ -41,7 +40,8 @@ def all_files(root, patterns='*', single_level=False, yield_folders=False): # Enable to stop data being deleted, and stop server from being terminated -def debugging(): return False +def debugging(): + return False def ecf_home(port): @@ -54,9 +54,13 @@ def ecf_home(port): build_type = "debug" if ecf.debug_build() else "release" pid = os.getpid() - ecf_home_dir = f"ecf_home_py{python_version}__pid_{pid}__build_{build_type}__port_{port}" + ecf_home_dir = ( + f"ecf_home_py{python_version}__pid_{pid}__build_{build_type}__port_{port}" + ) - ecf_home_path = pathlib.Path.joinpath(pathlib.Path.cwd(), "test", "data", ecf_home_dir) + ecf_home_path = pathlib.Path.joinpath( + pathlib.Path.cwd(), "test", "data", ecf_home_dir + ) return str(ecf_home_path.absolute()) @@ -65,27 +69,50 @@ def get_parent_dir(file_path): return os.path.dirname(file_path) -def log_file_path(port): return "./" + gethostname() + "." + port + ".ecf.log" +def log_file_path(port): + return "./" + gethostname() + "." + port + ".ecf.log" -def checkpt_file_path(port): return "./" + gethostname() + "." + port + ".ecf.check" +def checkpt_file_path(port): + return "./" + gethostname() + "." + port + ".ecf.check" -def backup_checkpt_file_path(port): return "./" + gethostname() + "." + port + ".ecf.check.b" +def backup_checkpt_file_path(port): + return "./" + gethostname() + "." + port + ".ecf.check.b" -def white_list_file_path(port): return "./" + gethostname() + "." + port + ".ecf.lists" +def white_list_file_path(port): + return "./" + gethostname() + "." + port + ".ecf.lists" def print_test_start(test_name): - print("#######################################################################################") + print( + "#######################################################################################" + ) print(test_name) - print("ecflow version(" + ecf.Client().version() + ") debug build(" + str(ecf.debug_build()) + ") python(" + platform.python_version() + ")") - print("PYTHONPATH : " + str(os.environ['PYTHONPATH'].split(os.pathsep))) + print( + "ecflow version(" + + ecf.Client().version() + + ") debug build(" + + str(ecf.debug_build()) + + ") python(" + + platform.python_version() + + ")" + ) + print( + "PYTHONPATH : " + str(os.environ["PYTHONPATH"].split(os.pathsep)) + ) # print("sys.path : " + str(sys.path)) print("Current working directory : " + str(os.getcwd())) - print("Python version : " + str(sys.version_info[0]) + "." + str(sys.version_info[1])) - print("#######################################################################################") + print( + "Python version : " + + str(sys.version_info[0]) + + "." + + str(sys.version_info[1]) + ) + print( + "#######################################################################################" + ) def clean_up_server(port): @@ -115,7 +142,6 @@ def clean_up_data(port): print(" Remove OK") except: print(" Remove Failed") - pass class Protocol(Enum): @@ -131,22 +157,33 @@ class EcfPortLock(object): def __init__(self, protocol): print(" EcfPortLock:__init__") self._protocol = protocol - pass def at_time(self): - return datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S') + return datetime.datetime.fromtimestamp(time.time()).strftime("%H:%M:%S") def find_free_port(self, seed_port): - print(" EcfPortLock:find_free_port starting with " + str(seed_port) + " at time " + self.at_time()) + print( + " EcfPortLock:find_free_port starting with " + + str(seed_port) + + " at time " + + self.at_time() + ) port = seed_port while 1: # port must be free for at least 15 seconds - if self._timed_free_port(port, 3) == True: - print(" *FOUND* free server port " + str(port) + " : " + self.at_time()) + if self._timed_free_port(port, 3): + print( + " *FOUND* free server port " + str(port) + " : " + self.at_time() + ) if self.do_lock(port): break else: - print(" *Server* port " + str(port) + " busy( by ping), trying next port " + self.at_time()) + print( + " *Server* port " + + str(port) + + " busy( by ping), trying next port " + + self.at_time() + ) port = port + 1 return str(port) @@ -154,7 +191,7 @@ def find_free_port(self, seed_port): def _timed_free_port(self, port, wait_time=10): count = 0 while count < wait_time: - if self._free_port(port) == True: + if self._free_port(port): count = count + 1 time.sleep(1) else: @@ -175,26 +212,48 @@ def _free_port(self, port): def do_lock(self, port): file = self._lock_file(port) if os.path.exists(file): - print(" *LOCKED* lock file exists " + file + " trying next port : " + self.at_time()) + print( + " *LOCKED* lock file exists " + + file + + " trying next port : " + + self.at_time() + ) return False try: - fp = open(file, 'w') + fp = open(file, "w") try: self.lock_time = self.at_time() fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB) self.lock_file_fp = fp print(" *LOCKED* file " + file + " : " + self.lock_time) - return True; + return True except IOError: - print(" Could *NOT* lock file " + file + " trying next port : " + self.at_time()) + print( + " Could *NOT* lock file " + + file + + " trying next port : " + + self.at_time() + ) return False except IOError as e: - print(" Could not open file " + file + " for write trying next port : " + self.at_time()) + print( + " Could not open file " + + file + + " for write trying next port : " + + self.at_time() + ) return False def remove(self, port): file = self._lock_file(port) - print(" Remove lock file : " + file + " : lock_time: " + self.lock_time + " release_time: " + self.at_time()) + print( + " Remove lock file : " + + file + + " : lock_time: " + + self.lock_time + + " release_time: " + + self.at_time() + ) self.lock_file_fp.close() os.remove(file) @@ -212,6 +271,7 @@ def _lock_file(self, port): # =============================================================================== + class Server(object): """TestServer: allow debug and release version of python tests to run at the same time, by generating a unique port each time""" @@ -220,10 +280,11 @@ def __init__(self, protocol=Protocol.CUSTOM): print("Server:__init__: Starting server") self._protocol = protocol if not debugging(): - seed_port = int(os.getenv('ECF_FREE_PORT', '3153')) + seed_port = int(os.getenv("ECF_FREE_PORT", "3153")) if sys.version_info[0] == 3: # python3 can run at same time - seed_port = int(os.getenv('ECF_FREE_PORT', '3154')) - if ecf.debug_build(): seed_port = seed_port + 1 + seed_port = int(os.getenv("ECF_FREE_PORT", "3154")) + if ecf.debug_build(): + seed_port = seed_port + 1 self.lock_file = EcfPortLock(self._protocol) self.the_port = self.lock_file.find_free_port(seed_port) else: @@ -238,13 +299,25 @@ def __init__(self, protocol=Protocol.CUSTOM): self.ci.enable_http() def at_time(self): - return datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.fromtimestamp(time.time()).strftime( + "%Y-%m-%d %H:%M:%S" + ) def __enter__(self): try: - print("Server:__enter__: About to ping localhost: " + self.the_port + " : " + self.at_time()) + print( + "Server:__enter__: About to ping localhost: " + + self.the_port + + " : " + + self.at_time() + ) self.ci.ping() - print(" ------- Server all ready running on port " + self.the_port + " *UNEXPECTED* -- : " + self.at_time()) + print( + " ------- Server all ready running on port " + + self.the_port + + " *UNEXPECTED* -- : " + + self.at_time() + ) sys.exit(1) except RuntimeError as e: while 1: @@ -256,13 +329,21 @@ def __enter__(self): if self._protocol == Protocol.HTTP: self.ci.enable_http() - print(" ------- Server *NOT* running on port " + self.the_port + " as *EXPECTED* ------ ") - print(" ------- Start the server on port " + self.the_port + " ---------") + print( + " ------- Server *NOT* running on port " + + self.the_port + + " as *EXPECTED* ------ " + ) + print( + " ------- Start the server on port " + + self.the_port + + " ---------" + ) if not debugging(): clean_up_server(str(self.the_port)) clean_up_data(str(self.the_port)) - server_exe = ecf.File.find_server(); + server_exe = ecf.File.find_server() assert len(server_exe) != 0, "Could not locate the server executable" server_exe += " --port=" + self.the_port + " --ecfinterval=4" @@ -277,8 +358,13 @@ def __enter__(self): print(" Server has started " + self.at_time()) break else: - print(" Server failed to start after 60 second, trying next port !!!!!! " + self.at_time()) - self.the_port = self.lock_file.find_free_port(int(self.the_port) + 1) + print( + " Server failed to start after 60 second, trying next port !!!!!! " + + self.at_time() + ) + self.the_port = self.lock_file.find_free_port( + int(self.the_port) + 1 + ) print(" Run the tests, leaving Server:__enter__:") @@ -286,7 +372,10 @@ def __enter__(self): return [self.ci, self._protocol] def __exit__(self, exctype, value, tb): - print("Server:__exit__: Kill the server, clean up log file, check pt files and lock files, ECF_HOME " + self.at_time()) + print( + "Server:__exit__: Kill the server, clean up log file, check pt files and lock files, ECF_HOME " + + self.at_time() + ) print(" exctype:", exctype) print(" value:", value) print(" tb:", tb) @@ -309,8 +398,8 @@ def __init__(self, env): self.expected_env = env def __enter__(self): - self.original_env = {}; - for (name, value) in self.expected_env.items(): + self.original_env = {} + for name, value in self.expected_env.items(): if name in os.environ: self.original_env[name] = os.environ[name] else: @@ -318,7 +407,7 @@ def __enter__(self): os.environ[name] = value def __exit__(self, exception_type, exception_value, exception_traceback): - for (name, value) in self.original_env.items(): + for name, value in self.original_env.items(): if value is None: del os.environ[name] else: @@ -340,7 +429,6 @@ def __enter__(self): def __exit__(self, exception_type, exception_value, exception_traceback): os.remove(self.name) - pass def __str__(self): return f"Test.MockFile({self.name}, {self.content})" @@ -360,9 +448,13 @@ def test_started(): # ============================================================================== def test_success(): - print("#######################################################################################") + print( + "#######################################################################################" + ) print(" All Tests passed") - print("#######################################################################################") + print( + "#######################################################################################" + ) def execute_all(clazz): diff --git a/libs/pyext/test/py_s_TestClientApi.py b/libs/pyext/test/py_s_TestClientApi.py index 21c6a86a1..3c8917767 100644 --- a/libs/pyext/test/py_s_TestClientApi.py +++ b/libs/pyext/test/py_s_TestClientApi.py @@ -13,16 +13,38 @@ import time import os import pwd -import select import sys import threading from datetime import datetime import shutil # used to remove directory tree +import pytest + # ecflow_test_util, see File ecflow_test_util.py import ecflow_test_util as Test -from ecflow import Defs, Suite, Family, Task, Edit, Meter, Clock, DState, Style, State, RepeatDate, RepeatDateTime, \ - PrintStyle, File, Client, SState, CheckPt, Cron, Late, debug_build, Flag, FlagType +from ecflow import ( + Defs, + Suite, + Family, + Task, + Edit, + Meter, + Clock, + DState, + Style, + State, + RepeatDate, + RepeatDateTime, + PrintStyle, + File, + Client, + SState, + CheckPt, + Cron, + Late, + Flag, + FlagType, +) import re import platform @@ -41,24 +63,28 @@ def wrapper(*args, **kwargs): return decorator -def ecf_includes(): return os.getcwd() + "/test/data/includes" +def ecf_includes(): + return os.getcwd() + "/test/data/includes" -def debugging(): return False # Use to enable auto flush and disable log file tests +def debugging(): + return False # Use to enable auto flush and disable log file tests def create_defs(name, port, protocol): defs = Defs() suite_name = name - if len(suite_name) == 0: suite_name = "s1" - suite = defs.add_suite(suite_name); + if len(suite_name) == 0: + suite_name = "s1" + suite = defs.add_suite(suite_name) - ecfhome = Test.ecf_home(port); - suite.add_variable("ECF_HOME", ecfhome); + ecfhome = Test.ecf_home(port) + suite.add_variable("ECF_HOME", ecfhome) protocol_option = "--http" if protocol == Test.Protocol.HTTP else "" suite.add_variable("ECF_CLIENT_EXE_PATH", f"{File.find_client()} {protocol_option}") - suite.add_variable("SLEEP", "1"); # not strictly required since default is 1 second - suite.add_variable("ECF_INCLUDE", ecf_includes()); + suite.add_variable("SLEEP", "1") + # not strictly required since default is 1 second + suite.add_variable("ECF_INCLUDE", ecf_includes()) family = suite.add_family("f1") family.add_task("t1") @@ -66,7 +92,7 @@ def create_defs(name, port, protocol): return defs -def test_host_port(ci, host, port): +def _can_set_host_port(ci, host, port): try: ci.set_host_port(host, port) return True @@ -74,7 +100,7 @@ def test_host_port(ci, host, port): return False -def test_host_port_(ci, host_port): +def _can_set_combined_host_port(ci, host_port): try: ci.set_host_port(host_port) return True @@ -82,7 +108,7 @@ def test_host_port_(ci, host_port): return False -def test_client_host_port(host, port): +def _can_construct_client_with_host_port(host, port): try: Client(host, port) return True @@ -90,7 +116,7 @@ def test_client_host_port(host, port): return False -def test_client_host_port_(host_port): +def _can_construct_client_with_combined_host_port(host_port): try: Client(host_port) return True @@ -104,500 +130,22 @@ def sync_local(ci): ci.sync_local() -def test_set_host_port(): - print("test_set_host_port") - ci = Client() - ci.debug(True) - ci.enable_http() - print(" Client.get_host() = " + ci.get_host()) - print(" Client.get_port() = " + ci.get_port()) - assert test_host_port(ci, "host", "3141"), "Expected no errors" - assert test_host_port(ci, "host", 4444), "Expected no errors" - assert test_host_port_(ci, "host:4444"), "Expected no errors" - assert test_host_port_(ci, "host@4444"), "Expected no errors" - assert test_host_port(ci, "", "") == False, "Expected errors" - assert test_host_port(ci, "host", "") == False, "Expected errors" - assert test_host_port(ci, "host", "host") == False, "Expected errors" - assert test_host_port_(ci, "host:host") == False, "Expected errors" - assert test_host_port_(ci, "3141:host") == False, "Expected errors" - assert test_host_port_(ci, "3141@host") == False, "Expected errors" - assert test_host_port_(ci, "3141@") == False, "Expected errors" - - assert test_client_host_port("host", "3141"), "Expected no errors" - assert test_client_host_port("host", 4444), "Expected no errors" - assert test_client_host_port_("host:4444"), "Expected no errors" - assert test_client_host_port("", "") == False, "Expected errors" - assert test_client_host_port("host", "") == False, "Expected errors" - assert test_client_host_port("host", "host") == False, "Expected errors" - assert test_client_host_port_("host:host") == False, "Expected errors" - assert test_client_host_port_("3141:host") == False, "Expected errors" - - def print_test(ci, test_name): - print(test_name, flush=True) + print(f"=====> Running: {test_name}", flush=True) if ci.is_auto_sync_enabled(): - ci.log_msg(test_name + " ============= AUTO SYNC ENABLED ================================ ") + ci.log_msg( + test_name + + " ============= AUTO SYNC ENABLED ================================ " + ) else: - ci.log_msg(test_name + " ================================================================ ") - - -def test_version(ci): - print_test(ci, "test_version") - client_version = ci.version(); - server_version = ci.server_version(); - print(" client_version: ", client_version) - print(" server_version: ", server_version) - assert client_version == server_version, "Expected client version(" + client_version + ") and server version(" + server_version + ") to match\n"; - - -def test_client_get_server_defs(ci, protocol): - print_test(ci, "test_client_get_server_defs") - ci.delete_all() # start fresh - ci.load(create_defs("", ci.get_port(), protocol)) - ci.get_server_defs() - assert ci.get_defs().find_suite("s1") is not None, "Expected to find suite of name s1:\n" + str(ci.get_defs()) - - ci.delete_all() # start fresh - ci.load(create_defs("", ci.get_port(), protocol)) - sync_local(ci) - - assert ci.get_defs().find_suite("s1") is not None, "Expected to find suite of name s1:\n" + str(ci.get_defs()) - - -def test_client_new_log(ci): - print_test(ci, "test_client_new_log") - log_path = Test.log_file_path(ci.get_port()) - if not os.path.exists(log_path): - print(log_path + " : log does not exist ?") - - new_log_file_name = "./test_client_new_log_" + str(os.getpid()) + ".log" - try: - os.remove(new_log_file_name) # delete file if it exists - except: - pass - - ci.new_log(new_log_file_name) - ci.flush_log() # close log file and force write to disk - assert os.path.exists(new_log_file_name), new_log_file_name + " : New log does not exist" - try: - os.remove(new_log_file_name) - except: - pass - - # reset new log to original - ci.new_log(log_path) - ci.ping() - ci.flush_log() # close log file and force write to disk - log_file = open(log_path) - try: - log_text = log_file.read(); # assume log file not to big - finally: - log_file.close(); - assert log_text.find("--ping") != -1, "Expected to find --ping in log file" - - if not os.path.exists(log_path): - print(log_path + " : log does not exist ?") - - -def test_client_clear_log(ci): - if debugging(): return # dont run this test when debugging as log file is lost - print_test(ci, "test_client_clear_log") - log_path = Test.log_file_path(ci.get_port()) - # populate log - ci.ping(); - ci.ping(); - ci.flush_log() # close log file and force write to disk - log_file = open(log_path) - try: - log_text = log_file.read(); # assume log file not to big - finally: - log_file.close(); - assert log_text.find("--ping") != -1, "Expected to find --ping in log file" - - ci.clear_log() - if not os.path.exists(log_path): - print(log_path + " : log does not exist ?") - - log_file = open(log_path) - try: - log_text = log_file.read(); # assume log file not to big - finally: - log_file.close(); - assert len(log_text) == 0, "Expected log file to be empty but found " + log_text - - if not os.path.exists(log_path): - print(log_path + " : log does not exist ?") - - -def test_client_log_msg(ci): - if debugging(): return # dont run this test when debugging - - print_test(ci, "test_client_log_msg") - log_path = Test.log_file_path(ci.get_port()) - if not os.path.exists(log_path): - print(log_path + " : log does not exist ?") - - # Send a message to the log file, then make sure it was written - ci.log_msg("Humpty dumpty sat on a wall!") - ci.flush_log(); # flush and close log file, so we can open it - log_file = open(log_path) - try: - log_text = log_file.read(); # assume log file not to big - finally: - log_file.close(); - assert log_text.find("Humpty dumpty sat on a wall!") != -1, "Expected to find Humpty dumpty in the log file" - - if not os.path.exists(log_path): - print(log_path + " : log does not exist ?") - - -def test_client_restart_server(ci): - print_test(ci, "test_client_restart_server") - ci.restart_server() - sync_local(ci) - assert ci.get_defs().get_server_state() == SState.RUNNING, "Expected server to be running" - - paths = list(ci.changed_node_paths) - assert len(paths) == 1, "expected changed node to be the root node: " + paths - assert paths[0] == "/", "Expected root path but found " + str(paths[0]) - - -def test_client_halt_server(ci): - print_test(ci, "test_client_halt_server") - ci.halt_server() - sync_local(ci) - assert ci.get_defs().get_server_state() == SState.HALTED, "Expected server to be halted" - - paths = list(ci.changed_node_paths) - assert len(paths) == 1, "expected changed node to be the root node" - assert paths[0] == "/", "Expected root path but found " + str(paths[0]) - ci.restart_server() - - -def test_client_shutdown_server(ci): - print_test(ci, "test_client_shutdown_server") - ci.shutdown_server() - sync_local(ci) - assert ci.get_defs().get_server_state() == SState.SHUTDOWN, "Expected server to be shutdown" - - paths = list(ci.changed_node_paths) - assert len(paths) == 1, "expected changed node to be the root node" - assert paths[0] == "/", "Expected root path but found " + str(paths[0]) - - -def test_client_load_in_memory_defs(ci, protocol): - print_test(ci, "test_client_load_in_memory_defs") - ci.delete_all() # start fresh - ci.load(create_defs("", ci.get_port(), protocol)) - sync_local(ci) - assert ci.get_defs().find_suite("s1") is not None, "Expected to find suite of name s1:\n" + str(ci.get_defs()) - - -def test_client_load_from_disk(ci, protocol): - print_test(ci, "test_client_load_from_disk") - ci.delete_all() # start fresh - defs = create_defs("", ci.get_port(), protocol); - defs_file = "test_client_load_from_disk_" + str(os.getpid()) + ".def" - defs.save_as_defs(defs_file) - assert os.path.exists(defs_file), "Expected file " + defs_file + " to exist after defs.save_as_defs()" - ci.load(defs_file) # open and parse defs file, and load into server.\n" + ci.log_msg( + test_name + + " ================================================================ " + ) - sync_local(ci) - assert ci.get_defs().find_suite("s1") is not None, "Expected to find suite of name s1:\n" + str(ci.get_defs()) - os.remove(defs_file) - -def test_client_checkpt(ci, protocol): - print_test(ci, "test_client_checkpt") - # start fresh - ci.delete_all() - port = ci.get_port(); - try: - os.remove(Test.checkpt_file_path(port)) - os.remove(Test.backup_checkpt_file_path(port)) - except: - pass - - ci.load(create_defs("", ci.get_port(), protocol)) - ci.checkpt() - assert os.path.exists(Test.checkpt_file_path(port)), "Expected check pt file to exist after ci.checkpt()" - assert os.path.exists(Test.backup_checkpt_file_path(port)) == False, "Expected back up check pt file to *NOT* exist" - - ci.checkpt() # second check pt should cause backup check pt to be written - assert os.path.exists( - Test.backup_checkpt_file_path(port)), "Expected back up check pt file to exist after second ci.checkpt()" - - ci.checkpt(CheckPt.NEVER) # switch of check pointing - ci.checkpt(CheckPt.ALWAYS) # always check point, at any state change - ci.checkpt(CheckPt.ON_TIME) # Check point periodically, by interval set in server - ci.checkpt(CheckPt.ON_TIME, 200) # Check point periodically, by interval set in server - ci.checkpt(CheckPt.UNDEFINED, 0, 35) # Change check point save time alarm - - os.remove(Test.checkpt_file_path(port)) - os.remove(Test.backup_checkpt_file_path(port)) - - -def test_client_restore_from_checkpt(ci, protocol): - print_test(ci, "test_client_restore_from_checkpt") - # start fresh - ci.delete_all() - port = ci.get_port() - try: - os.remove(Test.checkpt_file_path(port)) - os.remove(Test.backup_checkpt_file_path(port)) - except: - pass - - ci.load(create_defs("", ci.get_port(), protocol)) - ci.checkpt() - ci.delete_all() - - sync_local(ci) - assert ci.get_defs().find_suite("s1") is None, "Expected all suites to be delete:\n" - - ci.halt_server() # server must be halted, otherwise restore_from_checkpt will throw - ci.restore_from_checkpt() - - sync_local(ci) - assert ci.get_defs().find_suite("s1") is not None, "Expected to find suite s1 after restore from checkpt:\n" + str( - ci.get_defs()) - - os.remove(Test.checkpt_file_path(port)) - ci.restart_server() - - -def get_username(): return pwd.getpwuid(os.getuid())[0] - - -def test_client_reload_wl_file(ci): - print_test(ci, "test_client_reload_wl_file") - - expected = False - try: - ci.reload_wl_file(); - except: - expected = True - assert expected, "Expected reload to fail when no white list specified" - - # create a white list file - port = ci.get_port() - wl_file = open(Test.white_list_file_path(port), 'w') - wl_file.write("#\n") - wl_file.write("4.4.14 # comment\n\n") - wl_file.write("# These user have read and write access to the server\n") - wl_file.write( - get_username() + "\n") # add current user otherwise remaining test's, wont have access from server anymore - wl_file.write("axel # admin\n") - wl_file.write("john # admin\n\n") - wl_file.write("# Read only users\n") - wl_file.write("-fred # needs read access only\n") - wl_file.write("-joe90 # needs read access only\n") - wl_file.close(); - - ci.reload_wl_file(); - os.remove(Test.white_list_file_path(port)) - - -def test_client_run(ci, protocol): - print_test(ci, "test_client_run") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_client_run", port, protocol) - suite = defs.find_suite("test_client_run") - suite.add_defstatus(DState.suspended) - - defs.generate_scripts(); - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - ci.begin_all_suites() - ci.run("/test_client_run", False) - - count = 0 - while 1: - count += 1 - ci.sync_local() # get the changes, synced with local defs - suite = ci.get_defs().find_suite("test_client_run") - assert suite is not None, "Expected to find suite test_client_run:\n" + str(ci.get_defs()) - print(f"Suite state: {suite.get_state()}, {count}, at {datetime.now()}") - if suite.get_state() == State.complete: - break; - assert suite.get_state() != State.aborted, "Expected suite to complete, but found it was aborted" - time.sleep(3) - if count > 20: - assert False, "test_client_run aborted after " + str(count) + " loops:\n" + str(ci.get_defs()) - - ci.log_msg("Looped " + str(count) + " times") - dir_to_remove = Test.ecf_home(port) + "/" + "test_client_run" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_client_run_with_multiple_paths(ci, protocol): - print_test(ci, "test_client_run_with_multiple_paths") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_client_run_with_multiple_paths", port, protocol) - suite = defs.find_suite("test_client_run_with_multiple_paths") - suite.add_defstatus(DState.suspended) - - defs.generate_scripts(); - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - ci.begin_all_suites() - path_list = ["/test_client_run_with_multiple_paths/f1/t1", "/test_client_run_with_multiple_paths/f1/t2"] - ci.run(path_list, False) - - count = 0 - while 1: - count += 1 - ci.sync_local() # get the changes, synced with local defs - suite = ci.get_defs().find_suite("test_client_run_with_multiple_paths") - assert suite is not None, "Expected to find suite test_client_run_with_multiple_paths:\n" + str(ci.get_defs()) - if suite.get_state() == State.complete: - break; - time.sleep(3) - if count > 20: - assert False, "test_client_run_with_multiple_paths aborted after " + str(count) + " loops:\n" + str( - ci.get_defs()) - - ci.log_msg("Looped " + str(count) + " times") - - dir_to_remove = Test.ecf_home(port) + "/" + "test_client_run_with_multiple_paths" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_client_requeue(ci, protocol): - print_test(ci, "test_client_requeue") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_client_requeue", port, protocol) - suite = defs.find_suite("test_client_requeue") - suite.add_defstatus(DState.suspended) - - defs.generate_scripts() - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - ci.begin_all_suites() - - ci.force_state_recursive("/test_client_requeue", State.unknown) - sync_local(ci) - suite = ci.get_defs().find_suite("test_client_requeue") - assert suite.get_state() == State.unknown, "Expected to find suite with state unknown" - - ci.requeue("/test_client_requeue") - sync_local(ci); - suite = ci.get_defs().find_suite("test_client_requeue") - assert suite.get_state() == State.queued, "Expected to find suite with state queued" - - dir_to_remove = Test.ecf_home(port) + "/" + "test_client_requeue" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_client_requeue_with_multiple_paths(ci, protocol): - print_test(ci, "test_client_requeue_with_multiple_paths") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_client_requeue_with_multiple_paths", port, protocol) - suite = defs.find_suite("test_client_requeue_with_multiple_paths") - suite.add_defstatus(DState.suspended) - - defs.generate_scripts() - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - ci.begin_all_suites() - - ci.force_state_recursive("/test_client_requeue_with_multiple_paths", State.unknown) - sync_local(ci); - task1 = ci.get_defs().find_abs_node("/test_client_requeue_with_multiple_paths/f1/t1") - task2 = ci.get_defs().find_abs_node("/test_client_requeue_with_multiple_paths/f1/t2") - assert task1.get_state() == State.unknown, "Expected to find t1 with state unknown" - assert task2.get_state() == State.unknown, "Expected to find t2 with state unknown" - - path_list = ["/test_client_requeue_with_multiple_paths/f1/t1", "/test_client_requeue_with_multiple_paths/f1/t2"] - ci.requeue(path_list) - sync_local(ci); - task1 = ci.get_defs().find_abs_node("/test_client_requeue_with_multiple_paths/f1/t1") - task2 = ci.get_defs().find_abs_node("/test_client_requeue_with_multiple_paths/f1/t2") - assert task1.get_state() == State.queued, "Expected to find task t1 with state queued" - assert task2.get_state() == State.queued, "Expected to find task t2 with state queued" - - dir_to_remove = Test.ecf_home(port) + "/" + "test_client_requeue_with_multiple_paths" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_client_free_dep(ci, protocol): - print_test(ci, "test_client_free_dep") - ci.delete_all() - - # add a real clock, since we are adding date dependencies - # Note: adding a future time dependency on a task, will cause it to requeue, when complete - # Hence even when we free these dependency they get requeued. - # So we use todays date. - ltime = time.localtime(); - day = ltime.tm_mday - month = ltime.tm_mon - year = ltime.tm_year - - defs = Defs() - suite = defs.add_suite("test_client_free_dep"); - suite.add_clock(Clock(False)) # true means hybrid, False means real - ecfhome = Test.ecf_home(ci.get_port()); - suite.add_variable("ECF_HOME", ecfhome); - protocol_option = "--http" if protocol == Test.Protocol.HTTP else "" - suite.add_variable("ECF_CLIENT_EXE_PATH", f"{File.find_client()} {protocol_option}") - suite.add_variable("SLEEPTIME", "1"); - suite.add_variable("ECF_INCLUDE", ecf_includes()); - family = suite.add_family("f1") - family.add_task("t1").add_time("00:01") - family.add_task("t2").add_date(day, month, year) - family.add_task("t3").add_trigger("1 == 0") - t4 = family.add_task("t4") - t4.add_time("00:01") - t4.add_date(day, month, year) - t4.add_trigger("1 == 0") - - defs.generate_scripts(); - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - ci.begin_all_suites() - - t1_path = "/test_client_free_dep/f1/t1" - t2_path = "/test_client_free_dep/f1/t2" - t3_path = "/test_client_free_dep/f1/t3" - t4_path = "/test_client_free_dep/f1/t4" - while 1: - ci.sync_local() - t1 = ci.get_defs().find_abs_node(t1_path) - t2 = ci.get_defs().find_abs_node(t2_path) - t3 = ci.get_defs().find_abs_node(t3_path) - t4 = ci.get_defs().find_abs_node(t4_path) - - if t1.get_state() == State.queued: ci.free_time_dep(t1_path) - if t2.get_state() == State.queued: ci.free_date_dep(t2_path) - if t3.get_state() == State.queued: ci.free_trigger_dep(t3_path) - if t4.get_state() == State.queued: ci.free_all_dep(t4_path) - - suite = ci.get_defs().find_suite("test_client_free_dep") - if suite.get_state() == State.complete: - break; - time.sleep(3) - - dir_to_remove = Test.ecf_home(ci.get_port()) + "/" + "test_client_free_dep" - shutil.rmtree(dir_to_remove, ignore_errors=True) +def get_username(): + return pwd.getpwuid(os.getuid())[0] class CustomStdOut: @@ -643,1682 +191,2618 @@ def _capture_output(self): self.captured += os.read(self.o_pipe, 1).decode(self.stream.encoding) if self.captured.endswith(self.marker): # we reached the end marker, so we stop capturing and remove the marker - self.captured = self.captured[:-len(self.marker)] + self.captured = self.captured[: -len(self.marker)] break def value(self): return self.captured -@disable_on("macOS-13.*-arm64-.*") -def test_client_stats(ci): - print_test(ci, "test_client_stats") - out = CustomStdOut() - with out: - stats = ci.stats() - assert "Statistics" in stats, "Expected 'Statistics' in the response" - assert "Statistics" in out.value(), "Expected 'Statistics' in the captured output" - - -@disable_on("macOS-13.*-arm64-.*") -def test_client_stats_with_stdout(ci): - print_test(ci, "test_client_stats_with_stdout") - out = CustomStdOut() - with out: - stats = ci.stats(True) - assert "Statistics" in stats, "Expected 'Statistics' in the response" - assert "Statistics" in out.value(), "Expected 'Statistics' in the captured output" - - -@disable_on("macOS-13.*-arm64-.*") -def test_client_stats_without_stdout(ci): - print_test(ci, "test_client_stats_without_stdout") - out = CustomStdOut() - with out: - stats = ci.stats(False) - assert "Statistics" in stats, "Expected 'Statistics' in the response" - assert not out.value(), "No captured output expected, but found: " + out.value() - - -def test_client_stats_reset(ci): - print_test(ci, "test_client_stats_reset") - ci.stats_reset() - stats = ci.stats() - assert "Statistics" in stats, "Expected 'Statistics' in the response" - - -def test_client_debug_server_on_off(ci): - print_test(ci, "test_client_debug_server_on_off") - ci.debug_server_on() # writes to standard out - ci.debug_server_off() - - -def test_client_check(ci): - print_test(ci, "test_client_check") - ci.delete_all() - - defs = Defs() - defs.add_extern("/a/b/c/d") - defs.add_extern("/a/b/c/d/e:event") - defs.add_extern("/a/b/c/d/e:meter") - defs.add_extern("/made/up/redundant/extren") - defs.add_extern("/made/up/redundant/extren") - defs.add_extern("/limits:c1a") - defs.add_extern("/limits:c1a") - defs.add_extern("fred") - defs.add_extern("limits:hpcd") - defs.add_extern("/suiteName:sg1") - defs.add_extern("/obs/limits:hpcd") - suite = defs.add_suite("extern") - family_f1 = suite.add_family("f1") - family_f1.add_task("p").add_trigger("/a/b/c/d == complete") # extern path - family_f1.add_task("q").add_trigger("/a/b/c/d/e:event == set") # extern event path - family_f1.add_task("r").add_trigger("/a/b/c/d/e:meter le 30") # extern meter path - - suite.add_inlimit("c1a", "/limits") - suite.add_inlimit("fred") - - family_anon = suite.add_family("anon") - family_anon.add_inlimit("hpcd", "limits") - family_anon.add_task("t1").add_inlimit("sg1", "/suiteName") - family_anon.add_task("t2").add_inlimit("hpcd", "/obs/limits") - family_anon.add_task("t3").add_inlimit("c1a", "/limits") - - # CLIENT side check - client_check = defs.check() - assert len(client_check) == 0, "Expected clean defs check due to externs but found:\n" + client_check + "\n" + str( - defs) - - # SERVER side check - ci.load(defs) - server_check = ci.check("") # empty string means check the whole defs, otherwise a node path can be specified. - # print(server_check) - assert len(server_check) > 0, "Expected defs to fail, since no externs in server " - - -def test_client_suites(ci, protocol): - print_test(ci, "test_client_suites") - ci.delete_all() - assert len(ci.suites()) == 0, "expected 0 suite " - - defs = create_defs("test_client_suites", ci.get_port(), protocol) - ci.load(defs) - assert len(ci.suites()) == 1, "expected 1 suite " - - ci.delete_all() - defs.add_suite("s2") - ci.load(defs) - assert len(ci.suites()) == 2, "expected 2 suite " - - -def test_client_ch_suites(ci): - print_test(ci, "test_client_ch_suites") - try: - ci.ch_drop_user("") # drop all handle associated with current user - except: - pass # Drop throws if no handle registered - ci.delete_all() - - defs = Defs() - for i in range(1, 7): defs.add_suite("s" + str(i)) - ci.load(defs) - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 6, "Expected 6 after load, but found " + str( - len(list((ci.get_defs().suites)))) - - suite_names = ['s1', 's2', 's3'] - ci.ch_register(True, suite_names) # register interest in suites s1,s2,s3 and any new suites - print("ch_handle after register : ", ci.ch_handle()) - ci.ch_suites() # writes to standard out, list of suites and handles - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 3, "Expected 3 registered suites but found " + str( - len(list((ci.get_defs().suites)))) - - ci.ch_register(False, ["s1"]) # register interest in suites s1. ci remembers the last client handle - ci.ch_suites() # writes to standard out, list of suites and handles - sync_local(ci) - ci.ch_suites() # writes to standard out, list of suites and handles - assert len(list(ci.get_defs().suites)) == 1, "Expected 1 registered suites but found " + str( - len(list((ci.get_defs().suites)))) - - -def test_client_ch_register(ci): - print_test(ci, "test_client_ch_register") - try: - ci.ch_drop_user("") # drop all handle associated with current user - except: - pass # Drop throws if no handle registered - ci.delete_all() - - defs = Defs() - for i in range(1, 7): defs.add_suite("s" + str(i)) - ci.load(defs) - - suite_names = ['s1', 's2', 's3'] - ci.ch_register(True, suite_names) # register interest in suites s1,s2,s3 and any new suites - ci.ch_register(False, suite_names) # register interest in suites s1,s2,s3 only - - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 3, "Expected 3 registered suites but found " + str( - len(list((ci.get_defs().suites)))) - - -def test_client_ch_drop(ci): - print_test(ci, "test_client_ch_drop") - try: - ci.ch_drop_user("") # drop all handle associated with current user - except: - pass # Drop throws if no handle registered - ci.delete_all() - - defs = Defs() - for i in range(1, 7): defs.add_suite("s" + str(i)) - ci.load(defs) - - try: - # register interest in suites s1,s2,s3 and any new suites - suite_names = ['s1', 's2', 's3'] - ci.ch_register(True, suite_names) - finally: - ci.ch_drop() # drop using handle stored in ci., from last register - - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 6, "Expected 6 suites but found " + str(len(list((ci.get_defs().suites)))) - - -def test_client_ch_drop_user(ci): - print_test(ci, "test_client_ch_drop_user") - ci.delete_all() - try: - ci.ch_drop_user("") # drop all handle associated with current user - except: - pass # Drop throws if no handle registered - - defs = Defs() - for i in range(1, 7): defs.add_suite("s" + str(i)) - ci.load(defs) - - try: - # register interest in suites s1,s2,s3 and any new suites - suite_names = ['s1', 's2', 's3'] - ci.ch_register(True, suite_names) - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 3, "Expected 3 suites but found " + str( - len(list((ci.get_defs().suites)))) - except RuntimeError as e: - print(str(e)) - - ci.ch_drop_user("") # drop all handle associated with current user - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 6, "Expected 6 suites but found " + str(len(list((ci.get_defs().suites)))) - - -def test_client_ch_add(ci): - print_test(ci, "test_client_ch_add") - ci.delete_all() - try: - ci.ch_drop_user("") # drop all handle associated with current user - except: - pass # Drop throws if no handle registered - - defs = Defs() - for i in range(1, 7): defs.add_suite("s" + str(i)) - ci.load(defs) - - try: - suite_names = [] - ci.ch_register(True, suite_names) # register interest in any new suites - suite_names = ['s1', 's2'] - ci.ch_add(suite_names) # add suites s1,s2 to the last added handle - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 2, "Expected 2 suites but found " + str( - len(list((ci.get_defs().suites)))) - - suite_names = ['s3', 's4'] - ci.ch_add(ci.ch_handle(), suite_names) # add suites s3,s4 using last handle - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 4, "Expected 4 suites but found " + str( - len(list((ci.get_defs().suites)))) - - except RuntimeError as e: - print(str(e)) - - ci.ch_drop_user("") # drop all handle associated with current user - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 6, "Expected 6 suites but found " + str(len(list((ci.get_defs().suites)))) - - -def test_client_ch_auto_add(ci): - print_test(ci, "test_client_ch_auto_add") - ci.delete_all() - try: - ci.ch_drop_user("") # drop all handle associated with current user - except: - pass # Drop throws if no handle registered +@pytest.fixture( + scope="class", + params=[Test.Protocol.CUSTOM, Test.Protocol.HTTP], + ids=["custom", "http"], +) +def protocol(request): + return request.param + + +@pytest.fixture(scope="class", params=[False, True], ids=["sync_local", "auto_sync"]) +def auto_sync(request): + return request.param + + +@pytest.fixture(scope="class") +def server(protocol, auto_sync): + with Test.Server(protocol) as ctx: + ci, protocol = ctx + server_version = ci.server_version() + print("Running ecflow server version " + server_version) + print("Running ecflow client version " + ci.version()) + assert ( + ci.version() == server_version + ), " Client version not same as server version" + PrintStyle.set_style(Style.STATE) # show node state + if auto_sync: + ci.set_auto_sync(True) + yield ci, protocol + + +class TestClientConstruction: + def test_set_host_port(self): + print("test_set_host_port") + ci = Client() + ci.debug(True) + ci.enable_http() + print(" Client.get_host() = " + ci.get_host()) + print(" Client.get_port() = " + ci.get_port()) + assert _can_set_host_port(ci, "host", "3141"), "Expected no errors" + assert _can_set_host_port(ci, "host", 4444), "Expected no errors" + assert _can_set_combined_host_port(ci, "host:4444"), "Expected no errors" + assert _can_set_combined_host_port(ci, "host@4444"), "Expected no errors" + assert not _can_set_host_port(ci, "", ""), "Expected errors" + assert not _can_set_host_port(ci, "host", ""), "Expected errors" + assert not _can_set_host_port(ci, "host", "host"), "Expected errors" + assert not _can_set_combined_host_port(ci, "host:host"), "Expected errors" + assert not _can_set_combined_host_port(ci, "3141:host"), "Expected errors" + assert not _can_set_combined_host_port(ci, "3141@host"), "Expected errors" + assert not _can_set_combined_host_port(ci, "3141@"), "Expected errors" + + assert _can_construct_client_with_host_port( + "host", "3141" + ), "Expected no errors" + assert _can_construct_client_with_host_port("host", 4444), "Expected no errors" + assert _can_construct_client_with_combined_host_port( + "host:4444" + ), "Expected no errors" + assert not _can_construct_client_with_host_port("", ""), "Expected errors" + assert not _can_construct_client_with_host_port("host", ""), "Expected errors" + assert not _can_construct_client_with_host_port( + "host", "host" + ), "Expected errors" + assert not _can_construct_client_with_combined_host_port( + "host:host" + ), "Expected errors" + assert not _can_construct_client_with_combined_host_port( + "3141:host" + ), "Expected errors" + + +class TestClientApi: + @pytest.fixture(autouse=True) + def setup(self, server): + self.ci, self.protocol = server + + def test_version(self): + print_test(self.ci, "test_version") + client_version = self.ci.version() + server_version = self.ci.server_version() + print(" client_version: ", client_version) + print(" server_version: ", server_version) + assert client_version == server_version, ( + "Expected client version(" + + client_version + + ") and server version(" + + server_version + + ") to match\n" + ) + + def test_client_get_server_defs(self): + print_test(self.ci, "test_client_get_server_defs") + self.ci.delete_all() # start fresh + self.ci.load(create_defs("", self.ci.get_port(), self.protocol)) + self.ci.get_server_defs() + assert ( + self.ci.get_defs().find_suite("s1") is not None + ), "Expected to find suite of name s1:\n" + str(self.ci.get_defs()) + + self.ci.delete_all() # start fresh + self.ci.load(create_defs("", self.ci.get_port(), self.protocol)) + sync_local(self.ci) + + assert ( + self.ci.get_defs().find_suite("s1") is not None + ), "Expected to find suite of name s1:\n" + str(self.ci.get_defs()) + + def test_client_new_log(self): + print_test(self.ci, "test_client_new_log") + log_path = Test.log_file_path(self.ci.get_port()) + if not os.path.exists(log_path): + print(log_path + " : log does not exist ?") + + new_log_file_name = "./test_client_new_log_" + str(os.getpid()) + ".log" + try: + os.remove(new_log_file_name) # delete file if it exists + except: + pass + + self.ci.new_log(new_log_file_name) + self.ci.flush_log() # close log file and force write to disk + assert os.path.exists(new_log_file_name), ( + new_log_file_name + " : New log does not exist" + ) + try: + os.remove(new_log_file_name) + except: + pass + + # reset new log to original + self.ci.new_log(log_path) + self.ci.ping() + self.ci.flush_log() # close log file and force write to disk + log_file = open(log_path) + try: + log_text = log_file.read() + # assume log file not to big + finally: + log_file.close() + assert log_text.find("--ping") != -1, "Expected to find --ping in log file" + + if not os.path.exists(log_path): + print(log_path + " : log does not exist ?") + + def test_client_clear_log(self): + if debugging(): + return # dont run this test when debugging as log file is lost + print_test(self.ci, "test_client_clear_log") + log_path = Test.log_file_path(self.ci.get_port()) + # populate log + self.ci.ping() + self.ci.ping() + self.ci.flush_log() # close log file and force write to disk + log_file = open(log_path) + try: + log_text = log_file.read() + # assume log file not to big + finally: + log_file.close() + assert log_text.find("--ping") != -1, "Expected to find --ping in log file" + + self.ci.clear_log() + if not os.path.exists(log_path): + print(log_path + " : log does not exist ?") + + log_file = open(log_path) + try: + log_text = log_file.read() + # assume log file not to big + finally: + log_file.close() + assert len(log_text) == 0, "Expected log file to be empty but found " + log_text + + if not os.path.exists(log_path): + print(log_path + " : log does not exist ?") + + def test_client_log_msg(self): + if debugging(): + return # dont run this test when debugging + + print_test(self.ci, "test_client_log_msg") + log_path = Test.log_file_path(self.ci.get_port()) + if not os.path.exists(log_path): + print(log_path + " : log does not exist ?") + + # Send a message to the log file, then make sure it was written + self.ci.log_msg("Humpty dumpty sat on a wall!") + self.ci.flush_log() + # flush and close log file, so we can open it + log_file = open(log_path) + try: + log_text = log_file.read() + # assume log file not to big + finally: + log_file.close() + assert ( + log_text.find("Humpty dumpty sat on a wall!") != -1 + ), "Expected to find Humpty dumpty in the log file" + + if not os.path.exists(log_path): + print(log_path + " : log does not exist ?") + + def test_client_restart_server(self): + print_test(self.ci, "test_client_restart_server") + self.ci.restart_server() + sync_local(self.ci) + assert ( + self.ci.get_defs().get_server_state() == SState.RUNNING + ), "Expected server to be running" + + paths = list(self.ci.changed_node_paths) + assert len(paths) == 1, "expected changed node to be the root node: " + paths + assert paths[0] == "/", "Expected root path but found " + str(paths[0]) + + def test_client_halt_server(self): + print_test(self.ci, "test_client_halt_server") + self.ci.halt_server() + sync_local(self.ci) + assert ( + self.ci.get_defs().get_server_state() == SState.HALTED + ), "Expected server to be halted" + + paths = list(self.ci.changed_node_paths) + assert len(paths) == 1, "expected changed node to be the root node" + assert paths[0] == "/", "Expected root path but found " + str(paths[0]) + self.ci.restart_server() + + def test_client_shutdown_server(self): + print_test(self.ci, "test_client_shutdown_server") + self.ci.shutdown_server() + sync_local(self.ci) + assert ( + self.ci.get_defs().get_server_state() == SState.SHUTDOWN + ), "Expected server to be shutdown" + + paths = list(self.ci.changed_node_paths) + assert len(paths) == 1, "expected changed node to be the root node" + assert paths[0] == "/", "Expected root path but found " + str(paths[0]) + + def test_client_load_in_memory_defs(self): + print_test(self.ci, "test_client_load_in_memory_defs") + self.ci.delete_all() # start fresh + self.ci.load(create_defs("", self.ci.get_port(), self.protocol)) + sync_local(self.ci) + assert ( + self.ci.get_defs().find_suite("s1") is not None + ), "Expected to find suite of name s1:\n" + str(self.ci.get_defs()) + + def test_client_load_from_disk(self): + print_test(self.ci, "test_client_load_from_disk") + self.ci.delete_all() # start fresh + defs = create_defs("", self.ci.get_port(), self.protocol) + defs_file = "test_client_load_from_disk_" + str(os.getpid()) + ".def" + defs.save_as_defs(defs_file) + assert os.path.exists(defs_file), ( + "Expected file " + defs_file + " to exist after defs.save_as_defs()" + ) + self.ci.load(defs_file) # open and parse defs file, and load into server.\n" + + sync_local(self.ci) + assert ( + self.ci.get_defs().find_suite("s1") is not None + ), "Expected to find suite of name s1:\n" + str(self.ci.get_defs()) + os.remove(defs_file) + + def test_client_checkpt(self): + print_test(self.ci, "test_client_checkpt") + # start fresh + self.ci.delete_all() + port = self.ci.get_port() + try: + os.remove(Test.checkpt_file_path(port)) + os.remove(Test.backup_checkpt_file_path(port)) + except: + pass + + self.ci.load(create_defs("", self.ci.get_port(), self.protocol)) + self.ci.checkpt() + assert os.path.exists( + Test.checkpt_file_path(port) + ), "Expected check pt file to exist after ci.checkpt()" + assert not os.path.exists( + Test.backup_checkpt_file_path(port) + ), "Expected back up check pt file to *NOT* exist" + + self.ci.checkpt() # second check pt should cause backup check pt to be written + assert os.path.exists( + Test.backup_checkpt_file_path(port) + ), "Expected back up check pt file to exist after second ci.checkpt()" + + self.ci.checkpt(CheckPt.NEVER) # switch of check pointing + self.ci.checkpt(CheckPt.ALWAYS) # always check point, at any state change + self.ci.checkpt( + CheckPt.ON_TIME + ) # Check point periodically, by interval set in server + self.ci.checkpt( + CheckPt.ON_TIME, 200 + ) # Check point periodically, by interval set in server + self.ci.checkpt(CheckPt.UNDEFINED, 0, 35) # Change check point save time alarm - defs = Defs() - for i in range(1, 7): defs.add_suite("s" + str(i)) - ci.load(defs) - - try: - suite_names = ['s1', 's2', 's3'] - ci.ch_register(True, suite_names) # register interest in suites s1,s2,s3 and any new suites - ci.ch_auto_add(False) # disable adding newly created suites to last registered handle\n" - ci.ch_auto_add(True) # enable adding newly created suites to last registered handle\n" - ci.ch_auto_add(ci.ch_handle(), False) # disable adding newly created suites to handle\n" - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 3, "Expected 3 suites but found " + str( - len(list((ci.get_defs().suites)))) - except RuntimeError as e: - print(str(e)) - - ci.ch_drop_user("") # drop all handle associated with current user - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 6, "Expected 6 suites but found " + str(len(list((ci.get_defs().suites)))) - - -def test_client_ch_remove(ci): - print_test(ci, "test_client_ch_remove") - ci.delete_all() - try: - ci.ch_drop_user("") # drop all handle associated with current user - except: - pass # Drop throws if no handle registered + os.remove(Test.checkpt_file_path(port)) + os.remove(Test.backup_checkpt_file_path(port)) - defs = Defs() - for i in range(1, 7): defs.add_suite("s" + str(i)) - ci.load(defs) + def test_client_restore_from_checkpt(self): + print_test(self.ci, "test_client_restore_from_checkpt") + # start fresh + self.ci.delete_all() + port = self.ci.get_port() + try: + os.remove(Test.checkpt_file_path(port)) + os.remove(Test.backup_checkpt_file_path(port)) + except: + pass + + self.ci.load(create_defs("", self.ci.get_port(), self.protocol)) + self.ci.checkpt() + self.ci.delete_all() + + sync_local(self.ci) + assert ( + self.ci.get_defs().find_suite("s1") is None + ), "Expected all suites to be delete:\n" + + self.ci.halt_server() # server must be halted, otherwise restore_from_checkpt will throw + self.ci.restore_from_checkpt() + + sync_local(self.ci) + assert ( + self.ci.get_defs().find_suite("s1") is not None + ), "Expected to find suite s1 after restore from checkpt:\n" + str( + self.ci.get_defs() + ) - try: - suite_names = ['s1', 's2', 's3'] - ci.ch_register(True, suite_names) # register interest in suites s1,s2,s3 and any new suites - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 3, "Expected 3 suites but found " + str( - len(list((ci.get_defs().suites)))) - - suite_names = ['s1'] - ci.ch_remove(suite_names) # remove suites s1 from the last added handle\n" - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 2, "Expected 2 suites but found " + str( - len(list((ci.get_defs().suites)))) - - suite_names = ['s2'] - ci.ch_remove(ci.ch_handle(), suite_names) # remove suites s2 from the last added handle\n" - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 1, "Expected 1 suites but found " + str( - len(list((ci.get_defs().suites)))) - - except RuntimeError as e: - print(str(e)) - - ci.ch_drop_user("") # drop all handle associated with current user - sync_local(ci) - assert len(list(ci.get_defs().suites)) == 6, "Expected 6 suites but found " + str(len(list((ci.get_defs().suites)))) - - -def test_client_get_file(ci): - print_test(ci, "test_client_get_file") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_client_get_file", port, protocol) - - # Also test where user has specified his OWN ECF_JOBOUT (rare) - t2 = defs.find_abs_node("/test_client_get_file/f1/t2") - t2_jobout = Test.ecf_home(ci.get_port()) + "/test_client_get_file/t2.xx" - t2.add_variable("ECF_JOBOUT", t2_jobout) - - defs.generate_scripts() - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - ci.begin_all_suites() - - while 1: - if ci.news_local(): - ci.sync_local() # get the changes, synced with local defs - suite = ci.get_defs().find_suite("test_client_get_file") - assert suite is not None, "Expected to find suite" + os.remove(Test.checkpt_file_path(port)) + self.ci.restart_server() + + def test_client_reload_wl_file(self): + print_test(self.ci, "test_client_reload_wl_file") + + expected = False + try: + self.ci.reload_wl_file() + except: + expected = True + assert expected, "Expected reload to fail when no white list specified" + + # create a white list file + port = self.ci.get_port() + wl_file = open(Test.white_list_file_path(port), "w") + wl_file.write("#\n") + wl_file.write("4.4.14 # comment\n\n") + wl_file.write("# These user have read and write access to the server\n") + wl_file.write( + get_username() + "\n" + ) # add current user otherwise remaining test's, wont have access from server anymore + wl_file.write("axel # admin\n") + wl_file.write("john # admin\n\n") + wl_file.write("# Read only users\n") + wl_file.write("-fred # needs read access only\n") + wl_file.write("-joe90 # needs read access only\n") + wl_file.close() + + self.ci.reload_wl_file() + os.remove(Test.white_list_file_path(port)) + + def test_client_run(self): + print_test(self.ci, "test_client_run") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs("test_client_run", port, self.protocol) + suite = defs.find_suite("test_client_run") + suite.add_defstatus(DState.suspended) + + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + self.ci.begin_all_suites() + self.ci.run("/test_client_run", False) + + count = 0 + while 1: + count += 1 + self.ci.sync_local() # get the changes, synced with local defs + suite = self.ci.get_defs().find_suite("test_client_run") + assert suite is not None, "Expected to find suite test_client_run:\n" + str( + self.ci.get_defs() + ) + print(f"Suite state: {suite.get_state()}, {count}, at {datetime.now()}") if suite.get_state() == State.complete: break - time.sleep(1) - - try: - for task in ['/test_client_get_file/f1/t1', '/test_client_get_file/f1/t2']: - - result_string = ci.get_file(task) - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}) to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}) to return something" - - result_string = ci.get_file(task, as_bytes=False) - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}, as_bytes=False) to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}, as_bytes=False) to return something" - - result_bytes = ci.get_file(task, as_bytes=True) - assert isinstance(result_bytes, bytes), \ - f"Expected ci.get_file({task}, as_bytes=True) to return 'bytes'" - assert len(result_bytes) > 0, \ - f"Expected ci.get_file({task}, as_bytes=True) to return something" - - assert len(result_string) == len(result_bytes), \ - f"Expected 'str' result and 'bytes' result to have the same length" - - result_string = ci.get_file(task, max_lines='10') - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}, max_lines='10') to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}, max_lines='10') to return something" - - result_string = ci.get_file(task, max_lines='10', as_bytes=False) - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}, max_lines='10', as_bytes=False) to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}, max_lines='10', as_bytes=False) to return something" - - result_bytes = ci.get_file(task, max_lines='10', as_bytes=True) - assert isinstance(result_bytes, bytes), \ - f"Expected ci.get_file({task}, max_lines='10', as_bytes=True) to return 'bytes'" - assert len(result_bytes) > 0, \ - f"Expected ci.get_file({task}, max_lines='10', as_bytes=True) to return something" - - assert len(result_string) == len(result_bytes), \ - f"Expected 'str' result and 'bytes' result to have the same length" - - for file_type in ['script', 'job', 'jobout', 'manual']: - result_string = ci.get_file(task, file_type) - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}, {file_type}) to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}, {file_type}) to return something" - - result_string = ci.get_file(task, file_type, as_bytes=False) - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}, {file_type}, as_bytes=False) to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}, {file_type}, as_bytes=False) to return something" - - result_bytes = ci.get_file(task, file_type, as_bytes=True) - assert isinstance(result_bytes, bytes), \ - f"Expected ci.get_file({task}, {file_type}, as_bytes=True) to return 'bytes'" - assert len(result_bytes) > 0, \ - f"Expected ci.get_file({task}, {file_type}, as_bytes=True) to return something" - - assert len(result_string) == len(result_bytes), \ - f"Expected 'str' result and 'bytes' result to have the same length" - - result_string = ci.get_file(task, file_type, '10') - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}, {file_type}, '10') to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}, {file_type}, '10') to return something" - - result_string = ci.get_file(task, file_type, '10', as_bytes=False) - assert isinstance(result_string, str), \ - f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=False) to return 'str'" - assert len(result_string) > 0, \ - f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=False) to return something" - - result_bytes = ci.get_file(task, file_type, '10', as_bytes=True) - assert isinstance(result_bytes, bytes), \ - f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=True) to return 'bytes'" - assert len(result_bytes) > 0, \ - f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=True) to return something" - - assert len(result_string) == len(result_bytes), \ - f"Expected 'str' result and 'bytes' result to have the same length" - - assert os.path.exists(t2_jobout), f"User specified ECF_JOBOUT file not created {t2_jobout}" - - except RuntimeError as e: - print(str(e)) - - dir_to_remove = Test.ecf_home(port) + "/" + "test_client_get_file" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_client_plug(ci): - pass - - -def test_client_alter_sort(ci, protocol): - print_test(ci, "test_client_alter_sort") - ci.delete_all() - - defs = create_defs("test_client_alter_sort", ci.get_port(), protocol) - t1 = "/test_client_alter_sort/f1/t1" - task_t1 = defs.find_abs_node(t1) - task_t1.add_variable("z", "value").add_variable("y", "value").add_variable("x", "value") - task_t1.add_event("z").add_event("y").add_event("x") - task_t1.add_meter("z", 0, 100, 100).add_meter("y", 0, 100, 100).add_meter("x", 0, 100, 100) - task_t1.add_label("z", "name").add_label("y", "name").add_label("x", "name") - task_t1.add_limit("z", 10).add_limit("y", 10).add_limit("x", 10) - - ci.load(defs) - - ci.sort_attributes(t1, "variable") - ci.sort_attributes(t1, "event") - ci.sort_attributes(t1, "meter") - ci.sort_attributes(t1, "label") - ci.sort_attributes(t1, "limit") - - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.variables)) == 3, "Expected 3 variable :\n" + str(ci.get_defs()) - assert len(list(task_t1.events)) == 3, "Expected 3 events :\n" + str(ci.get_defs()) - assert len(list(task_t1.meters)) == 3, "Expected 3 meters :\n" + str(ci.get_defs()) - assert len(list(task_t1.labels)) == 3, "Expected 3 labels :\n" + str(ci.get_defs()) - assert len(list(task_t1.limits)) == 3, "Expected 3 limits :\n" + str(ci.get_defs()) - expected = ['x', 'y', 'z']; - vactual = []; - eactual = []; - mactual = []; - lactual = []; - liactual = []; - for v in task_t1.variables: vactual.append(v.name()) - for v in task_t1.events: eactual.append(v.name()) - for v in task_t1.meters: mactual.append(v.name()) - for v in task_t1.labels: lactual.append(v.name()) - for v in task_t1.limits: liactual.append(v.name()) - assert expected == vactual, "variable Attributes not sorted, expected:" + str(expected) + " but found:" + str( - vactual) - assert expected == eactual, "event Attributes not sorted, expected:" + str(expected) + " but found:" + str(eactual) - assert expected == mactual, "meter Attributes not sorted, expected:" + str(expected) + " but found:" + str(mactual) - assert expected == lactual, "label Attributes not sorted, expected:" + str(expected) + " but found:" + str(lactual) - assert expected == liactual, "limit Attributes not sorted, expected:" + str(expected) + " but found:" + str( - liactual) - - -def test_client_alter_sort_defs(ci, protocol): - print_test(ci, "test_client_alter_sort_defs") - ci.delete_all() - - defs = create_defs("test_client_alter_sort_defs", ci.get_port(), protocol) - t1 = "/test_client_alter_sort_defs/f1/t1" - task_t1 = defs.find_abs_node(t1) - task_t1.add_variable("z", "value").add_variable("y", "value").add_variable("x", "value") - task_t1.add_event("z").add_event("y").add_event("x") - task_t1.add_meter("z", 0, 100, 100).add_meter("y", 0, 100, 100).add_meter("x", 0, 100, 100) - task_t1.add_label("z", "name").add_label("y", "name").add_label("x", "name") - task_t1.add_limit("z", 10).add_limit("y", 10).add_limit("x", 10) - - ci.load(defs) - - ci.sort_attributes("/", "variable", True) - ci.sort_attributes("/", "event", True) - ci.sort_attributes("/", "meter", True) - ci.sort_attributes("/", "label", True) - ci.sort_attributes("/", "limit", True) - - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.variables)) == 3, "Expected 3 variable :\n" + str(ci.get_defs()) - assert len(list(task_t1.events)) == 3, "Expected 3 events :\n" + str(ci.get_defs()) - assert len(list(task_t1.meters)) == 3, "Expected 3 meters :\n" + str(ci.get_defs()) - assert len(list(task_t1.labels)) == 3, "Expected 3 labels :\n" + str(ci.get_defs()) - assert len(list(task_t1.limits)) == 3, "Expected 3 limits :\n" + str(ci.get_defs()) - expected = ['x', 'y', 'z']; - vactual = []; - eactual = []; - mactual = []; - lactual = []; - liactual = []; - for v in task_t1.variables: vactual.append(v.name()) - for v in task_t1.events: eactual.append(v.name()) - for v in task_t1.meters: mactual.append(v.name()) - for v in task_t1.labels: lactual.append(v.name()) - for v in task_t1.limits: liactual.append(v.name()) - assert expected == vactual, "variable Attributes not sorted, expected:" + str(expected) + " but found:" + str( - vactual) - assert expected == eactual, "event Attributes not sorted, expected:" + str(expected) + " but found:" + str(eactual) - assert expected == mactual, "meter Attributes not sorted, expected:" + str(expected) + " but found:" + str(mactual) - assert expected == lactual, "label Attributes not sorted, expected:" + str(expected) + " but found:" + str(lactual) - assert expected == liactual, "limit Attributes not sorted, expected:" + str(expected) + " but found:" + str( - liactual) - - -def test_client_alter_add(ci, protocol): - print_test(ci, "test_client_alter_add") - ci.delete_all() - ci.load(create_defs("test_client_alter_add", ci.get_port(), protocol)) - - t1 = "/test_client_alter_add/f1/t1" - ci.alter(t1, "add", "variable", "var", "var_name") - ci.alter(t1, "add", "variable", "var2", "--") - ci.alter(t1, "add", "variable", "var3", "--fred") - ci.alter(t1, "add", "variable", "var4", " --fred ") - ci.alter(t1, "add", "variable", "var5", "--fred --jake") - ci.alter(t1, "add", "variable", "var6", " --fred --jake ") - ci.alter(t1, "add", "variable", "var7", "") - ci.alter(t1, "add", "time", "+00:30") - ci.alter(t1, "add", "time", "01:30") - ci.alter(t1, "add", "time", "01:30 20:00 00:30") - ci.alter(t1, "add", "today", "+00:30") - ci.alter(t1, "add", "today", "01:30") - ci.alter(t1, "add", "today", "01:30 20:00 00:30") - ci.alter(t1, "add", "date", "01.01.2001") - ci.alter(t1, "add", "date", "*.01.2001") - ci.alter(t1, "add", "date", "*.*.2001") - ci.alter(t1, "add", "date", "*.*.*") - ci.alter(t1, "add", "day", "sunday") - ci.alter(t1, "add", "day", "monday") - ci.alter(t1, "add", "day", "tuesday") - ci.alter(t1, "add", "day", "wednesday") - ci.alter(t1, "add", "day", "thursday") - ci.alter(t1, "add", "day", "friday") - ci.alter(t1, "add", "day", "saturday") - ci.alter(t1, "add", "late", "late -s +00:15 -a 20:00 -c +02:00") - ci.alter(t1, "add", "label", "label_name", "label_value") - ci.alter(t1, "add", "label", "label_name2", "/a/label/with/path/values") - - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.variables)) == 7, "Expected 7 variable :\n" + str(ci.get_defs()) - assert len(list(task_t1.times)) == 3, "Expected 3 time :\n" + str(ci.get_defs()) - assert len(list(task_t1.todays)) == 3, "Expected 3 today's :\n" + str(ci.get_defs()) - assert len(list(task_t1.dates)) == 4, "Expected 4 dates :\n" + str(ci.get_defs()) - assert len(list(task_t1.days)) == 7, "Expected 7 days :\n" + str(ci.get_defs()) - assert len(list(task_t1.labels)) == 2, "Expected 2 labels :\n" + str(ci.get_defs()) - assert str( - task_t1.get_late()) == "late -s +00:15 -a 20:00 -c +02:00", "Expected late 'late -s +00:15 -a 20:00 -c +02:00'" + str( - ci.get_defs()) - - -def test_client_alter_delete(ci, protocol): - print_test(ci, "test_client_alter_delete") - ci.delete_all() - defs = create_defs("test_client_alter_delete", ci.get_port(), protocol) - suite_with_limits = defs.add_suite("suite_with_limits") - suite_with_limits.add_limit("limitX", 10) - suite_with_limits_X = defs.add_suite("suite_with_limits_X") - suite_with_limits_X.add_limit("limitX", 10) - - t1 = "/test_client_alter_delete/f1/t1" - task_t1 = defs.find_abs_node(t1) - task_t1.add_variable("var", "value") - task_t1.add_variable("var1", "value") - task_t1.add_time("00:30") - task_t1.add_time("00:31") - task_t1.add_today("00:30") - task_t1.add_today("00:31") - task_t1.add_date(1, 1, 2001) - task_t1.add_date(1, 1, 2002) - task_t1.add_day("sunday") - task_t1.add_day("monday") - cron = Cron() - cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) - cron.set_time_series("+00:30") - task_t1.add_cron(cron) - task_t1.add_cron(cron) - task_t1.add_event("event") - task_t1.add_event("event1") - task_t1.add_meter("meter", 0, 100, 100) - task_t1.add_meter("meter1", 0, 100, 100) - task_t1.add_label("label", "name") - task_t1.add_label("label1", "name") - task_t1.add_limit("limit", 10) - task_t1.add_limit("limit1", 10) - task_t1.add_inlimit("limit", t1, 2) - task_t1.add_inlimit("limit1", t1, 2) - task_t1.add_inlimit("limitX", "/suite_with_limits", 2) - task_t1.add_inlimit("limitX", "/suite_with_limits_X", 2) - task_t1.add_trigger("t2 == active") - task_t1.add_complete("t2 == complete") - - assert task_t1.get_late() is None, "expected no late" - late = Late() - late.submitted(20, 10) - late.active(20, 10) - late.complete(20, 10, True) - task_t1.add_late(late) - assert task_t1.get_late() is not None, "expected late" - - t2 = "/test_client_alter_delete/f1/t2" - task_t2 = defs.find_abs_node(t2) - task_t2.add_repeat(RepeatDate("date", 20100111, 20100115, 2)) # can't add cron and repeat at the same level - - # print(defs) - ci.load(defs) - - ci.alter(t1, "delete", "variable", "var") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.variables)) == 1, "Expected 1 variable :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "variable") # delete all veriables - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.variables)) == 0, "Expected 0 variable :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "time", "00:30") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.times)) == 1, "Expected 1 time :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "time") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.times)) == 0, "Expected 0 time :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "today", "00:30") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.todays)) == 1, "Expected 1 today :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "today") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.todays)) == 0, "Expected 0 today :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "date", "01.01.2001") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.dates)) == 1, "Expected 1 date :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "date") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.dates)) == 0, "Expected 0 date :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "day", "sunday") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.days)) == 1, "Expected 1 day :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "day") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.days)) == 0, "Expected 0 day :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "event", "event") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.events)) == 1, "Expected 1 event :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "event") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.events)) == 0, "Expected 0 event :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "meter", "meter") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.meters)) == 1, "Expected 1 meter :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "meter") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.meters)) == 0, "Expected 0 meter :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "label", "label") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.labels)) == 1, "Expected 1 label :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "label") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.labels)) == 0, "Expected 0 label :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "inlimit", "limit") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.inlimits)) == 3, "Expected 3 inlimit :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "inlimit", "/suite_with_limits:limitX") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.inlimits)) == 2, "Expected 2 inlimit :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "inlimit", "/suite_with_limits_X:limitX") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.inlimits)) == 1, "Expected 1 inlimit :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "inlimit") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.inlimits)) == 0, "Expected 0 inlimit :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "limit", "limit") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.limits)) == 1, "Expected 1 limit :\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "limit") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.limits)) == 0, "Expected 0 limit :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "cron") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.crons)) == 0, "Expected 0 crons :\n" + str(ci.get_defs()) - - ci.alter(t1, "delete", "late") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert task_t1.get_late() is None, "expected no late after delete" - - task_t1 = ci.get_defs().find_abs_node(t1) - assert task_t1.get_trigger() is not None, "Expected trigger:\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "trigger") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert task_t1.get_trigger() is None, "Expected trigger to be deleted:\n" + str(ci.get_defs()) - - task_t1 = ci.get_defs().find_abs_node(t1) - assert task_t1.get_complete() is not None, "Expected complete:\n" + str(ci.get_defs()) - ci.alter(t1, "delete", "complete") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert task_t1.get_complete() is None, "Expected complete to be deleted:\n" + str(ci.get_defs()) - - ci.alter(t2, "delete", "repeat") - sync_local(ci) - task_t2 = ci.get_defs().find_abs_node(t2) - repeat = task_t2.get_repeat() - assert repeat.empty(), "Expected repeat to be deleted:\n" + str(ci.get_defs()) - - -def test_client_alter_change(ci, protocol): - print_test(ci, "test_client_alter_change") - ci.delete_all() - defs = create_defs("test_client_alter_change", ci.get_port(), protocol) - t1 = "/test_client_alter_change/f1/t1" - repeat_date_path = "/test_client_alter_change/f1/repeat_date" - repeat_datetime_path = "/test_client_alter_change/f1/repeat_datetime" - - task_t1 = defs.find_abs_node(t1) - task_t1.add_variable("var", "value") - task_t1.add_variable("var1", "value") - task_t1.add_event("event") - task_t1.add_event("event1") - task_t1.add_meter("meter", 0, 100, 100) - task_t1.add_meter("meter1", 0, 100, 100) - task_t1.add_label("label", "name") - task_t1.add_label("label1", "name1") - task_t1.add_limit("limit", 10) - task_t1.add_limit("limit1", 10) - task_t1.add_inlimit("limit", t1, 2) - task_t1.add_inlimit("limit1", t1, 2) - task_t1.add_trigger("t2 == active") - task_t1.add_complete("t2 == complete") - task_t1.add_time("00:31") - task_t1.add_today("00:30") - - late = Late() - late.submitted(20, 10) - late.active(20, 10) - late.complete(20, 10, True) - task_t1.add_late(late) - - f1 = defs.find_abs_node("/test_client_alter_change/f1") - # Add a new Repeat (based on date) - repeat_date = f1.add_task("repeat_date") - repeat_date.add_repeat(RepeatDate("date", 20100111, 20100115, 2)) # can't add cron and repeat at the same level - # Add a new Repeat (based on date+time) - repeat_datetime = f1.add_task("repeat_datetime") - repeat_datetime.add_repeat(RepeatDateTime("datetime", "20100111T000000", "20100115T000000", "48:00:00")) - - ci.load(defs) - - ci.alter(t1, "change", "time", "00:31", "10:10") - ci.alter(t1, "change", "today", "00:30", "10:10") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - assert len(list(task_t1.times)) == 1, "Expected 1 time :\n" + str(ci.get_defs()) - assert len(list(task_t1.todays)) == 1, "Expected 1 time :\n" + str(ci.get_defs()) - for t in task_t1.times: - assert str(t) == "time 10:10", " expected 'time 10:10' but found:\n" + str(t) - for t in task_t1.todays: - assert str(t) == "today 10:10", " expected 'today 10:10' but found:\n" + str(t) - - ci.alter(t1, "change", "late", "-s +10:00") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - variable = task_t1.get_late() - assert str( - task_t1.get_late()) == "late -s +10:00", "Expected alter of late to be 'late -s +10:00' but found " + str( - task_t1.get_late()) - - ci.alter(t1, "change", "variable", "var", "changed_var") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - variable = task_t1.find_variable("var") - assert variable.value() == "changed_var", "Expected alter of variable to be 'change_var' but found " + variable.value() - res = ci.query('variable', task_t1.get_abs_node_path(), "var") - assert res == 'changed_var', "Expected alter of variable to be 'change_var' but found " + res - - ci.alter(t1, "change", "variable", "var", "--") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - variable = task_t1.find_variable("var") - assert variable.value() == "--", "Expected alter of variable to be '--' but found " + variable.value() - res = ci.query('variable', task_t1.get_abs_node_path(), "var") - assert res == '--', "Expected alter of variable to be '--' but found " + res - - ci.alter(t1, "change", "variable", "var", "--fred") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - variable = task_t1.find_variable("var") - assert variable.value() == "--fred", "Expected alter of variable to be '--fred' but found " + variable.value() - - ci.alter(t1, "change", "variable", "var", "--fred --bill") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - variable = task_t1.find_variable("var") - assert variable.value() == "--fred --bill", "Expected alter of variable to be '--fred --bill' but found " + variable.value() - - ci.alter(t1, "change", "meter", "meter", "10") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - meter = task_t1.find_meter("meter") - assert meter.value() == 10, "Expected alter of meter to be 10 but found " + str(meter.value()) - res = ci.query('meter', task_t1.get_abs_node_path(), "meter") - assert res == '10', "Expected alter of meter to be 10 but found " + res - - ci.alter(t1, "change", "event", "event", "set") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - event = task_t1.find_event("event") - assert event.value() == True, "Expected alter of event to be set but found " + str(event.value()) - res = ci.query('event', task_t1.get_abs_node_path(), "event") - assert res == 'set', "Expected alter of event to be 'set' but found " + res - - ci.alter(t1, "change", "trigger", "t2 == aborted") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - trigger = task_t1.get_trigger() - assert trigger.get_expression() == "t2 == aborted", "Expected alter of trigger to be 't2 == aborted' but found " + trigger.get_expression() - res = ci.query('trigger', task_t1.get_abs_node_path(), "t2 == aborted") - assert res == 'false', "Expected evaluation of trigger to fail, but found: " + res - - ci.alter(t1, "change", "trigger", "/test_client_alter_change/f1/t2 == complete") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - trigger = task_t1.get_trigger() - assert trigger.get_expression() == "/test_client_alter_change/f1/t2 == complete", "Expected alter of trigger to be '/test_client_alter_change/f1/t2 == complete' but found " + trigger.get_expression() - - ci.alter(t1, "change", "complete", "t2 == aborted") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - complete = task_t1.get_complete() - assert complete.get_expression() == "t2 == aborted", "Expected alter of complete to be 't2 == aborted' but found " + complete.get_expression() - - ci.alter(t1, "change", "complete", "/test_client_alter_change/f1/t2 == active") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - complete = task_t1.get_complete() - assert complete.get_expression() == "/test_client_alter_change/f1/t2 == active", "Expected alter of complete to be '/test_client_alter_change/f1/t2 == active' but found " + complete.get_expression() - - ci.alter(t1, "change", "limit_max", "limit", "2") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - limit = task_t1.find_limit("limit") - assert limit is not None, "Expected to find limit" - assert limit.limit() == 2, "Expected alter of limit_max to be 2 but found " + str(limit.limit()) - - ci.alter(t1, "change", "limit_value", "limit", "2") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - limit = task_t1.find_limit("limit") - assert limit is not None, "Expected to find limit" - assert limit.value() == 2, "Expected alter of limit_value to be 2 but found " + str(limit.value()) - - ci.alter(t1, "change", "label", "label", "new-value") - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - label = task_t1.find_label("label") - assert label.new_value() == "new-value", "Expected alter of label to be 'new-value' but found " + label.new_value() - - ci.alter(repeat_date_path, "change", "repeat", "20100113") - sync_local(ci) - task = ci.get_defs().find_abs_node(repeat_date_path) - repeat = task.get_repeat() - assert repeat.value() == 20100113, "Expected alter of repeat to be 20100113 but found " + str(repeat.value()) - res = ci.query('variable', task.get_abs_node_path(), repeat.name()) - assert res == "20100113", "Expected alter of repeat to be 20100113 but found " + res - - ci.alter(repeat_datetime_path, "change", "repeat", "20100113T000000") - sync_local(ci) - task = ci.get_defs().find_abs_node(repeat_datetime_path) - repeat = task.get_repeat() - assert repeat.value() == 1263340800, "Expected alter of repeat to be 1263340800 (i.e. seconds between 19700101T000000 and 20100113T000000) but found " + str( - repeat.value()) - res = ci.query('variable', task.get_abs_node_path(), repeat.name()) - assert res == "20100113T000000", "Expected alter of repeat to be 20100113T000000 but found " + res - - -def test_client_alter_flag(ci, protocol): - print_test(ci, "test_client_alter_flag") - ci.delete_all() - defs = create_defs("test_client_alter_flag", ci.get_port(), protocol) - t1 = "/test_client_alter_flag/f1/t1" - - task_t1 = defs.find_abs_node(t1) - - ci.load(defs) - - flag = Flag() - flag_list = flag.list() # flag_list is of type FlagTypeVec - for flg in flag_list: - ci.alter(t1, "set_flag", flag.type_to_string(flg)) - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - task_flag = task_t1.get_flag() - assert task_flag.is_set(flg), "expected flag %r to be set" % task_flag.type_to_string(flg) - - # alter itself causes the flag message to be set, and preserved - if flg == FlagType.message: continue - - ci.alter(t1, "clear_flag", flag.type_to_string(flg)) - sync_local(ci) - task_t1 = ci.get_defs().find_abs_node(t1) - task_flag = task_t1.get_flag() - assert not task_flag.is_set(flg), "expected flag %r NOT to be set" % task_flag.type_to_string(flg) - - # ISSUES: - # o Currently we can only change clock attr if we have one. - # o Even when we have a clock attr, it only makes sense to apply clock attr changes - # before begin(), i.e how do we apply change in gain after begin ?? - - # " change clock-type name # The name must be one of 'hybrid' or 'real'.\n" - # " change clock-gain name # The gain must be convertible to an integer.\n" - # " change label name value # sets the label\n" - # " change repeat value # If the repeat is a date, then the value must be a valid YMD ( ie. yyyymmdd)\n" - # " # and be convertible to an integer, additionally the value must be in range\n" - # " # of the repeat start and end dates. Like wise for repeat integer. For repeat\n" - # " # string and enum, the name must either be an integer, that is a valid index or\n" - # " # if it is a string, it must correspond to one of enum's or strings list\n" - - -def test_client_force(ci, protocol): - print_test(ci, "test_client_force") - ci.delete_all() - defs = create_defs("test_client_force", ci.get_port(), protocol) - - path_list = ["/test_client_force/f1/t1", "/test_client_force/f1/t2"] - t1 = path_list[0] - for path in path_list: - task = defs.find_abs_node(path) - assert task is not None, "Expected to find task at path " + path - task.add_event("event") - - ci.load(defs) - - state_list = [State.unknown, State.active, State.complete, State.queued, State.submitted, State.aborted] - for state in state_list: - ci.force_state(t1, state) - sync_local(ci) - task = ci.get_defs().find_abs_node(t1) - assert task.get_state() == state, "Expected state " + state + " but found " + str(task.get_state()) - for state in state_list: - ci.force_state(path_list, state) - sync_local(ci) - for path in path_list: - task = ci.get_defs().find_abs_node(path) - assert task.get_state() == state, "Expected state " + state + " but found " + str(task.get_state()) - - for state in state_list: - ci.force_state_recursive("/test_client_force", state) - sync_local(ci) - task = ci.get_defs().find_abs_node(t1) - assert task.get_state() == state, "Expected state " + state + " but found " + str(task.get_state()) - suite_paths = ["/test_client_force"] - for state in state_list: - ci.force_state_recursive(suite_paths, state) - sync_local(ci) - task = ci.get_defs().find_abs_node(t1) - assert task.get_state() == state, "Expected state " + state + " but found " + str(task.get_state()) - - event_states = ["set", "clear"] - for ev_state in event_states: - for path in path_list: - ci.force_event(path + ":event", ev_state) - sync_local(ci) - task = ci.get_defs().find_abs_node(path) - event_fnd = False - for event in task.events: - event_fnd = True - if ev_state == "set": - assert event.value() == True, " Expected event value to be set" - else: - assert event.value() == False, " Expected event value to be clear" - assert event_fnd == True, " Expected event to be found" - - event_path_list = ["/test_client_force/f1/t1:event", "/test_client_force/f1/t2:event"] - event_states = ["set", "clear"] - for ev_state in event_states: - ci.force_event(event_path_list, ev_state) - sync_local(ci) + assert ( + suite.get_state() != State.aborted + ), "Expected suite to complete, but found it was aborted" + time.sleep(3) + if count > 20: + assert False, ( + "test_client_run aborted after " + + str(count) + + " loops:\n" + + str(self.ci.get_defs()) + ) + + self.ci.log_msg("Looped " + str(count) + " times") + dir_to_remove = Test.ecf_home(port) + "/" + "test_client_run" + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_client_run_with_multiple_paths(self): + print_test(self.ci, "test_client_run_with_multiple_paths") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs("test_client_run_with_multiple_paths", port, self.protocol) + suite = defs.find_suite("test_client_run_with_multiple_paths") + suite.add_defstatus(DState.suspended) + + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + self.ci.begin_all_suites() + path_list = [ + "/test_client_run_with_multiple_paths/f1/t1", + "/test_client_run_with_multiple_paths/f1/t2", + ] + self.ci.run(path_list, False) + + count = 0 + while 1: + count += 1 + self.ci.sync_local() # get the changes, synced with local defs + suite = self.ci.get_defs().find_suite("test_client_run_with_multiple_paths") + assert ( + suite is not None + ), "Expected to find suite test_client_run_with_multiple_paths:\n" + str( + self.ci.get_defs() + ) + if suite.get_state() == State.complete: + break + time.sleep(3) + if count > 20: + assert False, ( + "test_client_run_with_multiple_paths aborted after " + + str(count) + + " loops:\n" + + str(self.ci.get_defs()) + ) + + self.ci.log_msg("Looped " + str(count) + " times") + + dir_to_remove = ( + Test.ecf_home(port) + "/" + "test_client_run_with_multiple_paths" + ) + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_client_requeue(self): + print_test(self.ci, "test_client_requeue") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs("test_client_requeue", port, self.protocol) + suite = defs.find_suite("test_client_requeue") + suite.add_defstatus(DState.suspended) + + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + self.ci.begin_all_suites() + + self.ci.force_state_recursive("/test_client_requeue", State.unknown) + sync_local(self.ci) + suite = self.ci.get_defs().find_suite("test_client_requeue") + assert ( + suite.get_state() == State.unknown + ), "Expected to find suite with state unknown" + + self.ci.requeue("/test_client_requeue") + sync_local(self.ci) + suite = self.ci.get_defs().find_suite("test_client_requeue") + assert ( + suite.get_state() == State.queued + ), "Expected to find suite with state queued" + + dir_to_remove = Test.ecf_home(port) + "/" + "test_client_requeue" + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_client_requeue_with_multiple_paths(self): + print_test(self.ci, "test_client_requeue_with_multiple_paths") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs( + "test_client_requeue_with_multiple_paths", port, self.protocol + ) + suite = defs.find_suite("test_client_requeue_with_multiple_paths") + suite.add_defstatus(DState.suspended) + + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + self.ci.begin_all_suites() + + self.ci.force_state_recursive( + "/test_client_requeue_with_multiple_paths", State.unknown + ) + sync_local(self.ci) + task1 = self.ci.get_defs().find_abs_node( + "/test_client_requeue_with_multiple_paths/f1/t1" + ) + task2 = self.ci.get_defs().find_abs_node( + "/test_client_requeue_with_multiple_paths/f1/t2" + ) + assert ( + task1.get_state() == State.unknown + ), "Expected to find t1 with state unknown" + assert ( + task2.get_state() == State.unknown + ), "Expected to find t2 with state unknown" + + path_list = [ + "/test_client_requeue_with_multiple_paths/f1/t1", + "/test_client_requeue_with_multiple_paths/f1/t2", + ] + self.ci.requeue(path_list) + sync_local(self.ci) + task1 = self.ci.get_defs().find_abs_node( + "/test_client_requeue_with_multiple_paths/f1/t1" + ) + task2 = self.ci.get_defs().find_abs_node( + "/test_client_requeue_with_multiple_paths/f1/t2" + ) + assert ( + task1.get_state() == State.queued + ), "Expected to find task t1 with state queued" + assert ( + task2.get_state() == State.queued + ), "Expected to find task t2 with state queued" + + dir_to_remove = ( + Test.ecf_home(port) + "/" + "test_client_requeue_with_multiple_paths" + ) + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_client_free_dep(self): + print_test(self.ci, "test_client_free_dep") + self.ci.delete_all() + + # add a value clock, since we are adding date dependencies + # Note: adding a future time dependency on a task, will cause it to requeue, when complete + # Hence even when we free these dependency they get requeued. + # So we use todays date. + ltime = time.localtime() + day = ltime.tm_mday + month = ltime.tm_mon + year = ltime.tm_year + + defs = Defs() + suite = defs.add_suite("test_client_free_dep") + suite.add_clock(Clock(False)) # true means hybrid, False means value + ecfhome = Test.ecf_home(self.ci.get_port()) + suite.add_variable("ECF_HOME", ecfhome) + protocol_option = "--http" if self.protocol == Test.Protocol.HTTP else "" + suite.add_variable( + "ECF_CLIENT_EXE_PATH", f"{File.find_client()} {protocol_option}" + ) + suite.add_variable("SLEEPTIME", "1") + suite.add_variable("ECF_INCLUDE", ecf_includes()) + family = suite.add_family("f1") + family.add_task("t1").add_time("00:01") + family.add_task("t2").add_date(day, month, year) + family.add_task("t3").add_trigger("1 == 0") + t4 = family.add_task("t4") + t4.add_time("00:01") + t4.add_date(day, month, year) + t4.add_trigger("1 == 0") + + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + self.ci.begin_all_suites() + + t1_path = "/test_client_free_dep/f1/t1" + t2_path = "/test_client_free_dep/f1/t2" + t3_path = "/test_client_free_dep/f1/t3" + t4_path = "/test_client_free_dep/f1/t4" + while 1: + self.ci.sync_local() + t1 = self.ci.get_defs().find_abs_node(t1_path) + t2 = self.ci.get_defs().find_abs_node(t2_path) + t3 = self.ci.get_defs().find_abs_node(t3_path) + t4 = self.ci.get_defs().find_abs_node(t4_path) + + if t1.get_state() == State.queued: + self.ci.free_time_dep(t1_path) + if t2.get_state() == State.queued: + self.ci.free_date_dep(t2_path) + if t3.get_state() == State.queued: + self.ci.free_trigger_dep(t3_path) + if t4.get_state() == State.queued: + self.ci.free_all_dep(t4_path) + + suite = self.ci.get_defs().find_suite("test_client_free_dep") + if suite.get_state() == State.complete: + break + time.sleep(3) + + dir_to_remove = Test.ecf_home(self.ci.get_port()) + "/" + "test_client_free_dep" + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_client_suites(self): + print_test(self.ci, "test_client_suites") + self.ci.delete_all() + assert len(self.ci.suites()) == 0, "expected 0 suite " + + defs = create_defs("test_client_suites", self.ci.get_port(), self.protocol) + self.ci.load(defs) + assert len(self.ci.suites()) == 1, "expected 1 suite " + + self.ci.delete_all() + defs.add_suite("s2") + self.ci.load(defs) + assert len(self.ci.suites()) == 2, "expected 2 suite " + + def test_client_ch_with_drops_handles(self): + print_test(self.ci, "test_client_ch_with_drops_handles") + try: + port = self.ci.get_port() + print( + " test using with but without register, handle should be zero, ie do nothing" + ) + with Client("localhost", port) as local_ci: + local_ci.debug(True) + if self.protocol == Test.Protocol.HTTP: + local_ci.enable_http() + assert local_ci.ch_handle() == 0, "Expected handle to be zero" + local_ci.ch_suites() + print(" Test Client with register, should drop handle ") + with Client("localhost", port) as local_ci: + local_ci.debug(True) + if self.protocol == Test.Protocol.HTTP: + local_ci.enable_http() + local_ci.ch_register(True, ["s1", "s2"]) + # print("Handle:",local_ci .ch_handle()) + local_ci.ch_suites() + # raise RuntimeError("xxx") # check exeption is still caught + except RuntimeError as e: + print("Exception", e) + print("after with:") + # valuely need a way to get hold of the suites, via python api. + self.ci.ch_suites() # should be empty + + def test_client_ch_suites(self): + print_test(self.ci, "test_client_ch_suites") + try: + self.ci.ch_drop_user("") # drop all handle associated with current user + except: + pass # Drop throws if no handle registered + self.ci.delete_all() + + defs = Defs() + for i in range(1, 7): + defs.add_suite("s" + str(i)) + self.ci.load(defs) + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 6 + ), "Expected 6 after load, but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + suite_names = ["s1", "s2", "s3"] + self.ci.ch_register( + True, suite_names + ) # register interest in suites s1,s2,s3 and any new suites + print("ch_handle after register : ", self.ci.ch_handle()) + self.ci.ch_suites() # writes to standard out, list of suites and handles + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 3 + ), "Expected 3 registered suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + self.ci.ch_register( + False, ["s1"] + ) # register interest in suites s1. ci remembers the last client handle + self.ci.ch_suites() # writes to standard out, list of suites and handles + sync_local(self.ci) + self.ci.ch_suites() # writes to standard out, list of suites and handles + assert ( + len(list(self.ci.get_defs().suites)) == 1 + ), "Expected 1 registered suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + def test_client_ch_register(self): + print_test(self.ci, "test_client_ch_register") + try: + self.ci.ch_drop_user("") # drop all handle associated with current user + except: + pass # Drop throws if no handle registered + self.ci.delete_all() + + defs = Defs() + for i in range(1, 7): + defs.add_suite("s" + str(i)) + self.ci.load(defs) + + suite_names = ["s1", "s2", "s3"] + self.ci.ch_register( + True, suite_names + ) # register interest in suites s1,s2,s3 and any new suites + self.ci.ch_register( + False, suite_names + ) # register interest in suites s1,s2,s3 only + + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 3 + ), "Expected 3 registered suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + def test_client_ch_drop(self): + print_test(self.ci, "test_client_ch_drop") + try: + self.ci.ch_drop_user("") # drop all handle associated with current user + except: + pass # Drop throws if no handle registered + self.ci.delete_all() + + defs = Defs() + for i in range(1, 7): + defs.add_suite("s" + str(i)) + self.ci.load(defs) + + try: + # register interest in suites s1,s2,s3 and any new suites + suite_names = ["s1", "s2", "s3"] + self.ci.ch_register(True, suite_names) + finally: + self.ci.ch_drop() # drop using handle stored in ci., from last register + + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 6 + ), "Expected 6 suites but found " + str(len(list((self.ci.get_defs().suites)))) + + def test_client_ch_drop_user(self): + print_test(self.ci, "test_client_ch_drop_user") + self.ci.delete_all() + try: + self.ci.ch_drop_user("") # drop all handle associated with current user + except: + pass # Drop throws if no handle registered + + defs = Defs() + for i in range(1, 7): + defs.add_suite("s" + str(i)) + self.ci.load(defs) + + try: + # register interest in suites s1,s2,s3 and any new suites + suite_names = ["s1", "s2", "s3"] + self.ci.ch_register(True, suite_names) + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 3 + ), "Expected 3 suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + except RuntimeError as e: + print(str(e)) + + self.ci.ch_drop_user("") # drop all handle associated with current user + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 6 + ), "Expected 6 suites but found " + str(len(list((self.ci.get_defs().suites)))) + + def test_client_ch_add(self): + print_test(self.ci, "test_client_ch_add") + self.ci.delete_all() + try: + self.ci.ch_drop_user("") # drop all handle associated with current user + except: + pass # Drop throws if no handle registered + + defs = Defs() + for i in range(1, 7): + defs.add_suite("s" + str(i)) + self.ci.load(defs) + + try: + suite_names = [] + self.ci.ch_register( + True, suite_names + ) # register interest in any new suites + suite_names = ["s1", "s2"] + self.ci.ch_add(suite_names) # add suites s1,s2 to the last added handle + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 2 + ), "Expected 2 suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + suite_names = ["s3", "s4"] + self.ci.ch_add( + self.ci.ch_handle(), suite_names + ) # add suites s3,s4 using last handle + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 4 + ), "Expected 4 suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + except RuntimeError as e: + print(str(e)) + + self.ci.ch_drop_user("") # drop all handle associated with current user + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 6 + ), "Expected 6 suites but found " + str(len(list((self.ci.get_defs().suites)))) + + def test_client_ch_auto_add(self): + print_test(self.ci, "test_client_ch_auto_add") + self.ci.delete_all() + try: + self.ci.ch_drop_user("") # drop all handle associated with current user + except: + pass # Drop throws if no handle registered + + defs = Defs() + for i in range(1, 7): + defs.add_suite("s" + str(i)) + self.ci.load(defs) + + try: + suite_names = ["s1", "s2", "s3"] + self.ci.ch_register( + True, suite_names + ) # register interest in suites s1,s2,s3 and any new suites + self.ci.ch_auto_add( + False + ) # disable adding newly created suites to last registered handle\n" + self.ci.ch_auto_add( + True + ) # enable adding newly created suites to last registered handle\n" + self.ci.ch_auto_add( + self.ci.ch_handle(), False + ) # disable adding newly created suites to handle\n" + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 3 + ), "Expected 3 suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + except RuntimeError as e: + print(str(e)) + + self.ci.ch_drop_user("") # drop all handle associated with current user + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 6 + ), "Expected 6 suites but found " + str(len(list((self.ci.get_defs().suites)))) + + def test_client_ch_remove(self): + print_test(self.ci, "test_client_ch_remove") + self.ci.delete_all() + try: + self.ci.ch_drop_user("") # drop all handle associated with current user + except: + pass # Drop throws if no handle registered + + defs = Defs() + for i in range(1, 7): + defs.add_suite("s" + str(i)) + self.ci.load(defs) + + try: + suite_names = ["s1", "s2", "s3"] + self.ci.ch_register( + True, suite_names + ) # register interest in suites s1,s2,s3 and any new suites + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 3 + ), "Expected 3 suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + suite_names = ["s1"] + self.ci.ch_remove( + suite_names + ) # remove suites s1 from the last added handle\n" + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 2 + ), "Expected 2 suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + suite_names = ["s2"] + self.ci.ch_remove( + self.ci.ch_handle(), suite_names + ) # remove suites s2 from the last added handle\n" + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 1 + ), "Expected 1 suites but found " + str( + len(list((self.ci.get_defs().suites))) + ) + + except RuntimeError as e: + print(str(e)) + + self.ci.ch_drop_user("") # drop all handle associated with current user + sync_local(self.ci) + assert ( + len(list(self.ci.get_defs().suites)) == 6 + ), "Expected 6 suites but found " + str(len(list((self.ci.get_defs().suites)))) + + def test_client_get_file(self): + print_test(self.ci, "test_client_get_file") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs("test_client_get_file", port, self.protocol) + + # Also test where user has specified his OWN ECF_JOBOUT (rare) + t2 = defs.find_abs_node("/test_client_get_file/f1/t2") + t2_jobout = Test.ecf_home(self.ci.get_port()) + "/test_client_get_file/t2.xx" + t2.add_variable("ECF_JOBOUT", t2_jobout) + + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + self.ci.begin_all_suites() + + while 1: + if self.ci.news_local(): + self.ci.sync_local() # get the changes, synced with local defs + suite = self.ci.get_defs().find_suite("test_client_get_file") + assert suite is not None, "Expected to find suite" + if suite.get_state() == State.complete: + break + time.sleep(1) + + try: + for task in ["/test_client_get_file/f1/t1", "/test_client_get_file/f1/t2"]: + + result_string = self.ci.get_file(task) + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}) to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}) to return something" + + result_string = self.ci.get_file(task, as_bytes=False) + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}, as_bytes=False) to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}, as_bytes=False) to return something" + + result_bytes = self.ci.get_file(task, as_bytes=True) + assert isinstance( + result_bytes, bytes + ), f"Expected ci.get_file({task}, as_bytes=True) to return 'bytes'" + assert ( + len(result_bytes) > 0 + ), f"Expected ci.get_file({task}, as_bytes=True) to return something" + + assert len(result_string) == len( + result_bytes + ), f"Expected 'str' result and 'bytes' result to have the same length" + + result_string = self.ci.get_file(task, max_lines="10") + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}, max_lines='10') to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}, max_lines='10') to return something" + + result_string = self.ci.get_file(task, max_lines="10", as_bytes=False) + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}, max_lines='10', as_bytes=False) to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}, max_lines='10', as_bytes=False) to return something" + + result_bytes = self.ci.get_file(task, max_lines="10", as_bytes=True) + assert isinstance( + result_bytes, bytes + ), f"Expected ci.get_file({task}, max_lines='10', as_bytes=True) to return 'bytes'" + assert ( + len(result_bytes) > 0 + ), f"Expected ci.get_file({task}, max_lines='10', as_bytes=True) to return something" + + assert len(result_string) == len( + result_bytes + ), f"Expected 'str' result and 'bytes' result to have the same length" + + for file_type in ["script", "job", "jobout", "manual"]: + result_string = self.ci.get_file(task, file_type) + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}, {file_type}) to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}, {file_type}) to return something" + + result_string = self.ci.get_file(task, file_type, as_bytes=False) + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}, {file_type}, as_bytes=False) to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}, {file_type}, as_bytes=False) to return something" + + result_bytes = self.ci.get_file(task, file_type, as_bytes=True) + assert isinstance( + result_bytes, bytes + ), f"Expected ci.get_file({task}, {file_type}, as_bytes=True) to return 'bytes'" + assert ( + len(result_bytes) > 0 + ), f"Expected ci.get_file({task}, {file_type}, as_bytes=True) to return something" + + assert len(result_string) == len( + result_bytes + ), f"Expected 'str' result and 'bytes' result to have the same length" + + result_string = self.ci.get_file(task, file_type, "10") + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}, {file_type}, '10') to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}, {file_type}, '10') to return something" + + result_string = self.ci.get_file( + task, file_type, "10", as_bytes=False + ) + assert isinstance( + result_string, str + ), f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=False) to return 'str'" + assert ( + len(result_string) > 0 + ), f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=False) to return something" + + result_bytes = self.ci.get_file( + task, file_type, "10", as_bytes=True + ) + assert isinstance( + result_bytes, bytes + ), f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=True) to return 'bytes'" + assert ( + len(result_bytes) > 0 + ), f"Expected ci.get_file({task}, {file_type}, '10', as_bytes=True) to return something" + + assert len(result_string) == len( + result_bytes + ), f"Expected 'str' result and 'bytes' result to have the same length" + + assert os.path.exists( + t2_jobout + ), f"User specified ECF_JOBOUT file not created {t2_jobout}" + + except RuntimeError as e: + print(str(e)) + + dir_to_remove = Test.ecf_home(port) + "/" + "test_client_get_file" + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_client_alter_sort(self): + print_test(self.ci, "test_client_alter_sort") + self.ci.delete_all() + + defs = create_defs("test_client_alter_sort", self.ci.get_port(), self.protocol) + t1 = "/test_client_alter_sort/f1/t1" + task_t1 = defs.find_abs_node(t1) + task_t1.add_variable("z", "value").add_variable("y", "value").add_variable( + "x", "value" + ) + task_t1.add_event("z").add_event("y").add_event("x") + task_t1.add_meter("z", 0, 100, 100).add_meter("y", 0, 100, 100).add_meter( + "x", 0, 100, 100 + ) + task_t1.add_label("z", "name").add_label("y", "name").add_label("x", "name") + task_t1.add_limit("z", 10).add_limit("y", 10).add_limit("x", 10) + + self.ci.load(defs) + + self.ci.sort_attributes(t1, "variable") + self.ci.sort_attributes(t1, "event") + self.ci.sort_attributes(t1, "meter") + self.ci.sort_attributes(t1, "label") + self.ci.sort_attributes(t1, "limit") + + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.variables)) == 3, "Expected 3 variable :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.events)) == 3, "Expected 3 events :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.meters)) == 3, "Expected 3 meters :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.labels)) == 3, "Expected 3 labels :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.limits)) == 3, "Expected 3 limits :\n" + str( + self.ci.get_defs() + ) + expected = ["x", "y", "z"] + vactual = [] + eactual = [] + mactual = [] + lactual = [] + liactual = [] + for v in task_t1.variables: + vactual.append(v.name()) + for v in task_t1.events: + eactual.append(v.name()) + for v in task_t1.meters: + mactual.append(v.name()) + for v in task_t1.labels: + lactual.append(v.name()) + for v in task_t1.limits: + liactual.append(v.name()) + assert expected == vactual, ( + "variable Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(vactual) + ) + assert expected == eactual, ( + "event Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(eactual) + ) + assert expected == mactual, ( + "meter Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(mactual) + ) + assert expected == lactual, ( + "label Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(lactual) + ) + assert expected == liactual, ( + "limit Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(liactual) + ) + + def test_client_alter_sort_defs(self): + print_test(self.ci, "test_client_alter_sort_defs") + self.ci.delete_all() + + defs = create_defs( + "test_client_alter_sort_defs", self.ci.get_port(), self.protocol + ) + t1 = "/test_client_alter_sort_defs/f1/t1" + task_t1 = defs.find_abs_node(t1) + task_t1.add_variable("z", "value").add_variable("y", "value").add_variable( + "x", "value" + ) + task_t1.add_event("z").add_event("y").add_event("x") + task_t1.add_meter("z", 0, 100, 100).add_meter("y", 0, 100, 100).add_meter( + "x", 0, 100, 100 + ) + task_t1.add_label("z", "name").add_label("y", "name").add_label("x", "name") + task_t1.add_limit("z", 10).add_limit("y", 10).add_limit("x", 10) + + self.ci.load(defs) + + self.ci.sort_attributes("/", "variable", True) + self.ci.sort_attributes("/", "event", True) + self.ci.sort_attributes("/", "meter", True) + self.ci.sort_attributes("/", "label", True) + self.ci.sort_attributes("/", "limit", True) + + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.variables)) == 3, "Expected 3 variable :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.events)) == 3, "Expected 3 events :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.meters)) == 3, "Expected 3 meters :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.labels)) == 3, "Expected 3 labels :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.limits)) == 3, "Expected 3 limits :\n" + str( + self.ci.get_defs() + ) + expected = ["x", "y", "z"] + vactual = [] + eactual = [] + mactual = [] + lactual = [] + liactual = [] + for v in task_t1.variables: + vactual.append(v.name()) + for v in task_t1.events: + eactual.append(v.name()) + for v in task_t1.meters: + mactual.append(v.name()) + for v in task_t1.labels: + lactual.append(v.name()) + for v in task_t1.limits: + liactual.append(v.name()) + assert expected == vactual, ( + "variable Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(vactual) + ) + assert expected == eactual, ( + "event Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(eactual) + ) + assert expected == mactual, ( + "meter Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(mactual) + ) + assert expected == lactual, ( + "label Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(lactual) + ) + assert expected == liactual, ( + "limit Attributes not sorted, expected:" + + str(expected) + + " but found:" + + str(liactual) + ) + + def test_client_alter_add(self): + print_test(self.ci, "test_client_alter_add") + self.ci.delete_all() + self.ci.load( + create_defs("test_client_alter_add", self.ci.get_port(), self.protocol) + ) + + t1 = "/test_client_alter_add/f1/t1" + self.ci.alter(t1, "add", "variable", "var", "var_name") + self.ci.alter(t1, "add", "variable", "var2", "--") + self.ci.alter(t1, "add", "variable", "var3", "--fred") + self.ci.alter(t1, "add", "variable", "var4", " --fred ") + self.ci.alter(t1, "add", "variable", "var5", "--fred --jake") + self.ci.alter(t1, "add", "variable", "var6", " --fred --jake ") + self.ci.alter(t1, "add", "variable", "var7", "") + self.ci.alter(t1, "add", "time", "+00:30") + self.ci.alter(t1, "add", "time", "01:30") + self.ci.alter(t1, "add", "time", "01:30 20:00 00:30") + self.ci.alter(t1, "add", "today", "+00:30") + self.ci.alter(t1, "add", "today", "01:30") + self.ci.alter(t1, "add", "today", "01:30 20:00 00:30") + self.ci.alter(t1, "add", "date", "01.01.2001") + self.ci.alter(t1, "add", "date", "*.01.2001") + self.ci.alter(t1, "add", "date", "*.*.2001") + self.ci.alter(t1, "add", "date", "*.*.*") + self.ci.alter(t1, "add", "day", "sunday") + self.ci.alter(t1, "add", "day", "monday") + self.ci.alter(t1, "add", "day", "tuesday") + self.ci.alter(t1, "add", "day", "wednesday") + self.ci.alter(t1, "add", "day", "thursday") + self.ci.alter(t1, "add", "day", "friday") + self.ci.alter(t1, "add", "day", "saturday") + self.ci.alter(t1, "add", "late", "late -s +00:15 -a 20:00 -c +02:00") + self.ci.alter(t1, "add", "label", "label_name", "label_value") + self.ci.alter(t1, "add", "label", "label_name2", "/a/label/with/path/values") + + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.variables)) == 7, "Expected 7 variable :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.times)) == 3, "Expected 3 time :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.todays)) == 3, "Expected 3 today's :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.dates)) == 4, "Expected 4 dates :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.days)) == 7, "Expected 7 days :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.labels)) == 2, "Expected 2 labels :\n" + str( + self.ci.get_defs() + ) + assert ( + str(task_t1.get_late()) == "late -s +00:15 -a 20:00 -c +02:00" + ), "Expected late 'late -s +00:15 -a 20:00 -c +02:00'" + str(self.ci.get_defs()) + + def test_client_alter_delete(self): + print_test(self.ci, "test_client_alter_delete") + self.ci.delete_all() + defs = create_defs( + "test_client_alter_delete", self.ci.get_port(), self.protocol + ) + suite_with_limits = defs.add_suite("suite_with_limits") + suite_with_limits.add_limit("limitX", 10) + suite_with_limits_X = defs.add_suite("suite_with_limits_X") + suite_with_limits_X.add_limit("limitX", 10) + + t1 = "/test_client_alter_delete/f1/t1" + task_t1 = defs.find_abs_node(t1) + task_t1.add_variable("var", "value") + task_t1.add_variable("var1", "value") + task_t1.add_time("00:30") + task_t1.add_time("00:31") + task_t1.add_today("00:30") + task_t1.add_today("00:31") + task_t1.add_date(1, 1, 2001) + task_t1.add_date(1, 1, 2002) + task_t1.add_day("sunday") + task_t1.add_day("monday") + cron = Cron() + cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) + cron.set_time_series("+00:30") + task_t1.add_cron(cron) + task_t1.add_cron(cron) + task_t1.add_event("event") + task_t1.add_event("event1") + task_t1.add_meter("meter", 0, 100, 100) + task_t1.add_meter("meter1", 0, 100, 100) + task_t1.add_label("label", "name") + task_t1.add_label("label1", "name") + task_t1.add_limit("limit", 10) + task_t1.add_limit("limit1", 10) + task_t1.add_inlimit("limit", t1, 2) + task_t1.add_inlimit("limit1", t1, 2) + task_t1.add_inlimit("limitX", "/suite_with_limits", 2) + task_t1.add_inlimit("limitX", "/suite_with_limits_X", 2) + task_t1.add_trigger("t2 == active") + task_t1.add_complete("t2 == complete") + + assert task_t1.get_late() is None, "expected no late" + late = Late() + late.submitted(20, 10) + late.active(20, 10) + late.complete(20, 10, True) + task_t1.add_late(late) + assert task_t1.get_late() is not None, "expected late" + + t2 = "/test_client_alter_delete/f1/t2" + task_t2 = defs.find_abs_node(t2) + task_t2.add_repeat( + RepeatDate("date", 20100111, 20100115, 2) + ) # can't add cron and repeat at the same level + + # print(defs) + self.ci.load(defs) + + self.ci.alter(t1, "delete", "variable", "var") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.variables)) == 1, "Expected 1 variable :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "variable") # delete all veriables + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.variables)) == 0, "Expected 0 variable :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "time", "00:30") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.times)) == 1, "Expected 1 time :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "time") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.times)) == 0, "Expected 0 time :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "today", "00:30") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.todays)) == 1, "Expected 1 today :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "today") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.todays)) == 0, "Expected 0 today :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "date", "01.01.2001") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.dates)) == 1, "Expected 1 date :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "date") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.dates)) == 0, "Expected 0 date :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "day", "sunday") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.days)) == 1, "Expected 1 day :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "day") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.days)) == 0, "Expected 0 day :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "event", "event") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.events)) == 1, "Expected 1 event :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "event") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.events)) == 0, "Expected 0 event :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "meter", "meter") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.meters)) == 1, "Expected 1 meter :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "meter") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.meters)) == 0, "Expected 0 meter :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "label", "label") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.labels)) == 1, "Expected 1 label :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "label") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.labels)) == 0, "Expected 0 label :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "inlimit", "limit") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.inlimits)) == 3, "Expected 3 inlimit :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "inlimit", "/suite_with_limits:limitX") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.inlimits)) == 2, "Expected 2 inlimit :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "inlimit", "/suite_with_limits_X:limitX") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.inlimits)) == 1, "Expected 1 inlimit :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "inlimit") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.inlimits)) == 0, "Expected 0 inlimit :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "limit", "limit") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.limits)) == 1, "Expected 1 limit :\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "limit") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.limits)) == 0, "Expected 0 limit :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "cron") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.crons)) == 0, "Expected 0 crons :\n" + str( + self.ci.get_defs() + ) + + self.ci.alter(t1, "delete", "late") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert task_t1.get_late() is None, "expected no late after delete" + + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert task_t1.get_trigger() is not None, "Expected trigger:\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "trigger") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert task_t1.get_trigger() is None, "Expected trigger to be deleted:\n" + str( + self.ci.get_defs() + ) + + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert task_t1.get_complete() is not None, "Expected complete:\n" + str( + self.ci.get_defs() + ) + self.ci.alter(t1, "delete", "complete") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert ( + task_t1.get_complete() is None + ), "Expected complete to be deleted:\n" + str(self.ci.get_defs()) + + self.ci.alter(t2, "delete", "repeat") + sync_local(self.ci) + task_t2 = self.ci.get_defs().find_abs_node(t2) + repeat = task_t2.get_repeat() + assert repeat.empty(), "Expected repeat to be deleted:\n" + str( + self.ci.get_defs() + ) + + def test_client_alter_change(self): + print_test(self.ci, "test_client_alter_change") + + # + # ISSUE: + # - Currently we can only change clock attr if we have one. + # - Even when we have a clock attr, it only makes sense to apply clock attr changes + # before begin(), i.e how do we apply change in gain after begin ?? + # + # " change clock-type name # The name must be one of 'hybrid' or 'value'.\n" + # " change clock-gain name # The gain must be convertible to an integer.\n" + # " change label name value # sets the label\n" + # " change repeat value # If the repeat is a date, then the value must be a valid YMD (ie. yyyymmdd)\n" + # " # and be convertible to an integer, additionally the value must be in range\n" + # " # of the repeat start and end dates. Likewise for repeat integer. For repeat\n" + # " # string and enum, the name must either be an integer, that is a valid index, or\n" + # " # if it is a string, it must correspond to one of enum's or strings list\n" + # + + self.ci.delete_all() + defs = create_defs( + "test_client_alter_change", self.ci.get_port(), self.protocol + ) + t1 = "/test_client_alter_change/f1/t1" + repeat_date_path = "/test_client_alter_change/f1/repeat_date" + repeat_datetime_path = "/test_client_alter_change/f1/repeat_datetime" + + task_t1 = defs.find_abs_node(t1) + task_t1.add_variable("var", "value") + task_t1.add_variable("var1", "value") + task_t1.add_event("event") + task_t1.add_event("event1") + task_t1.add_meter("meter", 0, 100, 100) + task_t1.add_meter("meter1", 0, 100, 100) + task_t1.add_label("label", "name") + task_t1.add_label("label1", "name1") + task_t1.add_limit("limit", 10) + task_t1.add_limit("limit1", 10) + task_t1.add_inlimit("limit", t1, 2) + task_t1.add_inlimit("limit1", t1, 2) + task_t1.add_trigger("t2 == active") + task_t1.add_complete("t2 == complete") + task_t1.add_time("00:31") + task_t1.add_today("00:30") + + late = Late() + late.submitted(20, 10) + late.active(20, 10) + late.complete(20, 10, True) + task_t1.add_late(late) + + f1 = defs.find_abs_node("/test_client_alter_change/f1") + # Add a new Repeat (based on date) + repeat_date = f1.add_task("repeat_date") + repeat_date.add_repeat( + RepeatDate("date", 20100111, 20100115, 2) + ) # can't add cron and repeat at the same level + # Add a new Repeat (based on date+time) + repeat_datetime = f1.add_task("repeat_datetime") + repeat_datetime.add_repeat( + RepeatDateTime("datetime", "20100111T000000", "20100115T000000", "48:00:00") + ) + + self.ci.load(defs) + + self.ci.alter(t1, "change", "time", "00:31", "10:10") + self.ci.alter(t1, "change", "today", "00:30", "10:10") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + assert len(list(task_t1.times)) == 1, "Expected 1 time :\n" + str( + self.ci.get_defs() + ) + assert len(list(task_t1.todays)) == 1, "Expected 1 time :\n" + str( + self.ci.get_defs() + ) + for t in task_t1.times: + assert str(t) == "time 10:10", " expected 'time 10:10' but found:\n" + str( + t + ) + for t in task_t1.todays: + assert ( + str(t) == "today 10:10" + ), " expected 'today 10:10' but found:\n" + str(t) + + self.ci.alter(t1, "change", "late", "-s +10:00") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + variable = task_t1.get_late() + assert ( + str(task_t1.get_late()) == "late -s +10:00" + ), "Expected alter of late to be 'late -s +10:00' but found " + str( + task_t1.get_late() + ) + + self.ci.alter(t1, "change", "variable", "var", "changed_var") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + variable = task_t1.find_variable("var") + assert variable.value() == "changed_var", ( + "Expected alter of variable to be 'change_var' but found " + + variable.value() + ) + res = self.ci.query("variable", task_t1.get_abs_node_path(), "var") + assert res == "changed_var", ( + "Expected alter of variable to be 'change_var' but found " + res + ) + + self.ci.alter(t1, "change", "variable", "var", "--") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + variable = task_t1.find_variable("var") + assert variable.value() == "--", ( + "Expected alter of variable to be '--' but found " + variable.value() + ) + res = self.ci.query("variable", task_t1.get_abs_node_path(), "var") + assert res == "--", "Expected alter of variable to be '--' but found " + res + + self.ci.alter(t1, "change", "variable", "var", "--fred") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + variable = task_t1.find_variable("var") + assert variable.value() == "--fred", ( + "Expected alter of variable to be '--fred' but found " + variable.value() + ) + + self.ci.alter(t1, "change", "variable", "var", "--fred --bill") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + variable = task_t1.find_variable("var") + assert variable.value() == "--fred --bill", ( + "Expected alter of variable to be '--fred --bill' but found " + + variable.value() + ) + + self.ci.alter(t1, "change", "meter", "meter", "10") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + meter = task_t1.find_meter("meter") + assert meter.value() == 10, "Expected alter of meter to be 10 but found " + str( + meter.value() + ) + res = self.ci.query("meter", task_t1.get_abs_node_path(), "meter") + assert res == "10", "Expected alter of meter to be 10 but found " + res + + self.ci.alter(t1, "change", "event", "event", "set") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + event = task_t1.find_event("event") + assert event.value(), "Expected alter of event to be set but found " + str( + event.value() + ) + res = self.ci.query("event", task_t1.get_abs_node_path(), "event") + assert res == "set", "Expected alter of event to be 'set' but found " + res + + self.ci.alter(t1, "change", "trigger", "t2 == aborted") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + trigger = task_t1.get_trigger() + assert trigger.get_expression() == "t2 == aborted", ( + "Expected alter of trigger to be 't2 == aborted' but found " + + trigger.get_expression() + ) + res = self.ci.query("trigger", task_t1.get_abs_node_path(), "t2 == aborted") + assert res == "false", ( + "Expected evaluation of trigger to fail, but found: " + res + ) + + self.ci.alter( + t1, "change", "trigger", "/test_client_alter_change/f1/t2 == complete" + ) + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + trigger = task_t1.get_trigger() + assert ( + trigger.get_expression() == "/test_client_alter_change/f1/t2 == complete" + ), ( + "Expected alter of trigger to be '/test_client_alter_change/f1/t2 == complete' but found " + + trigger.get_expression() + ) + + self.ci.alter(t1, "change", "complete", "t2 == aborted") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + complete = task_t1.get_complete() + assert complete.get_expression() == "t2 == aborted", ( + "Expected alter of complete to be 't2 == aborted' but found " + + complete.get_expression() + ) + + self.ci.alter( + t1, "change", "complete", "/test_client_alter_change/f1/t2 == active" + ) + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + complete = task_t1.get_complete() + assert ( + complete.get_expression() == "/test_client_alter_change/f1/t2 == active" + ), ( + "Expected alter of complete to be '/test_client_alter_change/f1/t2 == active' but found " + + complete.get_expression() + ) + + self.ci.alter(t1, "change", "limit_max", "limit", "2") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + limit = task_t1.find_limit("limit") + assert limit is not None, "Expected to find limit" + assert ( + limit.limit() == 2 + ), "Expected alter of limit_max to be 2 but found " + str(limit.limit()) + + self.ci.alter(t1, "change", "limit_value", "limit", "2") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + limit = task_t1.find_limit("limit") + assert limit is not None, "Expected to find limit" + assert ( + limit.value() == 2 + ), "Expected alter of limit_value to be 2 but found " + str(limit.value()) + + self.ci.alter(t1, "change", "label", "label", "new-value") + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + label = task_t1.find_label("label") + assert label.new_value() == "new-value", ( + "Expected alter of label to be 'new-value' but found " + label.new_value() + ) + + self.ci.alter(repeat_date_path, "change", "repeat", "20100113") + sync_local(self.ci) + task = self.ci.get_defs().find_abs_node(repeat_date_path) + repeat = task.get_repeat() + assert ( + repeat.value() == 20100113 + ), "Expected alter of repeat to be 20100113 but found " + str(repeat.value()) + res = self.ci.query("variable", task.get_abs_node_path(), repeat.name()) + assert res == "20100113", ( + "Expected alter of repeat to be 20100113 but found " + res + ) + + self.ci.alter(repeat_datetime_path, "change", "repeat", "20100113T000000") + sync_local(self.ci) + task = self.ci.get_defs().find_abs_node(repeat_datetime_path) + repeat = task.get_repeat() + assert repeat.value() == 1263340800, ( + "Expected alter of repeat to be 1263340800 (i.e. seconds between 19700101T000000 and 20100113T000000) but found " + + str(repeat.value()) + ) + res = self.ci.query("variable", task.get_abs_node_path(), repeat.name()) + assert res == "20100113T000000", ( + "Expected alter of repeat to be 20100113T000000 but found " + res + ) + + def test_client_alter_flag(self): + print_test(self.ci, "test_client_alter_flag") + self.ci.delete_all() + defs = create_defs("test_client_alter_flag", self.ci.get_port(), self.protocol) + t1 = "/test_client_alter_flag/f1/t1" + + task_t1 = defs.find_abs_node(t1) + + self.ci.load(defs) + + flag = Flag() + flag_list = flag.list() # flag_list is of type FlagTypeVec + for flg in flag_list: + self.ci.alter(t1, "set_flag", flag.type_to_string(flg)) + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + task_flag = task_t1.get_flag() + assert task_flag.is_set( + flg + ), "expected flag %r to be set" % task_flag.type_to_string(flg) + + # alter itself causes the flag message to be set, and preserved + if flg == FlagType.message: + continue + + self.ci.alter(t1, "clear_flag", flag.type_to_string(flg)) + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node(t1) + task_flag = task_t1.get_flag() + assert not task_flag.is_set( + flg + ), "expected flag %r NOT to be set" % task_flag.type_to_string(flg) + + def test_client_force(self): + print_test(self.ci, "test_client_force") + self.ci.delete_all() + defs = create_defs("test_client_force", self.ci.get_port(), self.protocol) + + path_list = ["/test_client_force/f1/t1", "/test_client_force/f1/t2"] + t1 = path_list[0] for path in path_list: - task = ci.get_defs().find_abs_node(path) - event_fnd = False - for event in task.events: - event_fnd = True - if ev_state == "set": - assert event.value() == True, " Expected event value to be set" - else: - assert event.value() == False, " Expected event value to be clear" - assert event_fnd == True, " Expected event to be found" - - -def test_client_replace(ci, on_disk, protocol): - print_test(ci, "test_client_replace client_defs on disk = " + str(on_disk)) - # Create and load the following defs - # s1 - # f1 - # t1 - # t2 - ci.delete_all() - ci.load(create_defs("s1", ci.get_port(), protocol)) - - # =============================================================================== - # Example of using replace to ADD a *NEW* node hierarchy to an existing suite - # we should end up with: - # s1 - # f1 - # t1 - # t2 - # f2 - # t1 - # t2 - test_client_replace_def_file = "test_client_replace_" + str(os.getpid()) + ".def" - client_def = create_defs("s1", ci.get_port(), protocol) - client_def.find_suite("s1").add_family("f2").add_task("t1") - if on_disk: - client_def.save_as_defs(test_client_replace_def_file) - client_def = test_client_replace_def_file - - ci.replace("/s1/f2", client_def, True, - False) # True means create parents as needed, False means don't bypass checks/zombies - ci.get_server_defs() - assert len(list(ci.get_defs().suites)) == 1, "Expected 1 suites:\n" + str(ci.get_defs()) - assert ci.get_defs().find_abs_node("/s1/f2/t1") is not None, "Expected to find task /s1/f2/t1\n" + str( - ci.get_defs()) - - # Example of using replace to *REMOVE* node hierarchy to an existing suite, could have used delete - assert ci.get_defs().find_abs_node("/s1/f1/t1") is not None, "Expected to find task /s1/f1/t1\n" + str( - ci.get_defs()) - assert ci.get_defs().find_abs_node("/s1/f2/t1") is not None, "Expected to find task /s1/f2/t1\n" + str( - ci.get_defs()) - client_def = Defs() - client_def.add_suite("s1") # should only have the suite - if on_disk: - client_def.save_as_defs(test_client_replace_def_file) - client_def = test_client_replace_def_file - - ci.replace("/s1", client_def) - ci.get_server_defs() - assert len(list(ci.get_defs().suites)) == 1, "Expected 1 suites:\n" + str(ci.get_defs()) - assert ci.get_defs().find_abs_node("/s1/f1/t1") is None, "Expected NOT to find task /s1/f1/t1\n" + str( - ci.get_defs()) - assert ci.get_defs().find_abs_node("/s1/f2/t1") is None, "Expected NOT to find task /s1/f2/t1\n" + str( - ci.get_defs()) - - # =============================================================================== - # Example of using replace to add a *NEW* suite - client_def = Defs(); - client_def.add_suite("s2") - if on_disk: - client_def.save_as_defs(test_client_replace_def_file) - client_def = test_client_replace_def_file - - ci.replace("/s2", client_def, True, - False) # True means create parents as needed, False means don't bypass checks/zombies - ci.get_server_defs() - assert len(list(ci.get_defs().suites)) == 2, " Expected two suites:\n" + str(ci.get_defs()) - - # replace added suite s2 with a new s2 which has a task, - # s2 must exist on the client defs - client_def = Defs(); - client_def.add_suite("s2").add_task("t1") - if on_disk: - client_def.save_as_defs(test_client_replace_def_file) - client_def = test_client_replace_def_file - - ci.replace("/s2", client_def) - - ci.get_server_defs() - assert len(list(ci.get_defs().suites)) == 2, " Expected two suites:\n" + str(ci.get_defs()) - assert ci.get_defs().find_abs_node("/s2/t1") is not None, "Expected to find task /s2/t1\n" + str(ci.get_defs()) - if on_disk: - os.remove(test_client_replace_def_file) - - -def test_node_replace(ci): - print_test(ci, "test_node_replace") - PrintStyle.set_style(Style.MIGRATE) # show node state - ci.delete_all() - defs = Defs() + (Suite("s1") + Family('f1').add(Task('t1'), Task('t2'))) - ci.load(defs) - - # We should have 4 nodes - sync_local(ci) - ci_defs = ci.get_defs() - node_vec = ci_defs.get_all_nodes() - assert len(list(node_vec)) == 4, "Expected two 4 nodes: \n" + str(ci.get_defs()) - - # replace each node, add variable first, then check, it was added - for node in node_vec: - node += Edit(var="XX", var2="xx") - node.replace_on_server(ci) # default is to suspend server node first, so replaced node is also suspended - - sync_local(ci) - replace_node = ci.get_defs().find_abs_node(node.get_abs_node_path()) - assert len(list(replace_node.variables)) == 2, "Expected two 2 variable: \n" + str(replace_node) + "\n" + str( - ci.get_defs()) - assert replace_node.get_dstate() == DState.suspended, "Expected node to be suspended:\n" + str( - replace_node) + "\n" + str(ci.get_defs()) - - # resume nodes, test that when False passed in we do not suspend the replaced node - for node in node_vec: - ci.resume(node.get_abs_node_path()) - sync_local(ci) - replace_node = ci.get_defs().find_abs_node(node.get_abs_node_path()) - assert replace_node.get_dstate() != DState.suspended, "Expected node not to suspended:\n" + str(replace_node) - - replace_node += Meter("meter", 0, 100) - - # cant use node i.e this is only client side, and is still suspended - # When we replace is server the client defs is sent to server. hence node would be suspended - replace_node.replace_on_server(ci, suspend_node_first=False) - sync_local(ci) - - replace_node = ci.get_defs().find_abs_node(node.get_abs_node_path()) - assert len(list(replace_node.meters)) == 1, "Expected 1 meter: \n" + str(replace_node) - assert len(list(replace_node.variables)) == 2, "Expected two 2 variable: \n" + str(replace_node) - assert replace_node.get_dstate() != DState.suspended, "Expected node not to suspended:\n" + str(replace_node) - - # replace the suite - defs = Defs() + Suite("s1") - host_port = ci.get_host() + ':' + ci.get_port() - defs.s1.replace_on_server(ci) - - ci.get_server_defs() - assert len(list(ci.get_defs().s1)) == 0, "Expected 0 family: \n" + str(ci.get_defs()) - assert ci.get_defs().s1.get_dstate() == DState.suspended, "Expected node to be suspended:\n" + str(ci.get_defs()) - - -def test_client_kill(ci): - pass - - -def test_client_status(ci): - pass - - -def test_client_order(ci): - pass - - -def test_client_group(ci): - pass - - -def test_client_suspend(ci, protocol): - print_test(ci, "test_client_suspend") - ci.delete_all() - defs = create_defs("test_client_suspend", ci.get_port(), protocol) - suite = defs.find_suite("test_client_suspend") - suite.add_variable("ECF_DUMMY_TASK", "") - - ci.load(defs) - ci.begin_all_suites() - - ci.suspend("/test_client_suspend") - - sync_local(ci); - suite = ci.get_defs().find_suite("test_client_suspend") - assert suite.is_suspended(), "Expected to find suite suspended" - - assert ci.query('dstate', - suite.get_abs_node_path()) == "suspended", "Expected to find suite dstate suspended but found: " + res - assert ci.query('state', - suite.get_abs_node_path()) == "queued", "Expected to find suite state queued but found: " + res - - -def test_client_suspend_multiple_paths(ci, protocol): - print_test(ci, "test_client_suspend_multiple_paths") - ci.delete_all() - defs = create_defs("test_client_suspend_multiple_paths", ci.get_port(), protocol) - suite = defs.find_suite("test_client_suspend_multiple_paths") - suite.add_variable("ECF_DUMMY_TASK", "") - - ci.load(defs) - ci.begin_all_suites() - - path_list = ["/test_client_suspend_multiple_paths/f1/t1", "/test_client_suspend_multiple_paths/f1/t2"] - ci.suspend(path_list) - - sync_local(ci); - task_t1 = ci.get_defs().find_abs_node("/test_client_suspend_multiple_paths/f1/t1") - task_t2 = ci.get_defs().find_abs_node("/test_client_suspend_multiple_paths/f1/t2") - assert task_t1.is_suspended(), "Expected to find task t1 to be suspended" - assert task_t2.is_suspended(), "Expected to find task t2 to be suspended" - - -def test_client_resume(ci, protocol): - print_test(ci, "test_client_resume") - ci.delete_all() - defs = create_defs("test_client_resume", ci.get_port(), protocol) - suite = defs.find_suite("test_client_resume") - suite.add_variable("ECF_DUMMY_TASK", "") - - ci.load(defs) - ci.begin_all_suites() - - ci.suspend("/test_client_resume") - sync_local(ci); - suite = ci.get_defs().find_suite("test_client_resume") - assert suite.is_suspended(), "Expected to find suite suspended" - - ci.resume("/test_client_resume") - sync_local(ci); - suite = ci.get_defs().find_suite("test_client_resume") - assert suite.is_suspended() == False, "Expected to find suite resumed" - - -def test_client_resume_multiple_paths(ci, protocol): - print_test(ci, "test_client_resume_multiple_paths") - ci.delete_all() - defs = create_defs("test_client_resume_multiple_paths", ci.get_port(), protocol) - suite = defs.find_suite("test_client_resume_multiple_paths") - suite.add_variable("ECF_DUMMY_TASK", "") - - ci.load(defs) - ci.begin_all_suites() - - path_list = ["/test_client_resume_multiple_paths/f1/t1", "/test_client_resume_multiple_paths/f1/t2"] - ci.suspend(path_list) - - sync_local(ci); - task_t1 = ci.get_defs().find_abs_node("/test_client_resume_multiple_paths/f1/t1") - task_t2 = ci.get_defs().find_abs_node("/test_client_resume_multiple_paths/f1/t2") - assert task_t1.is_suspended(), "Expected to find task t1 to be suspended" - assert task_t2.is_suspended(), "Expected to find task t2 to be suspended" - - ci.resume(path_list) - sync_local(ci); - task_t1 = ci.get_defs().find_abs_node("/test_client_resume_multiple_paths/f1/t1") - task_t2 = ci.get_defs().find_abs_node("/test_client_resume_multiple_paths/f1/t2") - assert task_t1.is_suspended() == False, "Expected to find task t1 to be resumed" - assert task_t2.is_suspended() == False, "Expected to find task t2 to be resumed" - + task = defs.find_abs_node(path) + assert task is not None, "Expected to find task at path " + path + task.add_event("event") + + self.ci.load(defs) + + state_list = [ + State.unknown, + State.active, + State.complete, + State.queued, + State.submitted, + State.aborted, + ] + for state in state_list: + self.ci.force_state(t1, state) + sync_local(self.ci) + task = self.ci.get_defs().find_abs_node(t1) + assert task.get_state() == state, ( + "Expected state " + state + " but found " + str(task.get_state()) + ) + for state in state_list: + self.ci.force_state(path_list, state) + sync_local(self.ci) + for path in path_list: + task = self.ci.get_defs().find_abs_node(path) + assert task.get_state() == state, ( + "Expected state " + state + " but found " + str(task.get_state()) + ) + + for state in state_list: + self.ci.force_state_recursive("/test_client_force", state) + sync_local(self.ci) + task = self.ci.get_defs().find_abs_node(t1) + assert task.get_state() == state, ( + "Expected state " + state + " but found " + str(task.get_state()) + ) + suite_paths = ["/test_client_force"] + for state in state_list: + self.ci.force_state_recursive(suite_paths, state) + sync_local(self.ci) + task = self.ci.get_defs().find_abs_node(t1) + assert task.get_state() == state, ( + "Expected state " + state + " but found " + str(task.get_state()) + ) + + event_states = ["set", "clear"] + for ev_state in event_states: + for path in path_list: + self.ci.force_event(path + ":event", ev_state) + sync_local(self.ci) + task = self.ci.get_defs().find_abs_node(path) + event_fnd = False + for event in task.events: + event_fnd = True + if ev_state == "set": + assert event.value(), " Expected event value to be set" + else: + assert not event.value(), " Expected event value to be clear" + assert event_fnd, " Expected event to be found" + + event_path_list = [ + "/test_client_force/f1/t1:event", + "/test_client_force/f1/t2:event", + ] + event_states = ["set", "clear"] + for ev_state in event_states: + self.ci.force_event(event_path_list, ev_state) + sync_local(self.ci) + for path in path_list: + task = self.ci.get_defs().find_abs_node(path) + event_fnd = False + for event in task.events: + event_fnd = True + if ev_state == "set": + assert event.value(), " Expected event value to be set" + else: + assert not event.value(), " Expected event value to be clear" + assert event_fnd, " Expected event to be found" + + @pytest.mark.parametrize("on_disk", [False, True], ids=["in_memory", "on_disk"]) + def test_client_replace(self, on_disk): + print_test(self.ci, "test_client_replace client_defs on disk = " + str(on_disk)) + # Create and load the following defs + # s1 + # f1 + # t1 + # t2 + self.ci.delete_all() + self.ci.load(create_defs("s1", self.ci.get_port(), self.protocol)) + + # =============================================================================== + # Example of using replace to ADD a *NEW* node hierarchy to an existing suite + # we should end up with: + # s1 + # f1 + # t1 + # t2 + # f2 + # t1 + # t2 + test_client_replace_def_file = ( + "test_client_replace_" + str(os.getpid()) + ".def" + ) + client_def = create_defs("s1", self.ci.get_port(), self.protocol) + client_def.find_suite("s1").add_family("f2").add_task("t1") + if on_disk: + client_def.save_as_defs(test_client_replace_def_file) + client_def = test_client_replace_def_file + + self.ci.replace( + "/s1/f2", client_def, True, False + ) # True means create parents as needed, False means don't bypass checks/zombies + self.ci.get_server_defs() + assert len(list(self.ci.get_defs().suites)) == 1, "Expected 1 suites:\n" + str( + self.ci.get_defs() + ) + assert ( + self.ci.get_defs().find_abs_node("/s1/f2/t1") is not None + ), "Expected to find task /s1/f2/t1\n" + str(self.ci.get_defs()) + + # Example of using replace to *REMOVE* node hierarchy to an existing suite, could have used delete + assert ( + self.ci.get_defs().find_abs_node("/s1/f1/t1") is not None + ), "Expected to find task /s1/f1/t1\n" + str(self.ci.get_defs()) + assert ( + self.ci.get_defs().find_abs_node("/s1/f2/t1") is not None + ), "Expected to find task /s1/f2/t1\n" + str(self.ci.get_defs()) + client_def = Defs() + client_def.add_suite("s1") # should only have the suite + if on_disk: + client_def.save_as_defs(test_client_replace_def_file) + client_def = test_client_replace_def_file + + self.ci.replace("/s1", client_def) + self.ci.get_server_defs() + assert len(list(self.ci.get_defs().suites)) == 1, "Expected 1 suites:\n" + str( + self.ci.get_defs() + ) + assert ( + self.ci.get_defs().find_abs_node("/s1/f1/t1") is None + ), "Expected NOT to find task /s1/f1/t1\n" + str(self.ci.get_defs()) + assert ( + self.ci.get_defs().find_abs_node("/s1/f2/t1") is None + ), "Expected NOT to find task /s1/f2/t1\n" + str(self.ci.get_defs()) + + # =============================================================================== + # Example of using replace to add a *NEW* suite + client_def = Defs() + client_def.add_suite("s2") + if on_disk: + client_def.save_as_defs(test_client_replace_def_file) + client_def = test_client_replace_def_file + + self.ci.replace( + "/s2", client_def, True, False + ) # True means create parents as needed, False means don't bypass checks/zombies + self.ci.get_server_defs() + assert ( + len(list(self.ci.get_defs().suites)) == 2 + ), " Expected two suites:\n" + str(self.ci.get_defs()) + + # replace added suite s2 with a new s2 which has a task, + # s2 must exist on the client defs + client_def = Defs() + client_def.add_suite("s2").add_task("t1") + if on_disk: + client_def.save_as_defs(test_client_replace_def_file) + client_def = test_client_replace_def_file + + self.ci.replace("/s2", client_def) + + self.ci.get_server_defs() + assert ( + len(list(self.ci.get_defs().suites)) == 2 + ), " Expected two suites:\n" + str(self.ci.get_defs()) + assert ( + self.ci.get_defs().find_abs_node("/s2/t1") is not None + ), "Expected to find task /s2/t1\n" + str(self.ci.get_defs()) + if on_disk: + os.remove(test_client_replace_def_file) + + def test_node_replace(self): + print_test(self.ci, "test_node_replace") + PrintStyle.set_style(Style.MIGRATE) # show node state + self.ci.delete_all() + defs = Defs() + (Suite("s1") + Family("f1").add(Task("t1"), Task("t2"))) + self.ci.load(defs) + + # We should have 4 nodes + sync_local(self.ci) + ci_defs = self.ci.get_defs() + node_vec = ci_defs.get_all_nodes() + assert len(list(node_vec)) == 4, "Expected two 4 nodes: \n" + str( + self.ci.get_defs() + ) + + # replace each node, add variable first, then check, it was added + for node in node_vec: + node += Edit(var="XX", var2="xx") + node.replace_on_server( + self.ci + ) # default is to suspend server node first, so replaced node is also suspended + + sync_local(self.ci) + replace_node = self.ci.get_defs().find_abs_node(node.get_abs_node_path()) + assert len(list(replace_node.variables)) == 2, ( + "Expected two 2 variable: \n" + + str(replace_node) + + "\n" + + str(self.ci.get_defs()) + ) + assert replace_node.get_dstate() == DState.suspended, ( + "Expected node to be suspended:\n" + + str(replace_node) + + "\n" + + str(self.ci.get_defs()) + ) + + # resume nodes, test that when False passed in we do not suspend the replaced node + for node in node_vec: + self.ci.resume(node.get_abs_node_path()) + sync_local(self.ci) + replace_node = self.ci.get_defs().find_abs_node(node.get_abs_node_path()) + assert ( + replace_node.get_dstate() != DState.suspended + ), "Expected node not to suspended:\n" + str(replace_node) + + replace_node += Meter("meter", 0, 100) + + # cant use node i.e this is only client side, and is still suspended + # When we replace is server the client defs is sent to server. hence node would be suspended + replace_node.replace_on_server(self.ci, suspend_node_first=False) + sync_local(self.ci) + + replace_node = self.ci.get_defs().find_abs_node(node.get_abs_node_path()) + assert len(list(replace_node.meters)) == 1, "Expected 1 meter: \n" + str( + replace_node + ) + assert ( + len(list(replace_node.variables)) == 2 + ), "Expected two 2 variable: \n" + str(replace_node) + assert ( + replace_node.get_dstate() != DState.suspended + ), "Expected node not to suspended:\n" + str(replace_node) + + # replace the suite + defs = Defs() + Suite("s1") + host_port = self.ci.get_host() + ":" + self.ci.get_port() + defs.s1.replace_on_server(self.ci) + + self.ci.get_server_defs() + assert len(list(self.ci.get_defs().s1)) == 0, "Expected 0 family: \n" + str( + self.ci.get_defs() + ) + assert ( + self.ci.get_defs().s1.get_dstate() == DState.suspended + ), "Expected node to be suspended:\n" + str(self.ci.get_defs()) + + def test_client_suspend(self): + print_test(self.ci, "test_client_suspend") + self.ci.delete_all() + defs = create_defs("test_client_suspend", self.ci.get_port(), self.protocol) + suite = defs.find_suite("test_client_suspend") + suite.add_variable("ECF_DUMMY_TASK", "") + + self.ci.load(defs) + self.ci.begin_all_suites() + + self.ci.suspend("/test_client_suspend") + + sync_local(self.ci) + suite = self.ci.get_defs().find_suite("test_client_suspend") + assert suite.is_suspended(), "Expected to find suite suspended" + + assert self.ci.query("dstate", suite.get_abs_node_path()) == "suspended", ( + "Expected to find suite dstate suspended but found: " + res + ) + assert self.ci.query("state", suite.get_abs_node_path()) == "queued", ( + "Expected to find suite state queued but found: " + res + ) + + def test_client_suspend_multiple_paths(self): + print_test(self.ci, "test_client_suspend_multiple_paths") + self.ci.delete_all() + defs = create_defs( + "test_client_suspend_multiple_paths", self.ci.get_port(), self.protocol + ) + suite = defs.find_suite("test_client_suspend_multiple_paths") + suite.add_variable("ECF_DUMMY_TASK", "") + + self.ci.load(defs) + self.ci.begin_all_suites() + + path_list = [ + "/test_client_suspend_multiple_paths/f1/t1", + "/test_client_suspend_multiple_paths/f1/t2", + ] + self.ci.suspend(path_list) + + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node( + "/test_client_suspend_multiple_paths/f1/t1" + ) + task_t2 = self.ci.get_defs().find_abs_node( + "/test_client_suspend_multiple_paths/f1/t2" + ) + assert task_t1.is_suspended(), "Expected to find task t1 to be suspended" + assert task_t2.is_suspended(), "Expected to find task t2 to be suspended" + + def test_client_resume(self): + print_test(self.ci, "test_client_resume") + self.ci.delete_all() + defs = create_defs("test_client_resume", self.ci.get_port(), self.protocol) + suite = defs.find_suite("test_client_resume") + suite.add_variable("ECF_DUMMY_TASK", "") + + self.ci.load(defs) + self.ci.begin_all_suites() + + self.ci.suspend("/test_client_resume") + sync_local(self.ci) + suite = self.ci.get_defs().find_suite("test_client_resume") + assert suite.is_suspended(), "Expected to find suite suspended" + + self.ci.resume("/test_client_resume") + sync_local(self.ci) + suite = self.ci.get_defs().find_suite("test_client_resume") + assert not suite.is_suspended(), "Expected to find suite resumed" + + def test_client_resume_multiple_paths(self): + print_test(self.ci, "test_client_resume_multiple_paths") + self.ci.delete_all() + defs = create_defs( + "test_client_resume_multiple_paths", self.ci.get_port(), self.protocol + ) + suite = defs.find_suite("test_client_resume_multiple_paths") + suite.add_variable("ECF_DUMMY_TASK", "") + + self.ci.load(defs) + self.ci.begin_all_suites() + + path_list = [ + "/test_client_resume_multiple_paths/f1/t1", + "/test_client_resume_multiple_paths/f1/t2", + ] + self.ci.suspend(path_list) + + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node( + "/test_client_resume_multiple_paths/f1/t1" + ) + task_t2 = self.ci.get_defs().find_abs_node( + "/test_client_resume_multiple_paths/f1/t2" + ) + assert task_t1.is_suspended(), "Expected to find task t1 to be suspended" + assert task_t2.is_suspended(), "Expected to find task t2 to be suspended" + + self.ci.resume(path_list) + sync_local(self.ci) + task_t1 = self.ci.get_defs().find_abs_node( + "/test_client_resume_multiple_paths/f1/t1" + ) + task_t2 = self.ci.get_defs().find_abs_node( + "/test_client_resume_multiple_paths/f1/t2" + ) + assert not task_t1.is_suspended(), "Expected to find task t1 to be resumed" + assert not task_t2.is_suspended(), "Expected to find task t2 to be resumed" + + def test_client_delete_node(self): + print_test(self.ci, "test_client_delete_node") + self.ci.delete_all() + defs = create_defs("test_client_delete_node", self.ci.get_port(), self.protocol) + + task_vec = defs.get_all_tasks() + assert len(task_vec) > 0, "Expected some tasks but found none:\n" + str(defs) + + self.ci.load(defs) + sync_local(self.ci) + for task in task_vec: + node = self.ci.get_defs().find_abs_node(task.get_abs_node_path()) + assert node is not None, ( + "Expected to find task " + + task.get_abs_node_path() + + ":\n" + + str(self.ci.get_defs()) + ) + + for task in task_vec: + self.ci.delete(task.get_abs_node_path()) + + sync_local(self.ci) + for task in task_vec: + node = self.ci.get_defs().find_abs_node(task.get_abs_node_path()) + assert node is None, ( + "Expected not to find task " + + task.get_abs_node_path() + + " as it should have been deleted:\n" + + str(self.ci.get_defs()) + ) + + def test_client_delete_node_multiple_paths(self): + print_test(self.ci, "test_client_delete_node_multiple_paths") + self.ci.delete_all() + defs = create_defs( + "test_client_delete_node_multiple_paths", self.ci.get_port(), self.protocol + ) + + task_vec = defs.get_all_tasks() + assert len(task_vec) > 0, "Expected some tasks but found none:\n" + str(defs) + + paths = [] + for task in task_vec: + paths.append(task.get_abs_node_path()) + + self.ci.load(defs) + + sync_local(self.ci) + for task in task_vec: + node = self.ci.get_defs().find_abs_node(task.get_abs_node_path()) + assert node is not None, ( + "Expected to find task " + + task.get_abs_node_path() + + ":\n" + + str(self.ci.get_defs()) + ) + + self.ci.delete(paths) + + sync_local(self.ci) + for task in task_vec: + node = self.ci.get_defs().find_abs_node(task.get_abs_node_path()) + assert node is None, ( + "Expected not to find task " + + task.get_abs_node_path() + + " as it should have been deleted:\n" + + str(self.ci.get_defs()) + ) + + def test_client_archive_and_restore(self): + suite_name = "test_client_archive_and_restore" + print_test(self.ci, suite_name) + self.ci.delete_all() + defs = create_defs(suite_name, self.ci.get_port(), self.protocol) + suite = defs.find_suite(suite_name) + suite_path = suite.get_abs_node_path() + + self.ci.restart_server() + self.ci.load(defs) + + sync_local(self.ci) # get the changes, synced with local defs + node_vec = self.ci.get_defs().get_all_nodes() + assert len(node_vec) == 4, "Expected 4 nodes, but found " + str(len(node_vec)) + + self.ci.archive(suite_path) + sync_local(self.ci) # get the changes, synced with local defs + node_vec = self.ci.get_defs().get_all_nodes() + assert len(node_vec) == 1, "Expected 1 nodes, but found " + str(len(node_vec)) + the_suite = self.ci.get_defs().find_suite(suite_name) + assert the_suite is not None, "Expected to find suite" + assert the_suite.get_flag().is_set( + FlagType.archived + ), " expected archive flag to be set" + + self.ci.restore(suite_path) + sync_local(self.ci) # get the changes, synced with local defs + node_vec = self.ci.get_defs().get_all_nodes() + assert len(node_vec) == 4, "Expected 4 nodes, but found " + str(len(node_vec)) + the_restored_suite = self.ci.get_defs().find_suite(suite_name) + assert the_restored_suite is not None, "Expected to find suite" + assert the_restored_suite.get_flag().is_set( + FlagType.restored + ), " expected restored flag to be set" + assert not the_restored_suite.get_flag().is_set( + FlagType.archived + ), "expected archive flag to be cleared" + + def test_client_check(self): + print_test(self.ci, "test_client_check") + self.ci.delete_all() + + defs = Defs() + defs.add_extern("/a/b/c/d") + defs.add_extern("/a/b/c/d/e:event") + defs.add_extern("/a/b/c/d/e:meter") + defs.add_extern("/made/up/redundant/extren") + defs.add_extern("/made/up/redundant/extren") + defs.add_extern("/limits:c1a") + defs.add_extern("/limits:c1a") + defs.add_extern("fred") + defs.add_extern("limits:hpcd") + defs.add_extern("/suiteName:sg1") + defs.add_extern("/obs/limits:hpcd") + suite = defs.add_suite("extern") + family_f1 = suite.add_family("f1") + family_f1.add_task("p").add_trigger("/a/b/c/d == complete") # extern path + family_f1.add_task("q").add_trigger( + "/a/b/c/d/e:event == set" + ) # extern event path + family_f1.add_task("r").add_trigger( + "/a/b/c/d/e:meter le 30" + ) # extern meter path + + suite.add_inlimit("c1a", "/limits") + suite.add_inlimit("fred") + + family_anon = suite.add_family("anon") + family_anon.add_inlimit("hpcd", "limits") + family_anon.add_task("t1").add_inlimit("sg1", "/suiteName") + family_anon.add_task("t2").add_inlimit("hpcd", "/obs/limits") + family_anon.add_task("t3").add_inlimit("c1a", "/limits") + + # CLIENT side check + client_check = defs.check() + assert len(client_check) == 0, ( + "Expected clean defs check due to externs but found:\n" + + client_check + + "\n" + + str(defs) + ) + + # SERVER side check + self.ci.load(defs) + server_check = self.ci.check( + "" + ) # empty string means check the whole defs, otherwise a node path can be specified. + # print(server_check) + assert ( + len(server_check) > 0 + ), "Expected defs to fail, since no externs in server " + + def test_client_check_defstatus(self): + print_test(self.ci, "test_client_check_defstatus") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs("test_client_check_defstatus", port, self.protocol) + + # stop defs form running when begin is called. + suite = defs.find_suite("test_client_check_defstatus") + suite.add_defstatus(DState.suspended) + + t1 = "/test_client_check_defstatus/f1/t1" + t2 = "/test_client_check_defstatus/f1/t2" + task_t1 = defs.find_abs_node(t1) + task_t1.add_defstatus(DState.suspended) + + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + self.ci.begin_all_suites() + + sync_local(self.ci) # get the changes, synced with local defs + # print(ci.get_defs()) + task_t1 = self.ci.get_defs().find_abs_node(t1) + task_t2 = self.ci.get_defs().find_abs_node(t2) + assert task_t1 is not None, "Could not find t1" + assert task_t2 is not None, "Could not find t2" + + assert ( + task_t1.get_state() == State.queued + ), "Expected state queued but found " + str(task_t1.get_state()) + assert task_t2.get_state() == State.queued, "Expected state queued " + str( + task_t2.get_state() + ) + + assert ( + task_t1.get_dstate() == DState.suspended + ), "Expected state suspended but found " + str(task_t1.get_state()) + assert ( + task_t2.get_dstate() == DState.queued + ), "Expected state queued but found " + str(task_t2.get_state()) + + dir_to_remove = Test.ecf_home(port) + "/" + "test_client_check_defstatus" + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_client_stats(self): + print_test(self.ci, "test_client_stats") + out = CustomStdOut() + with out: + stats = self.ci.stats() + assert "Statistics" in stats, "Expected 'Statistics' in the response" + assert ( + "Statistics" in out.value() + ), "Expected 'Statistics' in the captured output" + + def test_client_stats_with_stdout(self): + print_test(self.ci, "test_client_stats_with_stdout") + out = CustomStdOut() + with out: + stats = self.ci.stats(True) + assert "Statistics" in stats, "Expected 'Statistics' in the response" + assert ( + "Statistics" in out.value() + ), "Expected 'Statistics' in the captured output" + + def test_client_stats_without_stdout(self): + print_test(self.ci, "test_client_stats_without_stdout") + out = CustomStdOut() + with out: + stats = self.ci.stats(False) + assert "Statistics" in stats, "Expected 'Statistics' in the response" + assert not out.value(), "No captured output expected, but found: " + out.value() + + def test_client_stats_reset(self): + print_test(self.ci, "test_client_stats_reset") + self.ci.stats_reset() + stats = self.ci.stats() + assert "Statistics" in stats, "Expected 'Statistics' in the response" -def test_client_delete_node(ci, protocol): - print_test(ci, "test_client_delete_node") - ci.delete_all() - defs = create_defs("test_client_delete_node", ci.get_port(), protocol) + def test_client_debug_server_on_off(self): + print_test(self.ci, "test_client_debug_server_on_off") + self.ci.debug_server_on() # writes to standard out + self.ci.debug_server_off() + + def test_ECFLOW_189(self): + print_test(self.ci, "test_ECFLOW_189") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs("test_ECFLOW_189", port, self.protocol) + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + + self.ci.suspend("/test_ECFLOW_189") + self.ci.suspend("/test_ECFLOW_189/f1/t1") + self.ci.suspend("/test_ECFLOW_189/f1/t2") + + self.ci.begin_all_suites() + + sync_local(self.ci) # get the changes, synced with local defs + # print(ci.get_defs()) + task_t1 = self.ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t1") + task_t2 = self.ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t2") + assert task_t1 is not None, "Could not find /test_ECFLOW_189/f1/t1" + assert task_t2 is not None, "Could not find /test_ECFLOW_189/f1/t2" + + assert ( + task_t1.get_state() == State.queued + ), "Expected state queued but found " + str(task_t1.get_state()) + assert ( + task_t2.get_state() == State.queued + ), "Expected state queued but found " + str(task_t2.get_state()) + assert ( + task_t1.get_dstate() == DState.suspended + ), "Expected state suspended but found " + str(task_t1.get_dstate()) + assert ( + task_t2.get_dstate() == DState.suspended + ), "Expected state suspended but found " + str(task_t2.get_dstate()) + + # ok now resume t1/t2, they should remain queued, since the Suite is still suspended + self.ci.resume("/test_ECFLOW_189/f1/t1") + self.ci.resume("/test_ECFLOW_189/f1/t2") - task_vec = defs.get_all_tasks(); - assert len(task_vec) > 0, "Expected some tasks but found none:\n" + str(defs) - - ci.load(defs) - sync_local(ci); - for task in task_vec: - node = ci.get_defs().find_abs_node(task.get_abs_node_path()) - assert node is not None, "Expected to find task " + task.get_abs_node_path() + ":\n" + str(ci.get_defs()) - - for task in task_vec: - ci.delete(task.get_abs_node_path()) - - sync_local(ci); - for task in task_vec: - node = ci.get_defs().find_abs_node(task.get_abs_node_path()) - assert node is None, "Expected not to find task " + task.get_abs_node_path() + " as it should have been deleted:\n" + str( - ci.get_defs()) - - -def test_client_delete_node_multiple_paths(ci, protocol): - print_test(ci, "test_client_delete_node_multiple_paths") - ci.delete_all() - defs = create_defs("test_client_delete_node_multiple_paths", ci.get_port(), protocol) - - task_vec = defs.get_all_tasks(); - assert len(task_vec) > 0, "Expected some tasks but found none:\n" + str(defs) - - paths = [] - for task in task_vec: - paths.append(task.get_abs_node_path()) - - ci.load(defs) - - sync_local(ci); - for task in task_vec: - node = ci.get_defs().find_abs_node(task.get_abs_node_path()) - assert node is not None, "Expected to find task " + task.get_abs_node_path() + ":\n" + str(ci.get_defs()) - - ci.delete(paths) - - sync_local(ci); - for task in task_vec: - node = ci.get_defs().find_abs_node(task.get_abs_node_path()) - assert node is None, "Expected not to find task " + task.get_abs_node_path() + " as it should have been deleted:\n" + str( - ci.get_defs()) - - -def test_client_archive_and_restore(ci, protocol): - suite_name = "test_client_archive_and_restore" - print_test(ci, suite_name) - ci.delete_all() - defs = create_defs(suite_name, ci.get_port(), protocol) - suite = defs.find_suite(suite_name) - suite_path = suite.get_abs_node_path(); - - ci.restart_server() - ci.load(defs) - - sync_local(ci) # get the changes, synced with local defs - node_vec = ci.get_defs().get_all_nodes() - assert len(node_vec) == 4, "Expected 4 nodes, but found " + str(len(node_vec)) - - ci.archive(suite_path) - sync_local(ci) # get the changes, synced with local defs - node_vec = ci.get_defs().get_all_nodes() - assert len(node_vec) == 1, "Expected 1 nodes, but found " + str(len(node_vec)) - the_suite = ci.get_defs().find_suite(suite_name) - assert the_suite is not None, "Expected to find suite" - assert the_suite.get_flag().is_set(FlagType.archived), " expected archive flag to be set" - - ci.restore(suite_path) - sync_local(ci) # get the changes, synced with local defs - node_vec = ci.get_defs().get_all_nodes() - assert len(node_vec) == 4, "Expected 4 nodes, but found " + str(len(node_vec)) - the_restored_suite = ci.get_defs().find_suite(suite_name) - assert the_restored_suite is not None, "Expected to find suite" - assert the_restored_suite.get_flag().is_set(FlagType.restored), " expected restored flag to be set" - assert not the_restored_suite.get_flag().is_set(FlagType.archived), "expected archive flag to be cleared" - - -def test_client_check_defstatus(ci, protocol): - print_test(ci, "test_client_check_defstatus") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_client_check_defstatus", port, protocol) - - # stop defs form running when begin is called. - suite = defs.find_suite("test_client_check_defstatus") - suite.add_defstatus(DState.suspended) - - t1 = "/test_client_check_defstatus/f1/t1" - t2 = "/test_client_check_defstatus/f1/t2" - task_t1 = defs.find_abs_node(t1) - task_t1.add_defstatus(DState.suspended) - - defs.generate_scripts(); - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - ci.begin_all_suites() - - sync_local(ci) # get the changes, synced with local defs - # print(ci.get_defs()) - task_t1 = ci.get_defs().find_abs_node(t1) - task_t2 = ci.get_defs().find_abs_node(t2) - assert task_t1 is not None, "Could not find t1" - assert task_t2 is not None, "Could not find t2" - - assert task_t1.get_state() == State.queued, "Expected state queued but found " + str(task_t1.get_state()) - assert task_t2.get_state() == State.queued, "Expected state queued " + str(task_t2.get_state()) - - assert task_t1.get_dstate() == DState.suspended, "Expected state suspended but found " + str(task_t1.get_state()) - assert task_t2.get_dstate() == DState.queued, "Expected state queued but found " + str(task_t2.get_state()) - - dir_to_remove = Test.ecf_home(port) + "/" + "test_client_check_defstatus" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_ECFLOW_189(ci, protocol): - # Bug, when a node is resumed it ignored holding dependencies higher up the tree. - # i.e Previously when we resumed a node, it ignored trigger/time/node state, dependencies higher up the tree - print_test(ci, "test_ECFLOW_189") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_ECFLOW_189", port, protocol) - defs.generate_scripts(); - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - - ci.suspend("/test_ECFLOW_189") - ci.suspend("/test_ECFLOW_189/f1/t1") - ci.suspend("/test_ECFLOW_189/f1/t2") - - ci.begin_all_suites() - - sync_local(ci) # get the changes, synced with local defs - # print(ci.get_defs()) - task_t1 = ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t1") - task_t2 = ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t2") - assert task_t1 is not None, "Could not find /test_ECFLOW_189/f1/t1" - assert task_t2 is not None, "Could not find /test_ECFLOW_189/f1/t2" - - assert task_t1.get_state() == State.queued, "Expected state queued but found " + str(task_t1.get_state()) - assert task_t2.get_state() == State.queued, "Expected state queued but found " + str(task_t2.get_state()) - assert task_t1.get_dstate() == DState.suspended, "Expected state suspended but found " + str(task_t1.get_dstate()) - assert task_t2.get_dstate() == DState.suspended, "Expected state suspended but found " + str(task_t2.get_dstate()) - - # ok now resume t1/t2, they should remain queued, since the Suite is still suspended - ci.resume("/test_ECFLOW_189/f1/t1") - ci.resume("/test_ECFLOW_189/f1/t2") - - time.sleep(3) - sync_local(ci) # get the changes, synced with local defs - # print(ci.get_defs()) - task_t1 = ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t1") - task_t2 = ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t2") - assert task_t1.get_state() == State.queued, "Expected state queued but found " + str(task_t1.get_state()) - assert task_t2.get_state() == State.queued, "Expected state queued but found " + str(task_t2.get_state()) - assert task_t1.get_dstate() == DState.queued, "Expected state queued but found " + str(task_t1.get_dstate()) - assert task_t2.get_dstate() == DState.queued, "Expected state queued but found " + str(task_t2.get_dstate()) - - dir_to_remove = Test.ecf_home(port) + "/" + "test_ECFLOW_189" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_ECFLOW_1761(ci): - print_test(ci, "test_ECFLOW_1761") - print("# get log: last 10 lines") - ci.get_log(10) - - ci_defs = ci.get_defs() - task = ci_defs.get_all_nodes()[0] # take first task - - # print(ci.edit_script_edit(task.get_abs_node_path()) - - # print(ci.edit_script_preprocess(task.get_abs_node_path())) - - # ci.edit_script_submit( - # task.get_abs_node_path(), - # [], # name-value variables list - # [], # lines (script) - # False, # True, $ alias - # False) # run - - -def test_ECFLOW_199(ci, protocol): - # Test ClientInvoker::changed_node_paths - print_test(ci, "test_ECFLOW_199") - ci.delete_all() - port = ci.get_port() - defs = create_defs("test_ECFLOW_199", port, protocol) - defs.generate_scripts(); - msg = defs.check_job_creation(verbose=True) - assert len(msg) == 0, msg - - ci.restart_server() - ci.load(defs) - - ci.suspend("/test_ECFLOW_199") - ci.suspend("/test_ECFLOW_199/f1/t1") - ci.suspend("/test_ECFLOW_199/f1/t2") - - ci.begin_all_suites() - - sync_local(ci) # get the changes, synced with local defs - # print(ci.get_defs()) - assert len(list( - ci.changed_node_paths)) == 0, "Expected first call to sync_local, to have no changed paths but found " + str( - len(list(ci.changed_node_paths))) - - # ok now resume t1/t2, they should remain queued, since the Suite is still suspended - # Note: ECFLOW-1512, we may get additional paths.( i.e. suite path) due to changes in suite calendar - ci.resume("/test_ECFLOW_199/f1/t1") - sync_local(ci) - found_path = False - for path in ci.changed_node_paths: - print(" changed node path " + path); - if path == "/test_ECFLOW_199/f1/t1": - found_path = True - assert found_path, "Expected '/test_ECFLOW_199/f1/t1' in list of changed paths" - - ci.resume("/test_ECFLOW_199/f1/t2") - sync_local(ci) - found_path = False - for path in ci.changed_node_paths: - print(" changed node path " + path); - if path == "/test_ECFLOW_199/f1/t2": - found_path = True - assert found_path, "Expected '/test_ECFLOW_199/f1/t2' in list of changed paths" - - dir_to_remove = Test.ecf_home(port) + "/" + "test_ECFLOW_199" - shutil.rmtree(dir_to_remove, ignore_errors=True) - - -def test_client_ch_with_drops_handles(ci, protocol): - print_test(ci, "test_client_ch_with_drops_handles") - try: - port = ci.get_port() - print(" test using with but without register, handle should be zero, ie do nothing") - with Client("localhost", port) as local_ci: - local_ci.debug(True) - if protocol == Test.Protocol.HTTP: - local_ci.enable_http() - assert local_ci.ch_handle() == 0, "Expected handle to be zero" - local_ci.ch_suites() - print(" Test Client with register, should drop handle ") - with Client("localhost", port) as local_ci: - local_ci.debug(True) - if protocol == Test.Protocol.HTTP: - local_ci.enable_http() - local_ci.ch_register(True, ['s1', 's2']) - # print("Handle:",local_ci .ch_handle()) - local_ci.ch_suites() - # raise RuntimeError("xxx") # check exeption is still caught - except RuntimeError as e: - print("Exception", e) - print("after with:") - # really need a way to get hold of the suites, via python api. - ci.ch_suites() # should be empty - - -def do_tests(ci, protocol): - test_version(ci) - PrintStyle.set_style(Style.STATE) # show node state - - test_client_get_server_defs(ci, protocol) - test_client_new_log(ci) - test_client_clear_log(ci) - test_client_log_msg(ci) - - test_client_restart_server(ci) - test_client_halt_server(ci) - test_client_shutdown_server(ci) - - test_client_load_in_memory_defs(ci, protocol) - test_client_load_from_disk(ci, protocol) - test_client_checkpt(ci, protocol) - test_client_restore_from_checkpt(ci, protocol) - - test_client_reload_wl_file(ci) - - test_client_run(ci, protocol) - test_client_run_with_multiple_paths(ci, protocol) - test_client_requeue(ci, protocol) - test_client_requeue_with_multiple_paths(ci, protocol) - test_client_free_dep(ci, protocol) - - test_client_suites(ci, protocol) - test_client_ch_with_drops_handles(ci, protocol) - test_client_ch_suites(ci) - test_client_ch_register(ci) - test_client_ch_drop(ci) - test_client_ch_drop_user(ci) - test_client_ch_add(ci) - test_client_ch_auto_add(ci) - test_client_ch_remove(ci) - - test_client_get_file(ci) - # test_client_plug(ci) - test_client_alter_sort(ci, protocol) - test_client_alter_sort_defs(ci, protocol) - test_client_alter_add(ci, protocol) - test_client_alter_delete(ci, protocol) - test_client_alter_change(ci, protocol) - test_client_alter_flag(ci, protocol) - - test_client_force(ci, protocol) - test_client_replace(ci, False, protocol) - test_client_replace(ci, True, protocol) - test_node_replace(ci) - - # test_client_kill(ci) - # test_client_status(ci) - # test_client_order(ci) - # test_client_group(ci) - test_client_suspend(ci, protocol) - test_client_suspend_multiple_paths(ci, protocol) - test_client_resume(ci, protocol) - test_client_resume_multiple_paths(ci, protocol) - test_client_delete_node(ci, protocol) - test_client_delete_node_multiple_paths(ci, protocol) - test_client_archive_and_restore(ci, protocol) - - test_client_check(ci) - test_client_check_defstatus(ci, protocol) - - test_client_stats(ci) - test_client_stats_with_stdout(ci) - test_client_stats_without_stdout(ci) - test_client_stats_reset(ci) - test_client_debug_server_on_off(ci) - - test_ECFLOW_189(ci, protocol) - test_ECFLOW_199(ci, protocol) - test_ECFLOW_1761(ci) - - -def launch_tests(ci, protocol): - server_version = ci.server_version() - print("Running ecflow server version " + server_version) - print("Running ecflow client version " + ci.version()) - assert ci.version() == server_version, " Client version not same as server version" - - # test with sync_local - do_tests(ci, protocol) - - # test with auto sync - ci.set_auto_sync(True) - do_tests(ci, protocol) - - print("All Tests pass ======================================================================") - - -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) - - # server independent tests - test_set_host_port(); - - # Run tests using ecFlow server (using custom TCP/IP protocol) - with Test.Server(Test.Protocol.CUSTOM) as ctx: - ci = ctx[0] - protocol = ctx[1] - launch_tests(ci, protocol) - - # Run tests using ecFlow server (using HTTP protocol) - with Test.Server(Test.Protocol.HTTP) as ctx: - ci = ctx[0] - protocol = ctx[1] - launch_tests(ci, protocol) + time.sleep(3) + sync_local(self.ci) # get the changes, synced with local defs + # print(ci.get_defs()) + task_t1 = self.ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t1") + task_t2 = self.ci.get_defs().find_abs_node("/test_ECFLOW_189/f1/t2") + assert ( + task_t1.get_state() == State.queued + ), "Expected state queued but found " + str(task_t1.get_state()) + assert ( + task_t2.get_state() == State.queued + ), "Expected state queued but found " + str(task_t2.get_state()) + assert ( + task_t1.get_dstate() == DState.queued + ), "Expected state queued but found " + str(task_t1.get_dstate()) + assert ( + task_t2.get_dstate() == DState.queued + ), "Expected state queued but found " + str(task_t2.get_dstate()) + + dir_to_remove = Test.ecf_home(port) + "/" + "test_ECFLOW_189" + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_ECFLOW_199(self): + print_test(self.ci, "test_ECFLOW_199") + self.ci.delete_all() + port = self.ci.get_port() + defs = create_defs("test_ECFLOW_199", port, self.protocol) + defs.generate_scripts() + msg = defs.check_job_creation(verbose=True) + assert len(msg) == 0, msg + + self.ci.restart_server() + self.ci.load(defs) + + self.ci.suspend("/test_ECFLOW_199") + self.ci.suspend("/test_ECFLOW_199/f1/t1") + self.ci.suspend("/test_ECFLOW_199/f1/t2") + + self.ci.begin_all_suites() + + sync_local(self.ci) # get the changes, synced with local defs + # print(ci.get_defs()) + assert len(list(self.ci.changed_node_paths)) == 0, ( + "Expected first call to sync_local, to have no changed paths but found " + + str(len(list(self.ci.changed_node_paths))) + ) + + # ok now resume t1/t2, they should remain queued, since the Suite is still suspended + # Note: ECFLOW-1512, we may get additional paths.( i.e. suite path) due to changes in suite calendar + self.ci.resume("/test_ECFLOW_199/f1/t1") + sync_local(self.ci) + found_path = False + for path in self.ci.changed_node_paths: + print(" changed node path " + path) + if path == "/test_ECFLOW_199/f1/t1": + found_path = True + assert found_path, "Expected '/test_ECFLOW_199/f1/t1' in list of changed paths" + + self.ci.resume("/test_ECFLOW_199/f1/t2") + sync_local(self.ci) + found_path = False + for path in self.ci.changed_node_paths: + print(" changed node path " + path) + if path == "/test_ECFLOW_199/f1/t2": + found_path = True + assert found_path, "Expected '/test_ECFLOW_199/f1/t2' in list of changed paths" + + dir_to_remove = Test.ecf_home(port) + "/" + "test_ECFLOW_199" + shutil.rmtree(dir_to_remove, ignore_errors=True) + + def test_ECFLOW_1761(self): + print_test(self.ci, "test_ECFLOW_1761") + print("# get log: last 10 lines") + self.ci.get_log(10) + + ci_defs = self.ci.get_defs() + task = ci_defs.get_all_nodes()[0] # take first task diff --git a/libs/pyext/test/py_s_TestPythonChildApi.py b/libs/pyext/test/py_s_TestPythonChildApi.py index e92b880e8..70f687fc5 100644 --- a/libs/pyext/test/py_s_TestPythonChildApi.py +++ b/libs/pyext/test/py_s_TestPythonChildApi.py @@ -12,36 +12,46 @@ import time import os -import pwd import sys # determine python version -from datetime import datetime import shutil # used to remove directory tree -from ecflow import Defs, Clock, DState, Event, Style, State, PrintStyle, File, Client, SState, File, debug_build +import pytest + +from ecflow import ( + Defs, + DState, + Event, + Style, + State, + PrintStyle, + File, +) import ecflow_test_util as Test -def ecf_includes(): return File.source_dir() + "/libs/pyext/test/data/python_includes" +def ecf_includes(): + return File.source_dir() + "/libs/pyext/test/data/python_includes" def create_defs(name, port, protocol): defs = Defs() suite_name = name - if len(suite_name) == 0: suite_name = "s1" - suite = defs.add_suite(suite_name); + if len(suite_name) == 0: + suite_name = "s1" + suite = defs.add_suite(suite_name) - ecfhome = Test.ecf_home(port); - suite.add_variable("ECF_HOME", ecfhome); - suite.add_variable("ECF_INCLUDE", ecf_includes()); + ecfhome = Test.ecf_home(port) + suite.add_variable("ECF_HOME", ecfhome) + suite.add_variable("ECF_INCLUDE", ecf_includes()) protocol_option = "True" if protocol == Test.Protocol.HTTP else "False" - suite.add_variable("ECF_USING_HTTP_BACKEND", protocol_option); + suite.add_variable("ECF_USING_HTTP_BACKEND", protocol_option) # Setup the job command, based on the current environment job_cmd = "" - if 'PYTHONPATH' in os.environ: - job_cmd = "export PYTHONPATH=" + os.environ['PYTHONPATH'] + ";" - if 'LD_LIBRARY_PATH' in os.environ: - job_cmd += "export LD_LIBRARY_PATH=" + os.environ['LD_LIBRARY_PATH'] + ";" + if "PYTHONPATH" in os.environ: + job_cmd = "export PYTHONPATH=" + os.environ["PYTHONPATH"] + ";" + if "LD_LIBRARY_PATH" in os.environ: + job_cmd += "export LD_LIBRARY_PATH=" + os.environ["LD_LIBRARY_PATH"] + ";" # Use the current python interpreter to run the job job_cmd += f"{sys.executable} %ECF_JOB% 1> %ECF_JOBOUT% 2>&1" @@ -57,13 +67,16 @@ def create_defs(name, port, protocol): family.add_task("t2") # test wait family.add_task("t3").add_trigger( - "t1:q1 >= 3 and t1:event_fred and t1:event_set == clear") # wait on queue q1 and events + "t1:q1 >= 3 and t1:event_fred and t1:event_set == clear" + ) # wait on queue q1 and events family.add_task("t4").add_trigger( - "t1:name1 == 1 and t1:name2 == 2 and t1:name3 == 3 and t1:name4 == 4") # test ECFLOW-1573 + "t1:name1 == 1 and t1:name2 == 2 and t1:name3 == 3 and t1:name4 == 4" + ) # test ECFLOW-1573 defs.auto_add_externs( - True) # because variable name1,name2,name3,name4 are not added until t1 is active.(i.e. runtime) - return defs; + True + ) # because variable name1,name2,name3,name4 are not added until t1 is active.(i.e. runtime) + return defs def wait_for_suite_to_complete(ci, suite_name): @@ -73,21 +86,48 @@ def wait_for_suite_to_complete(ci, suite_name): count += 1 ci.sync_local() # get the changes, synced with local defs suite = ci.get_defs().find_suite(suite_name) - assert suite is not None, " Expected to find suite " + suite_name + ":\n" + str(ci.get_defs()) + assert suite is not None, ( + " Expected to find suite " + suite_name + ":\n" + str(ci.get_defs()) + ) if suite.get_state() == State.complete: - break; + break if suite.get_state() == State.aborted: - print(ci.get_defs()); + print(ci.get_defs()) assert False, " Suite aborted \n" time.sleep(2) if count > 20: - assert False, suite_name + " aborted after " + str(count) + " loops, printing defs:\n" + str(ci.get_defs()) + assert False, ( + suite_name + + " aborted after " + + str(count) + + " loops, printing defs:\n" + + str(ci.get_defs()) + ) ci.log_msg("Looped " + str(count) + " times") -def test_python_child_api(ci, protocol): +@pytest.fixture( + params=[Test.Protocol.CUSTOM, Test.Protocol.HTTP], ids=["custom", "http"] +) +def protocol(request): + return request.param + +@pytest.fixture +def server(protocol): + with Test.Server(protocol) as ctx: + yield ctx[0], ctx[1] + + +def test_python_child_api(server): + ci, protocol = server + server_version = ci.server_version() + print("Running ecflow server version " + server_version) + print("Running ecflow client version " + ci.version()) + assert ci.version() == server_version, "Client version not same as server version" + + PrintStyle.set_style(Style.STATE) # show node state suite_name = "test_python_child_api" host = ci.get_host() port = ci.get_port() @@ -97,7 +137,8 @@ def test_python_child_api(ci, protocol): family_dir = test_home + "/f1" # Make the directory tree for the suite - if not os.path.exists(family_dir): os.makedirs(family_dir) + if not os.path.exists(family_dir): + os.makedirs(family_dir) # Dump some information print("\n" + suite_name + " " + host + ":" + str(port)) @@ -111,15 +152,19 @@ def test_python_child_api(ci, protocol): defs = create_defs(suite_name, port, protocol) suite = defs.find_suite(suite_name) suite.add_defstatus(DState.suspended) - defs.save_as_defs(os.path.join(test_home, suite_name + ".def")) # ...//.def + defs.save_as_defs( + os.path.join(test_home, suite_name + ".def") + ) # ...//.def # Set the log file to a location inside the test - ci.new_log(os.path.join(test_home, suite_name + ".log")) # ...//.log + ci.new_log( + os.path.join(test_home, suite_name + ".log") + ) # ...//.log server_version = ci.server_version() if ci.version() != server_version: - assert False, 'Client and server versions different' + assert False, "Client and server versions different" # Create the Task script at ...//f1/t1 file = family_dir + "/t1.ecf" @@ -147,7 +192,7 @@ def test_python_child_api(ci, protocol): assert step == '','expected /f1/t2 @@ -162,30 +207,29 @@ def test_python_child_api(ci, protocol): ci.child_wait('/{suite_name}/f1/t1 == complete') print(' Finished waiting') """ - open(file, 'w').write(contents) + open(file, "w").write(contents) print(" Created file " + file) # Create the Task script at ...//f1/t3 file = family_dir + "/t3.ecf" contents = f""" %include - + with Client() as ci: print(' Running t3.ecf') """ - open(file, 'w').write(contents) + open(file, "w").write(contents) print(" Created file " + file) # Create the Task script at ...//f1/t4 file = family_dir + "/t4.ecf" contents = """ %include - -with Client() as ci: +with Client() as ci: print(' Running t4.ecf') """ - open(file, 'w').write(contents) + open(file, "w").write(contents) print(" Created file " + file) # Start the server @@ -193,7 +237,7 @@ def test_python_child_api(ci, protocol): # Load the definitions ci.load(defs) - ci.checkpt() # store the checkpoint, useful for debugging... + ci.checkpt() # store the checkpoint, useful for debugging... # Start the merry-go-round!... ci.begin_all_suites() @@ -201,38 +245,10 @@ def test_python_child_api(ci, protocol): print(" Running the test, wait for suite to complete ...") ci.run(f"/{suite_name}", False) - wait_for_suite_to_complete(ci, suite_name); + wait_for_suite_to_complete(ci, suite_name) - ci.checkpt() # store the checkpoint, useful for debugging... + ci.checkpt() # store the checkpoint, useful for debugging... if not Test.debugging(): print(" Test OK: removing directory ", test_home) shutil.rmtree(test_home, ignore_errors=True) - - -def launch_tests(ci, protocol): - server_version = ci.server_version(); - print("Running ecflow server version " + server_version) - print("Running ecflow client version " + ci.version()) - assert ci.version() == server_version, "Client version not same as server version" - - PrintStyle.set_style(Style.STATE) # show node state - test_python_child_api(ci, protocol) - - print("\nAll Tests pass ======================================================================") - - -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) - - # Run tests using ecFlow server (using custom TCP/IP protocol) - with Test.Server(Test.Protocol.CUSTOM) as ctx: - ci = ctx[0] - protocol = ctx[1] - launch_tests(ci, protocol) - - # Run tests using ecFlow server (using HTTP protocol) - with Test.Server(Test.Protocol.HTTP) as ctx: - ci = ctx[0] - protocol = ctx[1] - launch_tests(ci, protocol) diff --git a/libs/pyext/test/py_s_TestSslSetup.py b/libs/pyext/test/py_s_TestSslSetup.py index 2037a0783..bf1f1a9d1 100644 --- a/libs/pyext/test/py_s_TestSslSetup.py +++ b/libs/pyext/test/py_s_TestSslSetup.py @@ -12,149 +12,155 @@ import os -import ecflow_test_util as Test -import inspect +import pytest +import ecflow_test_util as Test import ecflow as ecf port = 31415 host = os.uname()[1] -class Tests: - - @staticmethod - def can_setup_client_without_ssl_and_get_empty_certificate(): - Test.name_this_test() - - expected_crt = "" - - client = ecf.Client(host, port) - crt = client.get_certificate() - assert crt is expected_crt, f"Expected certificate to be '{expected_crt}', but got '{crt}'" +@pytest.fixture(autouse=True) +def isolated_ssl_directory(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + monkeypatch.setenv("ECF_SSL_DIR", str(tmp_path) + os.sep) - @staticmethod - def can_setup_client_with_ssl_with_shared_certificate_solely_based_envvars_and_ecf_ssl_empty(): - Test.name_this_test() +def test_setup_client_without_ssl_and_get_empty_certificate(): + expected_crt = "" - expected_crt = f"server.crt" + client = ecf.Client(host, port) + crt = client.get_certificate() + assert ( + crt is expected_crt + ), f"Expected certificate to be '{expected_crt}', but got '{crt}'" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "", "ECF_HOST": host, "ECF_PORT": str(port)}) as env: - client = ecf.Client() - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_with_shared_certificate_solely_based_envvars(): - Test.name_this_test() +def test_setup_client_with_ssl_with_shared_certificate_solely_based_envvars_and_ecf_ssl_empty(): + expected_crt = f"server.crt" - expected_crt = f"server.crt" - - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "1", "ECF_HOST": host, "ECF_PORT": str(port)}) as env: - client = ecf.Client() - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment( + {"ECF_SSL": "", "ECF_HOST": host, "ECF_PORT": str(port)} + ) as env: + client = ecf.Client() + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_requesting_shared_but_finding_specific_certificate_solely_based_envvars(): - Test.name_this_test() - expected_crt = f"{host}.{port}.crt" +def test_setup_client_with_ssl_with_shared_certificate_solely_based_envvars(): + expected_crt = f"server.crt" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "1", "ECF_HOST": host, "ECF_PORT": str(port)}) as env: - client = ecf.Client() - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment( + {"ECF_SSL": "1", "ECF_HOST": host, "ECF_PORT": str(port)} + ) as env: + client = ecf.Client() + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_with_specific_certificate_solely_based_envvars(): - Test.name_this_test() - expected_crt = f"{host}.{port}.crt" +def test_setup_client_with_ssl_requesting_shared_but_finding_specific_certificate_solely_based_envvars(): + expected_crt = f"{host}.{port}.crt" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "X", "ECF_HOST": host, "ECF_PORT": str(port)}) as env: - client = ecf.Client() - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment( + {"ECF_SSL": "1", "ECF_HOST": host, "ECF_PORT": str(port)} + ) as env: + client = ecf.Client() + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_with_shared_certificate_using_envvar(): - Test.name_this_test() - expected_crt = f"server.crt" +def test_setup_client_with_ssl_with_specific_certificate_solely_based_envvars(): + expected_crt = f"{host}.{port}.crt" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "1"}) as env: - client = ecf.Client(host, port) - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment( + {"ECF_SSL": "X", "ECF_HOST": host, "ECF_PORT": str(port)} + ) as env: + client = ecf.Client() + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_with_shared_certificate_using_empty_envvar(): - Test.name_this_test() - expected_crt = f"server.crt" +def test_setup_client_with_ssl_with_shared_certificate_using_envvar(): + expected_crt = f"server.crt" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": ""}) as env: - client = ecf.Client(host, port) - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment({"ECF_SSL": "1"}) as env: + client = ecf.Client(host, port) + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_with_shared_certificate_using_explicit_option(): - Test.name_this_test() - expected_crt = f"server.crt" +def test_setup_client_with_ssl_with_shared_certificate_using_empty_envvar(): + expected_crt = f"server.crt" - with Test.MockFile(expected_crt) as crt_file: + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment({"ECF_SSL": ""}) as env: client = ecf.Client(host, port) - client.enable_ssl() crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" + - @staticmethod - def can_setup_client_with_ssl_with_specific_certificate_using_envvar(): - Test.name_this_test() +def test_setup_client_with_ssl_with_shared_certificate_using_explicit_option(): + expected_crt = f"server.crt" - expected_crt = f"{host}.{port}.crt" + with Test.MockFile(expected_crt) as crt_file: + client = ecf.Client(host, port) + client.enable_ssl() + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "X"}) as env: - client = ecf.Client(host, port) - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_with_specific_certificate_using_envvar_and_explicit_option(): - Test.name_this_test() +def test_setup_client_with_ssl_with_specific_certificate_using_envvar(): + expected_crt = f"{host}.{port}.crt" - expected_crt = f"{host}.{port}.crt" + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment({"ECF_SSL": "X"}) as env: + client = ecf.Client(host, port) + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "X"}) as env: - client = ecf.Client(host, port) - client.enable_ssl() - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" - @staticmethod - def can_setup_client_with_ssl_requesting_shared_certificate_using_envvar_but_falling_back_to_specific_certificate (): - Test.name_this_test() +def test_setup_client_with_ssl_with_specific_certificate_using_envvar_and_explicit_option(): + expected_crt = f"{host}.{port}.crt" - expected_crt = f"{host}.{port}.crt" + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment({"ECF_SSL": "X"}) as env: + client = ecf.Client(host, port) + client.enable_ssl() + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" - with Test.MockFile(expected_crt) as crt_file: - with Test.MockEnvironment({"ECF_SSL": "1"}) as env: - client = ecf.Client(host, port) - crt = client.get_certificate() - assert crt.endswith(expected_crt), f"Expected certificate to end with '{expected_crt}', but got {crt}" +def test_setup_client_with_ssl_requesting_shared_certificate_using_envvar_but_falling_back_to_specific_certificate(): + expected_crt = f"{host}.{port}.crt" -if __name__ == "__main__": - Test.execute_all(Tests) + with Test.MockFile(expected_crt) as crt_file: + with Test.MockEnvironment({"ECF_SSL": "1"}) as env: + client = ecf.Client(host, port) + crt = client.get_certificate() + assert crt.endswith( + expected_crt + ), f"Expected certificate to end with '{expected_crt}', but got {crt}" diff --git a/libs/pyext/test/py_u_TestAttributeAddDelete.py b/libs/pyext/test/py_u_TestAttributeAddDelete.py index 7ad3e2d54..627b74765 100644 --- a/libs/pyext/test/py_u_TestAttributeAddDelete.py +++ b/libs/pyext/test/py_u_TestAttributeAddDelete.py @@ -8,207 +8,200 @@ # nor does it submit to any jurisdiction. # -import sys -import os import copy -import unittest # for assertItemsEqual + +import pytest + import ecflow -import ecflow_test_util as Test -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) +@pytest.fixture +def time_series(): + start = ecflow.TimeSlot(0, 0) + finish = ecflow.TimeSlot(23, 0) + incr = ecflow.TimeSlot(0, 30) + return ecflow.TimeSeries(start, finish, incr, True) + + +def _names(collection): + return [item.name() for item in collection] + - # =========================================================================== - # Defs: add, delete and sort *USER* variables, use set(a).intersection(b) to compare lists - # =========================================================================== +def test_defs_user_variable_add_delete_and_sort(): defs = ecflow.Defs() defs.add_variable("ZFRED", "/tmp/") - defs.add_variable("YECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%") + defs.add_variable( + "YECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%" + ) defs.add_variable("XECF_URL_BASE", "http://www.ecmwf.int") defs.add_variable("AECF_URL", "publications/manuals/sms") - assert len(list(defs.user_variables)) == 4, "Expected *user* 4 variable" + assert len(list(defs.user_variables)) == 4 - # sort - expected = ['AECF_URL', 'XECF_URL_BASE', 'YECF_URL_CMD', 'ZFRED'] - actual = [] - defs.sort_attributes("variable"); - for v in defs.user_variables: actual.append(v.name()) - assert actual == expected, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) + defs.sort_attributes("variable") + assert _names(defs.user_variables) == [ + "AECF_URL", + "XECF_URL_BASE", + "YECF_URL_CMD", + "ZFRED", + ] - expected = ['AECF_URL', 'XECF_URL_BASE', 'YECF_URL_CMD', 'ZFRED', 'ZZ'] - actual = [] defs.add_variable("ZZ", "x") - defs.sort_attributes(ecflow.AttrType.variable); - for v in defs.user_variables: actual.append(v.name()) - assert actual == expected, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) + defs.sort_attributes(ecflow.AttrType.variable) + assert _names(defs.user_variables) == [ + "AECF_URL", + "XECF_URL_BASE", + "YECF_URL_CMD", + "ZFRED", + "ZZ", + ] - expected = ['AA', 'AECF_URL', 'XECF_URL_BASE', 'YECF_URL_CMD', 'ZFRED', 'ZZ'] - actual = [] defs.add_variable("AA", "x") - defs.sort_attributes("all"); - for v in defs.user_variables: actual.append(v.name()) - assert actual == expected, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) + defs.sort_attributes("all") + assert _names(defs.user_variables) == [ + "AA", + "AECF_URL", + "XECF_URL_BASE", + "YECF_URL_CMD", + "ZFRED", + "ZZ", + ] - expected = ['AA', 'AECF_URL', 'BB', 'XECF_URL_BASE', 'YECF_URL_CMD', 'ZFRED', 'ZZ'] - actual = [] defs.add_variable("BB", "x") - defs.sort_attributes(ecflow.AttrType.all); - for v in defs.user_variables: actual.append(v.name()) - assert actual == expected, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) - - defs.delete_variable("ZFRED"); - assert len(list(defs.user_variables)) == 6, "Expected 6 variables since we just delete ZFRED" - defs.delete_variable(""); - assert len(list(defs.user_variables)) == 0, "Expected 0 variables since we should have deleted all" + defs.sort_attributes(ecflow.AttrType.all) + assert _names(defs.user_variables) == [ + "AA", + "AECF_URL", + "BB", + "XECF_URL_BASE", + "YECF_URL_CMD", + "ZFRED", + "ZZ", + ] + + defs.delete_variable("ZFRED") + assert len(list(defs.user_variables)) == 6 + defs.delete_variable("") + assert len(list(defs.user_variables)) == 0 a_dict = {"name": "value", "name2": "value2", "name3": "value3", "name4": "value4"} defs.add_variable(a_dict) - assert len(list(defs.user_variables)) == 4, "Expected 4 variable" - defs.delete_variable(""); - assert len(list(defs.user_variables)) == 0, "Expected 0 variable since we should have deleted all" + assert len(list(defs.user_variables)) == 4 + defs.delete_variable("") + assert len(list(defs.user_variables)) == 0 - # add a empty dictionary - a_dict = {} - defs.add_variable(a_dict) - assert len(list(defs.user_variables)) == 0, "Expected zero variables" + defs.add_variable({}) + assert len(list(defs.user_variables)) == 0 a_dict = {"name": 0, "name2": 1, "name3": 2, "name4": 3} defs.add_variable(a_dict) - assert len(list(defs.user_variables)) == 4, "Expected 4 variable" - defs.delete_variable(""); - assert len(list(defs.user_variables)) == 0, "Expected 0 variable since we should have deleted all" + assert len(list(defs.user_variables)) == 4 + defs.delete_variable("") + assert len(list(defs.user_variables)) == 0 + - # =========================================================================== - # Suite: add,delete and sort variables - # =========================================================================== +def test_suite_variable_add_delete_and_sort(): suite = ecflow.Suite("s1") suite.add_variable(ecflow.Variable("ECF_HOME", "/tmp/")) - suite.add_variable("ZZZZZZ", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%") + suite.add_variable( + "ZZZZZZ", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%" + ) suite.add_variable("YYYYY", "http://www.ecmwf.int") suite.add_variable("aaaa", "publications/manuals/sms") - assert len(list(suite.variables)) == 4, "Expected 4 variable" + assert len(list(suite.variables)) == 4 - # sort - expected = ['aaaa', 'ECF_HOME', 'YYYYY', 'ZZZZZZ'] - actual = [] - suite.sort_attributes("variable"); - suite.sort_attributes(ecflow.AttrType.variable); - for v in suite.variables: actual.append(v.name()) - assert set(expected).intersection(actual), "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) + suite.sort_attributes("variable") + suite.sort_attributes(ecflow.AttrType.variable) + actual = _names(suite.variables) + expected = ["aaaa", "ECF_HOME", "YYYYY", "ZZZZZZ"] + assert set(expected).issubset(actual) - suite.delete_variable("ECF_HOME"); - assert len(list(suite.variables)) == 3, "Expected 3 variable since we just delete ECF_HOME" - suite.delete_variable(""); - assert len(list(suite.variables)) == 0, "Expected 0 variable since we should have deleted all" + suite.delete_variable("ECF_HOME") + assert len(list(suite.variables)) == 3 + suite.delete_variable("") + assert len(list(suite.variables)) == 0 a_dict = {"name": "value", "name2": "value2", "name3": "value3", "name4": "value4"} suite.add_variable(a_dict) - assert len(list(suite.variables)) == 4, "Expected 4 variable" - suite.delete_variable(""); - assert len(list(suite.variables)) == 0, "Expected 0 variable since we should have deleted all" + assert len(list(suite.variables)) == 4 + suite.delete_variable("") + assert len(list(suite.variables)) == 0 a_dict = {"name": 1, "name2": 2, "name3": 3, "name4": 4} suite.add_variable(a_dict) - assert len(list(suite.variables)) == 4, "Expected 4 variable" - suite.delete_variable(""); - assert len(list(suite.variables)) == 0, "Expected 0 variable since we should have deleted all" + assert len(list(suite.variables)) == 4 + suite.delete_variable("") + assert len(list(suite.variables)) == 0 - # add a empty dictionary - a_dict = {} - suite.add_variable(a_dict) - assert len(list(suite.variables)) == 0, "Expected zero variables" + suite.add_variable({}) + assert len(list(suite.variables)) == 0 - # adding dictionary items that are not strings,or ints in value ,should result in a type error - expected_type_error = False - try: - a_bad_dict = {"name": "fred", "name2": 14, "name3": list(1, 2, 3), "name4": 12} - suite.add_variable(a_bad_dict) - except TypeError: - expected_type_error = True - - assert expected_type_error, "Expected Type error" - assert len(list(suite.variables)) == 0, "Expected 0 variable since we should have deleted all" + with pytest.raises(RuntimeError): + suite.add_variable( + {"name": "fred", "name2": 14, "name3": list("123"), "name4": 12} + ) + assert len(list(suite.variables)) == 0 suite.add_variable("ECF_URL_CMD", "test duplicates") - suite.add_variable("ECF_URL_CMD", "Expected warning") # expect a warning message to standard out + suite.add_variable("ECF_URL_CMD", "Expected warning") + - # =========================================================================== - # add,delete and sort limits - # =========================================================================== +def test_suite_limit_add_delete_and_sort(): + suite = ecflow.Suite("s1") suite.add_limit(ecflow.Limit("zlimitName1", 10)) suite.add_limit(ecflow.Limit("ylimitName2", 10)) suite.add_limit("xlimitName3", 10) suite.add_limit("alimitName4", 10) - assert len(list(suite.limits)) == 4, "Expected 4 Limits" - - # sort - expected = ['alimitName4', 'xlimitName3', 'ylimitName2', 'zlimitName1'] - actual = [] - suite.sort_attributes(ecflow.AttrType.limit); - suite.sort_attributes("limit"); - for v in suite.limits: actual.append(v.name()) - assert expected == actual, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) - - suite.delete_limit("zlimitName1"); - assert len(list(suite.limits)) == 3, "Expected 3 limits since we just deleted one limitName1" - suite.delete_limit(""); - assert len(list(suite.limits)) == 0, "Expected 0 limits since we just deleted all of them" - - # The following will fail, since the iterators are essentially read only, - # This is because we are using C++ vector iterators, hence we can't delete the vectors items, whilst traversing - # for limit in suite.limits: suite.delete_limit(limit.name()) - # We can get round this by copy the limits first - suite.add_limit(ecflow.Limit("limitName1", 10)) - suite.add_limit(ecflow.Limit("limitName2", 10)) - suite.add_limit("limitName3", 10) - suite.add_limit("limitName4", 10) - limit_names = [] - for limit in suite.limits: - limit_names.append(limit.name()) + assert len(list(suite.limits)) == 4 + + suite.sort_attributes(ecflow.AttrType.limit) + suite.sort_attributes("limit") + assert _names(suite.limits) == [ + "alimitName4", + "xlimitName3", + "ylimitName2", + "zlimitName1", + ] + + suite.delete_limit("zlimitName1") + assert len(list(suite.limits)) == 3 + suite.delete_limit("") + assert len(list(suite.limits)) == 0 + + for name in ["limitName1", "limitName2", "limitName3", "limitName4"]: + suite.add_limit(name, 10) + limit_names = [limit.name() for limit in suite.limits] for limit in limit_names: suite.delete_limit(limit) - assert len(list(suite.limits)) == 0, "Expected 0 Limits,since we just deleted all of them, via iteration" + assert len(list(suite.limits)) == 0 - # =========================================================================== - # Test Limit and node paths, ECFLOW-518 - # =========================================================================== + +def test_limit_increment_decrement_and_node_paths(): the_limit = ecflow.Limit("limitName1", 10) - assert the_limit.name() == "limitName1", "name not as expected" - assert the_limit.value() == 0, "Expected limit value of 0" - assert the_limit.limit() == 10, "Expected limit of 10" - assert len(list(the_limit.node_paths())) == 0, "Expected nodes which have consumed a limit to be empty" - - the_limit.increment(1, "/path1"); - assert the_limit.value() == 1, "Expected limit value of 1" - the_limit.increment(1, "/path2"); - assert the_limit.value() == 2, "Expected limit value of 2" - the_limit.increment(1, "/path3"); - assert the_limit.value() == 3, "Expected limit value of 3" - the_limit.increment(1, "/path4"); - assert the_limit.value() == 4, "Expected limit value of 4" - for path in the_limit.node_paths(): print(path) - assert len(list(the_limit.node_paths())) == 4, "expected 4 path" - the_limit.decrement(1, "/path1"); - assert the_limit.value() == 3, "Expected limit value of 3" - the_limit.decrement(1, "/path2"); - assert the_limit.value() == 2, "Expected limit value of 2" - the_limit.decrement(1, "/path3"); - assert the_limit.value() == 1, "Expected limit value of 1" - the_limit.decrement(1, "/path4"); - assert the_limit.value() == 0, "Expected limit value of 0" - assert len(list(the_limit.node_paths())) == 0, "Expected nodes which have consumed a limit to be empty" - - the_limit.increment(1, "/path1") # add same path, should only consume one token - the_limit.increment(1, "/path1") - the_limit.increment(1, "/path1") - the_limit.increment(1, "/path1") - assert len(list(the_limit.node_paths())) == 1, "expected 1 path" - assert the_limit.value() == 1, "Expected limit value of 1" - - # =========================================================================== - # add and delete inlimits - # =========================================================================== + assert the_limit.name() == "limitName1" + assert the_limit.value() == 0 + assert the_limit.limit() == 10 + assert len(list(the_limit.node_paths())) == 0 + + for i, path in enumerate(["/path1", "/path2", "/path3", "/path4"], start=1): + the_limit.increment(1, path) + assert the_limit.value() == i # verify value after each increment + assert the_limit.value() == 4 + assert len(list(the_limit.node_paths())) == 4 + + for i, path in enumerate(["/path1", "/path2", "/path3", "/path4"], start=1): + the_limit.decrement(1, path) + assert the_limit.value() == 4 - i # verify value after each decrement + assert the_limit.value() == 0 + assert len(list(the_limit.node_paths())) == 0 + + for _ in range(4): + the_limit.increment(1, "/path1") + assert len(list(the_limit.node_paths())) == 1 + assert the_limit.value() == 1 + + +def test_suite_inlimit_add_and_delete(): + suite = ecflow.Suite("s1") suite.add_inlimit(ecflow.InLimit("limitName1", "/s1/f1", 2)) suite.add_inlimit(ecflow.InLimit("limitName2", "/s1/f1", 2, True)) suite.add_inlimit("limitName3", "/s1/f1", 2) @@ -216,295 +209,228 @@ suite.add_inlimit("limitNameA", "/s1/f1", 2) suite.add_inlimit("limitNameA", "/s1/f1/a", 2) suite.add_inlimit("limitNameA", "/s1/f1/b", 2) - assert len(list(suite.inlimits)) == 7, "Expected 7 inLimits" - suite.delete_inlimit("limitName1"); - assert len(list(suite.inlimits)) == 6, "Expected 6 inlimits since we just deleted one limitName1" - suite.delete_inlimit("/s1/f1:limitNameA"); - assert len(list(suite.inlimits)) == 5, "Expected 5 inlimits since we just deleted one limitName1" - suite.delete_inlimit("/s1/f1/a:limitNameA"); - assert len(list(suite.inlimits)) == 4, "Expected 4 inlimits since we just deleted one limitName1" - suite.delete_inlimit("/s1/f1/b:limitNameA"); - assert len(list(suite.inlimits)) == 3, "Expected 3 inlimits since we just deleted one limitName1" - suite.delete_inlimit(""); - assert len(list(suite.inlimits)) == 0, "Expected 0 inlimits since we just deleted all of them" - - # =============================================================================== - # add and delete triggers and complete - # =============================================================================== + assert len(list(suite.inlimits)) == 7 + + suite.delete_inlimit("limitName1") + assert len(list(suite.inlimits)) == 6 + suite.delete_inlimit("/s1/f1:limitNameA") + assert len(list(suite.inlimits)) == 5 + suite.delete_inlimit("/s1/f1/a:limitNameA") + assert len(list(suite.inlimits)) == 4 + suite.delete_inlimit("/s1/f1/b:limitNameA") + assert len(list(suite.inlimits)) == 3 + suite.delete_inlimit("") + assert len(list(suite.inlimits)) == 0 + + +def test_task_trigger_complete_and_part_expressions(): task = ecflow.Task("task") task.add_trigger("t2 == active") task.add_complete("t2 == complete") - assert task.get_complete(), "Expected complete" - assert task.get_trigger(), "Expected trigger" - assert task.get_trigger().get_expression() == "t2 == active", "add trigger failed" - assert task.get_complete().get_expression() == "t2 == complete", "add complete failed" - task.delete_trigger(); - assert not task.get_trigger(), "Expected no trigger" - task.delete_complete(); - assert not task.get_complete(), "Expected no complete" + assert task.get_complete() + assert task.get_trigger() + assert task.get_trigger().get_expression() == "t2 == active" + assert task.get_complete().get_expression() == "t2 == complete" + task.delete_trigger() + assert not task.get_trigger() + task.delete_complete() + assert not task.get_complete() task.add_part_trigger(ecflow.PartExpression("t1 == complete")) - task.add_part_trigger(ecflow.PartExpression("t2 == active", True)) # for long and/or expressions, subsequent expr must be and/or + task.add_part_trigger(ecflow.PartExpression("t2 == active", True)) task.add_part_complete(ecflow.PartExpression("t3 == complete")) - task.add_part_complete(ecflow.PartExpression("t4 == active", False)) # for long and/or expressions, subsequent expr must be and/or - task.delete_trigger(); - assert not task.get_trigger(), "Expected no trigger" - task.delete_complete(); - assert not task.get_complete(), "Expected no complete" + task.add_part_complete(ecflow.PartExpression("t4 == active", False)) + task.delete_trigger() + assert not task.get_trigger() + task.delete_complete() + assert not task.get_complete() task.add_part_trigger("t1 == complete") - task.add_part_trigger("t2 == active", True) # for long and/or expressions, subsequent expr must be and/or + task.add_part_trigger("t2 == active", True) task.add_part_complete("t3 == complete") - task.add_part_complete("t4 == active", False) # for long and/or expressions, subsequent expr must be and/or - task.delete_trigger(); - assert not task.get_trigger(), "Expected no trigger" - task.delete_complete(); - assert not task.get_complete(), "Expected no complete" - - # =========================================================================== - # Add triggers using expressions - # =========================================================================== + task.add_part_complete("t4 == active", False) + task.delete_trigger() + assert not task.get_trigger() + task.delete_complete() + assert not task.get_complete() + + +def test_task_trigger_from_expression(): expr = ecflow.Expression("t1 == complete") task = ecflow.Task("task") task.add_trigger(expr) - assert task.get_trigger().get_expression() == "t1 == complete", "add trigger failed : " + task.get_trigger().get_expression() + assert task.get_trigger().get_expression() == "t1 == complete" + +def test_task_compound_expression_trigger(): expr = ecflow.Expression("t1 == complete") expr.add(ecflow.PartExpression("t2 == complete", True)) expr.add(ecflow.PartExpression("t3 == complete", True)) task = ecflow.Task("task") task.add_trigger(expr) - assert task.get_trigger().get_expression() == "t1 == complete AND t2 == complete AND t3 == complete", "add trigger failed : " + task.get_trigger().get_expression() + assert ( + task.get_trigger().get_expression() + == "t1 == complete AND t2 == complete AND t3 == complete" + ) - # =========================================================================== - # add,delete,find events - # =========================================================================== - task.add_event(ecflow.Event(1)); + +def test_task_event_add_find_delete(): + task = ecflow.Task("task") + task.add_event(ecflow.Event(1)) task.add_event(2) task.add_event(ecflow.Event(10, "Eventname")) task.add_event(10, "Eventname2") task.add_event("fred") - # sort - expected = ['1', '2', 'Eventname', 'Eventname2', 'fred'] - actual = [] - task.sort_attributes("event"); - task.sort_attributes(ecflow.AttrType.event); - for v in task.events: actual.append(v.name_or_number()) - assert expected == actual, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) + task.sort_attributes("event") + task.sort_attributes(ecflow.AttrType.event) + assert [e.name_or_number() for e in task.events] == [ + "1", + "2", + "Eventname", + "Eventname2", + "fred", + ] - # test find event = task.find_event("EVENT") - assert (event.empty()), "Expected to not to find event" - assert (event.name() == ""), "Expected to not to find event, number is maximum int" - assert (event.value() == 0), "Expected to not to find event" - - event = task.find_event("1"); - assert (not event.empty()), "Expected to find event" - assert (event.number() == 1), "Expected to find event 1" - assert (event.name() == ""), "Expected name to be empty" - assert (event.value() == 0), "Expected to not to find event" - - event = task.find_event("2"); - assert (not event.empty()), "Expected to find event" - assert (event.number() == 2), "Expected to find event 1" - assert (event.name() == ""), "Expected name to be empty" - assert (event.value() == 0), "Expected to not to find event" - - event = task.find_event("10"); - assert (not event.empty()), "Expected to find event" - assert (event.number() == 10), "Expected to find event 10" - assert (event.name() == "Eventname"), "Expected name to be empty" - assert (event.value() == 0), "Expected to not to find event" - - event = task.find_event("fred"); - assert (not event.empty()), "Expected to find event" - assert (event.name() == "fred"), "Expected name to be empty, when name defind an not number, number is max_int" - assert (event.value() == 0), "Expected to not to find event" - - for e in task.events: print(str(e), " # value: ", str(e.value())) - a_dict = {} - for e in task.events: - if e.name() != "": - a_dict[e.name()] = e.value() - else: - a_dict[e.number()] = e.value() - print(a_dict) - - assert len(list(task.events)) == 5, "Expected 5 Events" - task.delete_event("1"); - assert len(list(task.events)) == 4, "Expected 4 Events" - task.delete_event("Eventname"); - assert len(list(task.events)) == 3, "Expected 3 Events" - task.delete_event(""); - assert len(list(task.events)) == 0, "Expected 0 Events" - - # =========================================================================== - # add and delete meter - # =========================================================================== + assert event.empty() + assert event.name() == "" + assert event.value() == 0 + + event = task.find_event("1") + assert not event.empty() + assert event.number() == 1 + assert event.name() == "" + assert event.value() == 0 + + event = task.find_event("2") + assert not event.empty() + assert event.number() == 2 + assert event.name() == "" + assert event.value() == 0 + + event = task.find_event("10") + assert not event.empty() + assert event.number() == 10 + assert event.name() == "Eventname" + assert event.value() == 0 + + event = task.find_event("fred") + assert not event.empty() + assert event.name() == "fred" + assert event.value() == 0 + + assert len(list(task.events)) == 5 + task.delete_event("1") + assert len(list(task.events)) == 4 + task.delete_event("Eventname") + assert len(list(task.events)) == 3 + task.delete_event("") + assert len(list(task.events)) == 0 + + +def test_task_meter_add_delete_and_sort(): + task = ecflow.Task("task") task.add_meter(ecflow.Meter("zzzz", 0, 100, 50)) task.add_meter(ecflow.Meter("yyyy", 0, 100)) task.add_meter("bbbb", 0, 100, 50) task.add_meter("aaaa", 0, 100) - assert len(list(task.meters)) == 4, "Expected 4 Meters" - - # sort - expected = ['aaaa', 'bbbb', 'yyyy', 'zzzz'] - actual = [] - task.sort_attributes(ecflow.AttrType.meter); - task.sort_attributes("meter"); - for v in task.meters: actual.append(v.name()) - assert expected == actual, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) - - task.delete_meter("zzzz"); - assert len(list(task.meters)) == 3, "Expected 3 Meters" - task.delete_meter("yyyy"); - assert len(list(task.meters)) == 2, "Expected 2 Meters" - task.delete_meter(""); - assert len(list(task.meters)) == 0, "Expected 0 Meters" - - # =========================================================================== - # add and delete queue - # =========================================================================== + assert len(list(task.meters)) == 4 + + task.sort_attributes(ecflow.AttrType.meter) + task.sort_attributes("meter") + assert _names(task.meters) == ["aaaa", "bbbb", "yyyy", "zzzz"] + + task.delete_meter("zzzz") + assert len(list(task.meters)) == 3 + task.delete_meter("yyyy") + assert len(list(task.meters)) == 2 + task.delete_meter("") + assert len(list(task.meters)) == 0 + + +def test_task_queue_add_delete(): + task = ecflow.Task("task") queue_items = ["001", "002"] task.add_queue(ecflow.Queue("queue", queue_items)) task.add_queue("queue1", queue_items) task.add_queue("queue2", queue_items) task.add_queue("queue3", queue_items) - assert len(list(task.queues)) == 4, "Expected 4 Queues" - task.delete_queue("queue3"); - assert len(list(task.queues)) == 3, "Expected 3 Queues" - task.delete_queue("queue2"); - assert len(list(task.queues)) == 2, "Expected 2 Queues" - task.delete_queue(""); - assert len(list(task.queues)) == 0, "Expected 0 Queues" - - # =========================================================================== - # add and delete, find generic. These are arbitary attributes for future func. - # =========================================================================== + assert len(list(task.queues)) == 4 + task.delete_queue("queue3") + assert len(list(task.queues)) == 3 + task.delete_queue("queue2") + assert len(list(task.queues)) == 2 + task.delete_queue("") + assert len(list(task.queues)) == 0 + + +def test_task_generic_add_find_delete(): + task = ecflow.Task("task") generic_items = ["001", "002"] task.add_generic(ecflow.Generic("gen", generic_items)) task.add_generic("gen1", generic_items) task.add_generic("gen2", generic_items) task.add_generic("gen3", generic_items) - assert len(list(task.generics)) == 4, "Expected 4 Generics" + assert len(list(task.generics)) == 4 + gen = task.find_generic("gen3") - assert not gen.empty(), " find failed" - assert gen.name() == "gen3", "expected for find gen3" - - task.delete_generic("gen1"); - assert len(list(task.generics)) == 3, "Expected 3 Generics" - task.delete_generic("gen2"); - assert len(list(task.generics)) == 2, "Expected 2 Generics" - task.delete_generic(""); - assert len(list(task.generics)) == 0, "Expected 0 Generics" - - # =========================================================================== - # add and delete label - # =========================================================================== + assert not gen.empty() + assert gen.name() == "gen3" + + task.delete_generic("gen1") + assert len(list(task.generics)) == 3 + task.delete_generic("gen2") + assert len(list(task.generics)) == 2 + task.delete_generic("") + assert len(list(task.generics)) == 0 + + +def test_task_label_add_delete_and_sort(): + task = ecflow.Task("task") task.add_label(ecflow.Label("labela", "value")) task.add_label(ecflow.Label("labelb", "value")) task.add_label("labelc", "value") task.add_label("labeld", "value") - assert len(list(task.labels)) == 4, "Expected 4 labels" - - # sort - expected = ['labela', 'labelb', 'labelc', 'labeld'] - actual = [] - task.sort_attributes("label"); - task.sort_attributes(ecflow.AttrType.label); - for v in task.labels: actual.append(v.name()) - assert expected == actual, "Attributes not sorted, expected:" + str(expected) + " but found:" + str(actual) - - task.delete_label("labela"); - assert len(list(task.labels)) == 3, "Expected 3 Labels" - task.delete_label("labelb"); - assert len(list(task.labels)) == 2, "Expected 2 Labels" - task.delete_label(""); - assert len(list(task.labels)) == 0, "Expected 0 Labels" - - # =========================================================================== - # add delete Repeat - # =========================================================================== - task.add_repeat(ecflow.RepeatInteger("integer", 0, 100, 2)) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task.add_repeat(ecflow.RepeatEnumerated("enum", ["red", "green", "blue"])) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task.add_repeat(ecflow.RepeatDate("date", 20100111, 20100115, 2)) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task.add_repeat(ecflow.RepeatDateTime("datetime", "20100111T000000", "20100115T000000", "48:00:00")) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task.add_repeat(ecflow.RepeatDateList("date", [20100111, 20100115])) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task.add_repeat(ecflow.RepeatDateTimeList("datetime", ["20100111T000000", "20100115T000000"])) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task.add_repeat(ecflow.RepeatString("string", ["a", "b", "c"])) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task.add_repeat(ecflow.RepeatDay(1)) - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" + assert len(list(task.labels)) == 4 + + task.sort_attributes("label") + task.sort_attributes(ecflow.AttrType.label) + assert _names(task.labels) == ["labela", "labelb", "labelc", "labeld"] + + task.delete_label("labela") + assert len(list(task.labels)) == 3 + task.delete_label("labelb") + assert len(list(task.labels)) == 2 + task.delete_label("") + assert len(list(task.labels)) == 0 + + +@pytest.mark.parametrize( + "repeat_attr", + [ + ecflow.RepeatInteger("integer", 0, 100, 2), + ecflow.RepeatEnumerated("enum", ["red", "green", "blue"]), + ecflow.RepeatDate("date", 20100111, 20100115, 2), + ecflow.RepeatDateTime( + "datetime", "20100111T000000", "20100115T000000", "48:00:00" + ), + ecflow.RepeatDateList("date", [20100111, 20100115]), + ecflow.RepeatDateTimeList("datetime", ["20100111T000000", "20100115T000000"]), + ecflow.RepeatString("string", ["a", "b", "c"]), + ecflow.RepeatDay(1), + ], +) +def test_task_repeat_present_then_deleted(repeat_attr): + task = ecflow.Task("task") + task.add_repeat(repeat_attr) + assert not task.get_repeat().empty() task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" + assert task.get_repeat().empty() - # =========================================================================== - # create a time series, used for adding time and today - # =========================================================================== - start = ecflow.TimeSlot(0, 0) - finish = ecflow.TimeSlot(23, 0) - incr = ecflow.TimeSlot(0, 30) - time_series = ecflow.TimeSeries(start, finish, incr, True) - - # Add and delete today - # NOTE: ********************************************************************** - # Do *NOT* delete attributes using iterator traversal - # The iterators *are* bound to the c++ iterators hence if we delete - # over the traversal, we can corrupt the vector, leading to undefined behavour. - # Hence we **cannot** delete more than once over a traversal - # Soln 1: take a copy - # ***************************************************************************** - # task.add_today( Today( 0,10 )) - # task.add_today( Today( 0,59, True )) - # task.add_today( Today(TimeSlot(20,10)) ) - # task.add_today( Today(TimeSlot(20,20),False)) - # for today in task.todays: - # task.delete_today(today) # will corrupt C++ vector +def test_task_today_add_delete(time_series): + task = ecflow.Task("task") task.add_today("00:30") task.add_today("+00:30") task.add_today("+00:30 20:00 01:00") @@ -515,14 +441,15 @@ task.add_today(0, 59, True) task.add_today(ecflow.Today(ecflow.TimeSlot(20, 10))) task.add_today(ecflow.Today(ecflow.TimeSlot(20, 20), False)) - assert len(list(task.todays)) == 10, "Expected 8 todays" + assert len(list(task.todays)) == 10 deleting = [copy.copy(today) for today in task.todays] - for today in deleting: task.delete_today(today) - assert len(list(task.todays)) == 0, "Expected 0 todays" + for today in deleting: + task.delete_today(today) + assert len(list(task.todays)) == 0 - # =========================================================================== - # add and delete time - # =========================================================================== + +def test_task_time_add_delete(time_series): + task = ecflow.Task("task") task.add_time("00:30") task.add_time("+00:30") task.add_time("+00:30 20:00 01:00") @@ -533,45 +460,47 @@ task.add_time(0, 59, True) task.add_time(ecflow.Time(ecflow.TimeSlot(20, 10))) task.add_time(ecflow.Time(ecflow.TimeSlot(20, 20), False)) - assert len(list(task.times)) == 10, "Expected 8 times" + assert len(list(task.times)) == 10 deleting = [copy.copy(time) for time in task.times] - for time in deleting: task.delete_time(time) - assert len(list(task.todays)) == 0, "Expected 0 todays" + for time in deleting: + task.delete_time(time) + assert len(list(task.times)) == 0 + - # =========================================================================== - # add and delete date - # =========================================================================== +def test_task_date_add_delete(): + task = ecflow.Task("task") for i in [1, 2, 4, 8, 16]: task.add_date(i, 0, 0) task.add_date(ecflow.Date(1, 1, 2010)) - task.add_date(1, 1, 2010) # duplicate + task.add_date(1, 1, 2010) task.add_date(ecflow.Date(2, 1, 2010)) task.add_date(ecflow.Date(3, 1, 2010)) task.add_date(ecflow.Date(4, 1, 2010)) - assert len(list(task.dates)) == 10, "Expected 10 dates but found " + str(len(list(task.dates))) + assert len(list(task.dates)) == 10 deleting = [copy.copy(date) for date in task.dates] - for attr in deleting: task.delete_date(attr) - assert len(list(task.dates)) == 0, "Expected 0 dates" + for attr in deleting: + task.delete_date(attr) + assert len(list(task.dates)) == 0 + - # =========================================================================== - # add and delete day - # =========================================================================== +def test_task_day_add_delete(): + task = ecflow.Task("task") task.add_day(ecflow.Day(ecflow.Days.sunday)) task.add_day(ecflow.Days.monday) - task.add_day(ecflow.Days.tuesday) # duplicate ? + task.add_day(ecflow.Days.tuesday) task.add_day(ecflow.Day("tuesday")) task.add_day("sunday") - assert len(list(task.days)) == 5, "Expected 5 days" + assert len(list(task.days)) == 5 deleting = [copy.copy(attr) for attr in task.days] - for attr in deleting: task.delete_day(attr) - assert len(list(task.days)) == 0, "Expected 0 days" + for attr in deleting: + task.delete_day(attr) + assert len(list(task.days)) == 0 - # =========================================================================== - # add and delete crons - # =========================================================================== + +def test_task_cron_add_delete(): cron = ecflow.Cron() start = ecflow.TimeSlot(23, 0) - ts = ecflow.TimeSeries(start, True) # True means relative to suite start + ts = ecflow.TimeSeries(start, True) cron.set_time_series(ts) cron = ecflow.Cron() @@ -581,12 +510,12 @@ start = ecflow.TimeSlot(0, 0) finish = ecflow.TimeSlot(23, 0) incr = ecflow.TimeSlot(0, 30) - ts = ecflow.TimeSeries(start, finish, incr, True) # True means relative to suite start + ts = ecflow.TimeSeries(start, finish, incr, True) cron.set_time_series(ts) cron0 = ecflow.Cron() cron0.set_week_days([0, 1, 2, 3, 4, 5]) - cron0.set_time_series(1, 30) # default relative = false, added in release 4.0.7 + cron0.set_time_series(1, 30) cron1 = ecflow.Cron() cron1.set_week_days([0, 1, 2, 3, 4, 5, 6]) @@ -600,122 +529,113 @@ cron3.set_week_days([0, 1, 2, 3, 4, 5, 6]) cron3.set_time_series("+00:30") + task = ecflow.Task("task") task.add_cron(cron) task.add_cron(cron1) task.add_cron(cron2) task.add_cron(cron3) - assert len(list(task.crons)) == 4, "Expected 4 crons" + assert len(list(task.crons)) == 4 deleting = [copy.copy(cron) for cron in task.crons] - for attr in deleting: task.delete_cron(attr) - assert len(list(task.crons)) == 0, "Expected 0 crons" + for attr in deleting: + task.delete_cron(attr) + assert len(list(task.crons)) == 0 + - # =========================================================================== - # add autocancel - # =========================================================================== - print("test add autoCancel") +def test_task_autocancel_variants(): t1 = ecflow.Task("t1") - assert t1.get_autocancel() is None, " Expected no autocancel" - t1.add_autocancel(3) # 3 days - assert t1.get_autocancel() is not None, " Expected autocancel" - print(str(t1.get_autocancel())) + assert t1.get_autocancel() is None + t1.add_autocancel(3) + assert t1.get_autocancel() is not None t3 = ecflow.Task("t3") - t3.add_autocancel(20, 10, True) # hour,minutes,relative - print(str(t3.get_autocancel())) + t3.add_autocancel(20, 10, True) + assert t3.get_autocancel() is not None t4 = ecflow.Task("t4") - t4.add_autocancel(ecflow.TimeSlot(10, 10), True) # hour,minutes,relative - print(str(t4.get_autocancel())) + t4.add_autocancel(ecflow.TimeSlot(10, 10), True) + assert t4.get_autocancel() is not None t5 = ecflow.Task("t5") - t5.add_autocancel(ecflow.Autocancel(1, 10, True)) # hour,minutes,relative - print(str(t5.get_autocancel())) + t5.add_autocancel(ecflow.Autocancel(1, 10, True)) + assert t5.get_autocancel() is not None - # =========================================================================== - # add autoarchive - # =========================================================================== - print("test add autoarchive") + +def test_family_autocancel_variants(): + f5 = ecflow.Family("f5") + assert f5.get_autocancel() is None + f5.add_autocancel(ecflow.Autocancel(1, 10, True)) # hour,minutes,relative + assert f5.get_autocancel() is not None + + +def test_family_autoarchive_variants(): f1 = ecflow.Family("f1") - assert f1.get_autoarchive() is None, " Expected no autoarchive" - f1.add_autoarchive(3) # 3 days - assert f1.get_autoarchive() is not None, " Expected autoarchive" - print(str(f1.get_autoarchive())) + assert f1.get_autoarchive() is None + f1.add_autoarchive(3) + assert f1.get_autoarchive() is not None f2 = ecflow.Family("f2") - assert f2.get_autoarchive() is None, " Expected no autoarchive" - f2.add_autoarchive(3, True) # 3 days - assert f2.get_autoarchive() is not None, " Expected autoarchive" - print(str(f2.get_autoarchive())) + f2.add_autoarchive(3, True) + assert f2.get_autoarchive() is not None f3 = ecflow.Family("f3") - f3.add_autoarchive(20, 10, True) # hour,minutes,relative - print(str(f3.get_autoarchive())) + f3.add_autoarchive(20, 10, True) + assert f3.get_autoarchive() is not None f3_1 = ecflow.Family("f3_1") - f3_1.add_autoarchive(20, 10, True, True) # hour,minutes,relative,idle - print(str(f3_1.get_autoarchive())) + f3_1.add_autoarchive(20, 10, True, True) + assert f3_1.get_autoarchive() is not None f4 = ecflow.Family("f4") - f4.add_autoarchive(ecflow.TimeSlot(10, 10), True) # hour,minutes,relative - print(str(f4.get_autoarchive())) + f4.add_autoarchive(ecflow.TimeSlot(10, 10), True) + assert f4.get_autoarchive() is not None f4_1 = ecflow.Family("f4_1") - f4_1.add_autoarchive(ecflow.TimeSlot(10, 10), True, True) # hour,minutes,relative,idle - print(str(f4_1.get_autoarchive())) + f4_1.add_autoarchive(ecflow.TimeSlot(10, 10), True, True) + assert f4_1.get_autoarchive() is not None f4_2 = ecflow.Family("f4_2") - assert f4_2.get_autoarchive() is None, " Expected no autoarchive" - f4_2.add_autoarchive(ecflow.Autoarchive(10, True)) # Days,idle - assert f4_2.get_autoarchive() is not None, " Expected autoarchive" - print(str(f4_2.get_autoarchive())) - - # =========================================================================== - # autocancel - # =========================================================================== - f5 = ecflow.Family("f5") - f5.add_autocancel(ecflow.Autocancel(1, 10, True)) # hour,minutes,relative - print(str(f5.get_autocancel())) - - # =========================================================================== - # add autorestore - # =========================================================================== - print("test add autorestore") - t1 = ecflow.Task("f1") - assert t1.get_autorestore() is None, " Expected no autorestore" + assert f4_2.get_autoarchive() is None + f4_2.add_autoarchive(ecflow.Autoarchive(10, True)) + assert f4_2.get_autoarchive() is not None + + +def test_node_autorestore_variants(): + t1 = ecflow.Task("t1") + assert t1.get_autorestore() is None t1.add_autorestore(["/s1/f2"]) - assert t1.get_autorestore() is not None, " Expected autorestore" - print(str(t1.get_autorestore())) + assert t1.get_autorestore() is not None f5 = ecflow.Family("f5") - assert f5.get_autorestore() is None, " Expected no autorestore" + assert f5.get_autorestore() is None f5.add_autorestore(["/s1/f2", "/s2", "/s3"]) - assert f5.get_autorestore() is not None, " Expected autorestore" - print(str(f5.get_autoarchive())) + assert f5.get_autorestore() is not None f6 = ecflow.Family("f6") - assert f6.get_autorestore() is None, " Expected no autorestore" + assert f6.get_autorestore() is None f6.add_autorestore(ecflow.Autorestore(["/s1/f2", "/s2", "/s3"])) - assert f6.get_autorestore() is not None, " Expected autorestore" - print(str(f6.get_autoarchive())) + assert f6.get_autorestore() is not None + - # =========================================================================== - # add late - # =========================================================================== +def test_task_late_variants(): late = ecflow.Late() late.submitted(ecflow.TimeSlot(20, 10)) late.active(ecflow.TimeSlot(20, 10)) late.complete(ecflow.TimeSlot(20, 10), True) + task = ecflow.Task("task") task.add_late(late) + assert task.get_late() is not None + t1 = ecflow.Task("t1") late = ecflow.Late() late.submitted(20, 10) late.active(20, 10) late.complete(20, 10, True) t1.add_late(late) + assert t1.get_late() is not None - # =========================================================================== - # add defstatus, last one set takes effect - # =========================================================================== + +def test_task_defstatus_last_set_wins(): + task = ecflow.Task("task") task.add_defstatus(ecflow.DState.complete) assert task.get_defstatus() == ecflow.DState.complete task.add_defstatus(ecflow.DState.queued) @@ -724,91 +644,100 @@ assert task.get_defstatus() == ecflow.DState.aborted task.add_defstatus(ecflow.DState.submitted) assert task.get_defstatus() == ecflow.DState.submitted - task.add_defstatus(ecflow.Defstatus(ecflow.DState.suspended)); + task.add_defstatus(ecflow.Defstatus(ecflow.DState.suspended)) assert task.get_defstatus() == ecflow.DState.suspended task.add_defstatus(ecflow.Defstatus("active")) assert task.get_defstatus() == ecflow.DState.active - # the state should be unknown until the suite is begun assert task.get_state() == ecflow.State.unknown assert task.get_dstate() == ecflow.DState.unknown - # =========================================================================== - # add clock - # =========================================================================== - clock = ecflow.Clock(1, 1, 2010, False) # day,month, year, hybrid - clock.set_gain(1, 10, True) # True means positive gain + +def test_suite_clock_and_end_clock(): + clock = ecflow.Clock(1, 1, 2010, False) + clock.set_gain(1, 10, True) suite = ecflow.Suite("suite") suite.add_clock(clock) - clock = ecflow.Clock(1, 1, 2011, True) # day,month, year, hybrid + clock = ecflow.Clock(1, 1, 2011, True) clock.set_gain_in_seconds(12, True) s1 = ecflow.Suite("s1") s1.add_clock(clock) - print("#===========================================================================") - print("# get clock") - print("#===========================================================================") s0 = ecflow.Suite("s0") - assert s0.get_clock() is None, "Expected no clock" - + assert s0.get_clock() is None s0.add_clock(ecflow.Clock(1, 1, 2010, False)) - assert s0.get_clock() is not None, "Expected clock" + assert s0.get_clock() is not None - # =========================================================================== - # end clock, used in simulator only, not persisted - # =========================================================================== - clock = ecflow.Clock(1, 1, 2010, False) # day,month, year, hybrid - clock.set_gain(1, 10, True) # True means positive gain + clock = ecflow.Clock(1, 1, 2010, False) + clock.set_gain(1, 10, True) suite = ecflow.Suite("suite") suite.add_end_clock(clock) - clock = ecflow.Clock(1, 1, 2011, True) # day,month, year, hybrid + clock = ecflow.Clock(1, 1, 2011, True) clock.set_gain_in_seconds(12, True) s1 = ecflow.Suite("s1") s1.add_end_clock(clock) - print("#===========================================================================") - print("# get end clock") - print("#===========================================================================") s0 = ecflow.Suite("s0") - assert s0.get_end_clock() is None, "Expected no end clock" - + assert s0.get_end_clock() is None s0.add_end_clock(ecflow.Clock(1, 1, 2010, False)) - assert s0.get_end_clock() is not None, "Expected end clock" - # =========================================================================== - # Add zombie. Note we can *NOT* add two zombie attributes of the same ZombieType - # =========================================================================== + assert s0.get_end_clock() is not None + + +def test_suite_zombie_add_delete(): + s1 = ecflow.Suite("s1") zombie_life_time_in_server = 800 - child_list = [ecflow.ChildCmdType.init, ecflow.ChildCmdType.event, ecflow.ChildCmdType.meter, ecflow.ChildCmdType.label, ecflow.ChildCmdType.wait, ecflow.ChildCmdType.abort, ecflow.ChildCmdType.complete] - zombie_type_list = [ecflow.ZombieType.ecf, ecflow.ZombieType.ecf_pid, ecflow.ZombieType.ecf_pid_passwd, ecflow.ZombieType.ecf_passwd, ecflow.ZombieType.user, ecflow.ZombieType.path] + child_list = [ + ecflow.ChildCmdType.init, + ecflow.ChildCmdType.event, + ecflow.ChildCmdType.meter, + ecflow.ChildCmdType.label, + ecflow.ChildCmdType.wait, + ecflow.ChildCmdType.abort, + ecflow.ChildCmdType.complete, + ] + zombie_type_list = [ + ecflow.ZombieType.ecf, + ecflow.ZombieType.ecf_pid, + ecflow.ZombieType.ecf_pid_passwd, + ecflow.ZombieType.ecf_passwd, + ecflow.ZombieType.user, + ecflow.ZombieType.path, + ] + for zombie_type in zombie_type_list: - zombie_attr = ecflow.ZombieAttr(zombie_type, child_list, ecflow.ZombieUserActionType.block, zombie_life_time_in_server) + zombie_attr = ecflow.ZombieAttr( + zombie_type, + child_list, + ecflow.ZombieUserActionType.block, + zombie_life_time_in_server, + ) s1.add_zombie(zombie_attr) - assert len(list(s1.zombies)) == 6, "Expected 6 zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 6 - # delete all the zombies s1.delete_zombie("") - assert len(list(s1.zombies)) == 0, "Expected zero zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 0 - # add with zombie_life_time_in_server not set, this is optional for zombie_type in zombie_type_list: - zombie_attr = ecflow.ZombieAttr(zombie_type, child_list, ecflow.ZombieUserActionType.block) + zombie_attr = ecflow.ZombieAttr( + zombie_type, child_list, ecflow.ZombieUserActionType.block + ) s1.add_zombie(zombie_attr) - assert len(list(s1.zombies)) == 6, "Expected 6 zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 6 s1.delete_zombie("") - assert len(list(s1.zombies)) == 0, "Expected zero zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 0 - # repeat the the test with empty child list. Empty child list means apply to all child commands child_list = [] for zombie_type in zombie_type_list: - zombie_attr = ecflow.ZombieAttr(zombie_type, child_list, ecflow.ZombieUserActionType.block, zombie_life_time_in_server) + zombie_attr = ecflow.ZombieAttr( + zombie_type, + child_list, + ecflow.ZombieUserActionType.block, + zombie_life_time_in_server, + ) s1.add_zombie(zombie_attr) - - # test delete of specific zombie attribute - assert len(list(s1.zombies)) == 1, "Expected 1 zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 1 s1.delete_zombie(zombie_type) - assert len(list(s1.zombies)) == 0, "Expected 0 zombie attributes but found " + str(len(list(s1.zombies))) - - print("All Tests pass") + assert len(list(s1.zombies)) == 0 diff --git a/libs/pyext/test/py_u_TestAttributeAddDeleteError.py b/libs/pyext/test/py_u_TestAttributeAddDeleteError.py index e2fecc2f2..b4323be26 100644 --- a/libs/pyext/test/py_u_TestAttributeAddDeleteError.py +++ b/libs/pyext/test/py_u_TestAttributeAddDeleteError.py @@ -8,102 +8,64 @@ # nor does it submit to any jurisdiction. # -import os -from ecflow import Defs, Suite, Variable, Limit, InLimit, Task, PartExpression, \ - Event, Meter, Label, RepeatInteger, RepeatEnumerated, RepeatDate, RepeatDateList, RepeatDateTimeList, RepeatString, \ - TimeSlot, TimeSeries, Today, Time, Date, Day, Days, Cron, Autocancel, Late, \ - DState, Clock, ChildCmdType, ZombieType, ZombieAttr, ZombieUserActionType, Client, debug_build -import ecflow_test_util as Test - -if __name__ == "__main__": - - Test.print_test_start(os.path.basename(__file__)) - - # - # Test for: See ECFLOW-106 Times/Dates attributes attached to suite node - # - defs = Defs() - suite = defs.add_suite("s1") - - # - # Suite should not be allowed time based dependencies - # Check Today - expected_error = False - try: +import pytest + +from ecflow import ( + Defs, + Task, + RepeatDateList, + RepeatDateTimeList, +) + + +@pytest.fixture +def suite(): + return Defs().add_suite("s1") + + +def test_suite_disallows_today_string(suite): + with pytest.raises(RuntimeError): suite.add_today("00:30") - except RuntimeError: - expected_error = True - assert expected_error, "Suite should not allow any time based dependencies" - expected_error = False - try: + +def test_suite_disallows_today_ints(suite): + with pytest.raises(RuntimeError): suite.add_today(0, 30) - except RuntimeError: - expected_error = True - assert expected_error, "Suite should not allow any time based dependencies" - - # - # Check Time - expected_error = False - try: + + +def test_suite_disallows_time_string(suite): + with pytest.raises(RuntimeError): suite.add_time("+00:30") - except RuntimeError: - expected_error = True - assert expected_error, "Suite should not allow any time based dependencies" - expected_error = False - try: + +def test_suite_disallows_time_ints(suite): + with pytest.raises(RuntimeError): suite.add_time(0, 30) - except RuntimeError: - expected_error = True - assert expected_error, "Suite should not allow any time based dependencies" - - # - # Check Date::See ECFLOW-106 Times/Dates attributes attached to suite node - expected_error = False - try: + + +def test_suite_disallows_date(suite): + with pytest.raises(RuntimeError): suite.add_date(1, 1, 2010) - except RuntimeError: - expected_error = True - assert expected_error, "Suite should not allow any time based dependencies" - - # - # Check Day:: See ECFLOW-106 Times/Dates attributes attached to suite node - expected_error = False - try: + + +def test_suite_disallows_day(suite): + with pytest.raises(RuntimeError): suite.add_day("sunday") - except RuntimeError: - expected_error = True - assert expected_error, "Suite should not allow any time based dependencies" - # - # Adding a RepeatDateList with a empty list should be an error - expected_error = False + +def test_repeat_date_list_empty_list_raises(): task = Task("t") - try: + with pytest.raises(RuntimeError): task.add_repeat(RepeatDateList("date", [])) - except RuntimeError: - expected_error = True - assert expected_error, "RepeatDateList adding an empty date list should be an error" - # - # Adding a RepeatDateTimeList with an empty list should be an error - expected_error = False + +def test_repeat_datetime_list_empty_list_raises(): task = Task("t") - try: + with pytest.raises(RuntimeError): task.add_repeat(RepeatDateTimeList("dt", [])) - except RuntimeError: - expected_error = True - assert expected_error, "RepeatDateTimeList with empty list should raise RuntimeError" - # - # Adding a RepeatDateTimeList with an invalid datetime string should be an error - expected_error = False + +def test_repeat_datetime_list_invalid_datetime_raises(): task = Task("t") - try: + with pytest.raises(RuntimeError): task.add_repeat(RepeatDateTimeList("dt", ["not-a-datetime"])) - except RuntimeError: - expected_error = True - assert expected_error, "RepeatDateTimeList with invalid datetime string should raise RuntimeError" - - print("All Tests pass") diff --git a/libs/pyext/test/py_u_TestAttributeAddDeleteInTasks.py b/libs/pyext/test/py_u_TestAttributeAddDeleteInTasks.py index c36197879..fffc97e41 100644 --- a/libs/pyext/test/py_u_TestAttributeAddDeleteInTasks.py +++ b/libs/pyext/test/py_u_TestAttributeAddDeleteInTasks.py @@ -8,223 +8,260 @@ # nor does it submit to any jurisdiction. # -import os import copy -from ecflow import Defs, Suite, Variable, Limit, InLimit, Task, PartExpression, \ - Event, Meter, Label, Queue, RepeatInteger, RepeatEnumerated, RepeatDate, RepeatDateTime, \ - RepeatDateList, RepeatString, TimeSlot, TimeSeries, Today, Time, Date, Day, Days, Cron, Autocancel, \ - Late, DState, Clock, ChildCmdType, ZombieType, ZombieAttr, ZombieUserActionType, Client, debug_build -import ecflow_test_util as Test -if __name__ == "__main__": +import pytest + +from ecflow import ( + Defs, + Suite, + Variable, + Limit, + InLimit, + Task, + PartExpression, + Event, + Meter, + Label, + Queue, + RepeatInteger, + RepeatEnumerated, + RepeatDate, + RepeatDateTime, + RepeatDateList, + RepeatString, + TimeSlot, + TimeSeries, + Today, + Time, + Date, + Day, + Days, + Cron, + Autocancel, + Late, + DState, + Clock, + ChildCmdType, + ZombieType, + ZombieAttr, + ZombieUserActionType, +) + + +@pytest.fixture +def time_series(): + start = TimeSlot(0, 0) + finish = TimeSlot(23, 0) + incr = TimeSlot(0, 30) + return TimeSeries(start, finish, incr, True) - Test.print_test_start(os.path.basename(__file__)) - # - # Add Nodes functional way - # +def test_add_nodes_functionally(): defs = Defs() defs.add_suite("s1").add_task("t1").add_variable("var", "v") defs.add_suite("s2").add_family("f1").add_task("t1").add_variable("var", "v") - defs.add_suite("s3").add_family("f1").add_family("f2").add_task("t1").add_variable("var", "v") - assert len(defs) == 3, "Expected 3 suites" + defs.add_suite("s3").add_family("f1").add_family("f2").add_task("t1").add_variable( + "var", "v" + ) + assert len(defs) == 3 + - # add and delete variables +def test_suite_add_delete_variables_via_dict_and_chaining(): + suite = Suite("s1") a_dict = {"name": "value", "name2": "value2", "name3": "value3", "name4": "value4"} + suite.add_variable(Variable("ECF_HOME", "'/tmp/'")).add_variable( + "Fred", 1 + ).add_variable("ECF_URL_BASE", "http://www.ecmwf.int").add_variable( + "ECF_URL", '"publications/manuals/sms"' + ).add_variable( + a_dict + ) + assert len(list(suite.variables)) == 8 + suite.delete_variable("") + assert len(list(suite.variables)) == 0 + + +def test_suite_add_delete_limits(): suite = Suite("s1") - suite.add_variable(Variable("ECF_HOME", "'/tmp/'")) \ - .add_variable("Fred", 1) \ - .add_variable("ECF_URL_BASE", 'http://www.ecmwf.int') \ - .add_variable("ECF_URL", '"publications/manuals/sms"') \ - .add_variable(a_dict) - print(suite) - assert len(list(suite.variables)) == 8, "Expected 8 variable" - suite.delete_variable(""); - assert len(list(suite.variables)) == 0, "Expected 0 variable since we should have deleted all" - - # add and delete limits the_limit = Limit("limitName1", 10) - assert len(list(the_limit.node_paths())) == 0, "Expected nodes which have consumed a limit to be empty" - suite.add_limit(Limit("limitName1", 10)) \ - .add_limit(Limit("limitName2", 10)) \ - .add_limit("limitName3", 10) \ - .add_limit("limitName4", 10) - assert len(list(suite.limits)) == 4, "Expected 4 Limits" - suite.delete_limit(""); - assert len(list(suite.limits)) == 0, "Expected 0 limits since we just deleted all of them" - - # add and delete in-limits - suite.add_inlimit(InLimit("limitName1", "/s1/f1", 2, True)) \ - .add_inlimit(InLimit("limitName2", "/s1/f1", 2)) \ - .add_inlimit("limitName3", "/s1/f1", 2, True) \ - .add_inlimit("limitName4", "/s1/f1", 2) - assert len(list(suite.inlimits)) == 4, "Expected 4 inLimits" - suite.delete_inlimit("limitName1"); - assert len(list(suite.inlimits)) == 3, "Expected 3 inlimits since we just deleted one limitName1" - suite.delete_inlimit(""); - assert len(list(suite.inlimits)) == 0, "Expected 0 inlimits since we just deleted all of them" - - # add and delete triggers and complete + assert len(list(the_limit.node_paths())) == 0 + suite.add_limit(Limit("limitName1", 10)).add_limit( + Limit("limitName2", 10) + ).add_limit("limitName3", 10).add_limit("limitName4", 10) + assert len(list(suite.limits)) == 4 + suite.delete_limit("") + assert len(list(suite.limits)) == 0 + + +def test_suite_add_delete_inlimits(): + suite = Suite("s1") + suite.add_inlimit(InLimit("limitName1", "/s1/f1", 2, True)).add_inlimit( + InLimit("limitName2", "/s1/f1", 2) + ).add_inlimit("limitName3", "/s1/f1", 2, True).add_inlimit( + "limitName4", "/s1/f1", 2 + ) + assert len(list(suite.inlimits)) == 4 + suite.delete_inlimit("limitName1") + assert len(list(suite.inlimits)) == 3 + suite.delete_inlimit("") + assert len(list(suite.inlimits)) == 0 + + +def test_task_add_delete_trigger_and_complete(): task = Task("task") task.add_trigger("t2 == active").add_complete("t2 == complete") - assert task.get_complete(), "Expected complete" - assert task.get_trigger(), "Expected trigger" - task.delete_trigger(); - assert not task.get_trigger(), "Expected no trigger" - task.delete_complete(); - assert not task.get_complete(), "Expected no complete" - - task.add_part_trigger(PartExpression("t1 == complete")) \ - .add_part_complete(PartExpression("t3 == complete")) \ - .add_part_complete(PartExpression("t4 == active", False)) - task.delete_trigger(); - assert not task.get_trigger(), "Expected no trigger" - task.delete_complete(); - assert not task.get_complete(), "Expected no complete" - - task.add_part_trigger("t1 == complete") \ - .add_part_complete("t3 == complete") \ - .add_part_complete("t4 == active", False) # for long and/or expressions, subsequent expr must be and/or - task.delete_trigger(); - assert not task.get_trigger(), "Expected no trigger" - task.delete_complete(); - assert not task.get_complete(), "Expected no complete" - - # add and delete events - task.add_event(Event(1)).add_event(2).add_event(Event(10, "Eventname")).add_event(10, "Eventname2").add_event("fred") - assert len(list(task.events)) == 5, "Expected 5 Events" - task.delete_event(""); - assert len(list(task.events)) == 0, "Expected 0 Events" - - # add and delete meter - task.add_meter(Meter("metername1", 0, 100, 50)) \ - .add_meter(Meter("metername2", 0, 100)) \ - .add_meter("metername3", 0, 100, 50) \ - .add_meter("metername4", 0, 100) - assert len(list(task.meters)) == 4, "Expected 4 Meters" - task.delete_meter(""); - assert len(list(task.meters)) == 0, "Expected 0 Meters" - - # add and delete queue - queue_items = ["001", "002"] - task.add_queue(Queue("q1", queue_items)) \ - .add_queue(Queue("q2", queue_items)) \ - .add_queue("q3", queue_items) \ - .add_queue("q4", queue_items) - assert len(list(task.queues)) == 4, "Expected 4 Queues" - task.delete_queue(""); - assert len(list(task.queues)) == 0, "Expected 0 Queues" - - # add and delete label - task.add_label(Label("label_name1", "value")) \ - .add_label(Label("label_name2", "value")) \ - .add_label("label_name3", "value") \ - .add_label("label_name4", "value") - assert len(list(task.labels)) == 4, "Expected 4 labels" - task.delete_label(""); - assert len(list(task.labels)) == 0, "Expected 0 Labels" - - # add delete Repeat - task.add_repeat(RepeatInteger("integer", 0, 100, 2)).add_variable("fred", "j") - repeat = task.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" + assert task.get_complete() + assert task.get_trigger() + task.delete_trigger() + assert not task.get_trigger() + task.delete_complete() + assert not task.get_complete() - task.add_repeat(RepeatEnumerated("enum", ["red", "green", "blue"])).add_variable("Q", "j") - print(task) - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - task.add_repeat(RepeatDate("date", 20100111, 20100115, 2)).add_variable("W", "j") - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" +def test_task_add_delete_part_trigger_and_complete(): + task = Task("task") + task.add_part_trigger(PartExpression("t1 == complete")).add_part_complete( + PartExpression("t3 == complete") + ).add_part_complete(PartExpression("t4 == active", False)) + task.delete_trigger() + assert not task.get_trigger() + task.delete_complete() + assert not task.get_complete() - task.add_repeat(RepeatDateTime("datetime", "20100111T000000", "20100115T000000", "48:00:00")).add_variable("W", "j") - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" - task.add_repeat(RepeatDateList("date", [20100111, 20100115])).add_variable("W", "j") - task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" +def test_task_add_delete_part_trigger_and_complete_strings(): + task = Task("task") + task.add_part_trigger("t1 == complete").add_part_complete( + "t3 == complete" + ).add_part_complete("t4 == active", False) + task.delete_trigger() + assert not task.get_trigger() + task.delete_complete() + assert not task.get_complete() + + +def test_task_add_delete_events(): + task = Task("task") + task.add_event(Event(1)).add_event(2).add_event(Event(10, "Eventname")).add_event( + 10, "Eventname2" + ).add_event("fred") + assert len(list(task.events)) == 5 + task.delete_event("") + assert len(list(task.events)) == 0 + + +def test_task_add_delete_meters(): + task = Task("task") + task.add_meter(Meter("metername1", 0, 100, 50)).add_meter( + Meter("metername2", 0, 100) + ).add_meter("metername3", 0, 100, 50).add_meter("metername4", 0, 100) + assert len(list(task.meters)) == 4 + task.delete_meter("") + assert len(list(task.meters)) == 0 + - task.add_repeat(RepeatString("string", ["a", "b", "c"])).add_variable("E", "j") +def test_task_add_delete_queues(): + task = Task("task") + queue_items = ["001", "002"] + task.add_queue(Queue("q1", queue_items)).add_queue( + Queue("q2", queue_items) + ).add_queue("q3", queue_items).add_queue("q4", queue_items) + assert len(list(task.queues)) == 4 + task.delete_queue("") + assert len(list(task.queues)) == 0 + + +def test_task_add_delete_labels(): + task = Task("task") + task.add_label(Label("label_name1", "value")).add_label( + Label("label_name2", "value") + ).add_label("label_name3", "value").add_label("label_name4", "value") + assert len(list(task.labels)) == 4 + task.delete_label("") + assert len(list(task.labels)) == 0 + + +@pytest.mark.parametrize( + "repeat_attr", + [ + RepeatInteger("integer", 0, 100, 2), + RepeatEnumerated("enum", ["red", "green", "blue"]), + RepeatDate("date", 20100111, 20100115, 2), + RepeatDateTime("datetime", "20100111T000000", "20100115T000000", "48:00:00"), + RepeatDateList("date", [20100111, 20100115]), + RepeatString("string", ["a", "b", "c"]), + ], +) +def test_task_add_delete_repeats(repeat_attr): + task = Task("task") + task.add_repeat(repeat_attr).add_variable("var", "j") + assert not task.get_repeat().empty() task.delete_repeat() - repeat = task.get_repeat(); - assert repeat.empty(), "Expected no repeat" + assert task.get_repeat().empty() - # create a time series, used for adding time and today - start = TimeSlot(0, 0) - finish = TimeSlot(23, 0) - incr = TimeSlot(0, 30) - time_series = TimeSeries(start, finish, incr, True) - - # Add and delete today - # NOTE: ********************************************************************** - # Do *NOT* delete attributes using iterator traversal - # The iterators *are* bound to the c++ iterators hence if we delete - # over the traversal, we can corrupt the vector, leading to undefined behaviour. - # Hence we **cannot** delete more than once over a traversal - # Soln 1: take a copy - # ***************************************************************************** - # task.add_today( Today( 0,10 )) - # task.add_today( Today( 0,59, True )) - # task.add_today( Today(TimeSlot(20,10)) ) - # task.add_today( Today(TimeSlot(20,20),False)) - # for today in task.todays: - # task.delete_today(today) # will corrupt C++ vector - - task.add_today("00:30").add_today("+00:30").add_today("+00:30 20:00 01:00") \ - .add_today(Today(time_series)) \ - .add_today(Today(0, 10)).add_today(0, 59, True) \ - .add_today(Today(TimeSlot(20, 10))) \ - .add_today(Today(TimeSlot(20, 20), False)) - assert len(list(task.todays)) == 8, "Expected 8 todays" + +def test_task_add_delete_todays(time_series): + task = Task("task") + task.add_today("00:30").add_today("+00:30").add_today( + "+00:30 20:00 01:00" + ).add_today(Today(time_series)).add_today(Today(0, 10)).add_today( + 0, 59, True + ).add_today( + Today(TimeSlot(20, 10)) + ).add_today( + Today(TimeSlot(20, 20), False) + ) + assert len(list(task.todays)) == 8 deleting = [copy.copy(today) for today in task.todays] - for today in deleting: task.delete_today(today) - assert len(list(task.todays)) == 0, "Expected 0 todays" - - # add and delete time - task.add_time("00:30").add_time("+00:30").add_time("+00:30 20:00 01:00") \ - .add_time(Time(time_series)) \ - .add_time(Time(0, 10)) \ - .add_time(0, 59, True) \ - .add_time(Time(TimeSlot(20, 10))) \ - .add_time(Time(TimeSlot(20, 20), False)) - assert len(list(task.times)) == 8, "Expected 8 times" + for today in deleting: + task.delete_today(today) + assert len(list(task.todays)) == 0 + + +def test_task_add_delete_times(time_series): + task = Task("task") + task.add_time("00:30").add_time("+00:30").add_time("+00:30 20:00 01:00").add_time( + Time(time_series) + ).add_time(Time(0, 10)).add_time(0, 59, True).add_time( + Time(TimeSlot(20, 10)) + ).add_time( + Time(TimeSlot(20, 20), False) + ) + assert len(list(task.times)) == 8 deleting = [copy.copy(time) for time in task.times] - for time in deleting: task.delete_time(time) - assert len(list(task.todays)) == 0, "Expected 0 todays" + for time in deleting: + task.delete_time(time) + assert len(list(task.times)) == 0 + - # add and delete date +def test_task_add_delete_dates(): + task = Task("task") for i in [1, 2, 4, 8, 16]: task.add_date(i, 0, 0) - task.add_date(Date(1, 1, 2010)) \ - .add_date(Date(2, 1, 2010)) \ - .add_date(Date(3, 1, 2010)) \ - .add_date(Date(4, 1, 2010)) \ - .add_date(1, 1, 2010) - assert len(list(task.dates)) == 10, "Expected 10 dates but found " + str(len(list(task.dates))) + task.add_date(Date(1, 1, 2010)).add_date(Date(2, 1, 2010)).add_date( + Date(3, 1, 2010) + ).add_date(Date(4, 1, 2010)).add_date(1, 1, 2010) + assert len(list(task.dates)) == 10 deleting = [copy.copy(date) for date in task.dates] - for attr in deleting: task.delete_date(attr) - assert len(list(task.dates)) == 0, "Expected 0 dates" - - # add and delete day - task.add_day(Day(Days.sunday)) \ - .add_day(Days.monday) \ - .add_day(Days.tuesday) \ - .add_day("sunday") - assert len(list(task.days)) == 4, "Expected 4 days" + for attr in deleting: + task.delete_date(attr) + assert len(list(task.dates)) == 0 + + +def test_task_add_delete_days(): + task = Task("task") + task.add_day(Day(Days.sunday)).add_day(Days.monday).add_day(Days.tuesday).add_day( + "sunday" + ) + assert len(list(task.days)) == 4 deleting = [copy.copy(day) for day in task.days] - for attr in deleting: task.delete_day(attr) - assert len(list(task.days)) == 0, "Expected 0 days" + for attr in deleting: + task.delete_day(attr) + assert len(list(task.days)) == 0 - # add and delete crons + +def test_task_add_delete_crons(): cron = Cron() cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) cron.set_days_of_month([1, 2, 3, 4, 5, 6]) @@ -232,7 +269,7 @@ start = TimeSlot(0, 0) finish = TimeSlot(23, 0) incr = TimeSlot(0, 30) - ts = TimeSeries(start, finish, incr, True) # True means relative to suite start + ts = TimeSeries(start, finish, incr, True) cron.set_time_series(ts) cron1 = Cron() @@ -247,86 +284,106 @@ cron3.set_week_days([0, 1, 2, 3, 4, 5, 6]) cron3.set_time_series("+00:30") - task.add_cron(cron) \ - .add_cron(cron1) \ - .add_cron(cron2) \ - .add_cron(cron3) - assert len(list(task.crons)) == 4, "Expected 4 crons" + task = Task("task") + task.add_cron(cron).add_cron(cron1).add_cron(cron2).add_cron(cron3) + assert len(list(task.crons)) == 4 deleting = [copy.copy(cron) for cron in task.crons] - for attr in deleting: task.delete_cron(attr) - assert len(list(task.crons)) == 0, "Expected 0 crons" + for attr in deleting: + task.delete_cron(attr) + assert len(list(task.crons)) == 0 + - # add autocancel +def test_task_add_autocancel_variants(): t1 = Task("t1") t1.add_autocancel(3).add_variable("A", "j") + assert t1.get_autocancel() is not None + t3 = Task("t3") t3.add_autocancel(20, 10, True).add_variable("B", "j") + assert t3.get_autocancel() is not None + t4 = Task("t4") t4.add_autocancel(TimeSlot(10, 10), True).add_variable("C", "j") + assert t4.get_autocancel() is not None + t5 = Task("t5") t5.add_autocancel(Autocancel(1, 10, True)).add_variable("D", "j") + assert t5.get_autocancel() is not None + - # add late +def test_task_add_late(): + task = Task("task") late = Late() late.submitted(TimeSlot(20, 10)) late.active(TimeSlot(20, 10)) late.complete(TimeSlot(20, 10), True) task.add_late(late).add_variable("FRED33", "j") + assert task.get_late() is not None + + +def test_task_defstatus_last_set_wins(): + task = Task("task") + task.add_defstatus(DState.complete).add_defstatus(DState.queued).add_defstatus( + DState.aborted + ).add_defstatus(DState.submitted).add_defstatus(DState.suspended).add_defstatus( + DState.active + ) + assert task.get_defstatus() == DState.active - # add defstatus, last one set takes effect - task.add_defstatus(DState.complete) \ - .add_defstatus(DState.queued) \ - .add_defstatus(DState.aborted) \ - .add_defstatus(DState.submitted) \ - .add_defstatus(DState.suspended) \ - .add_defstatus(DState.active) - - # - # add clock - # - clock = Clock(1, 1, 2010, False) # day,month, year, hybrid - clock.set_gain(1, 10, True) # True means positive gain + +def test_suite_clock_and_end_clock(): + clock = Clock(1, 1, 2010, False) + clock.set_gain(1, 10, True) suite = Suite("suite") suite.add_clock(clock).add_variable("fred1", "j") + assert suite.get_clock() is not None - end_clock = Clock(1, 1, 2017, False) # day,month, year, hybrid + end_clock = Clock(1, 1, 2017, False) suite.add_end_clock(end_clock).add_variable("fred2", "j") + assert suite.get_end_clock() is not None - clock = Clock(1, 1, 2011, True) # day,month, year, hybrid + clock = Clock(1, 1, 2011, True) clock.set_gain_in_seconds(12, True) s1 = Suite("s1") s1.add_clock(clock).add_variable("fred3", "j") s1.add_end_clock(end_clock).add_variable("fred4", "j") + assert s1.get_clock() is not None + assert s1.get_end_clock() is not None + - # - # Add zombie. Note we can *NOT* add two zombie attributes of the same ZombieType - # +def test_suite_add_delete_zombies(): + s1 = Suite("s1") zombie_life_time_in_server = 800 - child_list = [ChildCmdType.init, ChildCmdType.event, ChildCmdType.meter, ChildCmdType.label, ChildCmdType.wait, ChildCmdType.abort, ChildCmdType.complete] + child_list = [ + ChildCmdType.init, + ChildCmdType.event, + ChildCmdType.meter, + ChildCmdType.label, + ChildCmdType.wait, + ChildCmdType.abort, + ChildCmdType.complete, + ] zombie_type_list = [ZombieType.ecf, ZombieType.user, ZombieType.path] - count = 1; + count = 1 for zombie_type in zombie_type_list: - zombie_attr = ZombieAttr(zombie_type, child_list, ZombieUserActionType.block, zombie_life_time_in_server) + zombie_attr = ZombieAttr( + zombie_type, + child_list, + ZombieUserActionType.block, + zombie_life_time_in_server, + ) s1.add_zombie(zombie_attr).add_variable("afred" + str(count), "j") count += 1 - assert len(list(s1.zombies)) == 3, "Expected 3 zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 3 - # delete all the zombies s1.delete_zombie("") - assert len(list(s1.zombies)) == 0, "Expected zero zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 0 - # repeat the the test with empty child list. Empty child list means apply to all child commands - # + don't specify zombie_life_time_in_server as this is optional child_list = [] - zombie_type_list = [ZombieType.ecf, ZombieType.user, ZombieType.path] for zombie_type in zombie_type_list: zombie_attr = ZombieAttr(zombie_type, child_list, ZombieUserActionType.block) s1.add_zombie(zombie_attr).add_variable("bfred" + str(count), "j") - - # test delete of specific zombie attribute - assert len(list(s1.zombies)) == 1, "Expected 1 zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 1 s1.delete_zombie(zombie_type) - assert len(list(s1.zombies)) == 0, "Expected 0 zombie attributes but found " + str(len(list(s1.zombies))) - count += 1; - - print("All Tests pass") + assert len(list(s1.zombies)) == 0 + count += 1 diff --git a/libs/pyext/test/py_u_TestAutoAddExtern.py b/libs/pyext/test/py_u_TestAutoAddExtern.py index 650108a75..3b192e3c1 100644 --- a/libs/pyext/test/py_u_TestAutoAddExtern.py +++ b/libs/pyext/test/py_u_TestAutoAddExtern.py @@ -8,49 +8,127 @@ # nor does it submit to any jurisdiction. # -import os -from ecflow import Suite, Family, Task, Defs, Client, debug_build -import ecflow_test_util as Test +import pytest +from ecflow import Defs -def check_then_auto_add_extern(defs): + +@pytest.fixture +def defs(): + return Defs() + + +def _expect_check_fails_then_passes(defs): error_msg = defs.check() - print(error_msg) - assert len(error_msg) != 0, "Expect error in checks\n" + str(defs) + assert error_msg, "Expect error in checks\n" + str(defs) defs.auto_add_externs(True) error_msg = defs.check() - assert len(error_msg) == 0, "Expect check to pass after auto add extern\n" + error_msg + "\n" + str(defs) + assert not error_msg, ( + "Expect check to pass after auto add extern\n" + error_msg + "\n" + str(defs) + ) + + +def test_empty_defs_passes_check(defs): + error_msg = defs.check() + assert not error_msg, "Expect empty defs to pass check" + + +def test_auto_add_extern_for_trigger_path(defs): + defs.add_suite("ext").add_family("f1").add_task("t").add_trigger( + "/a/b/c/d == complete" + ) + _expect_check_fails_then_passes(defs) + + +def test_auto_add_extern_for_event_path(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_task("t").add_trigger("/a/b/c/d == complete") + suite.add_family("f2").add_task("t1").add_trigger("/a/b/c/d/e:event == set") + _expect_check_fails_then_passes(defs) + + +def test_auto_add_extern_for_bare_event_path(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_task("t").add_trigger("/a/b/c/d == complete") + suite.add_family("f2").add_task("t1").add_trigger("/a/b/c/d/e:event == set") + suite.add_family("f3").add_task("t2").add_trigger("/a/b/c/d/x:event") + _expect_check_fails_then_passes(defs) + + +def test_auto_add_extern_for_meter_path(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_task("t").add_trigger("/a/b/c/d == complete") + suite.add_family("f2").add_task("t1").add_trigger("/a/b/c/d/e:event == set") + suite.add_family("f3").add_task("t2").add_trigger("/a/b/c/d/x:event") + suite.add_family("f4").add_task("t3").add_trigger("/a/b/c/d/y:meter le 30") + _expect_check_fails_then_passes(defs) + + +def test_auto_add_extern_for_flag_path(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_task("t").add_trigger("/a/b/c/d == complete") + suite.add_family("f2").add_task("t1").add_trigger("/a/b/c/d/e:event == set") + suite.add_family("f3").add_task("t2").add_trigger("/a/b/c/d/x:event") + suite.add_family("f4").add_task("t3").add_trigger("/a/b/c/d/y:meter le 30") + suite.add_family("f5").add_task("t4").add_trigger("/a/b/c/d/zlate") + _expect_check_fails_then_passes(defs) + + +def test_auto_add_extern_for_family_inlimit(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_inlimit("hpcd", "limits") + _expect_check_fails_then_passes(defs) + + +def test_auto_add_extern_for_task_inlimit(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_inlimit("hpcd", "limits") + suite.add_family("f2").add_task("t").add_inlimit("sg1", "/suiteName") + _expect_check_fails_then_passes(defs) + + +def test_auto_add_extern_for_task_inlimit_with_family_path(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_inlimit("hpcd", "limits") + suite.add_family("f2").add_task("t").add_inlimit("sg1", "/suiteName") + suite.add_family("f3").add_task("t1").add_inlimit("hpcd", "/obs/limits") + _expect_check_fails_then_passes(defs) + +def test_auto_add_extern_for_task_inlimit_with_suite_path(defs): + suite = defs.add_suite("ext") + suite.add_family("f1").add_inlimit("hpcd", "limits") + suite.add_family("f2").add_task("t").add_inlimit("sg1", "/suiteName") + suite.add_family("f3").add_task("t1").add_inlimit("hpcd", "/obs/limits") + suite.add_family("f4").add_task("t2").add_inlimit("c1a", "/limits") + _expect_check_fails_then_passes(defs) -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) - defs = Defs() - error_msg = defs.check(); - assert len(error_msg) == 0, "Expect empty defs to pass check" - suite = defs.add_suite("ext"); +def test_auto_add_extern_incrementally_across_multiple_check_cycles(defs): + """Repeatedly call check()/auto_add_externs(True) on the same, growing Defs, + to verify auto_add_externs behaves correctly across multiple incremental + invocations rather than just once against a fully pre-built Defs.""" + suite = defs.add_suite("ext") f1 = suite.add_family("f1") - f1.add_task("t").add_trigger("/a/b/c/d == complete"); - check_then_auto_add_extern(defs) - f1.add_task("t1").add_trigger("/a/b/c/d/e:event == set"); - check_then_auto_add_extern(defs) - f1.add_task("t2").add_trigger("/a/b/c/d/x:event"); - check_then_auto_add_extern(defs) - f1.add_task("t3").add_trigger("/a/b/c/d/y:meter le 30"); - check_then_auto_add_extern(defs) - f1.add_task("t4").add_trigger("/a/b/c/d/zlate"); - check_then_auto_add_extern(defs) + f1.add_task("t").add_trigger("/a/b/c/d == complete") + _expect_check_fails_then_passes(defs) + f1.add_task("t1").add_trigger("/a/b/c/d/e:event == set") + _expect_check_fails_then_passes(defs) + f1.add_task("t2").add_trigger("/a/b/c/d/x:event") + _expect_check_fails_then_passes(defs) + f1.add_task("t3").add_trigger("/a/b/c/d/y:meter le 30") + _expect_check_fails_then_passes(defs) + f1.add_task("t4").add_trigger("/a/b/c/d/zlate") + _expect_check_fails_then_passes(defs) f2 = suite.add_family("f2") - f2.add_inlimit("hpcd", "limits"); - check_then_auto_add_extern(defs) - f2.add_task("t").add_inlimit("sg1", "/suiteName"); - check_then_auto_add_extern(defs) - f2.add_task("t1").add_inlimit("hpcd", "/obs/limits"); - check_then_auto_add_extern(defs) - f2.add_task("t2").add_inlimit("c1a", "/limits"); - check_then_auto_add_extern(defs) - print(defs) - print("All Tests pass") + f2.add_inlimit("hpcd", "limits") + _expect_check_fails_then_passes(defs) + f2.add_task("t").add_inlimit("sg1", "/suiteName") + _expect_check_fails_then_passes(defs) + f2.add_task("t1").add_inlimit("hpcd", "/obs/limits") + _expect_check_fails_then_passes(defs) + f2.add_task("t2").add_inlimit("c1a", "/limits") + _expect_check_fails_then_passes(defs) diff --git a/libs/pyext/test/py_u_TestAviso.py b/libs/pyext/test/py_u_TestAviso.py index 3ff93f050..630251f93 100644 --- a/libs/pyext/test/py_u_TestAviso.py +++ b/libs/pyext/test/py_u_TestAviso.py @@ -8,21 +8,20 @@ # nor does it submit to any jurisdiction. # -import os import io -import ecflow as ecf -import itertools as it -import ecflow_test_util as Test +import pytest + +import ecflow as ecf -def to_str(defs): +def _to_str(defs): buffer = io.StringIO() print(defs, file=buffer) return buffer.getvalue() -def can_create_aviso_from_parameters(): +def test_create_aviso_from_parameters(): aviso = ecf.AvisoAttr("name", "listener", "url", "schema", "polling", "auth") assert aviso.name() == "name" assert aviso.listener() == "'listener'" @@ -32,9 +31,8 @@ def can_create_aviso_from_parameters(): assert aviso.auth() == "auth" -def can_create_aviso_from_default_parameters_0(): +def test_create_aviso_from_default_parameters_0(): suite = ecf.Suite("s1") - family = ecf.Family("f1") suite.add_family(family) @@ -50,9 +48,8 @@ def can_create_aviso_from_default_parameters_0(): assert actual.auth() == "%ECF_AVISO_AUTH%" -def can_create_aviso_from_default_parameters_1(): +def test_create_aviso_from_default_parameters_1(): suite = ecf.Suite("s1") - family = ecf.Family("f1") suite.add_family(family) @@ -68,9 +65,8 @@ def can_create_aviso_from_default_parameters_1(): assert actual.auth() == "%ECF_AVISO_AUTH%" -def can_create_aviso_from_default_parameters_2(): +def test_create_aviso_from_default_parameters_2(): suite = ecf.Suite("s1") - family = ecf.Family("f1") suite.add_family(family) @@ -86,9 +82,8 @@ def can_create_aviso_from_default_parameters_2(): assert actual.auth() == "%ECF_AVISO_AUTH%" -def can_create_aviso_from_default_parameters_3(): +def test_create_aviso_from_default_parameters_3(): suite = ecf.Suite("s1") - family = ecf.Family("f1") suite.add_family(family) @@ -104,33 +99,39 @@ def can_create_aviso_from_default_parameters_3(): assert actual.auth() == "%ECF_AVISO_AUTH%" -def can_create_aviso_with_listener_details(): +def test_create_aviso_with_listener_details(): defs = ecf.Defs() suite = defs.add_suite("s") family = ecf.Family("f") suite.add_family(family) - task = ecf.Task('t', - ecf.AvisoAttr('aviso', - '{ "event": "dissemination", "request": { "destination": "CL1", "class": "od", "expver": "1", "stream": "oper", "step": [0, 12] } }', - 'https://aviso.ecmwf.int', - 'schema.json', - '60', - '/.ecmwfapirc')) + task = ecf.Task( + "t", + ecf.AvisoAttr( + "aviso", + '{ "event": "dissemination", "request": { "destination": "CL1", "class": "od", "expver": "1", "stream": "oper", "step": [0, 12] } }', + "https://aviso.ecmwf.int", + "schema.json", + "60", + "/.ecmwfapirc", + ), + ) family.add_task(task) - content = to_str(defs) + content = _to_str(defs) - assert f"aviso" in content - assert f'--listener \'{{ "event": "dissemination", "request": {{ "destination": "CL1", "class": "od", "expver": "1", "stream": "oper", "step": [0, 12] }} }}\'' in content - assert f"--url https://aviso.ecmwf.int" in content - assert f"--schema schema.json" in content - assert f"--polling 60" in content - assert f"--auth /.ecmwfapirc" in content + assert "aviso" in content + assert ( + '--listener \'{ "event": "dissemination", "request": { "destination": "CL1", "class": "od", "expver": "1", "stream": "oper", "step": [0, 12] } }\'' + in content + ) + assert "--url https://aviso.ecmwf.int" in content + assert "--schema schema.json" in content + assert "--polling 60" in content + assert "--auth /.ecmwfapirc" in content -def can_add_aviso_to_task(): +def test_add_aviso_to_task(): suite = ecf.Suite("s1") - family = ecf.Family("f1") suite.add_family(family) @@ -150,13 +151,14 @@ def can_add_aviso_to_task(): assert actual.auth() == "auth" -def can_embed_aviso_into_task(): +def test_embed_aviso_into_task(): suite = ecf.Suite("s1") - family = ecf.Family("f1") suite.add_family(family) - task = ecf.Task("f1", ecf.AvisoAttr("name", "listener", "url", "schema", "polling", "auth")) + task = ecf.Task( + "f1", ecf.AvisoAttr("name", "listener", "url", "schema", "polling", "auth") + ) assert len(list(task.avisos)) == 1 actual = list(task.avisos)[0] @@ -168,23 +170,21 @@ def can_embed_aviso_into_task(): assert actual.auth() == "auth" -def cannot_have_multiple_avisos_in_single_task(): +def test_multiple_avisos_in_single_task_is_rejected(): suite = ecf.Suite("s1") - family = ecf.Family("f1") suite.add_family(family) - try: - task = ecf.Task("f1", ecf.AvisoAttr("name", "listener", "url", "schema", "polling", "auth"), - ecf.AvisoAttr("another", "listener", "url", "schema", "polling", "auth")) - assert False, "Expected exception indicating multiple avisos are not allowed in a task" - except RuntimeError as e: - assert True + with pytest.raises(RuntimeError): + ecf.Task( + "f1", + ecf.AvisoAttr("name", "listener", "url", "schema", "polling", "auth"), + ecf.AvisoAttr("another", "listener", "url", "schema", "polling", "auth"), + ) -def can_check_job_creation_with_aviso(): +def test_check_job_creation_with_aviso(): defs = ecf.Defs() - suite = ecf.Suite("s") defs.add_suite(suite) @@ -198,20 +198,3 @@ def can_check_job_creation_with_aviso(): task.add_aviso(aviso) defs.check_job_creation() - - -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) - - can_create_aviso_from_parameters() - can_create_aviso_from_default_parameters_0() - can_create_aviso_from_default_parameters_1() - can_create_aviso_from_default_parameters_2() - can_create_aviso_from_default_parameters_3() - can_create_aviso_with_listener_details() - can_add_aviso_to_task() - can_embed_aviso_into_task() - cannot_have_multiple_avisos_in_single_task() - can_check_job_creation_with_aviso() - - print("All tests pass") diff --git a/libs/pyext/test/py_u_TestCollection.py b/libs/pyext/test/py_u_TestCollection.py index 2a08120ee..a0a7c5866 100644 --- a/libs/pyext/test/py_u_TestCollection.py +++ b/libs/pyext/test/py_u_TestCollection.py @@ -8,101 +8,148 @@ # nor does it submit to any jurisdiction. # +import pytest + import ecflow -import unittest - - -def setup_test(self): - self.defs0 = ecflow.Defs() - self.suite0 = ecflow.Suite("s") - self.family0 = ecflow.Family("s") - self.task0 = ecflow.Task("s") - - self.defs1 = ecflow.Defs(); - self.defs1.add_suite("s1") - self.suite1 = ecflow.Suite("s"); - self.suite1.add_task("t") - self.family1 = ecflow.Family("s"); - self.family1.add_task("t") - - self.defs3 = ecflow.Defs() - self.suite3 = ecflow.Suite("s") - self.family3 = ecflow.Family("s") - [self.defs3.add_suite("s" + str(i)) for i in [1, 2, 3]] - [self.suite3.add_family("f" + str(i)) for i in [1, 2, 3]] - [self.family3.add_task("t" + str(i)) for i in [1, 2, 3]] - - -class TestSizedProtocol(unittest.TestCase): - def setUp(self): - setup_test(self) - - def test_empty(self): - self.assertEqual(len(self.defs0), 0, "Expected empty defs to be of size zero") - self.assertEqual(len(self.suite0), 0, "Expected empty suite to be of size zero") - self.assertEqual(len(self.family0), 0, "Expected empty family to be of size zero") - self.assertEqual(len(self.task0), 0, "Expected empty task to be of size zero") - - def test_size1(self): - self.assertEqual(len(self.defs1), 1, "Expected defs with one suite") - self.assertEqual(len(self.suite1), 1, "Expected suite with one child") - self.assertEqual(len(self.family1), 1, "Expected family with one child") - - def test_size3(self): - self.assertEqual(len(self.defs3), 3, "Expected defs with 3 suite but found " + str(len(self.defs3))) - self.assertEqual(len(self.suite3), 3, "Expected suite with 3 child but found " + str(len(self.suite3))) - self.assertEqual(len(self.family3), 3, "Expected family with 3 child but found " + str(len(self.family3))) - - -class TestContainerProtocol(unittest.TestCase): - def setUp(self): - setup_test(self) - - def test_contains(self): - self.assertEqual(("s" not in self.defs0), True, "Err in defs container") - self.assertEqual(("z" not in self.defs3), True, "Err in defs container") - self.assertEqual(("s1" in self.defs3), True, "Err in defs container") - self.assertEqual(("s2" in self.defs3), True, "Err in defs container") - self.assertEqual(("s3" in self.defs3), True, "Err in defs container") - - self.assertEqual(("f" not in self.suite0), True, "Err in suites container") - self.assertEqual(("z" not in self.suite3), True, "Err in suites container") - self.assertEqual(("f1" in self.suite3), True, "Err in suites container") - self.assertEqual(("f2" in self.suite3), True, "Err in suites container") - self.assertEqual(("f3" in self.suite3), True, "Err in suites container") - - self.assertEqual(("z" not in self.family0), True, "Err in family container") - self.assertEqual(("t" not in self.family0), True, "Err in family container") - self.assertEqual(("t1" in self.family3), True, "Err in family container") - self.assertEqual(("t2" in self.family3), True, "Err in family container") - self.assertEqual(("t3" in self.family3), True, "Err in family container") - - -class TestIterableProtocol(unittest.TestCase): - def setUp(self): - setup_test(self) - - def test_iterator(self): - self.assertTrue(hasattr(self.defs3, '__iter__'), "defs has no __iter__") - self.assertTrue(hasattr(self.suite3, '__iter__'), "suite has no __iter__") - self.assertTrue(hasattr(self.family3, '__iter__'), "family has no __iter__") - self.assertTrue(hasattr(self.task0, '__iter__'), "task has no __iter__ for aliases") - self.assertEqual([suite.name() for suite in self.defs3], ["s1", "s2", "s3"], "Defs iterator protocol not working") - self.assertEqual([child.name() for child in self.suite3], ["f1", "f2", "f3"], "Suites iterator protocol not working") - self.assertEqual([child.name() for child in self.family3], ["t1", "t2", "t3"], "Family iterator protocol not working") - - -class TestNodesIteration(unittest.TestCase): - def setUp(self): - setup_test(self) - - def test_node_iterator(self): - self.assertEqual([suite.name() for suite in self.defs3.suites], ["s1", "s2", "s3"], "Defs.suites not working") - self.assertEqual([child.name() for child in self.suite3.nodes], ["f1", "f2", "f3"], "Suite.nodes working") - self.assertEqual([child.name() for child in self.family3.nodes], ["t1", "t2", "t3"], "Family.nodes not working") - self.assertEqual([alias.name() for child in self.task0.nodes], [], "Task.nodes not working") - - -if __name__ == "__main__": - unittest.main() - print("All Tests pass") + + +@pytest.fixture +def empty_containers(): + return { + "defs": ecflow.Defs(), + "suite": ecflow.Suite("s"), + "family": ecflow.Family("s"), + "task": ecflow.Task("s"), + } + + +@pytest.fixture +def single_item_containers(): + defs = ecflow.Defs() + defs.add_suite("s1") + suite = ecflow.Suite("s") + suite.add_task("t") + family = ecflow.Family("s") + family.add_task("t") + return {"defs": defs, "suite": suite, "family": family} + + +@pytest.fixture +def three_item_containers(): + defs = ecflow.Defs() + suite = ecflow.Suite("s") + family = ecflow.Family("s") + [defs.add_suite("s" + str(i)) for i in [1, 2, 3]] + [suite.add_family("f" + str(i)) for i in [1, 2, 3]] + [family.add_task("t" + str(i)) for i in [1, 2, 3]] + return {"defs": defs, "suite": suite, "family": family} + + +def test_empty_containers_have_length_zero(empty_containers): + assert len(empty_containers["defs"]) == 0, "Expected empty defs to be of size zero" + assert ( + len(empty_containers["suite"]) == 0 + ), "Expected empty suite to be of size zero" + assert ( + len(empty_containers["family"]) == 0 + ), "Expected empty family to be of size zero" + assert len(empty_containers["task"]) == 0, "Expected empty task to be of size zero" + + +def test_single_item_containers_have_length_one(single_item_containers): + assert len(single_item_containers["defs"]) == 1, "Expected defs with one suite" + assert len(single_item_containers["suite"]) == 1, "Expected suite with one child" + assert len(single_item_containers["family"]) == 1, "Expected family with one child" + + +def test_three_item_containers_have_length_three(three_item_containers): + assert ( + len(three_item_containers["defs"]) == 3 + ), "Expected defs with 3 suite but found " + str(len(three_item_containers["defs"])) + assert ( + len(three_item_containers["suite"]) == 3 + ), "Expected suite with 3 child but found " + str( + len(three_item_containers["suite"]) + ) + assert ( + len(three_item_containers["family"]) == 3 + ), "Expected family with 3 child but found " + str( + len(three_item_containers["family"]) + ) + + +def test_container_protocol_membership(three_item_containers, empty_containers): + defs0 = empty_containers["defs"] + defs3 = three_item_containers["defs"] + assert "s" not in defs0, "Err in defs container" + assert "z" not in defs3, "Err in defs container" + assert "s1" in defs3, "Err in defs container" + assert "s2" in defs3, "Err in defs container" + assert "s3" in defs3, "Err in defs container" + + suite0 = empty_containers["suite"] + suite3 = three_item_containers["suite"] + assert "f" not in suite0, "Err in suites container" + assert "z" not in suite3, "Err in suites container" + assert "f1" in suite3, "Err in suites container" + assert "f2" in suite3, "Err in suites container" + assert "f3" in suite3, "Err in suites container" + + family0 = empty_containers["family"] + family3 = three_item_containers["family"] + assert "z" not in family0, "Err in family container" + assert "t" not in family0, "Err in family container" + assert "t1" in family3, "Err in family container" + assert "t2" in family3, "Err in family container" + assert "t3" in family3, "Err in family container" + + +def test_iterable_protocol_iteration(three_item_containers, empty_containers): + defs3 = three_item_containers["defs"] + suite3 = three_item_containers["suite"] + family3 = three_item_containers["family"] + task0 = empty_containers["task"] + + assert hasattr(defs3, "__iter__"), "defs has no __iter__" + assert hasattr(suite3, "__iter__"), "suite has no __iter__" + assert hasattr(family3, "__iter__"), "family has no __iter__" + assert hasattr(task0, "__iter__"), "task has no __iter__ for aliases" + assert [suite.name() for suite in defs3] == [ + "s1", + "s2", + "s3", + ], "Defs iterator protocol not working" + assert [child.name() for child in suite3] == [ + "f1", + "f2", + "f3", + ], "Suites iterator protocol not working" + assert [child.name() for child in family3] == [ + "t1", + "t2", + "t3", + ], "Family iterator protocol not working" + + +def test_named_node_iteration(three_item_containers, empty_containers): + defs3 = three_item_containers["defs"] + suite3 = three_item_containers["suite"] + family3 = three_item_containers["family"] + task0 = empty_containers["task"] + + assert [suite.name() for suite in defs3.suites] == [ + "s1", + "s2", + "s3", + ], "Defs.suites not working" + assert [child.name() for child in suite3.nodes] == [ + "f1", + "f2", + "f3", + ], "Suite.nodes working" + assert [child.name() for child in family3.nodes] == [ + "t1", + "t2", + "t3", + ], "Family.nodes not working" + assert [alias.name() for alias in task0.nodes] == [], "Task.nodes not working" diff --git a/libs/pyext/test/py_u_TestCopy.py b/libs/pyext/test/py_u_TestCopy.py index c7cd675a0..4f251eabf 100644 --- a/libs/pyext/test/py_u_TestCopy.py +++ b/libs/pyext/test/py_u_TestCopy.py @@ -8,57 +8,69 @@ # nor does it submit to any jurisdiction. # -import ecflow -import sys -import os import copy -import ecflow_test_util as Test -if __name__ == "__main__": +import pytest + +import ecflow + + +@pytest.fixture +def time_series(): + start = ecflow.TimeSlot(0, 0) + finish = ecflow.TimeSlot(23, 0) + incr = ecflow.TimeSlot(0, 30) + return ecflow.TimeSeries(start, finish, incr, True) - Test.print_test_start(os.path.basename(__file__)) +def test_copy_defs_preserves_equality(): defs = ecflow.Defs() - defs.add_suite("a").add_family("f1").add_task("t1").add_variable("a", "b").add_event(1).add_meter("meter", 0, 100).add_label("label", "v").add_time("+00:30 20:00 01:00") - print(defs) + defs.add_suite("a").add_family("f1").add_task("t1").add_variable( + "a", "b" + ).add_event(1).add_meter("meter", 0, 100).add_label("label", "v").add_time( + "+00:30 20:00 01:00" + ) defs_copy = copy.copy(defs) - print(defs_copy) assert defs_copy == defs, "defs should be equal after copy" - # =========================================================================== - # Defs: add and delete *USER* variables - # =========================================================================== + +def test_copy_defs_user_variables_is_independent(): defs = ecflow.Defs() defs.add_variable("FRED", "/tmp/") - defs.add_variable("ECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%") + defs.add_variable( + "ECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%" + ) defs.add_variable("ECF_URL_BASE", "http://www.ecmwf.int") defs.add_variable("ECF_URL", "publications/manuals/sms") assert len(list(defs.user_variables)) == 4, "Expected *user* 4 variable" + defs_copy = copy.copy(defs) assert len(list(defs_copy.user_variables)) == 4, "Expected *user* 4 variable" - defs.delete_variable("FRED"); - assert len(list(defs.user_variables)) == 3, "Expected 3 variables since we just delete FRED" - defs.delete_variable(""); - assert len(list(defs.user_variables)) == 0, "Expected 0 variables since we should have deleted all" + defs.delete_variable("FRED") + assert ( + len(list(defs.user_variables)) == 3 + ), "Expected 3 variables since we just delete FRED" + defs.delete_variable("") + assert ( + len(list(defs.user_variables)) == 0 + ), "Expected 0 variables since we should have deleted all" assert len(list(defs_copy.user_variables)) == 4, "Expected *user* 4 variable" - # =========================================================================== - # Suite: add and delete variables - # =========================================================================== + +def test_copy_suite_variables_and_limits_are_independent(): suite = ecflow.Suite("s1") suite.add_variable(ecflow.Variable("ECF_HOME", "/tmp/")) - suite.add_variable("ECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%") + suite.add_variable( + "ECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%" + ) suite.add_variable("ECF_URL_BASE", "http://www.ecmwf.int") suite.add_variable("ECF_URL", "publications/manuals/sms") assert len(list(suite.variables)) == 4, "Expected 4 variable" suite_copy = copy.copy(suite) assert len(list(suite_copy.variables)) == 4, "Expected 4 variables in copy" - # =========================================================================== - # add and delete limits - # =========================================================================== suite.add_limit(ecflow.Limit("limitName1", 10)) suite.add_limit(ecflow.Limit("limitName2", 10)) suite.add_limit("limitName3", 10) @@ -67,92 +79,92 @@ suite_copy = copy.copy(suite) assert len(list(suite_copy.limits)) == 4, "Expected 4 Limits in copy" - suite_copy.delete_limit("limitName1"); - assert len(list(suite_copy.limits)) == 3, "Expected 3 limits since we just deleted one limitName1" - suite_copy.delete_limit(""); - assert len(list(suite_copy.limits)) == 0, "Expected 0 limits since we just deleted all of them" - - # =========================================================================== - # Test Limit and node paths - # =========================================================================== + suite_copy.delete_limit("limitName1") + assert ( + len(list(suite_copy.limits)) == 3 + ), "Expected 3 limits since we just deleted one limitName1" + suite_copy.delete_limit("") + assert ( + len(list(suite_copy.limits)) == 0 + ), "Expected 0 limits since we just deleted all of them" + + +def test_limit_increment_and_node_paths(): the_limit = ecflow.Limit("limitName1", 10) - assert the_limit.name() == "limitName1", "name not as expected" - assert the_limit.value() == 0, "Expected limit value of 0" - assert the_limit.limit() == 10, "Expected limit of 10" - assert len(list(the_limit.node_paths())) == 0, "Expected nodes which have consumed a limit to be empty" - - the_limit.increment(1, "/path1"); - assert the_limit.value() == 1, "Expected limit value of 1" - the_limit.increment(1, "/path2"); - assert the_limit.value() == 2, "Expected limit value of 2" - the_limit.increment(1, "/path3"); - assert the_limit.value() == 3, "Expected limit value of 3" - the_limit.increment(1, "/path4"); - assert the_limit.value() == 4, "Expected limit value of 4" - for path in the_limit.node_paths(): print(path) - assert len(list(the_limit.node_paths())) == 4, "expected 4 path" + assert the_limit.name() == "limitName1" + assert the_limit.value() == 0 + assert the_limit.limit() == 10 + assert len(list(the_limit.node_paths())) == 0 + + for i, path in enumerate(["/path1", "/path2", "/path3", "/path4"], start=1): + the_limit.increment(1, path) + assert the_limit.value() == i # verify value after each increment + assert the_limit.value() == 4 + assert len(list(the_limit.node_paths())) == 4 + +def test_limit_copy_is_independent_and_decrement_resets(): + the_limit = ecflow.Limit("limitName1", 10) + for path in ("/path1", "/path2", "/path3", "/path4"): + the_limit.increment(1, path) limit_copy = copy.copy(the_limit) - assert len(list(limit_copy.node_paths())) == 4, "expected 4 path in copy" - - limit_copy.decrement(1, "/path1"); - assert limit_copy.value() == 3, "Expected limit value of 3" - limit_copy.decrement(1, "/path2"); - assert limit_copy.value() == 2, "Expected limit value of 2" - limit_copy.decrement(1, "/path3"); - assert limit_copy.value() == 1, "Expected limit value of 1" - limit_copy.decrement(1, "/path4"); - assert limit_copy.value() == 0, "Expected limit value of 0" - assert len(list(limit_copy.node_paths())) == 0, "Expected nodes which have consumed a limit to be empty" - - limit_copy.increment(1, "/path1") # add same path, should only consume one token - limit_copy.increment(1, "/path1") - limit_copy.increment(1, "/path1") - limit_copy.increment(1, "/path1") - assert len(list(limit_copy.node_paths())) == 1, "expected 1 path" - assert limit_copy.value() == 1, "Expected limit value of 1" - - # =========================================================================== - # add and delete inlimits - # =========================================================================== + assert len(list(limit_copy.node_paths())) == 4 + + for i, path in enumerate(("/path1", "/path2", "/path3", "/path4"), start=1): + limit_copy.decrement(1, path) + assert limit_copy.value() == 4 - i # verify value after each decrement + assert limit_copy.value() == 0 + assert len(list(limit_copy.node_paths())) == 0 + + +def test_limit_same_path_reconsumes_single_token(): + limit_copy = ecflow.Limit("limitName1", 10) + for _ in range(4): + limit_copy.increment(1, "/path1") + assert len(list(limit_copy.node_paths())) == 1 + assert limit_copy.value() == 1 + + +def test_copy_suite_inlimits_independent(): + suite = ecflow.Suite("s1") suite.add_inlimit(ecflow.InLimit("limitName1", "/s1/f1", 2)) suite.add_inlimit(ecflow.InLimit("limitName2", "/s1/f1", 2, True)) suite.add_inlimit("limitName3", "/s1/f1", 2) suite.add_inlimit("limitName4", "/s1/f1", 2, True) - assert len(list(suite.inlimits)) == 4, "Expected 4 inLimits" + assert len(list(suite.inlimits)) == 4 suite_copy = copy.copy(suite) - assert len(list(suite_copy.inlimits)) == 4, "Expected 4 inLimits" - suite_copy.delete_inlimit("limitName1"); - assert len(list(suite_copy.inlimits)) == 3, "Expected 3 inlimits since we just deleted one limitName1" - suite_copy.delete_inlimit(""); - assert len(list(suite_copy.inlimits)) == 0, "Expected 0 inlimits since we just deleted all of them" - - # =============================================================================== - # add and delete triggers and complete - # =============================================================================== + assert len(list(suite_copy.inlimits)) == 4 + suite_copy.delete_inlimit("limitName1") + assert len(list(suite_copy.inlimits)) == 3 + suite_copy.delete_inlimit("") + assert len(list(suite_copy.inlimits)) == 0 + + +def test_copy_task_trigger_and_complete(): task = ecflow.Task("task") task.add_trigger("t2 == active") task.add_complete("t2 == complete") task_copy = copy.copy(task) - assert task_copy.get_complete(), "Expected complete" - assert task_copy.get_trigger(), "Expected trigger" - task_copy.delete_trigger(); - assert not task_copy.get_trigger(), "Expected no trigger" - task_copy.delete_complete(); - assert not task_copy.get_complete(), "Expected no complete" - - # =========================================================================== - # Add triggers using expressions - # =========================================================================== + assert task_copy.get_complete() + assert task_copy.get_trigger() + task_copy.delete_trigger() + assert not task_copy.get_trigger() + task_copy.delete_complete() + assert not task_copy.get_complete() + + +def test_copy_task_trigger_from_expression(): expr = ecflow.Expression("t1 == complete") task = ecflow.Task("task") task.add_trigger(expr) task_copy = copy.copy(task) - assert task_copy.get_trigger(), "Expected trigger" + assert task_copy.get_trigger() + +def test_copy_task_compound_expression_trigger(): expr = ecflow.Expression("t1 == complete") expr.add(ecflow.PartExpression("t1 == complete", True)) expr.add(ecflow.PartExpression("t2 == complete", True)) @@ -160,211 +172,149 @@ task.add_trigger(expr) task_copy = copy.copy(task) - assert task_copy.get_trigger(), "Expected trigger" + assert task_copy.get_trigger() + - # =========================================================================== - # add,delete,find events - # =========================================================================== - task.add_event(ecflow.Event(1)); +def test_copy_task_event_add_delete_find(): + task = ecflow.Task("task") + task.add_event(ecflow.Event(1)) task.add_event(2) task.add_event(ecflow.Event(10, "Eventname")) task.add_event(10, "Eventname2") task.add_event("fred") - # test find task_copy = copy.copy(task) event = task_copy.find_event("EVENT") - assert (event.empty()), "Expected to not find event" - assert (event.name() == ""), "Expected to not to find event, number is maximum int" - assert (event.value() == 0), "Expected to not to find event" - - event = task_copy.find_event("1"); - assert (not event.empty()), "Expected to find event" - assert (event.number() == 1), "Expected to find event 1" - assert (event.name() == ""), "Expected name to be empty" - assert (event.value() == 0), "Expected to not to find event" - - event = task_copy.find_event("2"); - assert (not event.empty()), "Expected to find event" - assert (event.number() == 2), "Expected to find event 1" - assert (event.name() == ""), "Expected name to be empty" - assert (event.value() == 0), "Expected to not to find event" - - event = task_copy.find_event("10"); - assert (not event.empty()), "Expected to find event" - assert (event.number() == 10), "Expected to find event 10" - assert (event.name() == "Eventname"), "Expected name to be empty" - assert (event.value() == 0), "Expected to not to find event" - - event = task_copy.find_event("fred"); - assert (not event.empty()), "Expected to find event" - assert (event.name() == "fred"), "Expected name to be empty, when name defind an not number, number is max_int" - assert (event.value() == 0), "Expected to not to find event" - - for e in task_copy.events: print(str(e), " # value: ", str(e.value())) - a_dict = {} - for e in task_copy.events: - if e.name() != "": - a_dict[e.name()] = e.value() - else: - a_dict[e.number()] = e.value() - print(a_dict) - - assert len(list(task_copy.events)) == 5, "Expected 5 Events" - task_copy.delete_event("1"); - assert len(list(task_copy.events)) == 4, "Expected 4 Events" - task_copy.delete_event("Eventname"); - assert len(list(task_copy.events)) == 3, "Expected 3 Events" - task_copy.delete_event(""); - assert len(list(task_copy.events)) == 0, "Expected 0 Events" - - # =========================================================================== - # add and delete meter - # =========================================================================== + assert event.empty() + assert event.name() == "" + assert event.value() == 0 + + event = task_copy.find_event("1") + assert not event.empty() + assert event.number() == 1 + assert event.name() == "" + assert event.value() == 0 + + event = task_copy.find_event("2") + assert not event.empty() + assert event.number() == 2 + assert event.name() == "" + assert event.value() == 0 + + event = task_copy.find_event("10") + assert not event.empty() + assert event.number() == 10 + assert event.name() == "Eventname" + assert event.value() == 0 + + event = task_copy.find_event("fred") + assert not event.empty() + assert event.name() == "fred" + assert event.value() == 0 + + assert len(list(task_copy.events)) == 5 + task_copy.delete_event("1") + assert len(list(task_copy.events)) == 4 + task_copy.delete_event("Eventname") + assert len(list(task_copy.events)) == 3 + task_copy.delete_event("") + assert len(list(task_copy.events)) == 0 + + +def test_copy_task_meter_delete(): + task = ecflow.Task("task") task.add_meter(ecflow.Meter("metername1", 0, 100, 50)) task.add_meter(ecflow.Meter("metername2", 0, 100)) task.add_meter("metername3", 0, 100, 50) task.add_meter("metername4", 0, 100) task_copy = copy.copy(task) - assert len(list(task_copy.meters)) == 4, "Expected 4 Meters" - task_copy.delete_meter("metername1"); - assert len(list(task_copy.meters)) == 3, "Expected 3 Meters" - task_copy.delete_meter("metername4"); - assert len(list(task_copy.meters)) == 2, "Expected 2 Meters" - task_copy.delete_meter(""); - assert len(list(task_copy.meters)) == 0, "Expected 0 Meters" - - # =========================================================================== - # add and delete queue - # =========================================================================== + assert len(list(task_copy.meters)) == 4 + task_copy.delete_meter("metername1") + assert len(list(task_copy.meters)) == 3 + task_copy.delete_meter("metername4") + assert len(list(task_copy.meters)) == 2 + task_copy.delete_meter("") + assert len(list(task_copy.meters)) == 0 + + +def test_copy_task_queue_delete(): queue_items = ["a", "b"] + task = ecflow.Task("task") task.add_queue(ecflow.Queue("q1", queue_items)) task.add_queue(ecflow.Queue("q2", queue_items)) task.add_queue("q3", ["a", "b"]) task.add_queue("q4", ["a", "b"]) task_copy = copy.copy(task) - assert len(list(task_copy.queues)) == 4, "Expected 4 queues" - task_copy.delete_queue("q1"); - assert len(list(task_copy.queues)) == 3, "Expected 3 queues" - task_copy.delete_queue("q2"); - assert len(list(task_copy.queues)) == 2, "Expected 2 queues" - task_copy.delete_queue(""); - assert len(list(task_copy.queues)) == 0, "Expected 0 queues" - - # =========================================================================== - # copy generic - # =========================================================================== - generic_items = ["a", "b"] - task.add_generic(ecflow.Generic("q1", generic_items)) - task.add_generic(ecflow.Generic("q2", generic_items)) + assert len(list(task_copy.queues)) == 4 + task_copy.delete_queue("q1") + assert len(list(task_copy.queues)) == 3 + task_copy.delete_queue("q2") + assert len(list(task_copy.queues)) == 2 + task_copy.delete_queue("") + assert len(list(task_copy.queues)) == 0 + + +def test_copy_task_generic_delete(): + task = ecflow.Task("task") + task.add_generic(ecflow.Generic("q1", ["a", "b"])) + task.add_generic(ecflow.Generic("q2", ["a", "b"])) task.add_generic("q3", ["a", "b"]) task.add_generic("q4", ["a", "b"]) task_copy = copy.copy(task) - assert len(list(task_copy.generics)) == 4, "Expected 4 Generics after copy" - task_copy.delete_generic("q1"); - assert len(list(task_copy.generics)) == 3, "Expected 3 Generics" - task_copy.delete_generic("q2"); - assert len(list(task_copy.generics)) == 2, "Expected 2 Generics" - task_copy.delete_generic(""); - assert len(list(task_copy.generics)) == 0, "Expected 0 Generics" - - # =========================================================================== - # add and delete label - # =========================================================================== + assert len(list(task_copy.generics)) == 4 + task_copy.delete_generic("q1") + assert len(list(task_copy.generics)) == 3 + task_copy.delete_generic("q2") + assert len(list(task_copy.generics)) == 2 + task_copy.delete_generic("") + assert len(list(task_copy.generics)) == 0 + + +def test_copy_task_label_delete(): + task = ecflow.Task("task") task.add_label(ecflow.Label("label_name1", "value")) task.add_label(ecflow.Label("label_name2", "value")) task.add_label("label_name3", "value") task.add_label("label_name4", "value") task_copy = copy.copy(task) - assert len(list(task_copy.labels)) == 4, "Expected 4 labels" - task_copy.delete_label("label_name1"); - assert len(list(task_copy.labels)) == 3, "Expected 3 Labels" - task_copy.delete_label("label_name4"); - assert len(list(task_copy.labels)) == 2, "Expected 2 Labels" - task_copy.delete_label(""); - assert len(list(task_copy.labels)) == 0, "Expected 0 Labels" - - # =========================================================================== - # add delete Repeat - # =========================================================================== - task = ecflow.Task("task") - task.add_repeat(ecflow.RepeatInteger("integer", 0, 100, 2)) - task_copy = copy.copy(task) - repeat = task_copy.get_repeat(); - assert not repeat.empty(), "Expected repeat" - task_copy.delete_repeat() - repeat = task_copy.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task = ecflow.Task("task") - task.add_repeat(ecflow.RepeatEnumerated("enum", ["red", "green", "blue"])) - task_copy = copy.copy(task) - task_copy.delete_repeat() - repeat = task_copy.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task = ecflow.Task("task") - task.add_repeat(ecflow.RepeatDate("date", 20100111, 20100115, 2)) - task_copy = copy.copy(task) - task_copy.delete_repeat() - repeat = task_copy.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - task = ecflow.Task("task") - task.add_repeat(ecflow.RepeatDateTime("datetime", "20100111T000000", "20100115T000000", "48:00:00")) - task_copy = copy.copy(task) - task_copy.delete_repeat() - repeat = task_copy.get_repeat(); - assert repeat.empty(), "Expected no repeat" - + assert len(list(task_copy.labels)) == 4 + task_copy.delete_label("label_name1") + assert len(list(task_copy.labels)) == 3 + task_copy.delete_label("label_name4") + assert len(list(task_copy.labels)) == 2 + task_copy.delete_label("") + assert len(list(task_copy.labels)) == 0 + + +@pytest.mark.parametrize( + "repeat_attr", + [ + ecflow.RepeatInteger("integer", 0, 100, 2), + ecflow.RepeatEnumerated("enum", ["red", "green", "blue"]), + ecflow.RepeatDate("date", 20100111, 20100115, 2), + ecflow.RepeatDateTime( + "datetime", "20100111T000000", "20100115T000000", "48:00:00" + ), + ecflow.RepeatDateList("date", [20100111, 20100115]), + ecflow.RepeatDateTimeList("datetime", ["20100111T000000", "20100115T000000"]), + ecflow.RepeatString("string", ["a", "b", "c"]), + ], +) +def test_copy_task_repeat_present_then_deleted(repeat_attr): task = ecflow.Task("task") - task.add_repeat(ecflow.RepeatDateList("date", [20100111, 20100115])) + task.add_repeat(repeat_attr) task_copy = copy.copy(task) + assert not task_copy.get_repeat().empty(), "Expected repeat" task_copy.delete_repeat() - repeat = task_copy.get_repeat(); - assert repeat.empty(), "Expected no repeat" + assert task_copy.get_repeat().empty(), "Expected no repeat" - task = ecflow.Task("task") - task.add_repeat(ecflow.RepeatDateTimeList("datetime", ["20100111T000000", "20100115T000000"])) - task_copy = copy.copy(task) - task_copy.delete_repeat() - repeat = task_copy.get_repeat(); - assert repeat.empty(), "Expected no repeat" +def test_copy_task_today_delete(time_series): task = ecflow.Task("task") - task.add_repeat(ecflow.RepeatString("string", ["a", "b", "c"])) - task_copy = copy.copy(task) - task_copy.delete_repeat() - repeat = task_copy.get_repeat(); - assert repeat.empty(), "Expected no repeat" - - # =========================================================================== - # create a time series, used for adding time and today - # =========================================================================== - start = ecflow.TimeSlot(0, 0) - finish = ecflow.TimeSlot(23, 0) - incr = ecflow.TimeSlot(0, 30) - time_series = ecflow.TimeSeries(start, finish, incr, True) - - # Add and delete today - # NOTE: ********************************************************************** - # Do *NOT* delete attributes using iterator traversal - # The iterators *are* bound to the c++ iterators hence if we delete - # over the traversal, we can corrupt the vector, leading to undefined behavour. - # Hence we **cannot** delete more than once over a traversal - # Soln 1: take a copy - # ***************************************************************************** - # task.add_today( Today( 0,10 )) - # task.add_today( Today( 0,59, True )) - # task.add_today( Today(TimeSlot(20,10)) ) - # task.add_today( Today(TimeSlot(20,20),False)) - # for today in task.todays: - # task.delete_today(today) # will corrupt C++ vector - task.add_today("00:30") task.add_today("+00:30") task.add_today("+00:30 20:00 01:00") @@ -375,14 +325,15 @@ task.add_today(ecflow.Today(ecflow.TimeSlot(20, 20), False)) task_copy = copy.copy(task) - assert len(list(task_copy.todays)) == 8, "Expected 8 todays" + assert len(list(task_copy.todays)) == 8 deleting = [copy.copy(today) for today in task_copy.todays] - for today in deleting: task_copy.delete_today(today) - assert len(list(task_copy.todays)) == 0, "Expected 0 todays" + for today in deleting: + task_copy.delete_today(today) + assert len(list(task_copy.todays)) == 0 - # =========================================================================== - # add and delete time - # =========================================================================== + +def test_copy_task_time_delete(time_series): + task = ecflow.Task("task") task.add_time("00:30") task.add_time("+00:30") task.add_time("+00:30 20:00 01:00") @@ -393,48 +344,50 @@ task.add_time(ecflow.Time(ecflow.TimeSlot(20, 20), False)) task_copy = copy.copy(task) - assert len(list(task_copy.times)) == 8, "Expected 8 times" + assert len(list(task_copy.times)) == 8 deleting = [copy.copy(time) for time in task_copy.times] - for time in deleting: task_copy.delete_time(time) - assert len(list(task_copy.times)) == 0, "Expected 0 todays" + for time in deleting: + task_copy.delete_time(time) + assert len(list(task_copy.times)) == 0 + - # =========================================================================== - # add and delete date - # =========================================================================== +def test_copy_task_date_delete(): + task = ecflow.Task("task") for i in [1, 2, 4, 8, 16]: task.add_date(i, 0, 0) task.add_date(ecflow.Date(1, 1, 2010)) - task.add_date(1, 1, 2010) # duplicate + task.add_date(1, 1, 2010) task.add_date(ecflow.Date(2, 1, 2010)) task.add_date(ecflow.Date(3, 1, 2010)) task.add_date(ecflow.Date(4, 1, 2010)) task_copy = copy.copy(task) - assert len(list(task_copy.dates)) == 10, "Expected 10 dates but found " + str(len(list(task_copy.dates))) + assert len(list(task_copy.dates)) == 10 deleting = [copy.copy(attr) for attr in task_copy.dates] - for attr in deleting: task_copy.delete_date(attr) - assert len(list(task_copy.dates)) == 0, "Expected 0 dates" + for attr in deleting: + task_copy.delete_date(attr) + assert len(list(task_copy.dates)) == 0 + - # =========================================================================== - # add and delete day - # =========================================================================== +def test_copy_task_day_delete(): + task = ecflow.Task("task") task.add_day(ecflow.Day(ecflow.Days.sunday)) task.add_day(ecflow.Days.monday) - task.add_day(ecflow.Days.tuesday) # duplicate ? + task.add_day(ecflow.Days.tuesday) task.add_day("sunday") task_copy = copy.copy(task) - assert len(list(task_copy.days)) == 4, "Expected 4 days" + assert len(list(task_copy.days)) == 4 deleting = [copy.copy(attr) for attr in task_copy.days] - for attr in deleting: task_copy.delete_day(attr) - assert len(list(task_copy.days)) == 0, "Expected 0 days" + for attr in deleting: + task_copy.delete_day(attr) + assert len(list(task_copy.days)) == 0 + - # =========================================================================== - # add and delete crons - # =========================================================================== +def test_copy_task_cron_delete(): cron = ecflow.Cron() start = ecflow.TimeSlot(23, 0) - ts = ecflow.TimeSeries(start, True) # True means relative to suite start + ts = ecflow.TimeSeries(start, True) cron.set_time_series(ts) cron = ecflow.Cron() @@ -444,12 +397,12 @@ start = ecflow.TimeSlot(0, 0) finish = ecflow.TimeSlot(23, 0) incr = ecflow.TimeSlot(0, 30) - ts = ecflow.TimeSeries(start, finish, incr, True) # True means relative to suite start + ts = ecflow.TimeSeries(start, finish, incr, True) cron.set_time_series(ts) cron0 = ecflow.Cron() cron0.set_week_days([0, 1, 2, 3, 4, 5]) - cron0.set_time_series(1, 30) # default relative = false, added in release 4.0.7 + cron0.set_time_series(1, 30) cron1 = ecflow.Cron() cron1.set_week_days([0, 1, 2, 3, 4, 5, 6]) @@ -470,78 +423,92 @@ task.add_cron(cron3) task_copy = copy.copy(task) - assert len(list(task_copy.crons)) == 4, "Expected 4 crons" + assert len(list(task_copy.crons)) == 4 deleting = [copy.copy(attr) for attr in task_copy.crons] - for attr in deleting: task_copy.delete_cron(attr) - assert len(list(task_copy.crons)) == 0, "Expected 0 crons" + for attr in deleting: + task_copy.delete_cron(attr) + assert len(list(task_copy.crons)) == 0 + - # =========================================================================== - # add autocancel - # =========================================================================== - print("test add autoCancel") +def test_copy_task_autocancel(): t1 = ecflow.Task("t1") - assert t1.get_autocancel() is None, " Expected no autocancel" - t1.add_autocancel(3) # 3 days + assert t1.get_autocancel() is None + t1.add_autocancel(3) task_copy = copy.copy(t1) - assert task_copy.get_autocancel() is not None, " Expected autocancel" - print(str(task_copy.get_autocancel())) + assert task_copy.get_autocancel() is not None - # =========================================================================== - # add late - # =========================================================================== + +def test_copy_task_late(): + task = ecflow.Task("task") late = ecflow.Late() late.submitted(ecflow.TimeSlot(20, 10)) late.active(ecflow.TimeSlot(20, 10)) late.complete(ecflow.TimeSlot(20, 10), True) task.add_late(late) task_copy = copy.copy(task) - assert task_copy.get_late() is not None, " Expected late" + assert task_copy.get_late() is not None - # =========================================================================== - # add defstatus, last one set takes effect - # =========================================================================== + +def test_copy_task_defstatus(): + task = ecflow.Task("task") task.add_defstatus(ecflow.DState.complete) task_copy = copy.copy(task) assert task_copy.get_defstatus() == ecflow.DState.complete - # =========================================================================== - # add clock - # =========================================================================== - clock = ecflow.Clock(1, 1, 2010, False) # day,month, year, hybrid - clock.set_gain(1, 10, True) # True means positive gain + +def test_copy_suite_clock(): + clock = ecflow.Clock(1, 1, 2010, False) + clock.set_gain(1, 10, True) suite = ecflow.Suite("suite") - assert suite.get_clock() is None, "Expected no clock" + assert suite.get_clock() is None suite.add_clock(clock) suite_copy = copy.copy(suite) - assert suite_copy.get_clock() is not None, "expected clock" + assert suite_copy.get_clock() is not None - # =========================================================================== - # Add zombie. Note we can *NOT* add two zombie attributes of the same ZombieType - # =========================================================================== + +def test_copy_zombie_attributes(): s1 = ecflow.Task("s1") zombie_life_time_in_server = 800 - child_list = [ecflow.ChildCmdType.init, ecflow.ChildCmdType.event, ecflow.ChildCmdType.meter, ecflow.ChildCmdType.label, ecflow.ChildCmdType.wait, ecflow.ChildCmdType.abort, ecflow.ChildCmdType.complete] - zombie_type_list = [ecflow.ZombieType.ecf, ecflow.ZombieType.ecf_pid, ecflow.ZombieType.ecf_pid_passwd, ecflow.ZombieType.ecf_passwd, ecflow.ZombieType.user, ecflow.ZombieType.path] + child_list = [ + ecflow.ChildCmdType.init, + ecflow.ChildCmdType.event, + ecflow.ChildCmdType.meter, + ecflow.ChildCmdType.label, + ecflow.ChildCmdType.wait, + ecflow.ChildCmdType.abort, + ecflow.ChildCmdType.complete, + ] + zombie_type_list = [ + ecflow.ZombieType.ecf, + ecflow.ZombieType.ecf_pid, + ecflow.ZombieType.ecf_pid_passwd, + ecflow.ZombieType.ecf_passwd, + ecflow.ZombieType.user, + ecflow.ZombieType.path, + ] for zombie_type in zombie_type_list: - zombie_attr = ecflow.ZombieAttr(zombie_type, child_list, ecflow.ZombieUserActionType.block, zombie_life_time_in_server) + zombie_attr = ecflow.ZombieAttr( + zombie_type, + child_list, + ecflow.ZombieUserActionType.block, + zombie_life_time_in_server, + ) s1.add_zombie(zombie_attr) task_copy = copy.copy(s1) - assert len(list(task_copy.zombies)) == 6, "Expected 6 zombie attributes but found " + str(len(list(task_copy.zombies))) + assert len(list(task_copy.zombies)) == 6 - # delete all the zombies s1.delete_zombie("") - assert len(list(s1.zombies)) == 0, "Expected zero zombie attributes but found " + str(len(list(s1.zombies))) + assert len(list(s1.zombies)) == 0 - # add with zombie_life_time_in_server not set, this is optional for zombie_type in zombie_type_list: - zombie_attr = ecflow.ZombieAttr(zombie_type, child_list, ecflow.ZombieUserActionType.block) + zombie_attr = ecflow.ZombieAttr( + zombie_type, child_list, ecflow.ZombieUserActionType.block + ) s1.add_zombie(zombie_attr) task_copy = copy.copy(s1) - assert len(list(task_copy.zombies)) == 6, "Expected 6 zombie attributes but found " + str(len(list(task_copy.zombies))) + assert len(list(task_copy.zombies)) == 6 s1.delete_zombie("") - assert len(list(s1.zombies)) == 0, "Expected zero zombie attributes but found " + str(len(list(s1.zombies))) - - print("All Tests pass") + assert len(list(s1.zombies)) == 0 diff --git a/libs/pyext/test/py_u_TestCron.py b/libs/pyext/test/py_u_TestCron.py index ed42cea0b..816fc0626 100644 --- a/libs/pyext/test/py_u_TestCron.py +++ b/libs/pyext/test/py_u_TestCron.py @@ -8,105 +8,189 @@ # nor does it submit to any jurisdiction. # -from ecflow import Alias, AttrType, Autocancel, CheckPt, ChildCmdType, Client, Clock, Cron, DState, Date, Day, Days, \ - Defs, Ecf, Event, Expression, Family, FamilyVec, File, Flag, FlagType, FlagTypeVec, InLimit, \ - JobCreationCtrl, Label, Late, Limit, Meter, Node, NodeContainer, NodeVec, PartExpression, PrintStyle, \ - Repeat, RepeatDate, RepeatDay, RepeatEnumerated, RepeatInteger, RepeatString, SState, State, Style, \ - Submittable, Suite, SuiteVec, Task, TaskVec, Time, TimeSeries, TimeSlot, Today, UrlCmd, Variable, \ - VariableList, Verify, WhyCmd, ZombieAttr, ZombieType, ZombieUserActionType, Trigger, Complete, Edit, Defstatus -import unittest -import sys -import os - - -class TestCron(unittest.TestCase): - def setUp(self): - cron = Cron() - cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) - cron.set_days_of_month([1, 2, 3, 4, 5, 6]) - cron.set_months([1, 2, 3, 4, 5, 6]) - cron.set_time_series("+00:00 23:00 00:30") - self.cron1 = cron - - cron2 = Cron() - cron2.set_time_series(1, 30) # default relative = false, added in release 4.0.7 - self.cron2 = cron2 - - cron3 = Cron() - cron3.set_week_days([0, 1, 2, 3, 4, 5, 6]) - cron3.set_time_series(1, 30, True) - self.cron3 = cron3 - - cron4 = Cron() - cron4.set_week_days([0, 1, 2, 3, 4, 5, 6]) - cron4.set_time_series("00:30 01:30 00:01") - self.cron4 = cron4 - - cron5 = Cron() - cron5.set_week_days([0, 1, 2, 3, 4, 5, 6]) - cron5.set_time_series("+00:30") - self.cron5 = cron5 - - start = TimeSlot(0, 0) - finish = TimeSlot(23, 0) - incr = TimeSlot(0, 30) - time_series = TimeSeries(start, finish, incr, True) - cron6 = Cron(time_series) - cron6.set_week_days([0, 1, 2, 3]) - self.cron6 = cron6 - - cron7 = Cron() - cron7.set_week_days([0, 1, 2]) # This cant overlap with last_week_days_of_the_month - cron7.set_last_week_days_of_the_month([3, 4, 5, 6]) # This cant overlap with days_of_week - cron7.set_days_of_month([1, 2, 3, 4, 5, 6]) - cron7.set_months([1, 2, 3, 4, 5, 6]) - cron7.set_time_series("+00:00 23:00 00:30") - cron7.set_last_day_of_the_month() - self.cron7 = cron7 - - def test_me0(self): - cron = Cron("+00:00 23:00 00:30", days_of_week=[0, 1, 2, 3, 4, 5, 6], days_of_month=[1, 2, 3, 4, 5, 6], months=[1, 2, 3, 4, 5, 6]) - self.assertEqual(self.cron1, cron, "cron not equal\n" + str(self.cron1) + "\n" + str(cron)) - - cron = Cron("01:30") - self.assertEqual(self.cron2, cron, "cron not equal\n" + str(self.cron2) + "\n" + str(cron)) - - cron = Cron("+01:30", days_of_week=[0, 1, 2, 3, 4, 5, 6]) - self.assertEqual(self.cron3, cron, "cron not equal\n" + str(self.cron3) + "\n" + str(cron)) - - cron = Cron("00:30 01:30 00:01", days_of_week=[0, 1, 2, 3, 4, 5, 6]) - self.assertEqual(self.cron4, cron, "cron not equal\n" + str(self.cron4) + "\n" + str(cron)) - - cron = Cron("+00:30", days_of_week=[0, 1, 2, 3, 4, 5, 6]) - self.assertEqual(self.cron5, cron, "cron not equal\n" + str(self.cron5) + "\n" + str(cron)) - - cron = Cron("+00:00 23:00 00:30", days_of_week=[0, 1, 2, 3]) - self.assertEqual(self.cron6, cron, "cron not equal\n" + str(self.cron6) + "\n" + str(cron)) - - cron = Cron("+00:00 23:00 00:30", days_of_week=[0, 1, 2], # This cant overlap with last_week_days_of_the_month - last_week_days_of_the_month=[3, 4, 5, 6], # This cant overlap with days_of_week - days_of_month=[1, 2, 3, 4, 5, 6], - months=[1, 2, 3, 4, 5, 6], - last_day_of_the_month=True) - self.assertEqual(self.cron7, cron, "cron not equal\n" + str(self.cron7) + "\n" + str(cron)) - - def test_fail(self): - with self.assertRaises(RuntimeError): - cron = Cron(""); # empty - with self.assertRaises(RuntimeError): - cron = Cron("", days_of_week=[0, 1, 2, 3, 4, 5, 6], days_of_month=[1, 2, 3, 4, 5, 6], months=[1, 2, 3, 4, 5, 6]); # empty - with self.assertRaises(RuntimeError): - cron = Cron("0023", days_of_week=[0, 1, 2, 3, 4, 5, 6], days_of_month=[1, 2, 3, 4, 5, 6], months=[1, 2, 3, 4, 5, 6]) # bad time - with self.assertRaises(RuntimeError): - cron = Cron("00:10", days_of_weekss=[0, 1, 2, 3, 4, 5, 6], days_of_month=[1, 2, 3, 4, 5, 6], months=[1, 2, 3, 4, 5, 6]) # bad kw,days_of_weekss - with self.assertRaises(RuntimeError): - cron = Cron("00:10", days_of_week=[0, 1, 2, 3, 4, 5, 6], days_of_months=[1, 2, 3, 4, 5, 6], months=[1, 2, 3, 4, 5, 6]) # bad kw, days_of_months - with self.assertRaises(RuntimeError): - cron = Cron("00:10", days_of_week=[0, 1, 2, 3, 4, 5, 6], days_of_month=[1, 2, 3, 4, 5, 6], monthss=[1, 2, 3, 4, 5, 6]) # bad kw, monthss - with self.assertRaises(RuntimeError): - cron = Cron("00:10", days_of_week="0,1,2,3,4,5,6", days_of_month=[1, 2, 3, 4, 5, 6], month=[1, 2, 3, 4, 5, 6]) # bad kw, expect vector of string - - -if __name__ == "__main__": - unittest.main() - print("All Tests pass") +import pytest + +from ecflow import Cron, TimeSlot, TimeSeries + + +@pytest.fixture +def cron1(): + cron = Cron() + cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) + cron.set_days_of_month([1, 2, 3, 4, 5, 6]) + cron.set_months([1, 2, 3, 4, 5, 6]) + cron.set_time_series("+00:00 23:00 00:30") + return cron + + +@pytest.fixture +def cron2(): + cron = Cron() + cron.set_time_series(1, 30) # default relative = false, added in release 4.0.7 + return cron + + +@pytest.fixture +def cron3(): + cron = Cron() + cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) + cron.set_time_series(1, 30, True) + return cron + + +@pytest.fixture +def cron4(): + cron = Cron() + cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) + cron.set_time_series("00:30 01:30 00:01") + return cron + + +@pytest.fixture +def cron5(): + cron = Cron() + cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) + cron.set_time_series("+00:30") + return cron + + +@pytest.fixture +def cron6(): + start = TimeSlot(0, 0) + finish = TimeSlot(23, 0) + incr = TimeSlot(0, 30) + time_series = TimeSeries(start, finish, incr, True) + cron = Cron(time_series) + cron.set_week_days([0, 1, 2, 3]) + return cron + + +@pytest.fixture +def cron7(): + cron = Cron() + cron.set_week_days([0, 1, 2]) # This can't overlap with last_week_days_of_the_month + cron.set_last_week_days_of_the_month( + [3, 4, 5, 6] + ) # This can't overlap with days_of_week + cron.set_days_of_month([1, 2, 3, 4, 5, 6]) + cron.set_months([1, 2, 3, 4, 5, 6]) + cron.set_time_series("+00:00 23:00 00:30") + cron.set_last_day_of_the_month() + return cron + + +def test_cron_with_week_days_days_of_month_and_months(cron1): + cron = Cron( + "+00:00 23:00 00:30", + days_of_week=[0, 1, 2, 3, 4, 5, 6], + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + ) + assert cron1 == cron, "cron not equal\n" + str(cron1) + "\n" + str(cron) + + +def test_cron_with_time_series_only(cron2): + cron = Cron("01:30") + assert cron2 == cron, "cron not equal\n" + str(cron2) + "\n" + str(cron) + + +def test_cron_with_relative_time_series_and_week_days(cron3): + cron = Cron("+01:30", days_of_week=[0, 1, 2, 3, 4, 5, 6]) + assert cron3 == cron, "cron not equal\n" + str(cron3) + "\n" + str(cron) + + +def test_cron_with_three_part_time_series_and_week_days(cron4): + cron = Cron("00:30 01:30 00:01", days_of_week=[0, 1, 2, 3, 4, 5, 6]) + assert cron4 == cron, "cron not equal\n" + str(cron4) + "\n" + str(cron) + + +def test_cron_with_single_relative_time_series_and_week_days(cron5): + cron = Cron("+00:30", days_of_week=[0, 1, 2, 3, 4, 5, 6]) + assert cron5 == cron, "cron not equal\n" + str(cron5) + "\n" + str(cron) + + +def test_cron_with_time_series_object(cron6): + cron = Cron("+00:00 23:00 00:30", days_of_week=[0, 1, 2, 3]) + assert cron6 == cron, "cron not equal\n" + str(cron6) + "\n" + str(cron) + + +def test_cron_with_last_day_and_last_week_days(cron7): + cron = Cron( + "+00:00 23:00 00:30", + days_of_week=[0, 1, 2], # This can't overlap with last_week_days_of_the_month + last_week_days_of_the_month=[ + 3, + 4, + 5, + 6, + ], # This can't overlap with days_of_week + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + last_day_of_the_month=True, + ) + assert cron7 == cron, "cron not equal\n" + str(cron7) + "\n" + str(cron) + + +def test_cron_with_empty_string_raises(): + with pytest.raises(RuntimeError): + Cron("") # empty + + +def test_cron_with_empty_string_and_valid_kwargs_raises(): + with pytest.raises(RuntimeError): + Cron( + "", + days_of_week=[0, 1, 2, 3, 4, 5, 6], + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + ) # empty + + +def test_cron_with_bad_time_raises(): + with pytest.raises(RuntimeError): + Cron( + "0023", + days_of_week=[0, 1, 2, 3, 4, 5, 6], + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + ) # bad time + + +def test_cron_with_misspelt_days_of_week_keyword_raises(): + with pytest.raises(RuntimeError): + Cron( + "00:10", + days_of_weekss=[0, 1, 2, 3, 4, 5, 6], + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + ) # bad kw,days_of_weekss + + +def test_cron_with_misspelt_days_of_month_keyword_raises(): + with pytest.raises(RuntimeError): + Cron( + "00:10", + days_of_week=[0, 1, 2, 3, 4, 5, 6], + days_of_months=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + ) # bad kw, days_of_months + + +def test_cron_with_misspelt_months_keyword_raises(): + with pytest.raises(RuntimeError): + Cron( + "00:10", + days_of_week=[0, 1, 2, 3, 4, 5, 6], + days_of_month=[1, 2, 3, 4, 5, 6], + monthss=[1, 2, 3, 4, 5, 6], + ) # bad kw, monthss + + +def test_cron_with_non_vector_days_of_week_raises(): + with pytest.raises(RuntimeError): + Cron( + "00:10", + days_of_week="0,1,2,3,4,5,6", + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + ) # bad kw, expect vector of int diff --git a/libs/pyext/test/py_u_TestCronConstructor.py b/libs/pyext/test/py_u_TestCronConstructor.py index 4d5992e89..304d5f500 100644 --- a/libs/pyext/test/py_u_TestCronConstructor.py +++ b/libs/pyext/test/py_u_TestCronConstructor.py @@ -8,14 +8,10 @@ # nor does it submit to any jurisdiction. # -import os import ecflow as ecf -import itertools as it -import ecflow_test_util as Test - -def can_create_cron_from_default_parameters(): +def test_create_cron_from_default_parameters(): cron = ecf.Cron() cron.set_days_of_month([10, 20, 30]) @@ -27,69 +23,76 @@ def can_create_cron_from_default_parameters(): assert cron.months == [1, 2, 3, 4, 5, 6] -def can_create_cron_that_runs_every_day_at_2pm(): - cron = ecf.Cron('14:00') +def test_create_cron_that_runs_every_day_at_2pm(): + cron = ecf.Cron("14:00") assert cron.time() == ecf.TimeSeries(14, 00) -def can_create_cron_that_runs_every_30_minutes_between_0am_and_8pm_on_the_first_six_days_of_the_months_from_january_until_july(): - cron = ecf.Cron('+00:00 20:00 00:30', days_of_month=[1, 2, 3, 4, 5, 6], months=[1, 2, 3, 4, 5, 6]) +def test_create_cron_that_runs_every_30_minutes_between_0am_and_8pm_on_the_first_six_days_of_the_months_from_january_until_july(): + cron = ecf.Cron( + "+00:00 20:00 00:30", + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2, 3, 4, 5, 6], + ) - assert cron.time() == ecf.TimeSeries(ecf.TimeSlot(0, 0), ecf.TimeSlot(20, 0), ecf.TimeSlot(0, 30), True) + assert cron.time() == ecf.TimeSeries( + ecf.TimeSlot(0, 0), ecf.TimeSlot(20, 0), ecf.TimeSlot(0, 30), True + ) assert cron.days_of_month == [1, 2, 3, 4, 5, 6] assert cron.months == [1, 2, 3, 4, 5, 6] -def can_create_cron_that_runs_relative_to_suite_start_time_or_task_requeue(): - cron = ecf.Cron('+00:15 23:00 00:30', days_of_week=[0, 1, 2], days_of_month=[4, 5, 6], months=[1, 2, 3]) +def test_create_cron_relative_to_suite_start_time_or_task_requeue(): + cron = ecf.Cron( + "+00:15 23:00 00:30", + days_of_week=[0, 1, 2], + days_of_month=[4, 5, 6], + months=[1, 2, 3], + ) - assert cron.time() == ecf.TimeSeries(ecf.TimeSlot(0, 15), ecf.TimeSlot(23, 0), ecf.TimeSlot(0, 30), True) + assert cron.time() == ecf.TimeSeries( + ecf.TimeSlot(0, 15), ecf.TimeSlot(23, 0), ecf.TimeSlot(0, 30), True + ) assert cron.week_days == [0, 1, 2] assert cron.days_of_month == [4, 5, 6] assert cron.months == [1, 2, 3] -def can_create_cron_based_on_time_series(): - bgn = ecf.TimeSlot(0 , 0) + +def test_create_cron_based_on_time_series(): + bgn = ecf.TimeSlot(0, 0) end = ecf.TimeSlot(23, 0) inc = ecf.TimeSlot(0, 30) ts = ecf.TimeSeries(bgn, end, inc, True) - cron = ecf.Cron(ts, days_of_week=[0,1,2,3,4,5,6],days_of_month=[1,2,3,4,5,6], months=[1,2]) + cron = ecf.Cron( + ts, + days_of_week=[0, 1, 2, 3, 4, 5, 6], + days_of_month=[1, 2, 3, 4, 5, 6], + months=[1, 2], + ) assert cron.time() == ts - assert cron.week_days == [0,1,2,3,4,5,6] - assert cron.days_of_month == [1,2,3,4,5,6] - assert cron.months == [1,2] + assert cron.week_days == [0, 1, 2, 3, 4, 5, 6] + assert cron.days_of_month == [1, 2, 3, 4, 5, 6] + assert cron.months == [1, 2] + -def can_create_cron_with_defaults_and_set_time_series(): +def test_create_cron_with_defaults_and_set_time_series(): cron = ecf.Cron() cron.set_time_series(1, 30, True) # same as cron +01:30 assert cron.time() == ecf.TimeSeries(1, 30, True) -def can_create_cron_that_runs_on_the_last_day_of_the_month(): + +def test_create_cron_that_runs_on_the_last_day_of_the_month(): cron = ecf.Cron() cron.set_last_day_of_the_month() - assert cron.last_day_of_the_month() == True + assert cron.last_day_of_the_month() is True -def can_create_cron_that_runs_on_the_last_weekdays_of_the_month(): + +def test_create_cron_that_runs_on_the_last_weekdays_of_the_month(): cron = ecf.Cron() cron.set_last_week_days_of_the_month([3, 4, 5]) assert cron.last_week_days_of_the_month == [3, 4, 5] - - -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) - - can_create_cron_from_default_parameters() - can_create_cron_that_runs_every_day_at_2pm() - can_create_cron_that_runs_every_30_minutes_between_0am_and_8pm_on_the_first_six_days_of_the_months_from_january_until_july() - can_create_cron_that_runs_relative_to_suite_start_time_or_task_requeue() - can_create_cron_based_on_time_series() - can_create_cron_with_defaults_and_set_time_series() - can_create_cron_that_runs_on_the_last_day_of_the_month() - can_create_cron_that_runs_on_the_last_weekdays_of_the_month() - - print("All tests pass") diff --git a/libs/pyext/test/py_u_TestDefs.py b/libs/pyext/test/py_u_TestDefs.py index 17ef00cf2..bd8ed078e 100644 --- a/libs/pyext/test/py_u_TestDefs.py +++ b/libs/pyext/test/py_u_TestDefs.py @@ -8,80 +8,123 @@ # nor does it submit to any jurisdiction. # -# This test ensures the creation of Defs objects and their components works as expected. - import os -import ecflow - -print(ecflow.__doc__) -print(ecflow.Defs.__doc__) - -from ecflow import Suite, Family, Task, Defs, Clock, DState, PartExpression, Variable, Limit, InLimit, \ - Date, Day, Event, Meter, Label, Autocancel, Days, TimeSlot, TimeSeries, Style, State, \ - RepeatString, RepeatDate, RepeatDateTime, RepeatInteger, RepeatDay, RepeatEnumerated, \ - Verify, PrintStyle, Time, Today, Late, Cron, Client, debug_build, Ecf -import ecflow_test_util as Test - -if __name__ == "__main__": - - Test.print_test_start(os.path.basename(__file__)) +import ecflow +import pytest + +from ecflow import ( + Suite, + Family, + Task, + Defs, + Clock, + DState, + PartExpression, + Variable, + Limit, + InLimit, + Date, + Day, + Days, + Event, + Meter, + Label, + Autocancel, + TimeSlot, + TimeSeries, + Style, + State, + RepeatString, + RepeatDate, + RepeatDateTime, + RepeatInteger, + RepeatDay, + RepeatEnumerated, + Verify, + PrintStyle, + Time, + Today, + Late, + Cron, +) + + +def test_node_type_isinstance_relationships(): suite = Suite("s1") - assert (isinstance(suite, ecflow.Suite)), "Expected suite" - assert (not isinstance(suite, ecflow.Family)), "Expected suite" - assert (not isinstance(suite, ecflow.Task)), "Expected suite" + assert isinstance(suite, ecflow.Suite) + assert not isinstance(suite, ecflow.Family) + assert not isinstance(suite, ecflow.Task) family = Family("f1") - assert (isinstance(family, ecflow.Family)), "Expected Family" - assert (not isinstance(family, ecflow.Suite)), "Expected Family" - assert (not isinstance(family, ecflow.Task)), "Expected Family" - suite.add_family(family) + assert isinstance(family, ecflow.Family) + assert not isinstance(family, ecflow.Suite) + assert not isinstance(family, ecflow.Task) task = Task("f1") - assert (isinstance(task, ecflow.Task)), "Expected Task" - assert (not isinstance(task, ecflow.Suite)), "Expected Task" - assert (not isinstance(task, ecflow.Family)), "Expected Task" - family.add_task(task) + assert isinstance(task, ecflow.Task) + assert not isinstance(task, ecflow.Suite) + assert not isinstance(task, ecflow.Family) + +def test_empty_defs_are_equal_and_default_state_unknown(): defs = Defs() defs2 = Defs() - assert defs == defs2, "Empty defs should be equal" + assert defs == defs2 + assert defs.get_state() == ecflow.State.unknown - assert defs.get_state() == ecflow.State.unknown, "Expected default state to be unknown" - defs.add_suite(suite); # add existing suite to defs - suite = defs.add_suite("s2"); +def test_defs_change_after_adding_suite(): + defs = Defs() + defs2 = Defs() + suite = Suite("s1") + suite.add_family(Family("f1")).add_task(Task("f1")) + defs.add_suite(suite) + assert defs != defs2 + + +def test_suite_construction_and_variables(): + defs = Defs() + suite = defs.add_suite("s2") suite.add_variable(Variable("ECF_HOME", "/tmp/")) - suite.add_variable("ECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%") + suite.add_variable( + "ECF_URL_CMD", "${BROWSER:=firefox} -new-tab %ECF_URL_BASE%/%ECF_URL%" + ) suite.add_variable("ECF_URL_BASE", "http://www.ecmwf.int") suite.add_variable("ECF_URL", "publications/manuals/sms") suite.add_limit(Limit("limitName", 10)) suite.add_limit("limitName_2", 10) - assert suite.begun() == False, "Suite should not have begun" + assert not suite.begun(), "Suite should not have begun" - clock = Clock(1, 1, 2010, False); - clock.set_gain(1, 10, True); - suite.add_clock(clock); + clock = Clock(1, 1, 2010, False) + clock.set_gain(1, 10, True) + suite.add_clock(clock) - assert defs != defs2, "Defs should no longer compare as they are different" - defs.add_extern("/path/to/task"); - defs.add_extern("/fred/bill:event_name"); +def test_defs_externs(): + defs = Defs() + defs.add_extern("/path/to/task") + defs.add_extern("/fred/bill:event_name") + assert len(list(defs.externs)) == 2 - family = suite.add_family("f1") - family.add_defstatus(DState.active); +def test_task_with_trigger_complete_autocancel(): + family = Family("f1") + family.add_defstatus(DState.active) task = family.add_task("t1") task.add_trigger("t2 == active") task.add_complete("t2 == complete") - task.add_autocancel(Autocancel(3)); + task.add_autocancel(Autocancel(3)) + +def test_task_with_part_triggers_and_time_attributes(): + family = Family("f1") task2 = family.add_task("t2") - task2.add_defstatus(DState.complete); + task2.add_defstatus(DState.complete) task2.add_part_trigger(PartExpression("t1 == complete")) - task2.add_part_trigger(PartExpression("t1 == active", True)) # for long and/or expressions, subsequent expr must be and/or + task2.add_part_trigger(PartExpression("t1 == active", True)) task2.add_part_complete(PartExpression("t1 == complete")) - task2.add_part_complete(PartExpression("t1 == active", False)) # for long and/or expressions, subsequent expr must be and/or + task2.add_part_complete(PartExpression("t1 == active", False)) task2.add_label(Label("labelname", "label1")) task2.add_label("labelname2", "label1") task2.add_inlimit(InLimit("limitName", "/s1/f1", 2)) @@ -107,7 +150,7 @@ start = TimeSlot(0, 0) finish = TimeSlot(23, 0) incr = TimeSlot(0, 30) - ts = TimeSeries(start, finish, incr, True); + ts = TimeSeries(start, finish, incr, True) task2.add_today(Today(ts)) task2.add_time(20, 10) @@ -127,11 +170,17 @@ cron.set_days_of_month([1, 2, 3, 4, 5, 6]) cron.set_months([1, 2, 3, 4, 5, 6]) cron.set_time_series(ts) - task2.add_cron(cron); + task2.add_cron(cron) + + +def test_various_repeat_types(): + defs = Defs() + suite = defs.add_suite("s1") + family = suite.add_family("f1") task3 = family.add_task("t3") task3.add_repeat(RepeatDate("testDate", 20100111, 20100115, 2)) - task3.add_defstatus(DState.queued); + task3.add_defstatus(DState.queued) task3.add_verify(Verify(State.complete, 1)) task3.add_autocancel(Autocancel(20, 10, False)) task3.add_label(Label("label_name", "label_value")) @@ -140,96 +189,110 @@ task3_1.add_repeat(RepeatDate("testDate", 20100111, 20100115)) task3_2 = family.add_task("t3_2") - task3_2.add_repeat(RepeatDateTime("testDateTime", "20100111T000000", "20100115T000000")) + task3_2.add_repeat( + RepeatDateTime("testDateTime", "20100111T000000", "20100115T000000") + ) task4 = family.add_task("t4") task4.add_repeat(RepeatInteger("testInteger", 0, 100, 2)) - task4.add_defstatus(DState.aborted); + task4.add_defstatus(DState.aborted) task4_1 = family.add_task("t4_1") task4_1.add_repeat(RepeatInteger("testInteger", 0, 100)) task5 = family.add_task("t5") task5.add_repeat(RepeatEnumerated("testInteger", ["red", "green", "blue"])) - task5.add_defstatus(DState.submitted); + task5.add_defstatus(DState.submitted) task6 = family.add_task("t6") task6.add_repeat(RepeatString("test_string", ["a", "b", "c"])) - task6.add_defstatus(DState.suspended); + task6.add_defstatus(DState.suspended) task7 = family.add_task("t7") task7.add_repeat(RepeatDay(2)) - task7.add_defstatus(DState.active); + task7.add_defstatus(DState.active) task8 = family.add_task("t8") task8.add_repeat(RepeatDay()) - print("task8.get_try_no() :string:", task8.get_try_no()) - print("task8.get_int_try_no():int :", task8.get_int_try_no()) + assert task8.get_try_no() == "0" + assert task8.get_int_try_no() == 0 + assert task8.get_defs() is not None - the_defs = task8.get_defs() - # Test find - assert defs.find_suite("s1") is not None, "Expected to find suite of name s1" - assert defs.find_suite("sffff1") is None, "Should not be able to find suite of name sfffff1" +def test_defs_find_suite(): + defs = Defs() + suite = Suite("s1") + defs.add_suite(suite) + assert defs.find_suite("s1") is not None + assert defs.find_suite("sffff1") is None - # save the defs file as a check point file and restore it again - checkpt_file = "py_u_TestDefs.check" - defs_file = "py_u_TestDefs.def" - if debug_build(): - checkpt_file = "py_u_TestDefs_debug.check" - defs_file = "py_u_TestDefs_debug.def" - defs.save_as_checkpt(checkpt_file); +def test_save_and_restore_defs(tmp_path): + defs = Defs() + suite = Suite("s1") + suite.add_family(Family("f1")).add_task(Task("t1")) + defs.add_suite(suite) + defs.add_extern("/path/to/task") + defs.add_extern("/fred/bill:event_name") + + checkpt_file = str(tmp_path / "py_u_TestDefs.check") + defs_file = str(tmp_path / "py_u_TestDefs.def") + + defs.save_as_checkpt(checkpt_file) restored_checkpt_defs = Defs() restored_checkpt_defs.restore_from_checkpt(checkpt_file) - print("Save and restore as Defs ******************************************************") - defs.save_as_defs(defs_file) # default is to save as DEFS - restored_from_defs = Defs(defs_file) # restore the defs + defs.save_as_defs(defs_file) + restored_from_defs = Defs(defs_file) defs.save_as_defs(defs_file, Style.DEFS) - restored_from_defs = Defs(defs_file) # restore the defs + restored_from_defs = Defs(defs_file) - print("Save and restore using STATE ******************************************************") defs.save_as_defs(defs_file, Style.STATE) - restored_from_defs = Defs(defs_file) # restore the defs + restored_from_defs = Defs(defs_file) - print("Save and restore using MIGRATE ******************************************************") defs.save_as_defs(defs_file, Style.MIGRATE) - restored_from_defs = Defs(defs_file) # restore the defs - assert restored_checkpt_defs == restored_from_defs, "File should be the same" + restored_from_defs = Defs(defs_file) + + assert restored_checkpt_defs == restored_from_defs + + +def test_print_styles(): + defs = Defs() + suite = Suite("s1") + suite.add_family(Family("f1")).add_task(Task("t1")) + defs.add_suite(suite) + defs_file = "tmp_def_for_print.def" + defs.save_as_defs(defs_file, Style.MIGRATE) + restored_from_defs = Defs(defs_file) + try: + os.remove(defs_file) + except OSError: + pass - print("Print in DEFS style ******************************************************") PrintStyle.set_style(Style.DEFS) the_string = str(restored_from_defs) - assert the_string.find("defs_state") == -1, "Print in DEFS style failed" - print(the_string) + assert "defs_state" not in the_string - print("Print in STATE style *****************************************************") PrintStyle.set_style(Style.STATE) the_string = str(restored_from_defs) - assert the_string.find("defs_state STATE") != -1, "Print in STATE style failed" - print(the_string) + assert "defs_state STATE" in the_string - print("Print in MIGRATE style *****************************************************") - PrintStyle.set_style(Style.MIGRATE) - the_string = str(restored_from_defs) - assert the_string.find("defs_state MIGRATE") != -1, "Print in MIGRATE style failed" - print(the_string) + try: + PrintStyle.set_style(Style.MIGRATE) + the_string = str(restored_from_defs) + assert "defs_state MIGRATE" in the_string + finally: + PrintStyle.set_style(Style.DEFS) - # Comment this out if you want to see what the file looked like - os.remove(checkpt_file) - os.remove(defs_file) - # Test job creation failure +def test_job_creation_failure(): + defs = Defs() + suite = Suite("s1") + suite.add_family(Family("f1")).add_task(Task("t1")) + defs.add_suite(suite) msg = defs.check_job_creation() - assert len(msg) != 0, "Expected job creation to fail" + assert len(msg) != 0 - expected_exeption = False; - try: + with pytest.raises(RuntimeError): defs.check_job_creation(throw_on_error=True) - except RuntimeError as e: - expected_exeption = True - assert expected_exeption, "expected exception" - - print("All tests pass") diff --git a/libs/pyext/test/py_u_TestDefsCheck.py b/libs/pyext/test/py_u_TestDefsCheck.py index c8fd63e43..6fb3dabe5 100644 --- a/libs/pyext/test/py_u_TestDefsCheck.py +++ b/libs/pyext/test/py_u_TestDefsCheck.py @@ -8,57 +8,75 @@ # nor does it submit to any jurisdiction. # -import os -from ecflow import Defs, Limit, InLimit, Client, debug_build -import ecflow_test_util as Test +from ecflow import Defs, Limit, InLimit -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) +def test_trigger_with_mismatched_brackets_produces_check_error(): defs = Defs() - suite = defs.add_suite("s1"); + suite = defs.add_suite("s1") suite.add_task("t1").add_trigger("t2 == active)") - theCheckValue = defs.check(); - print("Message: '" + theCheckValue + "'") - assert len(theCheckValue) != 0, "Expected Error: triggers fail parse, miss-matched brackets in expression." + error = defs.check() + assert ( + error + ), "Expected Error: triggers fail parse, miss-matched brackets in expression." - # The number of tokens specified on the in-limit must be less than or equal to those specified on the LIMIT + +def test_inlimit_value_exceeds_limit_value_produces_check_error(): defs = Defs() - suite = defs.add_suite("s1"); - suite.add_limit(Limit("disk", 50)); - family = suite.add_family("anon"); + suite = defs.add_suite("s1") + suite.add_limit(Limit("disk", 50)) + family = suite.add_family("anon") family.add_inlimit(InLimit("disk", "/s1", 100)) - family.add_task("t1"); - theCheckValue = defs.check(); - print("Message: '" + theCheckValue + "'") - assert len(theCheckValue) != 0, "Expected Error: since inlimit value('100') is greater than the LIMIT value('50')" + family.add_task("t1") + error = defs.check() + assert ( + error + ), "Expected Error: since inlimit value('100') is greater than the LIMIT value('50')" + - # When a path is specified on the in-limit we search for the limit on that path, otherwise the extern's +def test_inlimit_with_missing_limit_path_produces_check_warning(): defs = Defs() - suite = defs.add_suite("s1"); - family = suite.add_family("anon"); + suite = defs.add_suite("s1") + family = suite.add_family("anon") family.add_inlimit(InLimit("disk", "/s1", 100)) - family.add_task("t1"); - theCheckValue = defs.check(); - print("Message: '" + theCheckValue + "'") - assert len(theCheckValue) != 0, "Expected warning: since inlimit PATH(/s1:disk) reference a limit('disk') that does not exist" + family.add_task("t1") + error = defs.check() + assert ( + error + ), "Expected warning: since inlimit PATH(/s1:disk) reference a limit('disk') that does not exist" + - # Add as extern and repeat the check +def test_inlimit_with_extern_resolves_warning(): + defs = Defs() + suite = defs.add_suite("s1") + family = suite.add_family("anon") + family.add_inlimit(InLimit("disk", "/s1", 100)) + family.add_task("t1") + assert defs.check(), "Expected warning before adding extern" defs.add_extern("/s1:disk") - assert len(defs.check()) == 0, "Expected no warnings, since extern specified: " + defs.check() + assert not defs.check(), ( + "Expected no warnings, since extern specified: " + defs.check() + ) - # When no path is specified on the in-limit, we search for the limit up the node tree, otherwise the extern's + +def test_inlimit_without_path_produces_check_warning(): defs = Defs() - suite = defs.add_suite("s1"); - family = suite.add_family("anon"); + suite = defs.add_suite("s1") + family = suite.add_family("anon") family.add_inlimit(InLimit("disk", "", 100)) - family.add_task("t1"); - theCheckValue = defs.check(); - print("Message: '" + theCheckValue + "'") - assert len(theCheckValue) != 0, "Expected warning: since inlimit SHOULD reference a limit('disk') somewhere UP parent hierarchy" + family.add_task("t1") + error = defs.check() + assert ( + error + ), "Expected warning: since inlimit SHOULD reference a limit('disk') somewhere UP parent hierarchy" - # Add as extern and repeat the check - defs.add_extern("disk") - assert len(defs.check()) == 0, "Expected no warnings, since extern specified" - print("All Tests pass") +def test_inlimit_without_path_with_extern_resolves_warning(): + defs = Defs() + suite = defs.add_suite("s1") + family = suite.add_family("anon") + family.add_inlimit(InLimit("disk", "", 100)) + family.add_task("t1") + assert defs.check(), "Expected warning before adding extern" + defs.add_extern("disk") + assert not defs.check(), "Expected no warnings, since extern specified" diff --git a/libs/pyext/test/py_u_TestDefsConstructor.py b/libs/pyext/test/py_u_TestDefsConstructor.py index 5344ad829..2ca2a3366 100644 --- a/libs/pyext/test/py_u_TestDefsConstructor.py +++ b/libs/pyext/test/py_u_TestDefsConstructor.py @@ -8,141 +8,225 @@ # nor does it submit to any jurisdiction. # -from ecflow import Alias, AttrType, Autocancel, CheckPt, ChildCmdType, Client, Clock, Cron, DState, Date, Day, Days, \ - Defs, Ecf, Event, Expression, Family, FamilyVec, File, Flag, FlagType, FlagTypeVec, InLimit, \ - JobCreationCtrl, Label, Late, Limit, Meter, Node, NodeContainer, NodeVec, PartExpression, PrintStyle, \ - Repeat, RepeatDate, RepeatDay, RepeatEnumerated, RepeatInteger, RepeatString, SState, State, Style, \ - Submittable, Suite, SuiteVec, Task, TaskVec, Time, TimeSeries, TimeSlot, Today, UrlCmd, Variable, \ - VariableList, Verify, WhyCmd, ZombieAttr, ZombieType, ZombieUserActionType, Trigger, Complete, Edit, Defstatus -import unittest -import sys import os +import pytest -class Test_default_defs(unittest.TestCase): - - def setUp(self): - self.file_name = 'Test_default_defs.defs' - defs = Defs() + Suite('s1') - defs.save_as_defs(self.file_name) - - def test_defs_default_constructor(self): - defs = Defs() - # PrintStyle.set_style( Style.MIGRATE ) - # print defs - self.assertEqual(defs.get_state(), State.unknown, "expected unknow state but found " + str(defs.get_state())) - self.assertEqual(defs.get_server_state(), SState.RUNNING, "expected default server state tobe running state but found " + str(defs.get_server_state())) - self.assertEqual(len(list(defs.suites)), 0, "expected no suites but found " + str(len(list(defs.suites)))) - - defs = Defs(self.file_name) - self.assertEqual(defs.get_state(), State.unknown, "expected unknow state but found " + str(defs.get_state())) - self.assertEqual(defs.get_server_state(), SState.RUNNING, "expected default server state tobe running state but found " + str(defs.get_server_state())) - self.assertEqual(len(list(defs.suites)), 1, "expected 1 suites but found " + str(len(list(defs.suites)))) - - def tearDown(self): - try: - os.remove(self.file_name) - except: - pass - - -class Test_defs_raw_constructor(unittest.TestCase): - - def test_defs_raw_constructor_errors(self): - with self.assertRaises(RuntimeError): - defs = Defs("file_name.def", Suite('s1')) - with self.assertRaises(RuntimeError): - defs = Defs("file_name.def", Edit(a='b')) - with self.assertRaises(RuntimeError): - defs = Defs("file_name.def", {'a': 'b'}) - with self.assertRaises(RuntimeError): - defs = Defs("file_name.def", Variable('a', 'b')) - with self.assertRaises(RuntimeError): - defs = Defs("file_name.def", [Variable('a', 'b')]) - - def test_defs_raw_constructor_suite_creation(self): - defs = Defs(Suite('s1')) - self.assertEqual(len(list(defs.suites)), 1, "expected 1 suites but found " + str(len(list(defs.suites)))) - - defs = Defs(Suite('s1'), Suite('s2'), Suite('s3')) - self.assertEqual(len(list(defs.suites)), 3, "expected 3 suites but found " + str(len(list(defs.suites)))) - - defs = Defs([Suite('s{0}'.format(i)) for i in range(1, 5)]) - self.assertEqual(len(list(defs.suites)), 4, "expected 4 suites but found " + str(len(list(defs.suites)))) - - def test_defs_raw_constructor_variables(self): - defs = Defs(Edit(a='b')) - self.assertEqual(len(list(defs.user_variables)), 1, "expected 1 user variable but found " + str(len(list(defs.user_variables)))) - - defs = Defs(Variable('a', 'b')) - self.assertEqual(len(list(defs.user_variables)), 1, "expected 1 user variable but found " + str(len(list(defs.user_variables)))) - - defs = Defs({'a': 'a', 'b': 'b'}) - self.assertEqual(len(list(defs.user_variables)), 2, "expected 2 user variable but found " + str(len(list(defs.user_variables)))) - - defs = Defs(a='a', b='b') # uses kwy word arguments - self.assertEqual(len(list(defs.user_variables)), 2, "expected 2 user variable but found " + str(len(list(defs.user_variables)))) - - defs = Defs(Edit(a='b'), Variable('b', 'b'), {'c': 'a', 'd': 'b'}, e='a', f='b') - self.assertEqual(len(list(defs.user_variables)), 6, "expected 6 user variable but found " + str(len(list(defs.user_variables)))) - - def test_defs_raw_constructor(self): - defs = Defs(Suite('s1'), Edit(a='b')) - self.assertEqual(len(list(defs.user_variables)), 1, "expected 1 user variable but found " + str(len(list(defs.user_variables)))) - self.assertEqual(len(list(defs.suites)), 1, "expected 1 suites but found " + str(len(list(defs.suites)))) - - defs = Defs(Suite('s1'), Suite('s2'), Suite('s3'), Variable('a', 'b')) - self.assertEqual(len(list(defs.user_variables)), 1, "expected 1 user variable but found " + str(len(list(defs.user_variables)))) - self.assertEqual(len(list(defs.suites)), 3, "expected 3 suites but found " + str(len(list(defs.suites)))) - - defs = Defs([Suite('s{0}'.format(i)) for i in range(1, 5)], {'a': 'a', 'b': 'b'}) - self.assertEqual(len(list(defs.user_variables)), 2, "expected 2 user variable but found " + str(len(list(defs.user_variables)))) - self.assertEqual(len(list(defs.suites)), 4, "expected 4 suites but found " + str(len(list(defs.suites)))) - - defs = Defs(Suite('s1'), Suite('s2'), Suite('s3'), a='a', b='b') # uses kwy word arguments - self.assertEqual(len(list(defs.user_variables)), 2, "expected 2 user variable but found " + str(len(list(defs.user_variables)))) - self.assertEqual(len(list(defs.suites)), 3, "expected 3 suites but found " + str(len(list(defs.suites)))) - - defs = Defs(Suite('s1'), Edit(a='b'), Variable('b', 'b'), {'c': 'a', 'd': 'b'}, e='a', f='b') - self.assertEqual(len(list(defs.user_variables)), 6, "expected 6 user variable but found " + str(len(list(defs.user_variables)))) - self.assertEqual(len(list(defs.suites)), 1, "expected 1 suites but found " + str(len(list(defs.suites)))) - - def test_defs_constructor(self): - defs = Defs(Suite('s1'), Edit(a='b')) - defs2 = Defs() + Suite('s1') + Edit(a='b') - defs3 = Defs() - defs3 += Suite('s1') - defs3 += Edit(a='b') - self.assertEqual(defs, defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2)) - self.assertEqual(defs, defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3)) - - defs = Defs(Suite('s1'), Suite('s2'), Suite('s3'), Variable('a', 'b')) - defs2 = Defs() + Suite('s1') + Suite('s2') + Suite('s3') + Variable('a', 'b') - defs3 = Defs().add(Suite('s1'), Suite('s2'), Suite('s3'), Variable('a', 'b')) - self.assertEqual(defs, defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2)) - self.assertEqual(defs, defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3)) - - defs = Defs([Suite('s{0}'.format(i)) for i in range(1, 5)], {'a': 'a'}) - defs2 = Defs() + [Suite('s{0}'.format(i)) for i in range(1, 5)] + {'a': 'a'} - defs3 = Defs() - for i in range(1, 5): - defs3.add_suite('s{0}'.format(i)) - defs3.add_variable('a', 'a') - self.assertEqual(defs, defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2)) - self.assertEqual(defs, defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3)) - - defs = Defs(Suite('s1'), Edit(a='a'), Variable('b', 'b'), {'c': 'c'}, {'d': 'd'}) - defs2 = Defs().add(Suite('s1'), Edit(a='a'), Variable('b', 'b'), {'c': 'c'}, {'d': 'd'}) - defs3 = Defs() - defs3.add_suite('s1') - defs3.add_variable('a', 'a') - defs3.add_variable('b', 'b') - defs3.add_variable('c', 'c') - defs3.add_variable('d', 'd') - self.assertEqual(defs, defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2)) - self.assertEqual(defs, defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3)) - - -if __name__ == "__main__": - unittest.main() - print("All Tests pass") +from ecflow import Defs, Suite, State, SState, Variable, Edit + + +@pytest.fixture +def temp_defs_file(tmp_path): + file_name = str(tmp_path / "Test_default_defs.defs") + defs = Defs() + Suite("s1") + defs.save_as_defs(file_name) + yield file_name + try: + os.remove(file_name) + except OSError: + pass + + +def test_defs_default_constructor(temp_defs_file): + defs = Defs() + assert defs.get_state() == State.unknown, "expected unknown state but found " + str( + defs.get_state() + ) + assert ( + defs.get_server_state() == SState.RUNNING + ), "expected default server state to be running but found " + str( + defs.get_server_state() + ) + assert len(list(defs.suites)) == 0, "expected no suites but found " + str( + len(list(defs.suites)) + ) + + defs = Defs(temp_defs_file) + assert defs.get_state() == State.unknown, "expected unknown state but found " + str( + defs.get_state() + ) + assert ( + defs.get_server_state() == SState.RUNNING + ), "expected default server state to be running but found " + str( + defs.get_server_state() + ) + assert len(list(defs.suites)) == 1, "expected 1 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_filename_and_suite_raises(): + with pytest.raises(RuntimeError): + Defs("file_name.def", Suite("s1")) + + +def test_defs_raw_constructor_with_filename_and_edit_raises(): + with pytest.raises(RuntimeError): + Defs("file_name.def", Edit(a="b")) + + +def test_defs_raw_constructor_with_filename_and_dict_raises(): + with pytest.raises(RuntimeError): + Defs("file_name.def", {"a": "b"}) + + +def test_defs_raw_constructor_with_filename_and_variable_raises(): + with pytest.raises(RuntimeError): + Defs("file_name.def", Variable("a", "b")) + + +def test_defs_raw_constructor_with_filename_and_variable_list_raises(): + with pytest.raises(RuntimeError): + Defs("file_name.def", [Variable("a", "b")]) + + +def test_defs_raw_constructor_with_single_suite(): + defs = Defs(Suite("s1")) + assert len(list(defs.suites)) == 1, "expected 1 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_multiple_suites(): + defs = Defs(Suite("s1"), Suite("s2"), Suite("s3")) + assert len(list(defs.suites)) == 3, "expected 3 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_suite_list(): + defs = Defs([Suite("s{0}".format(i)) for i in range(1, 5)]) + assert len(list(defs.suites)) == 4, "expected 4 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_edit(): + defs = Defs(Edit(a="b")) + assert ( + len(list(defs.user_variables)) == 1 + ), "expected 1 user variable but found " + str(len(list(defs.user_variables))) + + +def test_defs_raw_constructor_with_variable(): + defs = Defs(Variable("a", "b")) + assert ( + len(list(defs.user_variables)) == 1 + ), "expected 1 user variable but found " + str(len(list(defs.user_variables))) + + +def test_defs_raw_constructor_with_dict(): + defs = Defs({"a": "a", "b": "b"}) + assert ( + len(list(defs.user_variables)) == 2 + ), "expected 2 user variable but found " + str(len(list(defs.user_variables))) + + +def test_defs_raw_constructor_with_kwargs(): + defs = Defs(a="a", b="b") # uses keyword arguments + assert ( + len(list(defs.user_variables)) == 2 + ), "expected 2 user variable but found " + str(len(list(defs.user_variables))) + + +def test_defs_raw_constructor_with_mixed_variable_sources(): + defs = Defs(Edit(a="b"), Variable("b", "b"), {"c": "a", "d": "b"}, e="a", f="b") + assert ( + len(list(defs.user_variables)) == 6 + ), "expected 6 user variable but found " + str(len(list(defs.user_variables))) + + +def test_defs_raw_constructor_with_suite_and_variables(): + defs = Defs(Suite("s1"), Edit(a="b")) + assert ( + len(list(defs.user_variables)) == 1 + ), "expected 1 user variable but found " + str(len(list(defs.user_variables))) + assert len(list(defs.suites)) == 1, "expected 1 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_multiple_suites_and_variable(): + defs = Defs(Suite("s1"), Suite("s2"), Suite("s3"), Variable("a", "b")) + assert ( + len(list(defs.user_variables)) == 1 + ), "expected 1 user variable but found " + str(len(list(defs.user_variables))) + assert len(list(defs.suites)) == 3, "expected 3 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_suite_list_and_dict(): + defs = Defs([Suite("s{0}".format(i)) for i in range(1, 5)], {"a": "a", "b": "b"}) + assert ( + len(list(defs.user_variables)) == 2 + ), "expected 2 user variable but found " + str(len(list(defs.user_variables))) + assert len(list(defs.suites)) == 4, "expected 4 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_kwargs_suite_list_and_dict(): + defs = Defs(Suite("s1"), Suite("s2"), Suite("s3"), a="a", b="b") + assert ( + len(list(defs.user_variables)) == 2 + ), "expected 2 user variable but found " + str(len(list(defs.user_variables))) + assert len(list(defs.suites)) == 3, "expected 3 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_raw_constructor_with_all_sources(): + defs = Defs( + Suite("s1"), Edit(a="b"), Variable("b", "b"), {"c": "a", "d": "b"}, e="a", f="b" + ) + assert ( + len(list(defs.user_variables)) == 6 + ), "expected 6 user variable but found " + str(len(list(defs.user_variables))) + assert len(list(defs.suites)) == 1, "expected 1 suites but found " + str( + len(list(defs.suites)) + ) + + +def test_defs_constructor_equivalent_to_plus_operator(): + defs = Defs(Suite("s1"), Edit(a="b")) + defs2 = Defs() + Suite("s1") + Edit(a="b") + defs3 = Defs() + defs3 += Suite("s1") + defs3 += Edit(a="b") + assert defs == defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2) + assert defs == defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3) + + +def test_defs_constructor_equivalent_to_add_call(): + defs = Defs(Suite("s1"), Suite("s2"), Suite("s3"), Variable("a", "b")) + defs2 = Defs() + Suite("s1") + Suite("s2") + Suite("s3") + Variable("a", "b") + defs3 = Defs().add(Suite("s1"), Suite("s2"), Suite("s3"), Variable("a", "b")) + assert defs == defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2) + assert defs == defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3) + + +def test_defs_constructor_with_list_and_dict_variants(): + defs = Defs([Suite("s{0}".format(i)) for i in range(1, 5)], {"a": "a"}) + defs2 = Defs() + [Suite("s{0}".format(i)) for i in range(1, 5)] + {"a": "a"} + defs3 = Defs() + for i in range(1, 5): + defs3.add_suite("s{0}".format(i)) + defs3.add_variable("a", "a") + assert defs == defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2) + assert defs == defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3) + + +def test_defs_constructor_with_multiple_dict_variable_sources(): + defs = Defs(Suite("s1"), Edit(a="a"), Variable("b", "b"), {"c": "c"}, {"d": "d"}) + defs2 = Defs().add( + Suite("s1"), Edit(a="a"), Variable("b", "b"), {"c": "c"}, {"d": "d"} + ) + defs3 = Defs() + defs3.add_suite("s1") + defs3.add_variable("a", "a") + defs3.add_variable("b", "b") + defs3.add_variable("c", "c") + defs3.add_variable("d", "d") + assert defs == defs2, "defs not equal\n" + str(defs) + "\n\n" + str(defs2) + assert defs == defs3, "defs not equal\n" + str(defs) + "\n\n" + str(defs3) diff --git a/libs/pyext/test/py_u_TestDerivable.py b/libs/pyext/test/py_u_TestDerivable.py index 04b14373a..fab14a743 100644 --- a/libs/pyext/test/py_u_TestDerivable.py +++ b/libs/pyext/test/py_u_TestDerivable.py @@ -8,57 +8,79 @@ # nor does it submit to any jurisdiction. # -# This test ensures that ecflow classes can be derived from +# This test ensures that ecflow classes can be derived from. -import os -import sys import ecflow -import ecflow_test_util as Test -class MyDefs(ecflow.Defs): pass +class MyDefs(ecflow.Defs): + pass -class MySuite(ecflow.Suite): pass +class MySuite(ecflow.Suite): + pass -class MyFamily(ecflow.Family): pass +class MyFamily(ecflow.Family): + pass -class MyTask(ecflow.Task): pass +class MyTask(ecflow.Task): + pass -class MyClient(ecflow.Client): pass +class MyClient(ecflow.Client): + pass -if sys.version_info[0] > 2: - class MySuite2(ecflow.Suite): - def __init__(self, name): - super().__init__(name) +class MySuite2(ecflow.Suite): + def __init__(self, name): + super().__init__(name) - class MyFamily2(ecflow.Family): - def __init__(self, name): - super().__init__(name) +class MyFamily2(ecflow.Family): + def __init__(self, name): + super().__init__(name) - class MyTask2(ecflow.Task): - def __init__(self, name): - super().__init__(name) +class MyTask2(ecflow.Task): + def __init__(self, name): + super().__init__(name) - n = MySuite2('s') - print(n) +def test_can_subclass_defs(): + assert isinstance(MyDefs(), ecflow.Defs) - n = MyFamily2('f') - print(n) - n = MyTask2('t') - print(n) +def test_can_subclass_suite(): + assert isinstance(MySuite("s"), ecflow.Suite) -else: - print("Python2 ignoring derivation test") -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) - print("All tests pass") +def test_can_subclass_family(): + assert isinstance(MyFamily("f"), ecflow.Family) + + +def test_can_subclass_task(): + assert isinstance(MyTask("t"), ecflow.Task) + + +def test_can_subclass_client(): + assert isinstance(MyClient(), ecflow.Client) + + +def test_can_subclass_suite_with_custom_init(): + node = MySuite2("s") + assert isinstance(node, ecflow.Suite) + assert node.name() == "s" + + +def test_can_subclass_family_with_custom_init(): + node = MyFamily2("f") + assert isinstance(node, ecflow.Family) + assert node.name() == "f" + + +def test_can_subclass_task_with_custom_init(): + node = MyTask2("t") + assert isinstance(node, ecflow.Task) + assert node.name() == "t" diff --git a/libs/pyext/test/py_u_TestDynamicObjects.py b/libs/pyext/test/py_u_TestDynamicObjects.py index dd8fef9d9..5e988418c 100644 --- a/libs/pyext/test/py_u_TestDynamicObjects.py +++ b/libs/pyext/test/py_u_TestDynamicObjects.py @@ -31,112 +31,111 @@ """ import ecflow as ecf -import unittest -class TestTrigger(unittest.TestCase): +class TestTrigger: """Trigger is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Trigger supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Trigger("a == b") obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Trigger instance.""" obj = ecf.Trigger("a == b") obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestComplete(unittest.TestCase): +class TestComplete: """Complete is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Complete supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Complete("a == b") obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Complete instance.""" obj = ecf.Complete("a == b") obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestPartExpression(unittest.TestCase): +class TestPartExpression: """PartExpression is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """PartExpression supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.PartExpression("a == b") obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a PartExpression instance.""" obj = ecf.PartExpression("a == b") obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestExpression(unittest.TestCase): +class TestExpression: """Expression is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Expression supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Expression("a == b") obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from an Expression instance.""" obj = ecf.Expression("a == b") obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestFlagType(unittest.TestCase): +class TestFlagType: """FlagType is a py::enum_ bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """FlagType supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.FlagType.force_abort obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a FlagType member.""" obj = ecf.FlagType.force_abort obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestFlag(unittest.TestCase): +class TestFlag: """Flag is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Flag supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Flag() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Flag instance.""" obj = ecf.Flag() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestFlagTypeVec(unittest.TestCase): +class TestFlagTypeVec: """FlagTypeVec is a py::bind_vector type WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -146,34 +145,34 @@ def test_supports_dynamic_attribute(self): """FlagTypeVec should support setting arbitrary Python attributes.""" obj = ecf.FlagTypeVec() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from a FlagTypeVec instance.""" obj = ecf.FlagTypeVec() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestJobCreationCtrl(unittest.TestCase): +class TestJobCreationCtrl: """JobCreationCtrl is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """JobCreationCtrl supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.JobCreationCtrl() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a JobCreationCtrl instance.""" obj = ecf.JobCreationCtrl() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestZombieType(unittest.TestCase): +class TestZombieType: """ZombieType is a py::enum_ WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -183,17 +182,17 @@ def test_supports_dynamic_attribute(self): """ZombieType should support setting arbitrary Python attributes.""" obj = ecf.ZombieType.ecf obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from a ZombieType member.""" obj = ecf.ZombieType.ecf obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestZombieUserActionType(unittest.TestCase): +class TestZombieUserActionType: """ZombieUserActionType is a py::enum_ WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -203,17 +202,17 @@ def test_supports_dynamic_attribute(self): """ZombieUserActionType should support setting arbitrary Python attributes.""" obj = ecf.ZombieUserActionType.fob obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from a ZombieUserActionType member.""" obj = ecf.ZombieUserActionType.fob obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestChildCmdType(unittest.TestCase): +class TestChildCmdType: """ChildCmdType is a py::enum_ WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -223,17 +222,17 @@ def test_supports_dynamic_attribute(self): """ChildCmdType should support setting arbitrary Python attributes.""" obj = ecf.ChildCmdType.init obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from a ChildCmdType member.""" obj = ecf.ChildCmdType.init obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestAttrType(unittest.TestCase): +class TestAttrType: """AttrType is a py::enum_ WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -243,34 +242,34 @@ def test_supports_dynamic_attribute(self): """AttrType should support setting arbitrary Python attributes.""" obj = ecf.AttrType.event obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from an AttrType member.""" obj = ecf.AttrType.event obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestZombieAttr(unittest.TestCase): +class TestZombieAttr: """ZombieAttr is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """ZombieAttr supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.ZombieAttr(ecf.ZombieType.ecf, [], ecf.ZombieUserActionType.fob) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a ZombieAttr instance.""" obj = ecf.ZombieAttr(ecf.ZombieType.ecf, [], ecf.ZombieUserActionType.fob) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestZombieVec(unittest.TestCase): +class TestZombieVec: """ZombieVec is a py::bind_vector type WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -280,34 +279,34 @@ def test_supports_dynamic_attribute(self): """ZombieVec should support setting arbitrary Python attributes.""" obj = ecf.ZombieVec() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from a ZombieVec instance.""" obj = ecf.ZombieVec() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestZombie(unittest.TestCase): +class TestZombie: """Zombie is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Zombie supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Zombie() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Zombie instance.""" obj = ecf.Zombie() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestVariable(unittest.TestCase): +class TestVariable: """Variable is bound with py::dynamic_attr(); arbitrary attributes are allowed. (Moved here from py_u_TestExportNodeAttr.py.) @@ -317,17 +316,17 @@ def test_supports_dynamic_attribute(self): """Variable supports setting arbitrary Python attributes (py::dynamic_attr()).""" v = ecf.Variable("VAR", "val") v.my_custom = "sentinel" - self.assertEqual(v.my_custom, "sentinel") + assert v.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Variable instance.""" v = ecf.Variable("VAR", "val") v.tag = 99 del v.tag - self.assertFalse(hasattr(v, "tag")) + assert not hasattr(v, "tag") -class TestVariableList(unittest.TestCase): +class TestVariableList: """VariableList is a py::bind_vector type WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -337,153 +336,153 @@ def test_supports_dynamic_attribute(self): """VariableList should support setting arbitrary Python attributes.""" obj = ecf.VariableList() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from a VariableList instance.""" obj = ecf.VariableList() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestLabel(unittest.TestCase): +class TestLabel: """Label is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Label supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Label("MY_LABEL", "hello") obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Label instance.""" obj = ecf.Label("MY_LABEL", "hello") obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestLimit(unittest.TestCase): +class TestLimit: """Limit is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Limit supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Limit("MY_LIMIT", 10) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Limit instance.""" obj = ecf.Limit("MY_LIMIT", 10) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestInLimit(unittest.TestCase): +class TestInLimit: """InLimit is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """InLimit supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.InLimit("my_limit") obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from an InLimit instance.""" obj = ecf.InLimit("my_limit") obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestEvent(unittest.TestCase): +class TestEvent: """Event is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Event supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Event(1) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from an Event instance.""" obj = ecf.Event(1) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestMeter(unittest.TestCase): +class TestMeter: """Meter is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Meter supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Meter("my_meter", 0, 100) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Meter instance.""" obj = ecf.Meter("my_meter", 0, 100) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestQueue(unittest.TestCase): +class TestQueue: """Queue is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Queue supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Queue("my_queue", ["step1", "step2"]) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Queue instance.""" obj = ecf.Queue("my_queue", ["step1", "step2"]) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestGeneric(unittest.TestCase): +class TestGeneric: """Generic is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Generic supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Generic("my_generic", ["val1", "val2"]) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Generic instance.""" obj = ecf.Generic("my_generic", ["val1", "val2"]) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestDate(unittest.TestCase): +class TestDate: """Date is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Date supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Date(15, 6, 2024) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Date instance.""" obj = ecf.Date(15, 6, 2024) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestDays(unittest.TestCase): +class TestDays: """Days is a py::enum_ WITHOUT py::dynamic_attr(). Expected to FAIL until the binding adds dynamic-attribute support. @@ -493,372 +492,368 @@ def test_supports_dynamic_attribute(self): """Days should support setting arbitrary Python attributes.""" obj = ecf.Days.monday obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute should be deletable from a Days member.""" obj = ecf.Days.monday obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestDay(unittest.TestCase): +class TestDay: """Day is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Day supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Day(ecf.Days.monday) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Day instance.""" obj = ecf.Day(ecf.Days.monday) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestTime(unittest.TestCase): +class TestTime: """Time is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Time supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Time(ecf.TimeSlot(12, 30)) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Time instance.""" obj = ecf.Time(ecf.TimeSlot(12, 30)) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestToday(unittest.TestCase): +class TestToday: """Today is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Today supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Today(ecf.TimeSlot(10, 0)) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Today instance.""" obj = ecf.Today(ecf.TimeSlot(10, 0)) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestLate(unittest.TestCase): +class TestLate: """Late is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Late supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Late() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Late instance.""" obj = ecf.Late() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestAutocancel(unittest.TestCase): +class TestAutocancel: """Autocancel is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Autocancel supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Autocancel(1, 30, True) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from an Autocancel instance.""" obj = ecf.Autocancel(1, 30, True) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestAutoarchive(unittest.TestCase): +class TestAutoarchive: """Autoarchive is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Autoarchive supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Autoarchive(1, 30, True, False) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from an Autoarchive instance.""" obj = ecf.Autoarchive(1, 30, True, False) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestAutorestore(unittest.TestCase): +class TestAutorestore: """Autorestore is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Autorestore supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Autorestore(["/suite/family"]) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from an Autorestore instance.""" obj = ecf.Autorestore(["/suite/family"]) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatDate(unittest.TestCase): +class TestRepeatDate: """RepeatDate is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatDate supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatDate("YMD", 20100101, 20100110) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatDate instance.""" obj = ecf.RepeatDate("YMD", 20100101, 20100110) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatDateTime(unittest.TestCase): +class TestRepeatDateTime: """RepeatDateTime is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatDateTime supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatDateTime("DT", "20100101T000000", "20100110T000000") obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatDateTime instance.""" obj = ecf.RepeatDateTime("DT", "20100101T000000", "20100110T000000") obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatDateList(unittest.TestCase): +class TestRepeatDateList: """RepeatDateList is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatDateList supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatDateList("DL", [20100101]) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatDateList instance.""" obj = ecf.RepeatDateList("DL", [20100101]) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatDateTimeList(unittest.TestCase): +class TestRepeatDateTimeList: """RepeatDateTimeList is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatDateTimeList supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatDateTimeList("DTL", ["20100101T000000"]) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatDateTimeList instance.""" obj = ecf.RepeatDateTimeList("DTL", ["20100101T000000"]) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatInteger(unittest.TestCase): +class TestRepeatInteger: """RepeatInteger is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatInteger supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatInteger("VAR", 0, 10) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatInteger instance.""" obj = ecf.RepeatInteger("VAR", 0, 10) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatEnumerated(unittest.TestCase): +class TestRepeatEnumerated: """RepeatEnumerated is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatEnumerated supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatEnumerated("COLOR", ["red"]) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatEnumerated instance.""" obj = ecf.RepeatEnumerated("COLOR", ["red"]) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatString(unittest.TestCase): +class TestRepeatString: """RepeatString is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatString supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatString("LABEL", ["only"]) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatString instance.""" obj = ecf.RepeatString("LABEL", ["only"]) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeatDay(unittest.TestCase): +class TestRepeatDay: """RepeatDay is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """RepeatDay supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.RepeatDay() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a RepeatDay instance.""" obj = ecf.RepeatDay() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestRepeat(unittest.TestCase): +class TestRepeat: """Repeat is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Repeat supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Repeat(1) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Repeat instance.""" obj = ecf.Repeat(1) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestCron(unittest.TestCase): +class TestCron: """Cron is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Cron supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Cron() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Cron instance.""" obj = ecf.Cron() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestVerify(unittest.TestCase): +class TestVerify: """Verify is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Verify supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Verify(ecf.State.complete, 3) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Verify instance.""" obj = ecf.Verify(ecf.State.complete, 3) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestClock(unittest.TestCase): +class TestClock: """Clock is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """Clock supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.Clock(1, 3, 2024) obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Clock instance.""" obj = ecf.Clock(1, 3, 2024) obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestAvisoAttr(unittest.TestCase): +class TestAvisoAttr: """AvisoAttr is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """AvisoAttr supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.AvisoAttr() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from an AvisoAttr instance.""" obj = ecf.AvisoAttr() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) + assert not hasattr(obj, "tag") -class TestMirrorAttr(unittest.TestCase): +class TestMirrorAttr: """MirrorAttr is bound with py::dynamic_attr(); arbitrary attributes are allowed.""" def test_supports_dynamic_attribute(self): """MirrorAttr supports setting arbitrary Python attributes (py::dynamic_attr()).""" obj = ecf.MirrorAttr() obj.my_custom = "sentinel" - self.assertEqual(obj.my_custom, "sentinel") + assert obj.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a MirrorAttr instance.""" obj = ecf.MirrorAttr() obj.tag = 99 del obj.tag - self.assertFalse(hasattr(obj, "tag")) - - -if __name__ == "__main__": - unittest.main(verbosity=2) + assert not hasattr(obj, "tag") diff --git a/libs/pyext/test/py_u_TestEcf.py b/libs/pyext/test/py_u_TestEcf.py index 2ee9ef35f..29efe49f0 100644 --- a/libs/pyext/test/py_u_TestEcf.py +++ b/libs/pyext/test/py_u_TestEcf.py @@ -10,27 +10,28 @@ # This test ensures that creation of Defs files works as expected. -import os -import ecflow +from ecflow import Ecf -from ecflow import Ecf, Client, debug_build -if __name__ == "__main__": - print("####################################################################") - print("Running TestEcf, ecflow version " + Client().version() + " debug build(" + str(debug_build()) + ")") - print("####################################################################") +def test_default_debug_level_is_zero(): + assert Ecf.debug_level() == 0, "Expected default debug level to be 0" - default_debug_level = Ecf.debug_level() - assert default_debug_level == 0, "Expected default debug level to be 0" + +def test_debug_level_round_trip(): Ecf.set_debug_level(10) assert Ecf.debug_level() == 10, "Expected debug level to be 10" Ecf.set_debug_level(0) assert Ecf.debug_level() == 0, "Expected debug level to be 0" - assert Ecf.debug_equality() == False, "Expected default for Ecf.debug_equality() == False " + +def test_default_debug_equality_is_false(): + assert ( + Ecf.debug_equality() is False + ), "Expected default for Ecf.debug_equality() == False" + + +def test_debug_equality_round_trip(): Ecf.set_debug_equality(True) - assert Ecf.debug_equality() == True, "Expected debug_equality() == True " + assert Ecf.debug_equality() is True, "Expected debug_equality() == True" Ecf.set_debug_equality(False) - assert Ecf.debug_equality() == False, "Expected Ecf.debug_equality() == False " - - print("All tests pass") + assert Ecf.debug_equality() is False, "Expected Ecf.debug_equality() == False" diff --git a/libs/pyext/test/py_u_TestError.py b/libs/pyext/test/py_u_TestError.py index bd4d35a4d..60f7dad2f 100644 --- a/libs/pyext/test/py_u_TestError.py +++ b/libs/pyext/test/py_u_TestError.py @@ -8,611 +8,572 @@ # nor does it submit to any jurisdiction. # -import os -from ecflow import Day, Date, Meter, Event, Queue, Clock, Variable, Label, Limit, InLimit, \ - RepeatDate, RepeatDateTime, RepeatEnumerated, RepeatInteger, RepeatString, \ - Task, Family, Suite, Defs, Client, debug_build, Trigger -import ecflow_test_util as Test - - -def check_day(day): +import pytest + +from ecflow import ( + Day, + Date, + Meter, + Event, + Queue, + Clock, + Variable, + Label, + Limit, + InLimit, + RepeatDate, + RepeatDateTime, + RepeatEnumerated, + RepeatInteger, + RepeatString, + Task, + Family, + Suite, + Defs, + Trigger, +) + + +@pytest.mark.parametrize( + "day, expected_valid", + [ + ("monday", True), + ("tuesday", True), + ("wednesday", True), + ("thursday", True), + ("friday", True), + ("saturday", True), + ("sunday", True), + ("", False), + ("sunday1", False), + ("2", False), + ], +) +def test_day_validation(day, expected_valid): try: Day(day) - return True + assert expected_valid, f"Expected {day!r} to be invalid" except RuntimeError: - return False - - -def check_date(day, month, year): + assert not expected_valid, f"Expected {day!r} to be valid" + + +@pytest.mark.parametrize( + "day, month, year, expected_valid", + [ + (0, 1, 2010, True), + (10, 0, 2010, True), + (10, 1, 0, True), + (0, 0, 0, True), + (40, 1, 2010, False), + (-10, 1, 2010, False), + (1, 14, 2010, False), + (1, -1, 2010, False), + (1, 1, -2, False), + ], +) +def test_date_validation(day, month, year, expected_valid): try: Date(day, month, year) - return True + assert expected_valid except IndexError: - return False - - -def check_date_str(str_date): + assert not expected_valid + + +@pytest.mark.parametrize( + "str_date, expected_valid", + [ + ("*.1.2010", True), + ("10.*.2010", True), + ("10.1.*", True), + ("*.*.*", True), + ("40.1.2010", False), + ("-10.1.2010", False), + ("1.14.2010", False), + ("1.-1.2010", False), + ("1.1.-2", False), + ], +) +def test_date_string_validation(str_date, expected_valid): try: Date(str_date) - return True - except IndexError: - return False - except RuntimeError: - return False - - -def check_meter(name, min_meter_value, max_meter_value, color_change): - try: - Meter(name, min_meter_value, max_meter_value, color_change) - return True + assert expected_valid + except (IndexError, RuntimeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "day, month, year, expected_valid", + [ + (12, 1, 2010, True), + (10, 1, 2010, True), + (10, 1, 1400, True), + (31, 12, 2010, True), + (40, 1, 2010, False), + (-10, 1, 2010, False), + (1, 14, 2010, False), + (1, -1, 2010, False), + (1, 1, -2, False), + ], +) +def test_clock_validation(day, month, year, expected_valid): + try: + Clock(day, month, year) + assert expected_valid except IndexError: - return False - except RuntimeError: - return False - - -def check_queue(name, queue_items): + assert not expected_valid + + +@pytest.mark.parametrize( + "min_m, max_m, color_change", + [ + (0, 100, 100), + (0, 100, 0), + ], +) +def test_meter_valid_parameters(min_m, max_m, color_change): + Meter("m", min_m, max_m, color_change) # must not raise + + +@pytest.mark.parametrize( + "min_m, max_m, color_change", + [ + (200, 100, 100), # min > max + (0, 100, -20), # color_change < min + (0, 100, 200), # color_change > max + ], +) +def test_meter_invalid_numeric_bounds_raise_with_valid_name(min_m, max_m, color_change): + # Use a VALID name so that the failure can only be due to the numeric bounds + with pytest.raises((IndexError, RuntimeError)): + Meter("m", min_m, max_m, color_change) + + +def test_meter_empty_name_raises(): + with pytest.raises((IndexError, RuntimeError)): + Meter("", 0, 100, 100) + + +def test_meter_space_name_raises(): + with pytest.raises((IndexError, RuntimeError)): + Meter(" ", 0, 100, 100) + + +@pytest.mark.parametrize( + "name, queue_items, expected_valid", + [ + ("m", ["a"], True), + ("m", ["a", "b"], True), + ("", ["a", "b"], False), + (" ", ["a", "b"], False), + (".", ["a", "b"], False), + ("m", [], False), + ], +) +def test_queue_validation(name, queue_items, expected_valid): try: Queue(name, queue_items) - return True - except IndexError: - return False + assert expected_valid + except (IndexError, RuntimeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "number, name, expected_valid", + [ + (1, None, True), + (2, None, True), + (2, "fred", True), + (2, 2, False), + ], +) +def test_event_validation(number, name, expected_valid): + try: + if name is None: + Event(number) + else: + Event(number, name) + assert expected_valid + except (RuntimeError, TypeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "start, end, step, expected_valid", + [ + (20000101, 20001201, 200, True), + (20001201, 20000101, 200, False), + (200001011, 20001201, 200, False), + (20000101, 200012013, 200, False), + (00000000, 00000000, 200, False), + ], +) +def test_repeat_date_validation(start, end, step, expected_valid): + try: + RepeatDate("m", start, end, step) + assert expected_valid except RuntimeError: - return False + assert not expected_valid -def check_event_number_and_name(number, name): - try: - Event(number, name) - return True - except: - return False +@pytest.mark.parametrize("name", ["", " "]) +def test_repeat_date_invalid_name_raises(name): + with pytest.raises(RuntimeError): + RepeatDate(name, 20000101, 20001201, 200) -def check_event(number): +@pytest.mark.parametrize( + "start, end, step, expected_valid", + [ + ("20000101T000000", "20001201T000000", "4800:00:00", True), + ("20001201T000000", "20000101T000000", "4800:00:00", False), + ("200001011T000000", "20001201T000000", "4800:00:00", False), + ("20000101T000000", "200012013T000000", "4800:00:00", False), + ("00000000T000000", "00000000T000000", "4800:00:00", False), + ], +) +def test_repeat_datetime_validation(start, end, step, expected_valid): try: - Event(number) - return True + RepeatDateTime("m", start, end, step) + assert expected_valid except RuntimeError: - return False + assert not expected_valid -def check_clock(day_of_month, month, year): - try: - Clock(day_of_month, month, year) - return True - except IndexError: - return False +@pytest.mark.parametrize("name", ["", " "]) +def test_repeat_datetime_invalid_name_raises(name): + with pytest.raises(RuntimeError): + RepeatDateTime(name, "20000101T000000", "20001201T000000", "4800:00:00") -def check_variable(name, value): +@pytest.mark.parametrize( + "name, start, end, step, expected_valid", + [ + ("name", 0, 10, 2, True), + ("", 0, 10, 2, False), + (" ", 0, 10, 2, False), + ], +) +def test_repeat_integer_validation(name, start, end, step, expected_valid): + try: + RepeatInteger(name, start, end, step) + assert expected_valid + except (RuntimeError, TypeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "name, values, expected_valid", + [ + ("name", ["a"], True), + ("", ["a"], False), + (" ", ["a"], False), + ("name", [1, 2], False), + ("name", [], False), + ], +) +def test_repeat_string_validation(name, values, expected_valid): + try: + RepeatString(name, values) + assert expected_valid + except (RuntimeError, TypeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "name, values, expected_valid", + [ + ("name", ["a"], True), + ("", ["a"], False), + (" ", ["a"], False), + ("name", [1, 2], False), + ("name", [], False), + ], +) +def test_repeat_enumerated_validation(name, values, expected_valid): + try: + RepeatEnumerated(name, values) + assert expected_valid + except (RuntimeError, TypeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "name, value, expected_valid", + [ + ("name", "value", True), + ("name", "", True), + ("name", " ", True), + ("name", "12", True), + ("", "12", False), + (" ", "12", False), + ], +) +def test_variable_validation(name, value, expected_valid): try: Variable(name, value) - return True + assert expected_valid except RuntimeError: - return False - - -def check_label(name, value): + assert not expected_valid + + +@pytest.mark.parametrize( + "name, value, expected_valid", + [ + ("name", "value", True), + ("name", "", True), + ("name", " ", True), + ("name", "12", True), + ("", "12", False), + (" ", "12", False), + ], +) +def test_label_validation(name, value, expected_valid): try: Label(name, value) - return True + assert expected_valid except RuntimeError: - return False - - -def check_limit(name, int_token): + assert not expected_valid + + +@pytest.mark.parametrize( + "name, token, expected_valid", + [ + ("name", 1, True), + ("name", 20000, True), + ("name", "ten", False), + ("name", "2", False), + ("", "2", False), + (" ", "2", False), + ], +) +def test_limit_validation(name, token, expected_valid): + try: + Limit(name, token) + assert expected_valid + except (RuntimeError, TypeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "name, path, token, expected_valid", + [ + ("limit_name", "/path/to/limit", 1, True), + ("limit_name", "/path/to/limit", 999999, True), + ("limit_name", "", 1, True), + ("", "", 1, False), + (" ", "", 1, False), + ], +) +def test_inlimit_validation(name, path, token, expected_valid): + try: + InLimit(name, path, token) + assert expected_valid + except (RuntimeError, TypeError): + assert not expected_valid + + +@pytest.mark.parametrize( + "name, expected_valid", + [(str(i), True) for i in range(25)] + + [ + ("_", True), + ("__", True), + ("_._", True), + ("1.2", True), + ("fred.doc", True), + ("_.1", True), + (".", False), + ("", False), + (" ", False), + (" ", False), + ("fred doc", False), + ("1 ", False), + ], +) +def test_node_name_validation(name, expected_valid): try: - Limit(name, int_token) - return True + Task(name) + Family(name) + Suite(name) + assert expected_valid except RuntimeError: - return False - except TypeError: - return False + assert not expected_valid -def check_inlimit(name, path_to_node, int_token): - try: - InLimit(name, path_to_node, int_token) - return True - except RuntimeError: - return False - except TypeError: - return False +def test_defs_load_from_nonexistent_file_raises(): + with pytest.raises(RuntimeError): + Defs("a_made_up_path_that_doesnt_not_exit.def") -def check_repeat_date(name, start, end, step): - try: - RepeatDate(name, start, end, step) - return True - except RuntimeError: - return False +def test_save_and_reload_defs(tmp_path): + defs = Defs() + defs.add_suite("s1") + defs_file = tmp_path / "testerror.def" + defs.save_as_defs(str(defs_file)) + assert Defs(str(defs_file)) -def check_repeat_datetime(name, start, end, step): - try: - RepeatDateTime(name, start, end, step) - return True - except RuntimeError: - return False +def test_duplicate_suite_not_allowed(): + with pytest.raises(RuntimeError): + defs = Defs() + defs.add_suite("s1") + defs.add_suite("s1") -def check_repeat_integer(name, start, end, step): - try: - RepeatInteger(name, start, end, step) - return True - except RuntimeError: - return False - except TypeError: - return False +def test_adding_today_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_today("00:30") -def check_repeat_enumerated(name, list_of_strings): - try: - RepeatEnumerated(name, list_of_strings) - return True - except RuntimeError: - return False - except TypeError: - return False +def test_adding_time_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_time("00:30") -def check_repeat_string(name, list_of_strings): - try: - RepeatString(name, list_of_strings) - return True - except RuntimeError: - return False - except TypeError: - return False +def test_adding_date_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_date(1, 1, 2016) -def check_node_name(name): - try: - Task(name) - Family(name) - Suite(name) - return True; - except RuntimeError: - return False +def test_adding_day_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_day("monday") -def check_defs(path_to_defs): - try: - Defs(path_to_defs) - return True - except RuntimeError: - return False - - -if __name__ == "__main__": - Test.print_test_start(os.path.basename(__file__)) - - # Names with leading '.' should not be allowed. Will interfere with triggers - # Empty names not allowed - # Spaces not allowed - invalid_names = [".", "", " ", " ", "fred doc", "1 "] - - # Allow names with leading underscore - valid_names = ["_", "__", "_._", "1.2", "fred.doc", "_.1"] - - assert check_day("monday"), "Expected valid day" - assert check_day("tuesday"), "Expected valid day" - assert check_day("wednesday"), "Expected valid day" - assert check_day("thursday"), "Expected valid day" - assert check_day("friday"), "Expected valid day" - assert check_day("saturday"), "Expected valid day" - assert check_day("sunday"), "Expected valid day" - assert check_day("") == False, "Expected exeception" - assert check_day("sunday1") == False, "Expected exeception" - assert check_day("2") == False, "Expected exeception" - - assert check_date(0, 1, 2010), "Expected valid date" - assert check_date(10, 0, 2010), "Expected valid date" - assert check_date(10, 1, 0), "Expected valid date" - assert check_date(0, 0, 0), "Expected valid date" - assert check_date(40, 1, 2010) == False, "Expected exception since day > 31" - assert check_date(-10, 1, 2010) == False, "Expected exception since day >= 0" - assert check_date(1, 14, 2010) == False, "Expected exception since month > 12" - assert check_date(1, -1, 2010) == False, "Expected exception since month >= 0" - assert check_date(1, 1, -2) == False, "Expected exception since year >= 0" - - assert check_date_str("*.1.2010"), "Expected valid date" - assert check_date_str("10.*.2010"), "Expected valid date" - assert check_date_str("10.1.*"), "Expected valid date" - assert check_date_str("*.*.*"), "Expected valid date" - assert check_date_str("40.1.2010") == False, "Expected exception since day > 31" - assert check_date_str("-10.1.2010") == False, "Expected exception since day >= 0" - assert check_date_str("1.14.2010") == False, "Expected exception since month > 12" - assert check_date_str("1.-1.2010") == False, "Expected exception since month >= 0" - assert check_date_str("1.1.-2") == False, "Expected exception since year >= 0" - - # clock do not support wild carding hence we cant use 0 like in Date - assert check_clock(12, 1, 2010), "Expected valid date" - assert check_clock(10, 1, 2010), "Expected valid date" - assert check_clock(10, 1, 1400), "Expected valid date" - assert check_clock(31, 12, 2010), "Expected valid date" - assert check_clock(40, 1, 2010) == False, "Expected exception since day > 31" - assert check_clock(-10, 1, 2010) == False, "Expected exception since day >= 0" - assert check_clock(1, 14, 2010) == False, "Expected exception since month > 12" - assert check_clock(1, -1, 2010) == False, "Expected exception since month >= 0" - assert check_clock(1, 1, -2) == False, "Expected exception since year >= 0" - - assert check_meter("m", 0, 100, 100), "Expected valid Meter" - assert check_meter("m", 0, 100, 0), "Expected valid Meter" - assert check_meter("m", 200, 100, 100) == False, "Expected exception since min > max" - assert check_meter("m", 0, 100, -20) == False, "Expected exception since color_change should between min-max" - assert check_meter("m", 0, 100, 200) == False, "Expected exception since color_change should between min-max" - assert check_meter("", 0, 100, 100) == False, "Expected exception since no name specified" - assert check_meter(" ", 0, 100, 100) == False, "Expected Exception cannot have spaces for a name" - - assert check_queue("m", ["a"]), "Expected valid Queue" - assert check_queue("m", ["a", "b"]), "Expected valid Queue" - assert check_queue("", ["a", "b"]) == False, "Expected exception queue name is empty" - assert check_queue(" ", ["a", "b"]) == False, "Expected Exception cannot have spaces for a name" - assert check_queue(".", ["a", "b"]) == False, "Expected Exception cannot start name with a ." - assert check_queue("m", []) == False, "Expected Exception queue items list is empty" - - assert check_event(1), "Expected valid Event" - assert check_event(2), "Expected valid Event" - assert check_event_number_and_name(2, "fred"), "Expected valid Event" - assert check_event_number_and_name(2, 2) == False, "Expected failure since the name is not a string" - - assert check_repeat_date("m", 20000101, 20001201, 200), "Expected valid repeat" - assert check_repeat_date("m", 20001201, 20000101, 200) == False, "Expected exception since end YMD > start YMD" - assert check_repeat_date("m", 200001011, 20001201, 200) == False, "Expected Exception since start is invalid." - assert check_repeat_date("m", 20000101, 200012013, 200) == False, "Expected Exception since send is invalid." - assert check_repeat_date("m", 00000000, 00000000, 200) == False, "Expected Exception since start/end are not valid dates is invalid." - assert check_repeat_date("", 20000101, 20001201, 200) == False, "Expected Exception since no name specified" - assert check_repeat_date(" ", 20000101, 20001201, 200) == False, "Expected Exception cannot have spaces for a name" - assert check_repeat_datetime("m", "20000101T000000", "20001201T000000", "4800:00:00"), "Expected valid repeat" - assert check_repeat_datetime("m", "20001201T000000", "20000101T000000", "4800:00:00") == False, "Expected exception since end YMD > start YMD" - assert check_repeat_datetime("m", "200001011T000000", "20001201T000000", "4800:00:00") == False, "Expected Exception since start is invalid." - assert check_repeat_datetime("m", "20000101T000000", "200012013T000000", "4800:00:00") == False, "Expected Exception since send is invalid." - assert check_repeat_datetime("m", "00000000T000000", "00000000T000000", "4800:00:00") == False, "Expected Exception since start/end are not valid dates is invalid." - assert check_repeat_datetime("", "20000101T000000", "20001201T000000", "4800:00:00") == False, "Expected Exception since no name specified" - assert check_repeat_datetime(" ", "20000101T000000", "20001201T000000", "4800:00:00") == False, "Expected Exception cannot have spaces for a name" - assert check_repeat_integer("name", 0, 10, 2), "Expected valid repeat" - assert check_repeat_integer("", 0, 10, 2) == False, "Expected Exception since no name specified" - assert check_repeat_integer(" ", 0, 10, 2) == False, "Expected Exception cannot have spaces for a name" - assert check_repeat_string("name", ["a"]), "Expected valid repeat" - assert check_repeat_string("", ["a"]) == False, "Expected Exception since no name specified" - assert check_repeat_string(" ", ["a"]) == False, "Expected Exception cannot have spaces for a name" - assert check_repeat_string("name", [1, 2]) == False, "Expected Exception since a list of strings was expected" - assert check_repeat_string("name", []) == False, "Expected Exception since list of strings is empty" - assert check_repeat_enumerated("name", ["a"]), "Expected valid repeat" - assert check_repeat_enumerated("", ["a"]) == False, "Expected Exception since no name specified" - assert check_repeat_enumerated(" ", ["a"]) == False, "Expected Exception since no name specified" - assert check_repeat_enumerated("name", [1, 2]) == False, "Expected Exception since a list of strings was expected" - assert check_repeat_enumerated("name", []) == False, "Expected Exception since list is empty" - - assert check_variable("name", "value"), "Expected valid Variable" - assert check_variable("name", ""), "Expected valid Variable" - assert check_variable("name", " "), "Expected valid Variable" - assert check_variable("name", "12"), "Expected valid Variable" - assert check_variable("", "12") == False, "Expected Exception name must be specified" - assert check_variable(" ", "12") == False, "Expected Exception cannot have spaces for a name" - - assert check_label("name", "value"), "Expected valid label" - assert check_label("name", ""), "Expected valid label" - assert check_label("name", " "), "Expected valid label" - assert check_label("name", "12"), "Expected valid label" - assert check_label("", "12") == False, "Expected exception name must be specified" - assert check_label(" ", "12") == False, "Expected Exception cannot have spaces for a name" - - assert check_limit("name", 1), "Expected valid limit" - assert check_limit("name", 20000), "Expected valid limit" - assert check_limit("name", "ten") == False, "Expected exception, token must be a integer" - assert check_limit("name", "2") == False, "Expected exception, token must be a integer" - assert check_limit("", "2") == False, "Expected exception, no name specified" - assert check_limit(" ", "2") == False, "Expected exception, cannot have spaces for a name" - - assert check_inlimit("limit_name", "/path/to/limit", 1), "Expected valid in limit" - assert check_inlimit("limit_name", "/path/to/limit", 999999), "Expected valid in limit" - assert check_inlimit("limit_name", "", 1), "Expected valid in limit" - assert check_inlimit("", "", 1) == False, "Expected exception, no limit name specified" - assert check_inlimit(" ", "", 1) == False, "Expected exception, cannot have spaces for a name" - - # ======================================================================== - print("Check node names") - for i in range(25): - assert check_node_name(str(i)), "Integer names should be allowed" - - for name in valid_names: - assert check_node_name(name), "Expected valid name " + name - - for name in invalid_names: - assert check_node_name(name) == False, "Expected exception for invalid name " + name - - assert check_defs("a_made_up_path_that_doesnt_not_exit.def") == False, "Expected exception, Defs file does not exist" - - # ================================================================================= - print("test save_as_defs") - defs = Defs() # create a empty definition - s1 = defs.add_suite("s1") # create a suite "s1" and add to defs - defs.save_as_defs("testerror.def") # create a defs on disk - assert check_defs("testerror.def"), "Expected defs file to exist" - os.remove("testerror.def") - - # ================================================================================= - print("Check duplicate suites not allowed") - test_passed = False - try: - defs = Defs() # create a empty definition - s1 = defs.add_suite("s1") # create a suite "s1" and add to defs - s2 = defs.add_suite("s1") # Exception thrown trying to add suite name "s1" again - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate suite test failed" - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_today("00:30") - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding today at the suite level should fail" - print("check adding today at the suite level: RuntimeError: ") - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_time("00:30") - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding time at the suite level should fail" - print("check adding time at the suite level: RuntimeError: ") - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_date(1, 1, 2016) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding date at the suite level should fail" - print("check adding date at the suite level: RuntimeError: ") - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_day("monday") - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding day at the suite level should fail" - print("check adding day at the suite level: RuntimeError: ") - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_trigger("1 == 0") - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding a trigger at suite level should fail" - print("check adding trigger at the suite level: RuntimeError: ") - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_part_trigger("1 == 0") - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding a part trigger at suite level should fail" - print("check adding part trigger at the suite level: RuntimeError: ") - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_complete("1 == 0") - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding a complete trigger at suite level should fail" - print("check adding complete trigger at the suite level: RuntimeError: ") - - # ================================================================================= - test_passed = False - try: - defs = Defs() - defs.add_suite("1").add_part_complete("1 == 0") - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Adding a part complete trigger at suite level should fail" - print("check adding part complete trigger at the suite level: RuntimeError: ") - - # ================================================================================= - print("check adding trigger referencing parent should fail ECFLOW-1436") +def test_adding_trigger_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_trigger("1 == 0") + + +def test_adding_part_trigger_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_part_trigger("1 == 0") + + +def test_adding_complete_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_complete("1 == 0") + + +def test_adding_part_complete_at_suite_level_raises(): + with pytest.raises(RuntimeError): + Defs().add_suite("1").add_part_complete("1 == 0") + + +def test_trigger_referencing_parent_without_dot_fails(): defs = Defs() defs += Suite("obs") + Family("anon", Task("t1", Trigger("anon == complete"))) - assert len(defs.check()) > 0, "Adding a trigger referencing a parent without .. should fail" + assert ( + len(defs.check()) > 0 + ), "Adding a trigger referencing a parent without .. should fail" - # ================================================================================= - print("check duplicate family not allowed") - test_passed = False - try: - suite = Suite("1") - fam1 = Family("1") - fam2 = Family("1") - suite.add_family(fam1) - suite.add_family(fam2) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate Family test failed" - - # ================================================================================= - print("duplicate task not allowed") - test_passed = False - try: - suite = Suite("1") - ta = Task("a") - tb = Task("a") - suite.add_task(ta) - suite.add_task(tb) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate Task test failed" - - # ================================================================================= - print("Task and family of same name should not be allowed") - test_passed = False - try: - suite = Suite("1") - ta = Task("a") - tb = Family("a") - suite.add_task(ta) - suite.add_family(tb) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Task and family of same name should not be allowed" - - # ================================================================================= - print("check duplicate meter not allowed") - test_passed = False - try: + +def test_duplicate_family_not_allowed(): + suite = Suite("1") + suite.add_family(Family("1")) + with pytest.raises(RuntimeError): + suite.add_family(Family("1")) + + +def test_duplicate_task_not_allowed(): + suite = Suite("1") + suite.add_task(Task("a")) + with pytest.raises(RuntimeError): + suite.add_task(Task("a")) + + +def test_task_and_family_same_name_not_allowed(): + suite = Suite("1") + suite.add_task(Task("a")) + with pytest.raises(RuntimeError): + suite.add_family(Family("a")) + + +def test_duplicate_meter_not_allowed(): + with pytest.raises(RuntimeError): defs = Defs() suite = defs.add_suite("1") ta = suite.add_task("a") - ta.add_meter("meter", 0, 100); - ta.add_meter("meter", 0, 100); - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate meter test failed" - - # ================================================================================= - print("check duplicate queue not allowed") - test_passed = False - try: + ta.add_meter("meter", 0, 100) + ta.add_meter("meter", 0, 100) + + +def test_duplicate_queue_not_allowed(): + with pytest.raises(RuntimeError): defs = Defs() suite = defs.add_suite("1") - suite.add_queue("q", ["a"]); - suite.add_queue("q", ["a", "b"]); - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate queue test failed" - - # ================================================================================= - print("check duplicate event not allowed") - test_passed = False - try: + suite.add_queue("q", ["a"]) + suite.add_queue("q", ["a", "b"]) + + +def test_duplicate_event_by_number_and_string_not_allowed(): + with pytest.raises(RuntimeError): defs = Defs() suite = defs.add_suite("1") ta = suite.add_task("a") - ta.add_event(1); - ta.add_event("1"); - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate event test failed" - - test_passed = False - try: + ta.add_event(1) + ta.add_event("1") + + +def test_duplicate_named_event_not_allowed(): + with pytest.raises(RuntimeError): defs = Defs() suite = defs.add_suite("1") ta = suite.add_task("a") - ta.add_event("name"); - ta.add_event("name"); - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate event test failed" - - test_passed = False - try: + ta.add_event("name") + ta.add_event("name") + + +def test_duplicate_numbered_named_event_not_allowed(): + with pytest.raises(RuntimeError): defs = Defs() suite = defs.add_suite("1") ta = suite.add_task("a") - ta.add_event(1, "name"); - ta.add_event(1, "name"); - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "duplicate event test failed" - - # ================================================================================= - print("check cannot add same node to different containers") - defs1 = Defs(); + ta.add_event(1, "name") + ta.add_event(1, "name") + + +def test_cannot_add_same_suite_to_two_defs(): + defs1 = Defs() suite = defs1.add_suite("s1") + with pytest.raises(RuntimeError): + Defs().add_suite(suite) + + +def test_cannot_add_same_family_to_two_suites(): + defs3 = Defs() + suite = defs3.add_suite("s1") + family = suite.add_family("f1") + with pytest.raises(RuntimeError): + new_suite = Defs().add_suite("s2") + new_suite.add_family(family) + + +def test_cannot_add_same_task_to_two_containers(): + defs4 = Defs() + suite = defs4.add_suite("s1") family = suite.add_family("f1") task = family.add_task("t1") + with pytest.raises(RuntimeError): + Defs().add_suite("s1").add_task(task) - test_passed = False - try: - defs2 = Defs(); - defs2.add_suite(suite) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Can't add same suite to two different defs" - - test_passed = False - try: - defs3 = Defs(); - suite = defs3.add_suite(suite) - suite.add_family(family) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Can't add same family to two different suites" - - test_passed = False - try: - defs4 = Defs(); - suite = defs4.add_suite("s1") - suite.add_task(task) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Can't add same task to two different containers" - - test_passed = False - try: - defs4 = Defs(); - suite = defs4.add_suite("s1") + +def test_cannot_add_two_autocancel_on_same_node(): + with pytest.raises(RuntimeError): + suite = Defs().add_suite("s1") suite.add_autocancel(3) suite.add_autocancel(4) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Can't add two autocancel on same node" - test_passed = False - try: - defs4 = Defs(); - suite = defs4.add_suite("s1") + +def test_cannot_add_autocancel_and_autoarchive_together(): + with pytest.raises(RuntimeError): + suite = Defs().add_suite("s1") suite.add_autoarchive(3) suite.add_autocancel(4) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Can't add autocancel and autoarchive on same node" - test_passed = False - try: - defs4 = Defs(); - suite = defs4.add_suite("s1") + +def test_cannot_add_two_autorestore_on_same_node(): + with pytest.raises(RuntimeError): + suite = Defs().add_suite("s1") suite.add_autorestore(["/s1"]) suite.add_autorestore(["/s1"]) - except RuntimeError as e: - test_passed = True - pass - assert test_passed, "Can't add two autorestore on same node" - print("All Tests pass") diff --git a/libs/pyext/test/py_u_TestExportClient.py b/libs/pyext/test/py_u_TestExportClient.py index addd826d7..3cc827e69 100644 --- a/libs/pyext/test/py_u_TestExportClient.py +++ b/libs/pyext/test/py_u_TestExportClient.py @@ -8,12 +8,11 @@ # nor does it submit to any jurisdiction. # -import unittest - import ecflow as ecf +import pytest -class TestClient(unittest.TestCase): +class TestClient: """Tests for py::class_ as exposed in ExportClient.cpp. The class is registered under the Python name ``Client``. @@ -82,32 +81,32 @@ class TestClient(unittest.TestCase): def test_default_constructor_gives_localhost_3141(self): """Client() defaults to localhost:3141.""" ci = ecf.Client() - self.assertEqual(ci.get_host(), "localhost") - self.assertEqual(ci.get_port(), "3141") + assert ci.get_host() == "localhost" + assert ci.get_port() == "3141" def test_constructor_host_colon_port_string(self): """Client('host:port') parses both components correctly.""" ci = ecf.Client("myhost:4444") - self.assertEqual(ci.get_host(), "myhost") - self.assertEqual(ci.get_port(), "4444") + assert ci.get_host() == "myhost" + assert ci.get_port() == "4444" def test_constructor_host_at_port_string(self): """Client('host@port') is also a valid combined form.""" ci = ecf.Client("myhost@5555") - self.assertEqual(ci.get_host(), "myhost") - self.assertEqual(ci.get_port(), "5555") + assert ci.get_host() == "myhost" + assert ci.get_port() == "5555" def test_constructor_separate_host_and_port_strings(self): """Client(host, port_str) sets host and port individually.""" ci = ecf.Client("srv1", "6000") - self.assertEqual(ci.get_host(), "srv1") - self.assertEqual(ci.get_port(), "6000") + assert ci.get_host() == "srv1" + assert ci.get_port() == "6000" def test_constructor_separate_host_and_port_int(self): """Client(host, port_int) accepts an integer port and converts it.""" ci = ecf.Client("srv2", 7777) - self.assertEqual(ci.get_host(), "srv2") - self.assertEqual(ci.get_port(), "7777") + assert ci.get_host() == "srv2" + assert ci.get_port() == "7777" # ------------------------------------------------------------------ # get_host / get_port @@ -116,12 +115,12 @@ def test_constructor_separate_host_and_port_int(self): def test_get_host_returns_str(self): """get_host() always returns a Python str.""" ci = ecf.Client() - self.assertIsInstance(ci.get_host(), str) + assert isinstance(ci.get_host(), str) def test_get_port_returns_str(self): """get_port() always returns a Python str.""" ci = ecf.Client() - self.assertIsInstance(ci.get_port(), str) + assert isinstance(ci.get_port(), str) # ------------------------------------------------------------------ # set_host_port — three overloads @@ -131,71 +130,71 @@ def test_set_host_port_str_str_overload(self): """set_host_port(host, port_str) sets host and port.""" ci = ecf.Client() ci.set_host_port("testhost", "4242") - self.assertEqual(ci.get_host(), "testhost") - self.assertEqual(ci.get_port(), "4242") + assert ci.get_host() == "testhost" + assert ci.get_port() == "4242" def test_set_host_port_str_int_overload(self): """set_host_port(host, port_int) accepts an integer port.""" ci = ecf.Client() ci.set_host_port("inthost", 9999) - self.assertEqual(ci.get_host(), "inthost") - self.assertEqual(ci.get_port(), "9999") + assert ci.get_host() == "inthost" + assert ci.get_port() == "9999" def test_set_host_port_colon_combined_string(self): """set_host_port('host:port') parses both components from one string.""" ci = ecf.Client() ci.set_host_port("combined:3333") - self.assertEqual(ci.get_host(), "combined") - self.assertEqual(ci.get_port(), "3333") + assert ci.get_host() == "combined" + assert ci.get_port() == "3333" def test_set_host_port_at_combined_string(self): """set_host_port('host@port') is the alternative combined form.""" ci = ecf.Client() ci.set_host_port("athost@2222") - self.assertEqual(ci.get_host(), "athost") - self.assertEqual(ci.get_port(), "2222") + assert ci.get_host() == "athost" + assert ci.get_port() == "2222" def test_set_host_port_replaces_previous_values(self): """A second set_host_port call overwrites the first.""" ci = ecf.Client("first", "100") ci.set_host_port("second", "200") - self.assertEqual(ci.get_host(), "second") - self.assertEqual(ci.get_port(), "200") + assert ci.get_host() == "second" + assert ci.get_port() == "200" def test_set_host_port_empty_host_raises(self): """set_host_port('', port) raises RuntimeError for an empty host.""" ci = ecf.Client() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ci.set_host_port("", "3141") def test_set_host_port_empty_port_raises(self): """set_host_port(host, '') raises RuntimeError for an empty port.""" ci = ecf.Client() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ci.set_host_port("myhost", "") def test_set_host_port_non_numeric_port_raises(self): """set_host_port(host, 'notaport') raises RuntimeError for a non-numeric port.""" ci = ecf.Client() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ci.set_host_port("myhost", "notaport") def test_set_host_port_no_separator_raises(self): """set_host_port('hostonly') raises RuntimeError when no ':' or '@' separator.""" ci = ecf.Client() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ci.set_host_port("hostonly") def test_set_host_port_colon_only_host_raises(self): """set_host_port(':3141') raises RuntimeError for an empty host component.""" ci = ecf.Client() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ci.set_host_port(":3141") def test_set_host_port_colon_only_port_raises(self): """set_host_port('host:') raises RuntimeError for an empty port component.""" ci = ecf.Client() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ci.set_host_port("host:") # ------------------------------------------------------------------ @@ -206,19 +205,19 @@ def test_version_returns_non_empty_string(self): """version() returns a non-empty string (e.g. '5.14.0').""" ci = ecf.Client() v = ci.version() - self.assertIsInstance(v, str) - self.assertGreater(len(v), 0) + assert isinstance(v, str) + assert len(v) > 0 def test_version_is_idempotent(self): """Repeated calls to version() return the same value.""" ci = ecf.Client() - self.assertEqual(ci.version(), ci.version()) + assert ci.version() == ci.version() def test_version_matches_across_instances(self): """Two independent Client instances report the same library version.""" ci1 = ecf.Client() ci2 = ecf.Client() - self.assertEqual(ci1.version(), ci2.version()) + assert ci1.version() == ci2.version() # ------------------------------------------------------------------ # set_user_name @@ -274,33 +273,33 @@ def test_set_connection_attempts_zero_clamped_silently(self): def test_auto_sync_disabled_by_default(self): """is_auto_sync_enabled() returns False for a freshly constructed Client.""" ci = ecf.Client() - self.assertFalse(ci.is_auto_sync_enabled()) + assert not ci.is_auto_sync_enabled() def test_set_auto_sync_true_enables_it(self): """set_auto_sync(True) makes is_auto_sync_enabled() return True.""" ci = ecf.Client() ci.set_auto_sync(True) - self.assertTrue(ci.is_auto_sync_enabled()) + assert ci.is_auto_sync_enabled() def test_set_auto_sync_false_disables_it(self): """set_auto_sync(False) makes is_auto_sync_enabled() return False.""" ci = ecf.Client() ci.set_auto_sync(True) ci.set_auto_sync(False) - self.assertFalse(ci.is_auto_sync_enabled()) + assert not ci.is_auto_sync_enabled() def test_set_auto_sync_toggle(self): """is_auto_sync_enabled() reflects each toggle of set_auto_sync.""" ci = ecf.Client() for expected in [True, False, True, False]: ci.set_auto_sync(expected) - self.assertEqual(ci.is_auto_sync_enabled(), expected) + assert ci.is_auto_sync_enabled() == expected def test_is_auto_sync_enabled_returns_bool(self): """is_auto_sync_enabled() always returns a Python bool (or int-subclass).""" ci = ecf.Client() result = ci.is_auto_sync_enabled() - self.assertIsInstance(result, (bool, int)) + assert isinstance(result, (bool, int)) # ------------------------------------------------------------------ # get_defs @@ -309,7 +308,7 @@ def test_is_auto_sync_enabled_returns_bool(self): def test_get_defs_returns_none_before_sync(self): """get_defs() returns None for a freshly constructed Client (no sync done).""" ci = ecf.Client() - self.assertIsNone(ci.get_defs()) + assert ci.get_defs() is None # ------------------------------------------------------------------ # reset @@ -324,13 +323,13 @@ def test_reset_clears_defs(self): """get_defs() remains None after reset() (nothing to clear, still None).""" ci = ecf.Client() ci.reset() - self.assertIsNone(ci.get_defs()) + assert ci.get_defs() is None def test_reset_clears_handle(self): """ch_handle() is 0 after reset(), just as it was at construction.""" ci = ecf.Client() ci.reset() - self.assertEqual(ci.ch_handle(), 0) + assert ci.ch_handle() == 0 # ------------------------------------------------------------------ # in_sync @@ -339,12 +338,12 @@ def test_reset_clears_handle(self): def test_in_sync_returns_false_before_sync(self): """in_sync() returns False for a freshly constructed Client.""" ci = ecf.Client() - self.assertFalse(ci.in_sync()) + assert not ci.in_sync() def test_in_sync_returns_bool(self): """in_sync() returns a Python bool (or int-subclass).""" ci = ecf.Client() - self.assertIsInstance(ci.in_sync(), (bool, int)) + assert isinstance(ci.in_sync(), (bool, int)) # ------------------------------------------------------------------ # ch_handle @@ -353,12 +352,12 @@ def test_in_sync_returns_bool(self): def test_ch_handle_is_zero_by_default(self): """ch_handle() returns 0 before any ch_register call.""" ci = ecf.Client() - self.assertEqual(ci.ch_handle(), 0) + assert ci.ch_handle() == 0 def test_ch_handle_returns_int(self): """ch_handle() always returns a Python int.""" ci = ecf.Client() - self.assertIsInstance(ci.ch_handle(), int) + assert isinstance(ci.ch_handle(), int) # ------------------------------------------------------------------ # wait_for_server_reply @@ -368,13 +367,13 @@ def test_wait_for_server_reply_zero_timeout_returns_false(self): """wait_for_server_reply(0), without a running server, times out (almost) immediately and returns False.""" ci = ecf.Client("supercalifragilisticexpialidocious", "50000") result = ci.wait_for_server_reply(0) - self.assertFalse(result) + assert not result def test_wait_for_server_reply_returns_bool(self): """wait_for_server_reply() always returns a Python bool (or int-subclass).""" ci = ecf.Client() result = ci.wait_for_server_reply(0) - self.assertIsInstance(result, (bool, int)) + assert isinstance(result, (bool, int)) # ------------------------------------------------------------------ # changed_node_paths @@ -384,7 +383,7 @@ def test_changed_node_paths_empty_before_sync(self): """Iterating changed_node_paths before any sync yields no items.""" ci = ecf.Client() paths = list(ci.changed_node_paths) - self.assertEqual(paths, []) + assert paths == [] # ------------------------------------------------------------------ # debug @@ -393,7 +392,7 @@ def test_changed_node_paths_empty_before_sync(self): def test_debug_true_does_not_raise(self): """debug(True) enables verbose output; must not raise.""" ci = ecf.Client() - ci.debug(True) # must not raise + ci.debug(True) # must not raise ci.debug(False) # restore to silent def test_debug_false_does_not_raise(self): @@ -535,14 +534,14 @@ def test_context_manager_enter_returns_self(self): """__enter__ returns the Client itself (not a proxy or wrapper).""" ci = ecf.Client() with ci as ctx: - self.assertIs(ctx, ci) + assert ctx is ci def test_context_manager_inline_construction_enters_self(self): """'with Client() as c' binds the freshly created Client.""" with ecf.Client() as c: - self.assertIsInstance(c, ecf.Client) + assert isinstance(c, ecf.Client) # The instance is functional inside the block. - self.assertIsInstance(c.version(), str) + assert isinstance(c.version(), str) def test_context_manager_exit_returns_false(self): """__exit__ returns False so that exceptions propagate out of the block.""" @@ -556,7 +555,7 @@ class _Sentinel(Exception): raise _Sentinel("propagated") except _Sentinel: caught_outside = True - self.assertTrue(caught_outside) + assert caught_outside def test_context_manager_clean_exit_does_not_raise(self): """The with-block exits cleanly (no exception) without error.""" @@ -570,31 +569,31 @@ def test_context_manager_clean_exit_does_not_raise(self): def test_client_is_hashable(self): """Client instances are hashable; hash() returns an int.""" ci = ecf.Client() - self.assertIsInstance(hash(ci), int) + assert isinstance(hash(ci), int) def test_client_can_be_inserted_in_set(self): """Client is hashable, so instances can be stored in a Python set.""" ci = ecf.Client() s = {ci} - self.assertIn(ci, s) + assert ci in s def test_client_can_be_used_as_dict_key(self): """Client is hashable, so instances can be used as dictionary keys.""" ci = ecf.Client() d = {ci: "value"} - self.assertEqual(d[ci], "value") + assert d[ci] == "value" def test_two_clients_have_different_hashes(self): """Two independently created Clients have different identity-based hashes.""" ci1 = ecf.Client() ci2 = ecf.Client() - self.assertIsNot(ci1, ci2) - self.assertNotEqual(hash(ci1), hash(ci2)) + assert ci1 is not ci2 + assert hash(ci1) != hash(ci2) def test_same_client_has_stable_hash(self): """hash() of the same Client object returns the same value on repeated calls.""" ci = ecf.Client() - self.assertEqual(hash(ci), hash(ci)) + assert hash(ci) == hash(ci) # ------------------------------------------------------------------ # __eq__ / __ne__ — identity-based (noncopyable type) @@ -603,28 +602,28 @@ def test_same_client_has_stable_hash(self): def test_eq_same_object_is_true(self): """A Client compares equal to itself.""" ci = ecf.Client() - self.assertTrue(ci == ci) + assert ci == ci def test_eq_two_distinct_clients_is_false(self): """Two distinct Client objects with the same configuration are not equal (identity semantics — noncopyable C++ object).""" ci1 = ecf.Client() ci2 = ecf.Client() - self.assertFalse(ci1 == ci2) + assert not ci1 == ci2 def test_ne_same_object_is_false(self): """A Client is not not-equal to itself.""" ci = ecf.Client() - self.assertFalse(ci != ci) + assert not ci != ci def test_ne_two_distinct_clients_is_true(self): """Two distinct Client objects are not-equal under identity semantics.""" ci1 = ecf.Client() ci2 = ecf.Client() - self.assertTrue(ci1 != ci2) + assert ci1 != ci2 -class TestWhyCmd(unittest.TestCase): +class TestWhyCmd: """Tests for py::class_ as exposed in ExportClient.cpp. Exposed API @@ -689,12 +688,12 @@ def test_construct_with_empty_path_does_not_raise(self): def test_construct_with_nonexistent_path_raises(self): """WhyCmd(defs, '/no/such/node') raises RuntimeError.""" defs = self._make_defs_with_task() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.WhyCmd(defs, "/no/such/node") def test_construct_with_null_defs_raises(self): """WhyCmd(None, path) raises RuntimeError because the defs pointer is null.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.WhyCmd(None, "/s/f/t") # ------------------------------------------------------------------ @@ -705,35 +704,35 @@ def test_why_returns_str(self): """why() always returns a Python str.""" defs = self._make_defs_with_task() cmd = ecf.WhyCmd(defs, "/s/f/t") - self.assertIsInstance(cmd.why(), str) + assert isinstance(cmd.why(), str) def test_why_returns_non_empty_string(self): """why() returns a non-empty explanatory string (nodes not yet begun).""" defs = self._make_defs_with_task() cmd = ecf.WhyCmd(defs, "/s/f/t") result = cmd.why() - self.assertGreater(len(result), 0) + assert len(result) > 0 def test_why_is_idempotent(self): """Calling why() twice returns the same result.""" defs = self._make_defs_with_task() cmd = ecf.WhyCmd(defs, "/s/f/t") - self.assertEqual(cmd.why(), cmd.why()) + assert cmd.why() == cmd.why() def test_why_on_suite_path(self): """why() is callable when WhyCmd was constructed with a suite path.""" defs = self._make_defs_with_task() cmd = ecf.WhyCmd(defs, "/s") result = cmd.why() - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_why_on_empty_path_returns_root_analysis(self): """why() on a WhyCmd constructed with an empty path returns root analysis.""" defs = self._make_defs_with_task() cmd = ecf.WhyCmd(defs, "") result = cmd.why() - self.assertIsInstance(result, str) - self.assertGreater(len(result), 0) + assert isinstance(result, str) + assert len(result) > 0 # ------------------------------------------------------------------ # __hash__ — identity-based (noncopyable boost.python type) @@ -743,24 +742,24 @@ def test_why_cmd_is_hashable(self): """WhyCmd instances are hashable; hash() returns an int.""" defs = self._make_defs_with_task() cmd = ecf.WhyCmd(defs, "/s/f/t") - self.assertIsInstance(hash(cmd), int) + assert isinstance(hash(cmd), int) def test_why_cmd_can_be_inserted_in_set(self): """WhyCmd is hashable, so instances can be stored in a Python set.""" defs = self._make_defs_with_task() cmd = ecf.WhyCmd(defs, "/s/f/t") s = {cmd} - self.assertIn(cmd, s) + assert cmd in s def test_two_why_cmds_have_different_hashes(self): """Two independently created WhyCmd objects have different identity hashes.""" defs = self._make_defs_with_task() cmd1 = ecf.WhyCmd(defs, "/s/f/t") cmd2 = ecf.WhyCmd(defs, "/s/f/t") - self.assertNotEqual(hash(cmd1), hash(cmd2)) + assert hash(cmd1) != hash(cmd2) -class TestUrlCmd(unittest.TestCase): +class TestUrlCmd: """Tests for py::class_ as exposed in ExportClient.cpp. Exposed API @@ -833,18 +832,18 @@ def test_construct_without_url_cmd_variable(self): def test_construct_with_empty_path_raises(self): """UrlCmd(defs, '') raises RuntimeError for an empty node path.""" defs = self._make_minimal_defs() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.UrlCmd(defs, "") def test_construct_with_nonexistent_path_raises(self): """UrlCmd(defs, '/no/such/node') raises RuntimeError.""" defs = self._make_minimal_defs() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.UrlCmd(defs, "/no/such/node") def test_construct_with_null_defs_raises(self): """UrlCmd(None, path) raises RuntimeError because the defs pointer is null.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.UrlCmd(None, "/s/f/t") # ------------------------------------------------------------------ @@ -856,7 +855,7 @@ def test_execute_without_url_cmd_returns_none(self): defs = self._make_minimal_defs() cmd = ecf.UrlCmd(defs, "/s/f/t") result = cmd.execute() - self.assertIsNone(result) + assert result is None # ------------------------------------------------------------------ # __hash__ — identity-based (noncopyable boost.python type) @@ -866,22 +865,18 @@ def test_url_cmd_is_hashable(self): """UrlCmd instances are hashable; hash() returns an int.""" defs = self._make_minimal_defs() cmd = ecf.UrlCmd(defs, "/s/f/t") - self.assertIsInstance(hash(cmd), int) + assert isinstance(hash(cmd), int) def test_url_cmd_can_be_inserted_in_set(self): """UrlCmd is hashable, so instances can be stored in a Python set.""" defs = self._make_minimal_defs() cmd = ecf.UrlCmd(defs, "/s/f/t") s = {cmd} - self.assertIn(cmd, s) + assert cmd in s def test_two_url_cmds_have_different_hashes(self): """Two independently created UrlCmd objects have different identity hashes.""" defs = self._make_minimal_defs() cmd1 = ecf.UrlCmd(defs, "/s/f/t") cmd2 = ecf.UrlCmd(defs, "/s/f/t") - self.assertNotEqual(hash(cmd1), hash(cmd2)) - - -if __name__ == "__main__": - unittest.main(verbosity=2) + assert hash(cmd1) != hash(cmd2) diff --git a/libs/pyext/test/py_u_TestExportCore.py b/libs/pyext/test/py_u_TestExportCore.py index 076669369..1a67dbf8f 100644 --- a/libs/pyext/test/py_u_TestExportCore.py +++ b/libs/pyext/test/py_u_TestExportCore.py @@ -9,12 +9,11 @@ # import copy -import unittest - import ecflow as ecf +import pytest -class TestDebugBuild(unittest.TestCase): +class TestDebugBuild: """Tests for the free function debug_build() exposed in ExportCore.cpp. Exposed API @@ -29,19 +28,19 @@ class TestDebugBuild(unittest.TestCase): def test_returns_bool(self): """debug_build() returns a Python bool.""" - self.assertIsInstance(ecf.debug_build(), bool) + assert isinstance(ecf.debug_build(), bool) def test_callable_with_no_arguments(self): """debug_build() takes no arguments.""" result = ecf.debug_build() - self.assertIn(result, (True, False)) + assert result in (True, False) def test_result_is_stable(self): """Repeated calls return the same value (it is a build-time constant).""" - self.assertEqual(ecf.debug_build(), ecf.debug_build()) + assert ecf.debug_build() == ecf.debug_build() -class TestEdit(unittest.TestCase): +class TestEdit: """Tests for py::class_ exposed as ecf.Edit in ExportCore.cpp. Exposed API @@ -67,25 +66,25 @@ class TestEdit(unittest.TestCase): def test_ctor_dict_is_accepted(self): """Edit(dict) constructs successfully.""" e = ecf.Edit({"X": "1"}) - self.assertIsInstance(e, ecf.Edit) + assert isinstance(e, ecf.Edit) def test_ctor_empty_dict_is_accepted(self): """Edit({}) constructs without error.""" e = ecf.Edit({}) - self.assertIsInstance(e, ecf.Edit) + assert isinstance(e, ecf.Edit) def test_ctor_dict_adds_variable_when_passed_to_node(self): """Edit(dict) variables are visible on the node after Node.add(Edit(...)).""" t = ecf.Task("t1") t.add(ecf.Edit({"MY_VAR": "hello"})) - self.assertEqual(t.find_variable("MY_VAR").value(), "hello") + assert t.find_variable("MY_VAR").value() == "hello" def test_ctor_dict_multiple_entries_all_added(self): """All key-value pairs in the dict become node variables.""" t = ecf.Task("t1") t.add(ecf.Edit({"A": "1", "B": "2"})) - self.assertEqual(t.find_variable("A").value(), "1") - self.assertEqual(t.find_variable("B").value(), "2") + assert t.find_variable("A").value() == "1" + assert t.find_variable("B").value() == "2" # ------------------------------------------------------------------ # Constructor: Edit(dict, dict) @@ -95,13 +94,13 @@ def test_ctor_two_dicts_merges_both(self): """Edit(dict, dict) combines variables from both dicts.""" t = ecf.Task("t1") t.add(ecf.Edit({"X": "10"}, {"Y": "20"})) - self.assertEqual(t.find_variable("X").value(), "10") - self.assertEqual(t.find_variable("Y").value(), "20") + assert t.find_variable("X").value() == "10" + assert t.find_variable("Y").value() == "20" def test_ctor_two_empty_dicts_is_accepted(self): """Edit({}, {}) constructs without error.""" e = ecf.Edit({}, {}) - self.assertIsInstance(e, ecf.Edit) + assert isinstance(e, ecf.Edit) # ------------------------------------------------------------------ # Constructor: Edit(**kwargs) via raw_function @@ -111,8 +110,8 @@ def test_ctor_kwargs_adds_variables(self): """Edit(**kwargs) variables are visible on the node after Node.add(Edit(...)).""" t = ecf.Task("t1") t.add(ecf.Edit(ALPHA="a", BETA="b")) - self.assertEqual(t.find_variable("ALPHA").value(), "a") - self.assertEqual(t.find_variable("BETA").value(), "b") + assert t.find_variable("ALPHA").value() == "a" + assert t.find_variable("BETA").value() == "b" # ------------------------------------------------------------------ # Constructor: Edit(dict, **kwargs) @@ -122,8 +121,8 @@ def test_ctor_dict_and_kwargs_merges_both(self): """Edit(dict, **kwargs) combines positional dict and keyword arguments.""" t = ecf.Task("t1") t.add(ecf.Edit({"FROM_DICT": "d"}, FROM_KW="k")) - self.assertEqual(t.find_variable("FROM_DICT").value(), "d") - self.assertEqual(t.find_variable("FROM_KW").value(), "k") + assert t.find_variable("FROM_DICT").value() == "d" + assert t.find_variable("FROM_KW").value() == "k" # ------------------------------------------------------------------ # Invalid argument @@ -131,7 +130,7 @@ def test_ctor_dict_and_kwargs_merges_both(self): def test_ctor_non_dict_positional_raises(self): """Edit accepts only dict positional arguments; a plain string raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Edit("not_a_dict") # ------------------------------------------------------------------ @@ -142,11 +141,11 @@ def test_str_on_instance_raises_due_to_static_method_mismatch(self): """str(Edit(...)) raises because __str__ is bound to a static C++ method that takes no arguments; Boost.Python cannot pass the implicit self.""" e = ecf.Edit({"A": "1"}) - with self.assertRaises(Exception): + with pytest.raises(Exception): str(e) -class TestFile(unittest.TestCase): +class TestFile: """Tests for py::class_ exposed as ecf.File in ExportCore.cpp. Exposed API @@ -167,7 +166,7 @@ class TestFile(unittest.TestCase): def test_direct_construction_raises(self): """File cannot be instantiated from Python.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.File() # ------------------------------------------------------------------ @@ -176,12 +175,12 @@ def test_direct_construction_raises(self): def test_find_server_returns_str(self): """File.find_server() returns a Python str.""" - self.assertIsInstance(ecf.File.find_server(), str) + assert isinstance(ecf.File.find_server(), str) def test_find_server_callable_without_instance(self): """find_server() is a static method callable on the class.""" result = ecf.File.find_server() - self.assertIsInstance(result, str) + assert isinstance(result, str) # ------------------------------------------------------------------ # find_client() @@ -189,11 +188,11 @@ def test_find_server_callable_without_instance(self): def test_find_client_returns_str(self): """File.find_client() returns a Python str.""" - self.assertIsInstance(ecf.File.find_client(), str) + assert isinstance(ecf.File.find_client(), str) def test_find_client_callable_without_instance(self): """find_client() is a static method callable on the class.""" - self.assertIsInstance(ecf.File.find_client(), str) + assert isinstance(ecf.File.find_client(), str) # ------------------------------------------------------------------ # source_dir() @@ -201,11 +200,11 @@ def test_find_client_callable_without_instance(self): def test_source_dir_returns_str(self): """File.source_dir() returns a Python str.""" - self.assertIsInstance(ecf.File.source_dir(), str) + assert isinstance(ecf.File.source_dir(), str) def test_source_dir_callable_without_instance(self): """source_dir() is a static method callable on the class.""" - self.assertIsInstance(ecf.File.source_dir(), str) + assert isinstance(ecf.File.source_dir(), str) # ------------------------------------------------------------------ # build_dir() @@ -213,14 +212,14 @@ def test_source_dir_callable_without_instance(self): def test_build_dir_returns_str(self): """File.build_dir() returns a Python str.""" - self.assertIsInstance(ecf.File.build_dir(), str) + assert isinstance(ecf.File.build_dir(), str) def test_build_dir_callable_without_instance(self): """build_dir() is a static method callable on the class.""" - self.assertIsInstance(ecf.File.build_dir(), str) + assert isinstance(ecf.File.build_dir(), str) -class TestStyle(unittest.TestCase): +class TestStyle: """Tests for py::enum_ exposed as ecf.Style in ExportCore.cpp. Exposed API @@ -267,7 +266,7 @@ def test_all_four_values_are_distinct(self): for i, a in enumerate(values): for j, b in enumerate(values): if i != j: - self.assertNotEqual(a, b) + assert a != b # ------------------------------------------------------------------ # __eq__ and __ne__ @@ -275,19 +274,19 @@ def test_all_four_values_are_distinct(self): def test_eq_same_value(self): """A Style member compares equal to itself.""" - self.assertEqual(ecf.Style.DEFS, ecf.Style.DEFS) + assert ecf.Style.DEFS == ecf.Style.DEFS def test_ne_different_values(self): """Different Style members are not equal.""" - self.assertNotEqual(ecf.Style.DEFS, ecf.Style.STATE) + assert ecf.Style.DEFS != ecf.Style.STATE def test_eq_incompatible_type_returns_not_implemented(self): """__eq__ returns NotImplemented for an incompatible RHS type.""" - self.assertIs(ecf.Style.DEFS.__eq__("DEFS"), NotImplemented) + assert ecf.Style.DEFS.__eq__("DEFS") is NotImplemented def test_ne_incompatible_type_returns_not_implemented(self): """__ne__ returns NotImplemented for an incompatible RHS type.""" - self.assertIs(ecf.Style.DEFS.__ne__("DEFS"), NotImplemented) + assert ecf.Style.DEFS.__ne__("DEFS") is NotImplemented # ------------------------------------------------------------------ # __lt__ / __le__ / __gt__ / __ge__ @@ -295,35 +294,35 @@ def test_ne_incompatible_type_returns_not_implemented(self): def test_lt_orders_by_value(self): """Style.NOTHING < Style.DEFS (integer value 0 < 1).""" - self.assertLess(ecf.Style.NOTHING, ecf.Style.DEFS) + assert ecf.Style.NOTHING < ecf.Style.DEFS def test_gt_orders_by_value(self): """Style.DEFS > Style.NOTHING.""" - self.assertGreater(ecf.Style.DEFS, ecf.Style.NOTHING) + assert ecf.Style.DEFS > ecf.Style.NOTHING def test_le_equal_members(self): """Style.DEFS <= Style.DEFS holds.""" - self.assertLessEqual(ecf.Style.DEFS, ecf.Style.DEFS) + assert ecf.Style.DEFS <= ecf.Style.DEFS def test_ge_equal_members(self): """Style.DEFS >= Style.DEFS holds.""" - self.assertGreaterEqual(ecf.Style.DEFS, ecf.Style.DEFS) + assert ecf.Style.DEFS >= ecf.Style.DEFS def test_lt_incompatible_type_returns_not_implemented(self): """__lt__ returns NotImplemented for an incompatible RHS type.""" - self.assertIs(ecf.Style.DEFS.__lt__("DEFS"), NotImplemented) + assert ecf.Style.DEFS.__lt__("DEFS") is NotImplemented def test_le_incompatible_type_returns_not_implemented(self): """__le__ returns NotImplemented for an incompatible RHS type.""" - self.assertIs(ecf.Style.DEFS.__le__("DEFS"), NotImplemented) + assert ecf.Style.DEFS.__le__("DEFS") is NotImplemented def test_gt_incompatible_type_returns_not_implemented(self): """__gt__ returns NotImplemented for an incompatible RHS type.""" - self.assertIs(ecf.Style.DEFS.__gt__("DEFS"), NotImplemented) + assert ecf.Style.DEFS.__gt__("DEFS") is NotImplemented def test_ge_incompatible_type_returns_not_implemented(self): """__ge__ returns NotImplemented for an incompatible RHS type.""" - self.assertIs(ecf.Style.DEFS.__ge__("DEFS"), NotImplemented) + assert ecf.Style.DEFS.__ge__("DEFS") is NotImplemented # ------------------------------------------------------------------ # __int__ @@ -331,11 +330,11 @@ def test_ge_incompatible_type_returns_not_implemented(self): def test_int_conversion(self): """Style values can be converted to int.""" - self.assertIsInstance(int(ecf.Style.DEFS), int) + assert isinstance(int(ecf.Style.DEFS), int) def test_nothing_has_int_value_zero(self): """Style.NOTHING has integer value 0.""" - self.assertEqual(int(ecf.Style.NOTHING), 0) + assert int(ecf.Style.NOTHING) == 0 # ------------------------------------------------------------------ # py_finalize_enum — str / repr / hash / .values / .names @@ -343,37 +342,37 @@ def test_nothing_has_int_value_zero(self): def test_str_returns_member_name_only(self): """str(Style.DEFS) returns 'DEFS', not 'Style.DEFS'.""" - self.assertEqual(str(ecf.Style.DEFS), "DEFS") - self.assertEqual(str(ecf.Style.STATE), "STATE") + assert str(ecf.Style.DEFS) == "DEFS" + assert str(ecf.Style.STATE) == "STATE" def test_repr_is_module_qualified(self): """repr(Style.DEFS) returns 'ecflow.Style.DEFS'.""" - self.assertEqual(repr(ecf.Style.DEFS), "ecflow.Style.DEFS") + assert repr(ecf.Style.DEFS) == "ecflow.Style.DEFS" def test_hash_is_integer(self): """hash(Style.DEFS) is a Python int.""" - self.assertIsInstance(hash(ecf.Style.DEFS), int) + assert isinstance(hash(ecf.Style.DEFS), int) def test_hash_equals_underlying_int_value(self): """hash(member) equals member.value.""" - self.assertEqual(hash(ecf.Style.DEFS), ecf.Style.DEFS.value) + assert hash(ecf.Style.DEFS) == ecf.Style.DEFS.value def test_values_dict_maps_int_to_member(self): """Style.values is a dict mapping integer value -> enum member.""" values = ecf.Style.values - self.assertIsInstance(values, dict) - self.assertIn(ecf.Style.DEFS.value, values) - self.assertEqual(values[ecf.Style.DEFS.value], ecf.Style.DEFS) + assert isinstance(values, dict) + assert ecf.Style.DEFS.value in values + assert values[ecf.Style.DEFS.value] == ecf.Style.DEFS def test_names_dict_maps_str_to_member(self): """Style.names is a dict mapping name string -> enum member.""" names = ecf.Style.names - self.assertIsInstance(names, dict) - self.assertIn("DEFS", names) - self.assertEqual(names["DEFS"], ecf.Style.DEFS) + assert isinstance(names, dict) + assert "DEFS" in names + assert names["DEFS"] == ecf.Style.DEFS -class TestPrintStyle(unittest.TestCase): +class TestPrintStyle: """Tests for py::class_ exposed as ecf.PrintStyle in ExportCore.cpp. Exposed API @@ -386,11 +385,11 @@ class TestPrintStyle(unittest.TestCase): set_style(Style) -- sets the global print style """ - def setUp(self): + def setup_method(self): """Save current style so tests can restore it.""" self._original = ecf.PrintStyle.get_style() - def tearDown(self): + def teardown_method(self): """Restore the original style after each test.""" ecf.PrintStyle.set_style(self._original) @@ -400,7 +399,7 @@ def tearDown(self): def test_direct_construction_raises(self): """PrintStyle cannot be instantiated from Python.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.PrintStyle() # ------------------------------------------------------------------ @@ -410,10 +409,12 @@ def test_direct_construction_raises(self): def test_get_style_returns_a_style_value(self): """get_style() returns a member of the Style enum.""" style = ecf.PrintStyle.get_style() - self.assertIn( - style, - [ecf.Style.NOTHING, ecf.Style.DEFS, ecf.Style.STATE, ecf.Style.MIGRATE], - ) + assert style in [ + ecf.Style.NOTHING, + ecf.Style.DEFS, + ecf.Style.STATE, + ecf.Style.MIGRATE, + ] def test_get_style_callable_on_class(self): """get_style() is a static method callable without an instance.""" @@ -426,22 +427,22 @@ def test_get_style_callable_on_class(self): def test_set_style_defs(self): """set_style(Style.DEFS) makes get_style() return DEFS.""" ecf.PrintStyle.set_style(ecf.Style.DEFS) - self.assertEqual(ecf.PrintStyle.get_style(), ecf.Style.DEFS) + assert ecf.PrintStyle.get_style() == ecf.Style.DEFS def test_set_style_state(self): """set_style(Style.STATE) makes get_style() return STATE.""" ecf.PrintStyle.set_style(ecf.Style.STATE) - self.assertEqual(ecf.PrintStyle.get_style(), ecf.Style.STATE) + assert ecf.PrintStyle.get_style() == ecf.Style.STATE def test_set_style_migrate(self): """set_style(Style.MIGRATE) makes get_style() return MIGRATE.""" ecf.PrintStyle.set_style(ecf.Style.MIGRATE) - self.assertEqual(ecf.PrintStyle.get_style(), ecf.Style.MIGRATE) + assert ecf.PrintStyle.get_style() == ecf.Style.MIGRATE def test_set_style_nothing(self): """set_style(Style.NOTHING) makes get_style() return NOTHING.""" ecf.PrintStyle.set_style(ecf.Style.NOTHING) - self.assertEqual(ecf.PrintStyle.get_style(), ecf.Style.NOTHING) + assert ecf.PrintStyle.get_style() == ecf.Style.NOTHING def test_set_then_get_round_trips(self): """Each style value round-trips through set_style/get_style.""" @@ -452,10 +453,10 @@ def test_set_then_get_round_trips(self): ecf.Style.NOTHING, ]: ecf.PrintStyle.set_style(style) - self.assertEqual(ecf.PrintStyle.get_style(), style) + assert ecf.PrintStyle.get_style() == style -class TestCheckPt(unittest.TestCase): +class TestCheckPt: """Tests for py::enum_ exposed as ecf.CheckPt in ExportCore.cpp. Exposed API @@ -498,7 +499,7 @@ def test_all_four_values_are_distinct(self): for i, a in enumerate(values): for j, b in enumerate(values): if i != j: - self.assertNotEqual(a, b) + assert a != b # ------------------------------------------------------------------ # __eq__ and __ne__ @@ -506,11 +507,11 @@ def test_all_four_values_are_distinct(self): def test_eq_same_value(self): """A CheckPt member compares equal to itself.""" - self.assertEqual(ecf.CheckPt.ON_TIME, ecf.CheckPt.ON_TIME) + assert ecf.CheckPt.ON_TIME == ecf.CheckPt.ON_TIME def test_ne_different_values(self): """Different CheckPt members are not equal.""" - self.assertNotEqual(ecf.CheckPt.NEVER, ecf.CheckPt.ALWAYS) + assert ecf.CheckPt.NEVER != ecf.CheckPt.ALWAYS # ------------------------------------------------------------------ # py_finalize_enum — str / repr / hash / .values / .names @@ -518,37 +519,37 @@ def test_ne_different_values(self): def test_str_returns_member_name_only(self): """str(CheckPt.NEVER) returns 'NEVER', not 'CheckPt.NEVER'.""" - self.assertEqual(str(ecf.CheckPt.NEVER), "NEVER") - self.assertEqual(str(ecf.CheckPt.ALWAYS), "ALWAYS") + assert str(ecf.CheckPt.NEVER) == "NEVER" + assert str(ecf.CheckPt.ALWAYS) == "ALWAYS" def test_repr_is_module_qualified(self): """repr(CheckPt.ON_TIME) returns 'ecflow.CheckPt.ON_TIME'.""" - self.assertEqual(repr(ecf.CheckPt.ON_TIME), "ecflow.CheckPt.ON_TIME") + assert repr(ecf.CheckPt.ON_TIME) == "ecflow.CheckPt.ON_TIME" def test_hash_is_integer(self): """hash(CheckPt.NEVER) is a Python int.""" - self.assertIsInstance(hash(ecf.CheckPt.NEVER), int) + assert isinstance(hash(ecf.CheckPt.NEVER), int) def test_hash_equals_underlying_int_value(self): """hash(member) equals member.value.""" - self.assertEqual(hash(ecf.CheckPt.ON_TIME), ecf.CheckPt.ON_TIME.value) + assert hash(ecf.CheckPt.ON_TIME) == ecf.CheckPt.ON_TIME.value def test_values_dict_maps_int_to_member(self): """CheckPt.values is a dict mapping integer value -> enum member.""" values = ecf.CheckPt.values - self.assertIsInstance(values, dict) - self.assertIn(ecf.CheckPt.NEVER.value, values) - self.assertEqual(values[ecf.CheckPt.NEVER.value], ecf.CheckPt.NEVER) + assert isinstance(values, dict) + assert ecf.CheckPt.NEVER.value in values + assert values[ecf.CheckPt.NEVER.value] == ecf.CheckPt.NEVER def test_names_dict_maps_str_to_member(self): """CheckPt.names is a dict mapping name string -> enum member.""" names = ecf.CheckPt.names - self.assertIsInstance(names, dict) - self.assertIn("ALWAYS", names) - self.assertEqual(names["ALWAYS"], ecf.CheckPt.ALWAYS) + assert isinstance(names, dict) + assert "ALWAYS" in names + assert names["ALWAYS"] == ecf.CheckPt.ALWAYS -class TestEcf(unittest.TestCase): +class TestEcf: """Tests for py::class_ exposed as ecf.Ecf in ExportCore.cpp. Exposed API @@ -563,12 +564,12 @@ class TestEcf(unittest.TestCase): set_debug_level(int) -- set the debug level """ - def setUp(self): + def setup_method(self): """Save current Ecf state to restore after each test.""" self._orig_eq = ecf.Ecf.debug_equality() self._orig_lvl = ecf.Ecf.debug_level() - def tearDown(self): + def teardown_method(self): """Restore Ecf state after each test.""" ecf.Ecf.set_debug_equality(self._orig_eq) ecf.Ecf.set_debug_level(self._orig_lvl) @@ -579,7 +580,7 @@ def tearDown(self): def test_direct_construction_raises(self): """Ecf cannot be instantiated from Python.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Ecf() # ------------------------------------------------------------------ @@ -588,24 +589,24 @@ def test_direct_construction_raises(self): def test_debug_equality_returns_bool(self): """debug_equality() returns a Python bool.""" - self.assertIsInstance(ecf.Ecf.debug_equality(), bool) + assert isinstance(ecf.Ecf.debug_equality(), bool) def test_set_debug_equality_true_is_reflected(self): """set_debug_equality(True) causes debug_equality() to return True.""" ecf.Ecf.set_debug_equality(True) - self.assertTrue(ecf.Ecf.debug_equality()) + assert ecf.Ecf.debug_equality() def test_set_debug_equality_false_is_reflected(self): """set_debug_equality(False) causes debug_equality() to return False.""" ecf.Ecf.set_debug_equality(False) - self.assertFalse(ecf.Ecf.debug_equality()) + assert not ecf.Ecf.debug_equality() def test_set_debug_equality_toggles(self): """set_debug_equality can be toggled between True and False.""" ecf.Ecf.set_debug_equality(True) - self.assertTrue(ecf.Ecf.debug_equality()) + assert ecf.Ecf.debug_equality() ecf.Ecf.set_debug_equality(False) - self.assertFalse(ecf.Ecf.debug_equality()) + assert not ecf.Ecf.debug_equality() def test_debug_equality_callable_on_class(self): """debug_equality() is a static method callable without an instance.""" @@ -617,29 +618,29 @@ def test_debug_equality_callable_on_class(self): def test_debug_level_returns_int(self): """debug_level() returns a Python int.""" - self.assertIsInstance(ecf.Ecf.debug_level(), int) + assert isinstance(ecf.Ecf.debug_level(), int) def test_set_debug_level_zero(self): """set_debug_level(0) turns off debug output.""" ecf.Ecf.set_debug_level(0) - self.assertEqual(ecf.Ecf.debug_level(), 0) + assert ecf.Ecf.debug_level() == 0 def test_set_debug_level_one(self): """set_debug_level(1) sets debug level to 1.""" ecf.Ecf.set_debug_level(1) - self.assertEqual(ecf.Ecf.debug_level(), 1) + assert ecf.Ecf.debug_level() == 1 def test_set_debug_level_arbitrary_positive(self): """set_debug_level accepts arbitrary positive integers.""" ecf.Ecf.set_debug_level(5) - self.assertEqual(ecf.Ecf.debug_level(), 5) + assert ecf.Ecf.debug_level() == 5 def test_debug_level_callable_on_class(self): """debug_level() is a static method callable without an instance.""" _ = ecf.Ecf.debug_level() -class TestState(unittest.TestCase): +class TestState: """Tests for py::enum_ exposed as ecf.State in ExportCore.cpp. Exposed API @@ -694,7 +695,7 @@ def test_all_six_values_are_distinct(self): for i, a in enumerate(values): for j, b in enumerate(values): if i != j: - self.assertNotEqual(a, b) + assert a != b # ------------------------------------------------------------------ # __eq__ and __ne__ @@ -702,11 +703,11 @@ def test_all_six_values_are_distinct(self): def test_eq_same_value(self): """A State member compares equal to itself.""" - self.assertEqual(ecf.State.queued, ecf.State.queued) + assert ecf.State.queued == ecf.State.queued def test_ne_different_values(self): """Different State members are not equal.""" - self.assertNotEqual(ecf.State.queued, ecf.State.active) + assert ecf.State.queued != ecf.State.active # ------------------------------------------------------------------ # py_finalize_enum — str / repr / hash / .values / .names @@ -714,37 +715,37 @@ def test_ne_different_values(self): def test_str_returns_member_name_only(self): """str(State.active) returns 'active', not 'State.active'.""" - self.assertEqual(str(ecf.State.active), "active") - self.assertEqual(str(ecf.State.complete), "complete") + assert str(ecf.State.active) == "active" + assert str(ecf.State.complete) == "complete" def test_repr_is_module_qualified(self): """repr(State.active) returns 'ecflow.State.active'.""" - self.assertEqual(repr(ecf.State.active), "ecflow.State.active") + assert repr(ecf.State.active) == "ecflow.State.active" def test_hash_is_integer(self): """hash(State.active) is a Python int.""" - self.assertIsInstance(hash(ecf.State.active), int) + assert isinstance(hash(ecf.State.active), int) def test_hash_equals_underlying_int_value(self): """hash(member) equals member.value.""" - self.assertEqual(hash(ecf.State.active), ecf.State.active.value) + assert hash(ecf.State.active) == ecf.State.active.value def test_values_dict_maps_int_to_member(self): """State.values is a dict mapping integer value -> enum member.""" values = ecf.State.values - self.assertIsInstance(values, dict) - self.assertIn(ecf.State.active.value, values) - self.assertEqual(values[ecf.State.active.value], ecf.State.active) + assert isinstance(values, dict) + assert ecf.State.active.value in values + assert values[ecf.State.active.value] == ecf.State.active def test_names_dict_maps_str_to_member(self): """State.names is a dict mapping name string -> enum member.""" names = ecf.State.names - self.assertIsInstance(names, dict) - self.assertIn("active", names) - self.assertEqual(names["active"], ecf.State.active) + assert isinstance(names, dict) + assert "active" in names + assert names["active"] == ecf.State.active -class TestDState(unittest.TestCase): +class TestDState: """Tests for py::enum_ exposed as ecf.DState in ExportCore.cpp. Exposed API @@ -810,7 +811,7 @@ def test_all_seven_values_are_distinct(self): for i, a in enumerate(values): for j, b in enumerate(values): if i != j: - self.assertNotEqual(a, b) + assert a != b # ------------------------------------------------------------------ # __eq__ and __ne__ @@ -818,15 +819,15 @@ def test_all_seven_values_are_distinct(self): def test_eq_same_value(self): """A DState member compares equal to itself.""" - self.assertEqual(ecf.DState.complete, ecf.DState.complete) + assert ecf.DState.complete == ecf.DState.complete def test_ne_different_values(self): """Different DState members are not equal.""" - self.assertNotEqual(ecf.DState.queued, ecf.DState.suspended) + assert ecf.DState.queued != ecf.DState.suspended def test_suspended_not_in_state_enum(self): """ecf.State does not expose a 'suspended' member (only DState does).""" - self.assertFalse(hasattr(ecf.State, "suspended")) + assert not hasattr(ecf.State, "suspended") # ------------------------------------------------------------------ # py_finalize_enum — str / repr / hash / .values / .names @@ -834,37 +835,37 @@ def test_suspended_not_in_state_enum(self): def test_str_returns_member_name_only(self): """str(DState.active) returns 'active', not 'DState.active'.""" - self.assertEqual(str(ecf.DState.active), "active") - self.assertEqual(str(ecf.DState.suspended), "suspended") + assert str(ecf.DState.active) == "active" + assert str(ecf.DState.suspended) == "suspended" def test_repr_is_module_qualified(self): """repr(DState.complete) returns 'ecflow.DState.complete'.""" - self.assertEqual(repr(ecf.DState.complete), "ecflow.DState.complete") + assert repr(ecf.DState.complete) == "ecflow.DState.complete" def test_hash_is_integer(self): """hash(DState.queued) is a Python int.""" - self.assertIsInstance(hash(ecf.DState.queued), int) + assert isinstance(hash(ecf.DState.queued), int) def test_hash_equals_underlying_int_value(self): """hash(member) equals member.value.""" - self.assertEqual(hash(ecf.DState.active), ecf.DState.active.value) + assert hash(ecf.DState.active) == ecf.DState.active.value def test_values_dict_maps_int_to_member(self): """DState.values is a dict mapping integer value -> enum member.""" values = ecf.DState.values - self.assertIsInstance(values, dict) - self.assertIn(ecf.DState.active.value, values) - self.assertEqual(values[ecf.DState.active.value], ecf.DState.active) + assert isinstance(values, dict) + assert ecf.DState.active.value in values + assert values[ecf.DState.active.value] == ecf.DState.active def test_names_dict_maps_str_to_member(self): """DState.names is a dict mapping name string -> enum member.""" names = ecf.DState.names - self.assertIsInstance(names, dict) - self.assertIn("suspended", names) - self.assertEqual(names["suspended"], ecf.DState.suspended) + assert isinstance(names, dict) + assert "suspended" in names + assert names["suspended"] == ecf.DState.suspended -class TestDefstatus(unittest.TestCase): +class TestDefstatus: """Tests for py::class_ exposed as ecf.Defstatus in ExportCore.cpp. Exposed API @@ -887,37 +888,37 @@ class TestDefstatus(unittest.TestCase): def test_ctor_dstate_complete(self): """Defstatus(DState.complete) stores the complete state.""" ds = ecf.Defstatus(ecf.DState.complete) - self.assertEqual(ds.state(), ecf.DState.complete) + assert ds.state() == ecf.DState.complete def test_ctor_dstate_queued(self): """Defstatus(DState.queued) stores the queued state.""" ds = ecf.Defstatus(ecf.DState.queued) - self.assertEqual(ds.state(), ecf.DState.queued) + assert ds.state() == ecf.DState.queued def test_ctor_dstate_aborted(self): """Defstatus(DState.aborted) stores the aborted state.""" ds = ecf.Defstatus(ecf.DState.aborted) - self.assertEqual(ds.state(), ecf.DState.aborted) + assert ds.state() == ecf.DState.aborted def test_ctor_dstate_suspended(self): """Defstatus(DState.suspended) stores the suspended state.""" ds = ecf.Defstatus(ecf.DState.suspended) - self.assertEqual(ds.state(), ecf.DState.suspended) + assert ds.state() == ecf.DState.suspended def test_ctor_dstate_unknown(self): """Defstatus(DState.unknown) stores the unknown state.""" ds = ecf.Defstatus(ecf.DState.unknown) - self.assertEqual(ds.state(), ecf.DState.unknown) + assert ds.state() == ecf.DState.unknown def test_ctor_dstate_active(self): """Defstatus(DState.active) stores the active state.""" ds = ecf.Defstatus(ecf.DState.active) - self.assertEqual(ds.state(), ecf.DState.active) + assert ds.state() == ecf.DState.active def test_ctor_dstate_submitted(self): """Defstatus(DState.submitted) stores the submitted state.""" ds = ecf.Defstatus(ecf.DState.submitted) - self.assertEqual(ds.state(), ecf.DState.submitted) + assert ds.state() == ecf.DState.submitted # ------------------------------------------------------------------ # Constructor: Defstatus(str) @@ -926,36 +927,36 @@ def test_ctor_dstate_submitted(self): def test_ctor_str_complete(self): """Defstatus('complete') stores DState.complete.""" ds = ecf.Defstatus("complete") - self.assertEqual(ds.state(), ecf.DState.complete) + assert ds.state() == ecf.DState.complete def test_ctor_str_queued(self): """Defstatus('queued') stores DState.queued.""" ds = ecf.Defstatus("queued") - self.assertEqual(ds.state(), ecf.DState.queued) + assert ds.state() == ecf.DState.queued def test_ctor_str_aborted(self): """Defstatus('aborted') stores DState.aborted.""" ds = ecf.Defstatus("aborted") - self.assertEqual(ds.state(), ecf.DState.aborted) + assert ds.state() == ecf.DState.aborted def test_ctor_str_suspended(self): """Defstatus('suspended') stores DState.suspended.""" ds = ecf.Defstatus("suspended") - self.assertEqual(ds.state(), ecf.DState.suspended) + assert ds.state() == ecf.DState.suspended def test_ctor_str_unknown(self): """Defstatus('unknown') stores DState.unknown.""" ds = ecf.Defstatus("unknown") - self.assertEqual(ds.state(), ecf.DState.unknown) + assert ds.state() == ecf.DState.unknown def test_ctor_str_bad_name_raises(self): """Defstatus with an unrecognised string raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Defstatus("not_a_valid_state") def test_ctor_str_empty_string_raises(self): """Defstatus('') raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Defstatus("") # ------------------------------------------------------------------ @@ -964,15 +965,13 @@ def test_ctor_str_empty_string_raises(self): def test_state_returns_dstate_value(self): """state() returns the DState enum value passed at construction.""" - self.assertEqual( - ecf.Defstatus(ecf.DState.complete).state(), ecf.DState.complete - ) + assert ecf.Defstatus(ecf.DState.complete).state() == ecf.DState.complete def test_state_from_str_and_dstate_agree(self): """Defstatus(DState.complete).state() == Defstatus('complete').state().""" - self.assertEqual( - ecf.Defstatus(ecf.DState.complete).state(), - ecf.Defstatus("complete").state(), + assert ( + ecf.Defstatus(ecf.DState.complete).state() + == ecf.Defstatus("complete").state() ) # ------------------------------------------------------------------ @@ -981,11 +980,11 @@ def test_state_from_str_and_dstate_agree(self): def test_str_returns_state_name(self): """str(Defstatus(...)) returns the state name string.""" - self.assertEqual(str(ecf.Defstatus(ecf.DState.complete)), "complete") + assert str(ecf.Defstatus(ecf.DState.complete)) == "complete" def test_str_queued(self): """str(Defstatus(DState.queued)) returns 'queued'.""" - self.assertEqual(str(ecf.Defstatus(ecf.DState.queued)), "queued") + assert str(ecf.Defstatus(ecf.DState.queued)) == "queued" def test_str_is_consistent_with_ctor_str(self): """The string used in Defstatus(str) matches str(Defstatus(DState)).""" @@ -999,10 +998,10 @@ def test_str_is_consistent_with_ctor_str(self): "submitted", ]: ds = ecf.Defstatus(name) - self.assertEqual(str(ds), name) + assert str(ds) == name -class TestSState(unittest.TestCase): +class TestSState: """Tests for py::enum_ exposed as ecf.SState in ExportCore.cpp. Exposed API @@ -1035,7 +1034,7 @@ def test_all_three_values_are_distinct(self): for i, a in enumerate(values): for j, b in enumerate(values): if i != j: - self.assertNotEqual(a, b) + assert a != b # ------------------------------------------------------------------ # __eq__ and __ne__ @@ -1043,11 +1042,11 @@ def test_all_three_values_are_distinct(self): def test_eq_same_value(self): """An SState member compares equal to itself.""" - self.assertEqual(ecf.SState.RUNNING, ecf.SState.RUNNING) + assert ecf.SState.RUNNING == ecf.SState.RUNNING def test_ne_different_values(self): """Different SState members are not equal.""" - self.assertNotEqual(ecf.SState.HALTED, ecf.SState.RUNNING) + assert ecf.SState.HALTED != ecf.SState.RUNNING # ------------------------------------------------------------------ # py_finalize_enum — str / repr / hash / .values / .names @@ -1055,37 +1054,37 @@ def test_ne_different_values(self): def test_str_returns_member_name_only(self): """str(SState.RUNNING) returns 'RUNNING', not 'SState.RUNNING'.""" - self.assertEqual(str(ecf.SState.RUNNING), "RUNNING") - self.assertEqual(str(ecf.SState.HALTED), "HALTED") + assert str(ecf.SState.RUNNING) == "RUNNING" + assert str(ecf.SState.HALTED) == "HALTED" def test_repr_is_module_qualified(self): """repr(SState.RUNNING) returns 'ecflow.SState.RUNNING'.""" - self.assertEqual(repr(ecf.SState.RUNNING), "ecflow.SState.RUNNING") + assert repr(ecf.SState.RUNNING) == "ecflow.SState.RUNNING" def test_hash_is_integer(self): """hash(SState.RUNNING) is a Python int.""" - self.assertIsInstance(hash(ecf.SState.RUNNING), int) + assert isinstance(hash(ecf.SState.RUNNING), int) def test_hash_equals_underlying_int_value(self): """hash(member) equals member.value.""" - self.assertEqual(hash(ecf.SState.RUNNING), ecf.SState.RUNNING.value) + assert hash(ecf.SState.RUNNING) == ecf.SState.RUNNING.value def test_values_dict_maps_int_to_member(self): """SState.values is a dict mapping integer value -> enum member.""" values = ecf.SState.values - self.assertIsInstance(values, dict) - self.assertIn(ecf.SState.RUNNING.value, values) - self.assertEqual(values[ecf.SState.RUNNING.value], ecf.SState.RUNNING) + assert isinstance(values, dict) + assert ecf.SState.RUNNING.value in values + assert values[ecf.SState.RUNNING.value] == ecf.SState.RUNNING def test_names_dict_maps_str_to_member(self): """SState.names is a dict mapping name string -> enum member.""" names = ecf.SState.names - self.assertIsInstance(names, dict) - self.assertIn("RUNNING", names) - self.assertEqual(names["RUNNING"], ecf.SState.RUNNING) + assert isinstance(names, dict) + assert "RUNNING" in names + assert names["RUNNING"] == ecf.SState.RUNNING -class TestTimeSlot(unittest.TestCase): +class TestTimeSlot: """Tests for py::class_ exposed as ecf.TimeSlot in ExportCore.cpp. Exposed API @@ -1119,26 +1118,26 @@ class TestTimeSlot(unittest.TestCase): def test_ctor_stores_hour_and_minute(self): """TimeSlot(h, m) stores h and m verbatim.""" ts = ecf.TimeSlot(10, 30) - self.assertEqual(ts.hour(), 10) - self.assertEqual(ts.minute(), 30) + assert ts.hour() == 10 + assert ts.minute() == 30 def test_ctor_midnight(self): """TimeSlot(0, 0) constructs successfully.""" ts = ecf.TimeSlot(0, 0) - self.assertEqual(ts.hour(), 0) - self.assertEqual(ts.minute(), 0) + assert ts.hour() == 0 + assert ts.minute() == 0 def test_ctor_end_of_day(self): """TimeSlot(23, 59) constructs successfully.""" ts = ecf.TimeSlot(23, 59) - self.assertEqual(ts.hour(), 23) - self.assertEqual(ts.minute(), 59) + assert ts.hour() == 23 + assert ts.minute() == 59 def test_ctor_hour_zero_minute_nonzero(self): """TimeSlot(0, 45) correctly stores 0 hours and 45 minutes.""" ts = ecf.TimeSlot(0, 45) - self.assertEqual(ts.hour(), 0) - self.assertEqual(ts.minute(), 45) + assert ts.hour() == 0 + assert ts.minute() == 45 # ------------------------------------------------------------------ # hour() and minute() @@ -1146,11 +1145,11 @@ def test_ctor_hour_zero_minute_nonzero(self): def test_hour_returns_int(self): """hour() returns a Python int.""" - self.assertIsInstance(ecf.TimeSlot(5, 0).hour(), int) + assert isinstance(ecf.TimeSlot(5, 0).hour(), int) def test_minute_returns_int(self): """minute() returns a Python int.""" - self.assertIsInstance(ecf.TimeSlot(0, 15).minute(), int) + assert isinstance(ecf.TimeSlot(0, 15).minute(), int) # ------------------------------------------------------------------ # empty() @@ -1158,16 +1157,16 @@ def test_minute_returns_int(self): def test_empty_is_false_for_normal_slot(self): """empty() returns False for a normally constructed TimeSlot.""" - self.assertFalse(ecf.TimeSlot(10, 30).empty()) + assert not ecf.TimeSlot(10, 30).empty() def test_empty_is_false_for_midnight(self): """empty() returns False even for TimeSlot(0, 0); NULL means unset, not zero.""" - self.assertFalse(ecf.TimeSlot(0, 0).empty()) + assert not ecf.TimeSlot(0, 0).empty() def test_empty_is_true_for_null_slot_from_single_series_finish(self): """A NULL TimeSlot obtained from TimeSeries.finish() on a single slot reports empty.""" single = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertTrue(single.finish().empty()) + assert single.finish().empty() # ------------------------------------------------------------------ # __str__ @@ -1175,15 +1174,15 @@ def test_empty_is_true_for_null_slot_from_single_series_finish(self): def test_str_format_hh_mm(self): """str(TimeSlot) uses 'HH:MM' format with zero-padded fields.""" - self.assertEqual(str(ecf.TimeSlot(10, 30)), "10:30") + assert str(ecf.TimeSlot(10, 30)) == "10:30" def test_str_midnight(self): """str(TimeSlot(0, 0)) is '00:00'.""" - self.assertEqual(str(ecf.TimeSlot(0, 0)), "00:00") + assert str(ecf.TimeSlot(0, 0)) == "00:00" def test_str_single_digit_hour_is_zero_padded(self): """Single-digit hours are zero-padded in str output.""" - self.assertEqual(str(ecf.TimeSlot(5, 7)), "05:07") + assert str(ecf.TimeSlot(5, 7)) == "05:07" # ------------------------------------------------------------------ # __copy__ @@ -1192,12 +1191,12 @@ def test_str_single_digit_hour_is_zero_padded(self): def test_copy_is_value_equal(self): """copy.copy(TimeSlot) returns a value-equal instance.""" ts = ecf.TimeSlot(10, 30) - self.assertEqual(copy.copy(ts), ts) + assert copy.copy(ts) == ts def test_copy_is_identity_distinct(self): """copy.copy(TimeSlot) returns a different object.""" ts = ecf.TimeSlot(10, 30) - self.assertIsNot(copy.copy(ts), ts) + assert copy.copy(ts) is not ts # ------------------------------------------------------------------ # __eq__ and __ne__ @@ -1205,20 +1204,20 @@ def test_copy_is_identity_distinct(self): def test_eq_same_values(self): """Two TimeSlots with the same hour and minute are equal.""" - self.assertEqual(ecf.TimeSlot(10, 30), ecf.TimeSlot(10, 30)) + assert ecf.TimeSlot(10, 30) == ecf.TimeSlot(10, 30) def test_ne_different_hour(self): """TimeSlots differing only in hour are not equal.""" - self.assertNotEqual(ecf.TimeSlot(10, 30), ecf.TimeSlot(11, 30)) + assert ecf.TimeSlot(10, 30) != ecf.TimeSlot(11, 30) def test_ne_different_minute(self): """TimeSlots differing only in minute are not equal.""" - self.assertNotEqual(ecf.TimeSlot(10, 30), ecf.TimeSlot(10, 31)) + assert ecf.TimeSlot(10, 30) != ecf.TimeSlot(10, 31) def test_eq_with_itself(self): """A TimeSlot is equal to itself.""" ts = ecf.TimeSlot(10, 30) - self.assertEqual(ts, ts) + assert ts == ts # ------------------------------------------------------------------ # __hash__ @@ -1228,22 +1227,22 @@ def test_hash_is_identity_based(self): """Two value-equal TimeSlots kept simultaneously have different hashes.""" ts1 = ecf.TimeSlot(10, 30) ts2 = ecf.TimeSlot(10, 30) - self.assertEqual(ts1, ts2) - self.assertNotEqual(hash(ts1), hash(ts2)) + assert ts1 == ts2 + assert hash(ts1) != hash(ts2) def test_hash_same_object_is_stable(self): """The hash of a single TimeSlot is the same across repeated calls.""" ts = ecf.TimeSlot(10, 30) - self.assertEqual(hash(ts), hash(ts)) + assert hash(ts) == hash(ts) def test_hash_usable_as_dict_key(self): """A TimeSlot can be used as a dictionary key.""" ts = ecf.TimeSlot(10, 30) d = {ts: "value"} - self.assertEqual(d[ts], "value") + assert d[ts] == "value" -class TestTimeSeries(unittest.TestCase): +class TestTimeSeries: """Tests for py::class_ exposed as ecf.TimeSeries in ExportCore.cpp. Exposed API @@ -1269,7 +1268,7 @@ class TestTimeSeries(unittest.TestCase): __hash__ -- identity-based (boost.python C-extension type) """ - def setUp(self): + def setup_method(self): self.start = ecf.TimeSlot(0, 0) self.finish = ecf.TimeSlot(23, 0) self.incr = ecf.TimeSlot(1, 0) @@ -1281,47 +1280,47 @@ def setUp(self): def test_ctor_single_slot(self): """TimeSeries(TimeSlot) creates a single-slot series.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertFalse(ts.has_increment()) + assert not ts.has_increment() def test_ctor_single_slot_start_is_stored(self): """TimeSeries(TimeSlot) stores the slot as the start time.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertEqual(ts.start(), ecf.TimeSlot(10, 30)) + assert ts.start() == ecf.TimeSlot(10, 30) def test_ctor_single_slot_finish_is_null(self): """TimeSeries(TimeSlot) has an empty (NULL) finish slot.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertTrue(ts.finish().empty()) + assert ts.finish().empty() def test_ctor_single_slot_incr_is_null(self): """TimeSeries(TimeSlot) has an empty (NULL) increment slot.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertTrue(ts.incr().empty()) + assert ts.incr().empty() def test_ctor_single_slot_default_not_relative(self): """TimeSeries(TimeSlot) defaults to non-relative (absolute time).""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertFalse(ts.relative()) + assert not ts.relative() def test_ctor_single_slot_relative_true(self): """TimeSeries(TimeSlot, True) marks the series as relative.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30), True) - self.assertTrue(ts.relative()) + assert ts.relative() def test_ctor_single_slot_relative_false(self): """TimeSeries(TimeSlot, False) explicitly marks the series as non-relative.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30), False) - self.assertFalse(ts.relative()) + assert not ts.relative() def test_ctor_single_slot_str_absolute(self): """str(TimeSeries(TimeSlot)) renders as 'HH:MM'.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertEqual(str(ts), "10:30") + assert str(ts) == "10:30" def test_ctor_single_slot_str_relative(self): """str(TimeSeries(TimeSlot, True)) renders as '+HH:MM'.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30), True) - self.assertEqual(str(ts), "+10:30") + assert str(ts) == "+10:30" # ------------------------------------------------------------------ # Constructor: TimeSeries(int, int, [bool]) @@ -1330,24 +1329,24 @@ def test_ctor_single_slot_str_relative(self): def test_ctor_hour_minute(self): """TimeSeries(h, m) creates a single-slot series from integers.""" ts = ecf.TimeSeries(10, 30) - self.assertFalse(ts.has_increment()) - self.assertEqual(ts.start(), ecf.TimeSlot(10, 30)) + assert not ts.has_increment() + assert ts.start() == ecf.TimeSlot(10, 30) def test_ctor_hour_minute_default_not_relative(self): """TimeSeries(h, m) defaults to non-relative.""" - self.assertFalse(ecf.TimeSeries(10, 30).relative()) + assert not ecf.TimeSeries(10, 30).relative() def test_ctor_hour_minute_relative_true(self): """TimeSeries(h, m, True) marks the series as relative.""" - self.assertTrue(ecf.TimeSeries(10, 30, True).relative()) + assert ecf.TimeSeries(10, 30, True).relative() def test_ctor_hour_minute_relative_false(self): """TimeSeries(h, m, False) explicitly marks the series as non-relative.""" - self.assertFalse(ecf.TimeSeries(10, 30, False).relative()) + assert not ecf.TimeSeries(10, 30, False).relative() def test_ctor_hour_minute_str(self): """str(TimeSeries(h, m)) renders as 'HH:MM'.""" - self.assertEqual(str(ecf.TimeSeries(10, 30)), "10:30") + assert str(ecf.TimeSeries(10, 30)) == "10:30" # ------------------------------------------------------------------ # Constructor: TimeSeries(start, finish, incr, [bool]) @@ -1356,47 +1355,47 @@ def test_ctor_hour_minute_str(self): def test_ctor_series_has_increment(self): """TimeSeries(start, finish, incr) creates a series with has_increment True.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertTrue(ts.has_increment()) + assert ts.has_increment() def test_ctor_series_start_is_stored(self): """TimeSeries(start, finish, incr) stores the start slot.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertEqual(ts.start(), self.start) + assert ts.start() == self.start def test_ctor_series_finish_is_stored(self): """TimeSeries(start, finish, incr) stores the finish slot.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertEqual(ts.finish(), self.finish) + assert ts.finish() == self.finish def test_ctor_series_incr_is_stored(self): """TimeSeries(start, finish, incr) stores the increment slot.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertEqual(ts.incr(), self.incr) + assert ts.incr() == self.incr def test_ctor_series_default_not_relative(self): """TimeSeries(start, finish, incr) defaults to non-relative.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertFalse(ts.relative()) + assert not ts.relative() def test_ctor_series_relative_true(self): """TimeSeries(start, finish, incr, True) marks the series as relative.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr, True) - self.assertTrue(ts.relative()) + assert ts.relative() def test_ctor_series_relative_false(self): """TimeSeries(start, finish, incr, False) explicitly non-relative.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr, False) - self.assertFalse(ts.relative()) + assert not ts.relative() def test_ctor_series_str_absolute(self): """str(TimeSeries series) renders as 'HH:MM HH:MM HH:MM'.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertEqual(str(ts), "00:00 23:00 01:00") + assert str(ts) == "00:00 23:00 01:00" def test_ctor_series_str_relative(self): """str(TimeSeries series, relative=True) renders with leading '+'.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr, True) - self.assertEqual(str(ts), "+00:00 23:00 01:00") + assert str(ts) == "+00:00 23:00 01:00" # ------------------------------------------------------------------ # has_increment() @@ -1404,13 +1403,11 @@ def test_ctor_series_str_relative(self): def test_has_increment_false_for_single(self): """has_increment() is False for a single-slot TimeSeries.""" - self.assertFalse(ecf.TimeSeries(ecf.TimeSlot(10, 0)).has_increment()) + assert not ecf.TimeSeries(ecf.TimeSlot(10, 0)).has_increment() def test_has_increment_true_for_series(self): """has_increment() is True for a start/finish/incr TimeSeries.""" - self.assertTrue( - ecf.TimeSeries(self.start, self.finish, self.incr).has_increment() - ) + assert ecf.TimeSeries(self.start, self.finish, self.incr).has_increment() # ------------------------------------------------------------------ # relative() @@ -1418,11 +1415,11 @@ def test_has_increment_true_for_series(self): def test_relative_false_by_default(self): """relative() defaults to False.""" - self.assertFalse(ecf.TimeSeries(ecf.TimeSlot(10, 0)).relative()) + assert not ecf.TimeSeries(ecf.TimeSlot(10, 0)).relative() def test_relative_true_when_set(self): """relative() returns True when the relative flag was set at construction.""" - self.assertTrue(ecf.TimeSeries(ecf.TimeSlot(10, 0), True).relative()) + assert ecf.TimeSeries(ecf.TimeSlot(10, 0), True).relative() # ------------------------------------------------------------------ # __eq__ and __ne__ @@ -1430,27 +1427,27 @@ def test_relative_true_when_set(self): def test_eq_identical_single_slots(self): """Two single-slot TimeSeries with the same slot are equal.""" - self.assertEqual( - ecf.TimeSeries(ecf.TimeSlot(10, 30)), ecf.TimeSeries(ecf.TimeSlot(10, 30)) + assert ecf.TimeSeries(ecf.TimeSlot(10, 30)) == ecf.TimeSeries( + ecf.TimeSlot(10, 30) ) def test_ne_different_slots(self): """Single-slot TimeSeries with different slots are not equal.""" - self.assertNotEqual( - ecf.TimeSeries(ecf.TimeSlot(10, 30)), ecf.TimeSeries(ecf.TimeSlot(10, 31)) + assert ecf.TimeSeries(ecf.TimeSlot(10, 30)) != ecf.TimeSeries( + ecf.TimeSlot(10, 31) ) def test_eq_identical_series(self): """Two series TimeSeries with the same parameters are equal.""" ts1 = ecf.TimeSeries(self.start, self.finish, self.incr) ts2 = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertEqual(ts1, ts2) + assert ts1 == ts2 def test_ne_different_relative_flag(self): """TimeSeries objects differing only in the relative flag are not equal.""" ts1 = ecf.TimeSeries(ecf.TimeSlot(10, 30), False) ts2 = ecf.TimeSeries(ecf.TimeSlot(10, 30), True) - self.assertNotEqual(ts1, ts2) + assert ts1 != ts2 # ------------------------------------------------------------------ # __copy__ @@ -1459,12 +1456,12 @@ def test_ne_different_relative_flag(self): def test_copy_is_value_equal(self): """copy.copy(TimeSeries) returns a value-equal instance.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertEqual(copy.copy(ts), ts) + assert copy.copy(ts) == ts def test_copy_is_identity_distinct(self): """copy.copy(TimeSeries) returns a different object.""" ts = ecf.TimeSeries(self.start, self.finish, self.incr) - self.assertIsNot(copy.copy(ts), ts) + assert copy.copy(ts) is not ts # ------------------------------------------------------------------ # __hash__ @@ -1474,14 +1471,10 @@ def test_hash_is_identity_based(self): """Two value-equal TimeSeries kept simultaneously have different hashes.""" ts1 = ecf.TimeSeries(ecf.TimeSlot(10, 30)) ts2 = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertEqual(ts1, ts2) - self.assertNotEqual(hash(ts1), hash(ts2)) + assert ts1 == ts2 + assert hash(ts1) != hash(ts2) def test_hash_same_object_is_stable(self): """The hash of a single TimeSeries is the same across repeated calls.""" ts = ecf.TimeSeries(ecf.TimeSlot(10, 30)) - self.assertEqual(hash(ts), hash(ts)) - - -if __name__ == "__main__": - unittest.main(verbosity=2) + assert hash(ts) == hash(ts) diff --git a/libs/pyext/test/py_u_TestExportDefs.py b/libs/pyext/test/py_u_TestExportDefs.py index 9b0918032..52e0f0808 100644 --- a/libs/pyext/test/py_u_TestExportDefs.py +++ b/libs/pyext/test/py_u_TestExportDefs.py @@ -11,12 +11,11 @@ import copy import os import tempfile -import unittest - import ecflow as ecf +import pytest -class TestDefs(unittest.TestCase): +class TestDefs: """Tests for py::class_ exposed as ecf.Defs in ExportDefs.cpp. Defs is the root container of an ecFlow suite definition. It holds suites, @@ -106,19 +105,19 @@ def _simple_defs(self): def test_ctor_empty_creates_instance(self): """Defs() creates an empty Defs instance.""" - self.assertIsInstance(ecf.Defs(), ecf.Defs) + assert isinstance(ecf.Defs(), ecf.Defs) def test_ctor_empty_has_no_suites(self): """An empty Defs has zero suites.""" - self.assertEqual(len(ecf.Defs()), 0) + assert len(ecf.Defs()) == 0 def test_ctor_empty_str_contains_version(self): """str(Defs()) contains the ecFlow version header.""" - self.assertIn("#", str(ecf.Defs())) + assert "#" in str(ecf.Defs()) def test_ctor_empty_str_contains_enddef(self): """str(Defs()) contains the '# enddef' trailer.""" - self.assertIn("# enddef", str(ecf.Defs())) + assert "# enddef" in str(ecf.Defs()) # ------------------------------------------------------------------ # Constructor: Defs(Suite, ...) @@ -127,17 +126,17 @@ def test_ctor_empty_str_contains_enddef(self): def test_ctor_with_one_suite(self): """Defs(Suite) creates a Defs with one suite.""" d = ecf.Defs(ecf.Suite("s1")) - self.assertEqual(len(d), 1) + assert len(d) == 1 def test_ctor_with_two_suites(self): """Defs(Suite, Suite) creates a Defs with two suites.""" d = ecf.Defs(ecf.Suite("s1"), ecf.Suite("s2")) - self.assertEqual(len(d), 2) + assert len(d) == 2 def test_ctor_suite_name_preserved(self): """Suite name is preserved when passed to the constructor.""" d = ecf.Defs(ecf.Suite("alpha")) - self.assertIn("alpha", str(d)) + assert "alpha" in str(d) # ------------------------------------------------------------------ # Constructor: Defs(name=value, ...) @@ -147,20 +146,20 @@ def test_ctor_kwargs_become_user_variables(self): """Keyword arguments to Defs() are stored as user variables.""" d = ecf.Defs(MY_VAR="hello") names = [v.name() for v in d.user_variables] - self.assertIn("MY_VAR", names) + assert "MY_VAR" in names def test_ctor_kwargs_value_preserved(self): """Keyword argument value is stored verbatim.""" d = ecf.Defs(MY_VAR="hello") values = {v.name(): v.value() for v in d.user_variables} - self.assertEqual(values["MY_VAR"], "hello") + assert values["MY_VAR"] == "hello" def test_ctor_multiple_kwargs(self): """Multiple keyword arguments each become user variables.""" d = ecf.Defs(A="1", B="2") names = {v.name() for v in d.user_variables} - self.assertIn("A", names) - self.assertIn("B", names) + assert "A" in names + assert "B" in names # ------------------------------------------------------------------ # Constructor: Defs(filename) @@ -174,18 +173,18 @@ def test_ctor_from_file_loads_suites(self): fname = f.name try: d = ecf.Defs(fname) - self.assertIn("loaded", [s.name() for s in d.suites]) + assert "loaded" in [s.name() for s in d.suites] finally: os.unlink(fname) def test_ctor_nonexistent_file_raises(self): """Defs('/nonexistent') raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Defs("/nonexistent/path/that/does/not/exist.def") def test_ctor_mix_path_and_suite_raises(self): """Passing a filename string together with a Suite raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Defs("/some/path.def", ecf.Suite("s1")) # ------------------------------------------------------------------ @@ -196,33 +195,33 @@ def test_add_suite_ptr_increments_len(self): """add_suite(suite_ptr) increments the suite count.""" d = ecf.Defs() d.add_suite(ecf.Suite("s1")) - self.assertEqual(len(d), 1) + assert len(d) == 1 def test_add_suite_ptr_returns_suite(self): """add_suite(suite_ptr) returns the added Suite.""" d = ecf.Defs() s = d.add_suite(ecf.Suite("s1")) - self.assertIsInstance(s, ecf.Suite) + assert isinstance(s, ecf.Suite) def test_add_suite_str_creates_suite(self): """add_suite(name) creates a suite and returns it.""" d = ecf.Defs() s = d.add_suite("new_suite") - self.assertIsInstance(s, ecf.Suite) - self.assertEqual(len(d), 1) + assert isinstance(s, ecf.Suite) + assert len(d) == 1 def test_add_suite_str_name_preserved(self): """Suite added by name has the correct name.""" d = ecf.Defs() s = d.add_suite("my_suite") - self.assertEqual(s.name(), "my_suite") + assert s.name() == "my_suite" def test_add_multiple_suites(self): """Multiple add_suite calls accumulate suites.""" d = ecf.Defs() d.add_suite("alpha") d.add_suite("beta") - self.assertEqual(len(d), 2) + assert len(d) == 2 # ------------------------------------------------------------------ # add_extern / externs property @@ -233,18 +232,18 @@ def test_add_extern_stored(self): d = ecf.Defs() d.add_extern("/suite/task:event") exts = list(d.externs) - self.assertIn("/suite/task:event", exts) + assert "/suite/task:event" in exts def test_add_multiple_externs(self): """Multiple add_extern calls accumulate.""" d = ecf.Defs() d.add_extern("/a") d.add_extern("/b") - self.assertEqual(len(list(d.externs)), 2) + assert len(list(d.externs)) == 2 def test_externs_empty_on_fresh_defs(self): """A fresh Defs has no externs.""" - self.assertEqual(len(list(ecf.Defs().externs)), 0) + assert len(list(ecf.Defs().externs)) == 0 # ------------------------------------------------------------------ # auto_add_externs @@ -257,14 +256,14 @@ def test_auto_add_externs_discovers_trigger_refs(self): t = s.add_task("t1") t.add_trigger("/other/task == complete") d.auto_add_externs(True) - self.assertIn("/other/task", list(d.externs)) + assert "/other/task" in list(d.externs) def test_auto_add_externs_no_refs_no_externs(self): """auto_add_externs on a Defs with no external triggers adds nothing.""" d = ecf.Defs() d.add_suite("s1") d.auto_add_externs(True) - self.assertEqual(len(list(d.externs)), 0) + assert len(list(d.externs)) == 0 # ------------------------------------------------------------------ # add_variable / user_variables / delete_variable @@ -275,36 +274,36 @@ def test_add_variable_str_stored(self): d = ecf.Defs() d.add_variable("MY_VAR", "hello") names = [v.name() for v in d.user_variables] - self.assertIn("MY_VAR", names) + assert "MY_VAR" in names def test_add_variable_str_value(self): """add_variable(name, str) stores the correct value.""" d = ecf.Defs() d.add_variable("MY_VAR", "hello") values = {v.name(): v.value() for v in d.user_variables} - self.assertEqual(values["MY_VAR"], "hello") + assert values["MY_VAR"] == "hello" def test_add_variable_int_stored(self): """add_variable(name, int) coerces the int to a string.""" d = ecf.Defs() d.add_variable("COUNT", 42) values = {v.name(): v.value() for v in d.user_variables} - self.assertEqual(values["COUNT"], "42") + assert values["COUNT"] == "42" def test_add_variable_variable_obj(self): """add_variable(Variable) stores the variable.""" d = ecf.Defs() d.add_variable(ecf.Variable("X", "99")) names = [v.name() for v in d.user_variables] - self.assertIn("X", names) + assert "X" in names def test_add_variable_dict(self): """add_variable(dict) stores all key/value pairs.""" d = ecf.Defs() d.add_variable({"P": "1", "Q": "2"}) names = {v.name() for v in d.user_variables} - self.assertIn("P", names) - self.assertIn("Q", names) + assert "P" in names + assert "Q" in names def test_add_variable_updates_existing(self): """add_variable with an existing name updates the value.""" @@ -312,7 +311,7 @@ def test_add_variable_updates_existing(self): d.add_variable("X", "old") d.add_variable("X", "new") values = {v.name(): v.value() for v in d.user_variables} - self.assertEqual(values["X"], "new") + assert values["X"] == "new" def test_delete_variable_removes_named(self): """delete_variable(name) removes only the named variable.""" @@ -321,8 +320,8 @@ def test_delete_variable_removes_named(self): d.add_variable("Y", "2") d.delete_variable("X") names = [v.name() for v in d.user_variables] - self.assertNotIn("X", names) - self.assertIn("Y", names) + assert "X" not in names + assert "Y" in names def test_delete_variable_empty_string_removes_all(self): """delete_variable('') removes all user variables.""" @@ -330,11 +329,11 @@ def test_delete_variable_empty_string_removes_all(self): d.add_variable("X", "1") d.add_variable("Y", "2") d.delete_variable("") - self.assertEqual(len(list(d.user_variables)), 0) + assert len(list(d.user_variables)) == 0 def test_user_variables_empty_on_fresh_defs(self): """A fresh Defs has no user variables.""" - self.assertEqual(len(list(ecf.Defs().user_variables)), 0) + assert len(list(ecf.Defs().user_variables)) == 0 # ------------------------------------------------------------------ # server_variables property @@ -342,12 +341,12 @@ def test_user_variables_empty_on_fresh_defs(self): def test_server_variables_nonempty(self): """A fresh Defs has predefined server variables.""" - self.assertGreater(len(list(ecf.Defs().server_variables)), 0) + assert len(list(ecf.Defs().server_variables)) > 0 def test_server_variables_contains_ecf_micro(self): """ECF_MICRO is among the predefined server variables.""" svars = {v.name() for v in ecf.Defs().server_variables} - self.assertIn("ECF_MICRO", svars) + assert "ECF_MICRO" in svars # ------------------------------------------------------------------ # sort_attributes @@ -361,7 +360,7 @@ def test_sort_attributes_by_attr_type(self): s.add_variable("A", "2") d.sort_attributes(ecf.AttrType.variable) names = [v.name() for v in s.variables] - self.assertEqual(names, sorted(names)) + assert names == sorted(names) def test_sort_attributes_with_recurse_false_does_not_sort_child_vars(self): """sort_attributes(AttrType, False) does not recurse into suites; child variables remain unsorted.""" @@ -372,7 +371,7 @@ def test_sort_attributes_with_recurse_false_does_not_sort_child_vars(self): d.sort_attributes(ecf.AttrType.variable, False) # recurse=False from Defs level means suite variables are NOT touched names = [v.name() for v in s.variables] - self.assertEqual(names, ["Z", "A"]) + assert names == ["Z", "A"] def test_sort_attributes_with_no_sort_list(self): """sort_attributes with a no-sort list excludes the listed names.""" @@ -382,7 +381,7 @@ def test_sort_attributes_with_no_sort_list(self): s.add_variable("A", "2") d.sort_attributes(ecf.AttrType.variable, True, []) names = [v.name() for v in s.variables] - self.assertEqual(names, sorted(names)) + assert names == sorted(names) def test_sort_attributes_by_string_name(self): """sort_attributes('variable', ...) accepts the attribute type as a string.""" @@ -392,12 +391,12 @@ def test_sort_attributes_by_string_name(self): s.add_variable("A", "2") d.sort_attributes("variable", True, []) names = [v.name() for v in s.variables] - self.assertEqual(names, sorted(names)) + assert names == sorted(names) def test_sort_attributes_invalid_string_raises(self): """sort_attributes with an unknown attribute type string raises RuntimeError.""" d = ecf.Defs() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): d.sort_attributes("not_a_real_attribute_type", True, []) # ------------------------------------------------------------------ @@ -409,22 +408,22 @@ def test_find_suite_returns_suite(self): d = ecf.Defs() d.add_suite("alpha") result = d.find_suite("alpha") - self.assertIsInstance(result, ecf.Suite) + assert isinstance(result, ecf.Suite) def test_find_suite_name_matches(self): """find_suite returns a Suite with the queried name.""" d = ecf.Defs() d.add_suite("alpha") - self.assertEqual(d.find_suite("alpha").name(), "alpha") + assert d.find_suite("alpha").name() == "alpha" def test_find_suite_not_found_returns_none(self): """find_suite returns None when the name does not exist.""" d = ecf.Defs() - self.assertIsNone(d.find_suite("nonexistent")) + assert d.find_suite("nonexistent") is None def test_find_suite_empty_defs_returns_none(self): """find_suite on an empty Defs returns None.""" - self.assertIsNone(ecf.Defs().find_suite("any")) + assert ecf.Defs().find_suite("any") is None # ------------------------------------------------------------------ # find_abs_node @@ -437,8 +436,8 @@ def test_find_abs_node_task(self): f = s.add_family("f1") f.add_task("t1") node = d.find_abs_node("/s1/f1/t1") - self.assertIsNotNone(node) - self.assertEqual(node.name(), "t1") + assert node is not None + assert node.name() == "t1" def test_find_abs_node_family(self): """find_abs_node finds a Family node.""" @@ -446,22 +445,22 @@ def test_find_abs_node_family(self): s = d.add_suite("s1") s.add_family("f1") node = d.find_abs_node("/s1/f1") - self.assertIsNotNone(node) - self.assertEqual(node.name(), "f1") + assert node is not None + assert node.name() == "f1" def test_find_abs_node_suite(self): """find_abs_node finds a Suite node.""" d = ecf.Defs() d.add_suite("s1") node = d.find_abs_node("/s1") - self.assertIsNotNone(node) - self.assertEqual(node.name(), "s1") + assert node is not None + assert node.name() == "s1" def test_find_abs_node_not_found_returns_none(self): """find_abs_node returns None for a non-existent path.""" d = ecf.Defs() d.add_suite("s1") - self.assertIsNone(d.find_abs_node("/s1/nonexistent")) + assert d.find_abs_node("/s1/nonexistent") is None # ------------------------------------------------------------------ # find_node_path @@ -473,20 +472,20 @@ def test_find_node_path_task(self): s = d.add_suite("s1") f = s.add_family("f1") f.add_task("t1") - self.assertEqual(d.find_node_path("task", "t1"), "/s1/f1/t1") + assert d.find_node_path("task", "t1") == "/s1/f1/t1" def test_find_node_path_family(self): """find_node_path('family', 'f1') returns the full path.""" d = ecf.Defs() s = d.add_suite("s1") s.add_family("f1") - self.assertEqual(d.find_node_path("family", "f1"), "/s1/f1") + assert d.find_node_path("family", "f1") == "/s1/f1" def test_find_node_path_not_found_returns_empty(self): """find_node_path returns an empty string when not found.""" d = ecf.Defs() d.add_suite("s1") - self.assertEqual(d.find_node_path("task", "nonexistent"), "") + assert d.find_node_path("task", "nonexistent") == "" # ------------------------------------------------------------------ # find_node @@ -499,8 +498,8 @@ def test_find_node_task(self): f = s.add_family("f1") f.add_task("t1") node = d.find_node("task", "/s1/f1/t1") - self.assertIsNotNone(node) - self.assertEqual(node.name(), "t1") + assert node is not None + assert node.name() == "t1" def test_find_node_family(self): """find_node('family', path) returns the Family.""" @@ -508,8 +507,8 @@ def test_find_node_family(self): s = d.add_suite("s1") s.add_family("f1") node = d.find_node("family", "/s1/f1") - self.assertIsNotNone(node) - self.assertEqual(node.name(), "f1") + assert node is not None + assert node.name() == "f1" # ------------------------------------------------------------------ # get_all_nodes / get_all_tasks @@ -521,16 +520,14 @@ def test_get_all_nodes_includes_all_levels(self): s = d.find_suite("s1") s.add_family("f1").add_task("t2") names = {n.name() for n in d.get_all_nodes()} - self.assertIn("s1", names) - self.assertIn("f1", names) - self.assertIn("t1", names) - self.assertIn("t2", names) + assert "s1" in names + assert "f1" in names + assert "t1" in names + assert "t2" in names def test_get_all_nodes_empty_defs(self): """get_all_nodes on an empty Defs returns an empty list.""" - self.assertEqual( - d.get_all_nodes() if False else list(ecf.Defs().get_all_nodes()), [] - ) + assert d.get_all_nodes() if False else list(ecf.Defs().get_all_nodes()) == [] def test_get_all_tasks_only_tasks(self): """get_all_tasks returns only Task nodes.""" @@ -538,8 +535,8 @@ def test_get_all_tasks_only_tasks(self): s = d.find_suite("s1") s.add_family("f1") tasks = d.get_all_tasks() - self.assertEqual(len(tasks), 1) - self.assertEqual(tasks[0].name(), "t1") + assert len(tasks) == 1 + assert tasks[0].name() == "t1" def test_get_all_tasks_multiple(self): """get_all_tasks returns all tasks across all levels.""" @@ -547,11 +544,11 @@ def test_get_all_tasks_multiple(self): s = d.add_suite("s1") s.add_task("t1") s.add_task("t2") - self.assertEqual(len(d.get_all_tasks()), 2) + assert len(d.get_all_tasks()) == 2 def test_get_all_tasks_empty_defs(self): """get_all_tasks on an empty Defs returns an empty list.""" - self.assertEqual(list(ecf.Defs().get_all_tasks()), []) + assert list(ecf.Defs().get_all_tasks()) == [] # ------------------------------------------------------------------ # has_time_dependencies @@ -559,7 +556,7 @@ def test_get_all_tasks_empty_defs(self): def test_has_time_dependencies_false_when_none(self): """has_time_dependencies() is False when no time attributes exist.""" - self.assertFalse(self._simple_defs().has_time_dependencies()) + assert not self._simple_defs().has_time_dependencies() def test_has_time_dependencies_true_with_time(self): """has_time_dependencies() is True after adding a time attribute.""" @@ -567,7 +564,7 @@ def test_has_time_dependencies_true_with_time(self): s = d.add_suite("s1") t = s.add_task("t1") t.add_time("08:00") - self.assertTrue(d.has_time_dependencies()) + assert d.has_time_dependencies() # ------------------------------------------------------------------ # check @@ -577,7 +574,7 @@ def test_check_valid_defs_returns_empty(self): """check() returns an empty string for a structurally valid Defs.""" d = ecf.Defs() d.add_suite("s1") - self.assertEqual(d.check(), "") + assert d.check() == "" def test_check_invalid_trigger_returns_error(self): """check() returns a non-empty error string when a trigger references a missing node.""" @@ -585,7 +582,7 @@ def test_check_invalid_trigger_returns_error(self): s = d.add_suite("s1") t = s.add_task("t1") t.add_trigger("/nonexistent == complete") - self.assertNotEqual(d.check(), "") + assert d.check() != "" # ------------------------------------------------------------------ # check_job_creation @@ -594,19 +591,19 @@ def test_check_invalid_trigger_returns_error(self): def test_check_job_creation_returns_string(self): """check_job_creation() always returns a string.""" d = self._simple_defs() - self.assertIsInstance(d.check_job_creation(), str) + assert isinstance(d.check_job_creation(), str) def test_check_job_creation_no_error_flag(self): """check_job_creation(throw_on_error=False) does not raise on missing scripts.""" d = self._simple_defs() result = d.check_job_creation(throw_on_error=False) - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_check_job_creation_verbose_no_raise(self): """check_job_creation(verbose=True) runs without raising.""" d = self._simple_defs() result = d.check_job_creation(throw_on_error=False, verbose=True) - self.assertIsInstance(result, str) + assert isinstance(result, str) # ------------------------------------------------------------------ # get_state / get_server_state @@ -614,19 +611,19 @@ def test_check_job_creation_verbose_no_raise(self): def test_get_state_returns_state_type(self): """get_state() returns an ecf.State value.""" - self.assertIsInstance(ecf.Defs().get_state(), ecf.State) + assert isinstance(ecf.Defs().get_state(), ecf.State) def test_get_state_fresh_defs_is_unknown(self): """A fresh Defs has state 'unknown'.""" - self.assertEqual(ecf.Defs().get_state(), ecf.State.unknown) + assert ecf.Defs().get_state() == ecf.State.unknown def test_get_server_state_returns_sstate_type(self): """get_server_state() returns an ecf.SState value.""" - self.assertIsInstance(ecf.Defs().get_server_state(), ecf.SState) + assert isinstance(ecf.Defs().get_server_state(), ecf.SState) def test_get_server_state_fresh_defs_is_running(self): """A fresh Defs reports server state RUNNING.""" - self.assertEqual(ecf.Defs().get_server_state(), ecf.SState.RUNNING) + assert ecf.Defs().get_server_state() == ecf.SState.RUNNING # ------------------------------------------------------------------ # save_as_defs / save_as_checkpt / restore_from_checkpt @@ -639,7 +636,7 @@ def test_save_as_defs_creates_file(self): fname = f.name try: d.save_as_defs(fname) - self.assertTrue(os.path.exists(fname)) + assert os.path.exists(fname) finally: os.unlink(fname) @@ -651,7 +648,7 @@ def test_save_as_defs_content_is_readable(self): try: d.save_as_defs(fname) content = open(fname).read() - self.assertIn("suite s1", content) + assert "suite s1" in content finally: os.unlink(fname) @@ -663,7 +660,7 @@ def test_save_as_defs_with_style_defs(self): try: d.save_as_defs(fname, ecf.Style.DEFS) content = open(fname).read() - self.assertIn("suite s1", content) + assert "suite s1" in content finally: os.unlink(fname) @@ -675,7 +672,7 @@ def test_save_as_defs_roundtrip(self): try: d.save_as_defs(fname) d2 = ecf.Defs(fname) - self.assertEqual([s.name() for s in d2.suites], ["s1"]) + assert [s.name() for s in d2.suites] == ["s1"] finally: os.unlink(fname) @@ -686,7 +683,7 @@ def test_save_as_checkpt_creates_file(self): cname = f.name try: d.save_as_checkpt(cname) - self.assertTrue(os.path.exists(cname)) + assert os.path.exists(cname) finally: os.unlink(cname) @@ -699,7 +696,7 @@ def test_restore_from_checkpt_recovers_suites(self): d.save_as_checkpt(cname) d2 = ecf.Defs() d2.restore_from_checkpt(cname) - self.assertIn("s1", [s.name() for s in d2.suites]) + assert "s1" in [s.name() for s in d2.suites] finally: os.unlink(cname) @@ -711,46 +708,46 @@ def test_add_single_suite(self): """add(suite) appends the suite.""" d = ecf.Defs() d.add(ecf.Suite("s1")) - self.assertEqual(len(d), 1) + assert len(d) == 1 def test_add_two_suites_positional(self): """add(suite, suite) appends both suites.""" d = ecf.Defs() d.add(ecf.Suite("s1"), ecf.Suite("s2")) - self.assertEqual(len(d), 2) + assert len(d) == 2 def test_add_kwarg_variable(self): """add(name=value) stores the keyword argument as a user variable.""" d = ecf.Defs() d.add(MY_VAR="val") names = [v.name() for v in d.user_variables] - self.assertIn("MY_VAR", names) + assert "MY_VAR" in names def test_add_dict(self): """add({'key': 'value'}) stores the variable.""" d = ecf.Defs() d.add({"MYKEY": "myval"}) names = [v.name() for v in d.user_variables] - self.assertIn("MYKEY", names) + assert "MYKEY" in names def test_add_variable_obj(self): """add(Variable) stores the variable.""" d = ecf.Defs() d.add(ecf.Variable("X", "42")) names = [v.name() for v in d.user_variables] - self.assertIn("X", names) + assert "X" in names def test_add_returns_defs(self): """add() returns the Defs instance (enables chaining).""" d = ecf.Defs() result = d.add(ecf.Suite("s1")) - self.assertIsInstance(result, ecf.Defs) + assert isinstance(result, ecf.Defs) def test_add_none_is_ignored(self): """add(None) is silently ignored; suite count unchanged.""" d = ecf.Defs() d.add(None) - self.assertEqual(len(d), 0) + assert len(d) == 0 # ------------------------------------------------------------------ # __iadd__ @@ -760,20 +757,20 @@ def test_iadd_list_of_suites(self): """defs += [Suite, Suite] appends both suites.""" d = ecf.Defs() d += [ecf.Suite("s1"), ecf.Suite("s2")] - self.assertEqual(len(d), 2) + assert len(d) == 2 def test_iadd_single_suite(self): """defs += Suite appends the suite.""" d = ecf.Defs() d += ecf.Suite("s1") - self.assertEqual(len(d), 1) + assert len(d) == 1 def test_iadd_variable(self): """defs += [Variable] stores the variable.""" d = ecf.Defs() d += [ecf.Variable("V", "val")] names = [v.name() for v in d.user_variables] - self.assertIn("V", names) + assert "V" in names # ------------------------------------------------------------------ # __add__ @@ -783,7 +780,7 @@ def test_add_operator_appends_suite(self): """defs + Suite appends the suite and returns Defs.""" d = ecf.Defs() d = d + ecf.Suite("s1") - self.assertEqual(len(d), 1) + assert len(d) == 1 # ------------------------------------------------------------------ # __str__ @@ -792,21 +789,21 @@ def test_add_operator_appends_suite(self): def test_str_empty_defs(self): """str(Defs()) includes the version header and enddef marker.""" s = str(ecf.Defs()) - self.assertIn("# enddef", s) + assert "# enddef" in s def test_str_with_suite(self): """str(Defs) includes the suite block.""" d = ecf.Defs() d.add_suite("s1") - self.assertIn("suite s1", str(d)) + assert "suite s1" in str(d) def test_str_user_variables_not_in_output(self): """User variables on Defs are server-side state and do not appear in str().""" d = ecf.Defs() d.add_variable("MY_VAR", "hello") # user_variables are accessible via the property but not serialised into str() - self.assertNotIn("MY_VAR", str(d)) - self.assertIn("MY_VAR", [v.name() for v in d.user_variables]) + assert "MY_VAR" not in str(d) + assert "MY_VAR" in [v.name() for v in d.user_variables] # ------------------------------------------------------------------ # __len__ @@ -814,20 +811,20 @@ def test_str_user_variables_not_in_output(self): def test_len_empty(self): """len(Defs()) == 0.""" - self.assertEqual(len(ecf.Defs()), 0) + assert len(ecf.Defs()) == 0 def test_len_one_suite(self): """len increases to 1 after adding a suite.""" d = ecf.Defs() d.add_suite("s1") - self.assertEqual(len(d), 1) + assert len(d) == 1 def test_len_two_suites(self): """len reflects the correct count after adding two suites.""" d = ecf.Defs() d.add_suite("s1") d.add_suite("s2") - self.assertEqual(len(d), 2) + assert len(d) == 2 # ------------------------------------------------------------------ # __contains__ @@ -837,16 +834,16 @@ def test_contains_existing_suite(self): """'name' in defs is True when the suite exists.""" d = ecf.Defs() d.add_suite("s1") - self.assertIn("s1", d) + assert "s1" in d def test_contains_missing_suite(self): """'name' in defs is False when the suite does not exist.""" d = ecf.Defs() - self.assertNotIn("nonexistent", d) + assert "nonexistent" not in d def test_not_contains_after_no_suites(self): """An empty Defs does not contain any name.""" - self.assertNotIn("s1", ecf.Defs()) + assert "s1" not in ecf.Defs() # ------------------------------------------------------------------ # __iter__ @@ -858,7 +855,7 @@ def test_iter_yields_suites(self): d.add_suite("alpha") d.add_suite("beta") items = list(d) - self.assertTrue(all(isinstance(s, ecf.Suite) for s in items)) + assert all(isinstance(s, ecf.Suite) for s in items) def test_iter_yields_in_insertion_order(self): """Iteration yields suites in insertion order.""" @@ -866,11 +863,11 @@ def test_iter_yields_in_insertion_order(self): d.add_suite("alpha") d.add_suite("beta") names = [s.name() for s in d] - self.assertEqual(names, ["alpha", "beta"]) + assert names == ["alpha", "beta"] def test_iter_empty_defs(self): """Iterating an empty Defs yields nothing.""" - self.assertEqual(list(ecf.Defs()), []) + assert list(ecf.Defs()) == [] # ------------------------------------------------------------------ # suites property @@ -880,7 +877,7 @@ def test_suites_property_is_iterable(self): """The suites property is iterable.""" d = ecf.Defs() d.add_suite("s1") - self.assertGreater(len(list(d.suites)), 0) + assert len(list(d.suites)) > 0 def test_suites_property_names(self): """The suites property yields Suite objects with correct names.""" @@ -888,8 +885,8 @@ def test_suites_property_names(self): d.add_suite("s1") d.add_suite("s2") names = [s.name() for s in d.suites] - self.assertIn("s1", names) - self.assertIn("s2", names) + assert "s1" in names + assert "s2" in names # ------------------------------------------------------------------ # __getattr__ @@ -899,25 +896,25 @@ def test_getattr_returns_suite(self): """Accessing defs.suite_name returns the Suite.""" d = ecf.Defs() d.add_suite("myS") - self.assertIsInstance(d.myS, ecf.Suite) + assert isinstance(d.myS, ecf.Suite) def test_getattr_suite_name_matches(self): """The Suite returned by attribute access has the correct name.""" d = ecf.Defs() d.add_suite("myS") - self.assertEqual(d.myS.name(), "myS") + assert d.myS.name() == "myS" def test_getattr_returns_variable(self): """Accessing defs.VAR_NAME returns the Variable when no suite of that name exists.""" d = ecf.Defs() d.add_variable("MY_VAR", "42") result = d.MY_VAR - self.assertIsInstance(result, ecf.Variable) + assert isinstance(result, ecf.Variable) def test_getattr_nonexistent_raises(self): """Accessing a non-existent attribute raises RuntimeError.""" d = ecf.Defs() - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): _ = d.nonexistent_attr # ------------------------------------------------------------------ @@ -927,17 +924,17 @@ def test_getattr_nonexistent_raises(self): def test_context_manager_returns_defs(self): """The 'with Defs()' statement binds the Defs to the 'as' target.""" with ecf.Defs() as d: - self.assertIsInstance(d, ecf.Defs) + assert isinstance(d, ecf.Defs) def test_context_manager_mutations_persist(self): """Mutations inside a 'with' block are visible after the block exits.""" with ecf.Defs() as d: d.add_suite("s_ctx") - self.assertEqual(len(d), 1) + assert len(d) == 1 def test_context_manager_does_not_suppress_exceptions(self): """Exceptions raised inside a 'with Defs()' block are not suppressed.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): with ecf.Defs(): raise ValueError("test error") @@ -947,7 +944,7 @@ def test_context_manager_does_not_suppress_exceptions(self): def test_eq_two_empty_defs(self): """Two empty Defs instances are equal.""" - self.assertEqual(ecf.Defs(), ecf.Defs()) + assert ecf.Defs() == ecf.Defs() def test_eq_same_suite(self): """Two Defs with identical suites are equal.""" @@ -955,7 +952,7 @@ def test_eq_same_suite(self): d1.add_suite("s1") d2 = ecf.Defs() d2.add_suite("s1") - self.assertEqual(d1, d2) + assert d1 == d2 def test_eq_different_suites(self): """Two Defs with different suite names are not equal.""" @@ -963,18 +960,18 @@ def test_eq_different_suites(self): d1.add_suite("s1") d2 = ecf.Defs() d2.add_suite("s2") - self.assertNotEqual(d1, d2) + assert d1 != d2 def test_eq_one_has_suite_other_does_not(self): """A Defs with a suite is not equal to an empty Defs.""" d1 = ecf.Defs() d1.add_suite("s1") - self.assertNotEqual(d1, ecf.Defs()) + assert d1 != ecf.Defs() def test_eq_reflexive(self): """A Defs is equal to itself.""" d = ecf.Defs() - self.assertEqual(d, d) + assert d == d def test_ne_different_suites(self): """__ne__ returns True when suite names differ.""" @@ -982,7 +979,7 @@ def test_ne_different_suites(self): d1.add_suite("s1") d2 = ecf.Defs() d2.add_suite("s2") - self.assertTrue(d1 != d2) + assert d1 != d2 def test_ne_same_defs(self): """__ne__ returns False for structurally identical Defs.""" @@ -990,7 +987,7 @@ def test_ne_same_defs(self): d1.add_suite("s1") d2 = ecf.Defs() d2.add_suite("s1") - self.assertFalse(d1 != d2) + assert not d1 != d2 # ------------------------------------------------------------------ # __lt__ (not exposed) @@ -1000,7 +997,7 @@ def test_lt_not_supported(self): """Defs does not support < comparison.""" d1 = ecf.Defs() d2 = ecf.Defs() - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = d1 < d2 # ------------------------------------------------------------------ @@ -1009,19 +1006,19 @@ def test_lt_not_supported(self): def test_is_hashable(self): """Defs is hashable.""" - self.assertIsInstance(hash(ecf.Defs()), int) + assert isinstance(hash(ecf.Defs()), int) def test_same_object_same_hash(self): """The same Defs object always returns the same hash.""" d = ecf.Defs() - self.assertEqual(hash(d), hash(d)) + assert hash(d) == hash(d) def test_hash_is_identity_based(self): """Two value-equal Defs objects have different hashes.""" d1 = ecf.Defs() d2 = ecf.Defs() - self.assertEqual(d1, d2) - self.assertNotEqual(hash(d1), hash(d2)) + assert d1 == d2 + assert hash(d1) != hash(d2) def test_can_be_stored_in_set(self): """Defs instances can be stored in a Python set.""" @@ -1029,13 +1026,13 @@ def test_can_be_stored_in_set(self): d1.add_suite("s1") d2 = ecf.Defs() d2.add_suite("s2") - self.assertEqual(len({d1, d2}), 2) + assert len({d1, d2}) == 2 def test_can_be_used_as_dict_key(self): """Defs instances can be used as dictionary keys.""" d = ecf.Defs() mapping = {d: "defs"} - self.assertEqual(mapping[d], "defs") + assert mapping[d] == "defs" # ------------------------------------------------------------------ # __copy__ @@ -1045,20 +1042,20 @@ def test_copy_returns_equal(self): """copy.copy(Defs) returns an equal Defs.""" d = ecf.Defs() d.add_suite("s1") - self.assertEqual(copy.copy(d), d) + assert copy.copy(d) == d def test_copy_is_independent(self): """copy.copy(Defs) returns a distinct object.""" d = ecf.Defs() d.add_suite("s1") - self.assertIsNot(copy.copy(d), d) + assert copy.copy(d) is not d def test_copy_preserves_suites(self): """copy.copy preserves the suite list.""" d = ecf.Defs() d.add_suite("s1") c = copy.copy(d) - self.assertIn("s1", [s.name() for s in c.suites]) + assert "s1" in [s.name() for s in c.suites] def test_copy_preserves_user_variables(self): """copy.copy preserves user variables.""" @@ -1066,14 +1063,10 @@ def test_copy_preserves_user_variables(self): d.add_variable("MY_VAR", "hello") c = copy.copy(d) names = [v.name() for v in c.user_variables] - self.assertIn("MY_VAR", names) + assert "MY_VAR" in names def test_copy_preserves_str(self): """copy.copy(Defs) preserves str().""" d = ecf.Defs() d.add_suite("s1") - self.assertEqual(str(copy.copy(d)), str(d)) - - -if __name__ == "__main__": - unittest.main(verbosity=2) + assert str(copy.copy(d)) == str(d) diff --git a/libs/pyext/test/py_u_TestExportNode.py b/libs/pyext/test/py_u_TestExportNode.py index 25d91726d..0b4705d8b 100644 --- a/libs/pyext/test/py_u_TestExportNode.py +++ b/libs/pyext/test/py_u_TestExportNode.py @@ -8,13 +8,11 @@ # nor does it submit to any jurisdiction. # -import copy -import unittest - import ecflow as ecf +import pytest -class TestNodeVec(unittest.TestCase): +class TestNodeVec: """Tests for py::class_> exposed as ecf.NodeVec in ExportNode.cpp. Exposed API @@ -36,13 +34,13 @@ class TestNodeVec(unittest.TestCase): def test_default_constructor_creates_empty_vec(self): """NodeVec() creates an empty vector with length zero.""" nv = ecf.NodeVec() - self.assertEqual(len(nv), 0) + assert len(nv) == 0 def test_len_after_append(self): """After appending one task the length is 1.""" nv = ecf.NodeVec() nv.append(ecf.Task("t1")) - self.assertEqual(len(nv), 1) + assert len(nv) == 1 def test_len_after_multiple_appends(self): """After appending three nodes the length is 3.""" @@ -50,7 +48,7 @@ def test_len_after_multiple_appends(self): nv.append(ecf.Task("t1")) nv.append(ecf.Task("t2")) nv.append(ecf.Task("t3")) - self.assertEqual(len(nv), 3) + assert len(nv) == 3 # ------------------------------------------------------------------ # __getitem__ @@ -63,14 +61,14 @@ def test_getitem_returns_correct_node(self): t2 = ecf.Task("beta") nv.append(t1) nv.append(t2) - self.assertEqual(nv[0].name(), "alpha") - self.assertEqual(nv[1].name(), "beta") + assert nv[0].name() == "alpha" + assert nv[1].name() == "beta" def test_getitem_out_of_range_raises(self): """Index access beyond the vector length raises an exception.""" nv = ecf.NodeVec() nv.append(ecf.Task("t1")) - with self.assertRaises(Exception): + with pytest.raises(Exception): _ = nv[5] # ------------------------------------------------------------------ @@ -84,16 +82,16 @@ def test_iter_visits_all_nodes(self): for name in names: nv.append(ecf.Task(name)) visited = [node.name() for node in nv] - self.assertEqual(visited, names) + assert visited == names def test_iter_empty_vec_yields_nothing(self): """Iterating over an empty NodeVec yields no items.""" nv = ecf.NodeVec() items = list(nv) - self.assertEqual(items, []) + assert items == [] -class TestNode(unittest.TestCase): +class TestNode: """Tests for py::class_ exposed as ecf.Node in ExportNode.cpp. Exposed API @@ -273,22 +271,22 @@ class TestNode(unittest.TestCase): def test_name_returns_correct_string_for_task(self): """name() returns the name passed to the Task constructor.""" t = ecf.Task("myjob") - self.assertEqual(t.name(), "myjob") + assert t.name() == "myjob" def test_name_returns_correct_string_for_suite(self): """name() returns the name passed to the Suite constructor.""" s = ecf.Suite("mysuite") - self.assertEqual(s.name(), "mysuite") + assert s.name() == "mysuite" def test_name_returns_correct_string_for_family(self): """name() returns the name passed to the Family constructor.""" f = ecf.Family("myfamily") - self.assertEqual(f.name(), "myfamily") + assert f.name() == "myfamily" def test_name_returns_str_type(self): """name() always returns a Python str.""" t = ecf.Task("x") - self.assertIsInstance(t.name(), str) + assert isinstance(t.name(), str) # ------------------------------------------------------------------ # get_abs_node_path() @@ -297,7 +295,7 @@ def test_name_returns_str_type(self): def test_get_abs_node_path_detached_task(self): """A detached task has abs path equal to /name.""" t = ecf.Task("t1") - self.assertEqual(t.get_abs_node_path(), "/t1") + assert t.get_abs_node_path() == "/t1" def test_get_abs_node_path_nested(self): """A task nested inside suite/family gets a full path.""" @@ -305,12 +303,12 @@ def test_get_abs_node_path_nested(self): s = d.add_suite("s1") f = s.add_family("f1") t = f.add_task("t1") - self.assertEqual(t.get_abs_node_path(), "/s1/f1/t1") + assert t.get_abs_node_path() == "/s1/f1/t1" def test_get_abs_node_path_returns_str(self): """get_abs_node_path() always returns a Python str.""" t = ecf.Task("t1") - self.assertIsInstance(t.get_abs_node_path(), str) + assert isinstance(t.get_abs_node_path(), str) # ------------------------------------------------------------------ # Operators: __lt__ @@ -320,19 +318,19 @@ def test_lt_by_name_alphabetical(self): """Tasks are ordered lexicographically by name.""" ta = ecf.Task("alpha") tb = ecf.Task("beta") - self.assertLess(ta, tb) + assert ta < tb def test_lt_same_name_is_not_less(self): """A task is not less than another task with the same name.""" ta = ecf.Task("same") tb = ecf.Task("same") - self.assertFalse(ta < tb) + assert not ta < tb def test_lt_reverse_order(self): """'beta' is not less than 'alpha'.""" ta = ecf.Task("alpha") tb = ecf.Task("beta") - self.assertFalse(tb < ta) + assert not tb < ta # ------------------------------------------------------------------ # Operators: __add__ (do_add) @@ -342,25 +340,25 @@ def test_add_variable_returns_self(self): """__add__ with a Variable returns the same node.""" t = ecf.Task("t1") result = t + ecf.Variable("K", "V") - self.assertIs(result, t) + assert result is t def test_add_event_returns_self(self): """__add__ with an Event returns the same node.""" t = ecf.Task("t1") result = t + ecf.Event("done") - self.assertIs(result, t) + assert result is t def test_add_meter_returns_self(self): """__add__ with a Meter returns the same node.""" t = ecf.Task("t1") result = t + ecf.Meter("m", 0, 100) - self.assertIs(result, t) + assert result is t def test_add_adds_attribute(self): """__add__ actually adds the attribute.""" t = ecf.Task("t1") t + ecf.Variable("K", "V") - self.assertEqual(len(list(t.variables)), 1) + assert len(list(t.variables)) == 1 # ------------------------------------------------------------------ # Operators: __iadd__ @@ -370,25 +368,25 @@ def test_iadd_single_variable(self): """__iadd__ with a single Variable adds it to the node.""" t = ecf.Task("t1") t += ecf.Variable("K", "V") - self.assertEqual(len(list(t.variables)), 1) + assert len(list(t.variables)) == 1 def test_iadd_list_of_variables(self): """__iadd__ with a list of Variables adds all.""" t = ecf.Task("t1") t += [ecf.Variable("A", "1"), ecf.Variable("B", "2")] - self.assertEqual(len(list(t.variables)), 2) + assert len(list(t.variables)) == 2 def test_iadd_event(self): """__iadd__ with an Event adds it.""" t = ecf.Task("t1") t += ecf.Event("done") - self.assertEqual(len(list(t.events)), 1) + assert len(list(t.events)) == 1 def test_iadd_multiple_events_via_list(self): """__iadd__ with a list of Events adds all.""" t = ecf.Task("t1") t += [ecf.Event("e1"), ecf.Event("e2")] - self.assertEqual(len(list(t.events)), 2) + assert len(list(t.events)) == 2 # ------------------------------------------------------------------ # Operators: __rshift__ @@ -400,7 +398,7 @@ def test_rshift_adds_child(self): t1 = ecf.Task("t1") s >> t1 children = list(s.nodes) - self.assertEqual(len(children), 1) + assert len(children) == 1 def test_rshift_second_child_has_trigger_on_first(self): """Second child added with >> gets a trigger on the first.""" @@ -409,17 +407,17 @@ def test_rshift_second_child_has_trigger_on_first(self): t2 = ecf.Task("t2") s >> t1 >> t2 # t2 should have a trigger "t1 == complete" - self.assertIsNotNone(t2.get_trigger()) + assert t2.get_trigger() is not None expr = t2.get_trigger().get_expression() - self.assertIn("t1", expr) - self.assertIn("complete", expr) + assert "t1" in expr + assert "complete" in expr def test_rshift_returns_self(self): """>> returns self (the container node), enabling chaining.""" s = ecf.Suite("s1") t1 = ecf.Task("t1") result = s >> t1 - self.assertEqual(result.name(), s.name()) + assert result.name() == s.name() def test_rshift_first_child_has_no_trigger(self): """The first child added with >> has no trigger.""" @@ -427,7 +425,7 @@ def test_rshift_first_child_has_no_trigger(self): t1 = ecf.Task("t1") t2 = ecf.Task("t2") s >> t1 >> t2 - self.assertIsNone(t1.get_trigger()) + assert t1.get_trigger() is None # ------------------------------------------------------------------ # Operators: __lshift__ @@ -439,7 +437,7 @@ def test_lshift_adds_child(self): t1 = ecf.Task("t1") s << t1 children = list(s.nodes) - self.assertEqual(len(children), 1) + assert len(children) == 1 def test_lshift_first_child_gets_trigger_on_second(self): """With <<, the earlier child gets trigger on the later child.""" @@ -448,10 +446,10 @@ def test_lshift_first_child_gets_trigger_on_second(self): t2 = ecf.Task("t2") s << t1 << t2 # t1 should have a trigger on t2 == complete - self.assertIsNotNone(t1.get_trigger()) + assert t1.get_trigger() is not None expr = t1.get_trigger().get_expression() - self.assertIn("t2", expr) - self.assertIn("complete", expr) + assert "t2" in expr + assert "complete" in expr def test_lshift_last_child_has_no_trigger(self): """The last child added with << has no trigger.""" @@ -459,7 +457,7 @@ def test_lshift_last_child_has_no_trigger(self): t1 = ecf.Task("t1") t2 = ecf.Task("t2") s << t1 << t2 - self.assertIsNone(t2.get_trigger()) + assert t2.get_trigger() is None # ------------------------------------------------------------------ # Operator: __getattr__ (node_getattr) @@ -471,41 +469,41 @@ def test_getattr_resolves_child_node(self): s = d.add_suite("s1") t = s.add_task("t1") result = s.t1 - self.assertEqual(result.name(), "t1") + assert result.name() == "t1" def test_getattr_resolves_variable(self): """__getattr__ resolves a user variable by name.""" t = ecf.Task("t1") t.add_variable("MY_VAR", "hello") result = t.MY_VAR - self.assertIsInstance(result, ecf.Variable) - self.assertEqual(result.name(), "MY_VAR") + assert isinstance(result, ecf.Variable) + assert result.name() == "MY_VAR" def test_getattr_resolves_event(self): """__getattr__ resolves an event by name.""" t = ecf.Task("t1") t.add_event("done") result = t.done - self.assertIsInstance(result, ecf.Event) + assert isinstance(result, ecf.Event) def test_getattr_resolves_meter(self): """__getattr__ resolves a meter by name.""" t = ecf.Task("t1") t.add_meter("progress", 0, 100) result = t.progress - self.assertIsInstance(result, ecf.Meter) + assert isinstance(result, ecf.Meter) def test_getattr_resolves_limit(self): """__getattr__ resolves a limit by name.""" t = ecf.Task("t1") t.add_limit("myLimit", 5) result = t.myLimit - self.assertIsInstance(result, ecf.Limit) + assert isinstance(result, ecf.Limit) def test_getattr_unknown_name_raises_runtime_error(self): """__getattr__ raises RuntimeError for names that don't resolve.""" t = ecf.Task("t1") - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): _ = t.nonexistent_attr_xyz # ------------------------------------------------------------------ @@ -516,22 +514,22 @@ def test_add_single_variable_via_add(self): """add(Variable) appends a variable and returns self.""" t = ecf.Task("t1") ret = t.add(ecf.Variable("K", "V")) - self.assertIs(ret, t) - self.assertEqual(len(list(t.variables)), 1) + assert ret is t + assert len(list(t.variables)) == 1 def test_add_multiple_args(self): """add(v1, v2) adds both attributes in one call.""" t = ecf.Task("t1") t.add(ecf.Variable("A", "1"), ecf.Variable("B", "2")) - self.assertEqual(len(list(t.variables)), 2) + assert len(list(t.variables)) == 2 def test_add_with_kwargs_adds_variables(self): """add(key=value) adds variables from keyword arguments.""" t = ecf.Task("t1") t.add(MYKEY="myval") - self.assertEqual(len(list(t.variables)), 1) + assert len(list(t.variables)) == 1 names = [v.name() for v in t.variables] - self.assertIn("MYKEY", names) + assert "MYKEY" in names # ------------------------------------------------------------------ # remove() @@ -542,9 +540,9 @@ def test_remove_detaches_from_parent(self): d = ecf.Defs() s = d.add_suite("s1") t = s.add_task("t1") - self.assertEqual(t.get_parent().name(), "s1") + assert t.get_parent().name() == "s1" t.remove() - self.assertIsNone(t.get_parent()) + assert t.get_parent() is None def test_remove_returns_self(self): """remove() returns the node itself.""" @@ -552,7 +550,7 @@ def test_remove_returns_self(self): s = d.add_suite("s1") t = s.add_task("t1") result = t.remove() - self.assertEqual(result.name(), "t1") + assert result.name() == "t1" # ------------------------------------------------------------------ # add_trigger / add_complete / add_part_trigger / add_part_complete @@ -562,81 +560,81 @@ def test_add_trigger_by_str_returns_self(self): """add_trigger(str) returns the node (fluent interface).""" t = ecf.Task("t1") ret = t.add_trigger("t2 == complete") - self.assertIs(ret, t) + assert ret is t def test_add_trigger_by_str_sets_expression(self): """add_trigger(str) sets the trigger expression.""" t = ecf.Task("t1") t.add_trigger("t2 == complete") - self.assertIsNotNone(t.get_trigger()) - self.assertIn("t2", t.get_trigger().get_expression()) + assert t.get_trigger() is not None + assert "t2" in t.get_trigger().get_expression() def test_add_trigger_by_expression_object(self): """add_trigger(Expression) sets the trigger from an Expression object.""" t = ecf.Task("t1") t.add_trigger(ecf.Expression("t2 == complete")) - self.assertIsNotNone(t.get_trigger()) + assert t.get_trigger() is not None def test_add_complete_by_str_sets_expression(self): """add_complete(str) sets the complete expression.""" t = ecf.Task("t1") t.add_complete("t2 == complete") - self.assertIsNotNone(t.get_complete()) - self.assertIn("t2", t.get_complete().get_expression()) + assert t.get_complete() is not None + assert "t2" in t.get_complete().get_expression() def test_add_complete_by_expression_object(self): """add_complete(Expression) sets complete from an Expression object.""" t = ecf.Task("t1") t.add_complete(ecf.Expression("t2 == complete")) - self.assertIsNotNone(t.get_complete()) + assert t.get_complete() is not None def test_add_complete_returns_self(self): """add_complete(str) returns the node (fluent interface).""" t = ecf.Task("t1") ret = t.add_complete("t2 == complete") - self.assertIs(ret, t) + assert ret is t def test_add_part_trigger_by_part_expression(self): """add_part_trigger(PartExpression) adds a partial trigger.""" t = ecf.Task("t1") t.add_part_trigger(ecf.PartExpression("t2 == complete")) - self.assertIsNotNone(t.get_trigger()) + assert t.get_trigger() is not None def test_add_part_trigger_by_str(self): """add_part_trigger(str) adds a partial trigger from a string.""" t = ecf.Task("t1") ret = t.add_part_trigger("t2 == complete") - self.assertIs(ret, t) - self.assertIsNotNone(t.get_trigger()) + assert ret is t + assert t.get_trigger() is not None def test_add_part_trigger_by_str_and_bool(self): """add_part_trigger(str, bool) adds an AND-joined partial trigger.""" t = ecf.Task("t1") t.add_part_trigger("t2 == complete") ret = t.add_part_trigger("t3 == complete", True) - self.assertIs(ret, t) + assert ret is t expr = t.get_trigger().get_expression() - self.assertIn("AND", expr) + assert "AND" in expr def test_add_part_complete_by_part_expression(self): """add_part_complete(PartExpression) adds a partial complete.""" t = ecf.Task("t1") t.add_part_complete(ecf.PartExpression("t2 == complete")) - self.assertIsNotNone(t.get_complete()) + assert t.get_complete() is not None def test_add_part_complete_by_str(self): """add_part_complete(str) adds a partial complete from a string.""" t = ecf.Task("t1") ret = t.add_part_complete("t2 == complete") - self.assertIs(ret, t) - self.assertIsNotNone(t.get_complete()) + assert ret is t + assert t.get_complete() is not None def test_add_part_complete_by_str_and_bool(self): """add_part_complete(str, bool) adds an AND-joined partial complete.""" t = ecf.Task("t1") t.add_part_complete("t2 == complete") ret = t.add_part_complete("t3 == complete", True) - self.assertIs(ret, t) + assert ret is t # ------------------------------------------------------------------ # evaluate_trigger / evaluate_complete @@ -645,12 +643,12 @@ def test_add_part_complete_by_str_and_bool(self): def test_evaluate_trigger_false_when_no_trigger(self): """evaluate_trigger() returns False when there is no trigger.""" t = ecf.Task("t1") - self.assertFalse(t.evaluate_trigger()) + assert not t.evaluate_trigger() def test_evaluate_complete_false_when_no_complete(self): """evaluate_complete() returns False when there is no complete.""" t = ecf.Task("t1") - self.assertFalse(t.evaluate_complete()) + assert not t.evaluate_complete() # ------------------------------------------------------------------ # delete_trigger / delete_complete @@ -661,14 +659,14 @@ def test_delete_trigger_removes_trigger(self): t = ecf.Task("t1") t.add_trigger("t2 == complete") t.delete_trigger() - self.assertIsNone(t.get_trigger()) + assert t.get_trigger() is None def test_delete_complete_removes_complete(self): """delete_complete() removes the complete expression.""" t = ecf.Task("t1") t.add_complete("t2 == complete") t.delete_complete() - self.assertIsNone(t.get_complete()) + assert t.get_complete() is None def test_delete_trigger_on_empty_node_does_not_raise(self): """delete_trigger() on a node with no trigger does not raise.""" @@ -690,7 +688,7 @@ def test_change_trigger_replaces_expression(self): t.add_trigger("t2 == complete") t.change_trigger("t3 == active") expr = t.get_trigger().get_expression() - self.assertIn("t3", expr) + assert "t3" in expr def test_change_complete_replaces_expression(self): """change_complete(str) replaces the existing complete expression.""" @@ -698,7 +696,7 @@ def test_change_complete_replaces_expression(self): t.add_complete("t2 == complete") t.change_complete("t3 == active") expr = t.get_complete().get_expression() - self.assertIn("t3", expr) + assert "t3" in expr # ------------------------------------------------------------------ # Variables: add_variable / delete_variable / find_variable @@ -708,33 +706,33 @@ def test_add_variable_by_name_value(self): """add_variable(str, str) adds a variable and returns self.""" t = ecf.Task("t1") ret = t.add_variable("FOO", "bar") - self.assertIs(ret, t) - self.assertEqual(len(list(t.variables)), 1) + assert ret is t + assert len(list(t.variables)) == 1 def test_add_variable_by_name_int(self): """add_variable(str, int) adds an integer variable.""" t = ecf.Task("t1") t.add_variable("COUNT", 42) names = [v.name() for v in t.variables] - self.assertIn("COUNT", names) + assert "COUNT" in names def test_add_variable_by_object(self): """add_variable(Variable) adds the variable object.""" t = ecf.Task("t1") t.add_variable(ecf.Variable("VAR", "val")) - self.assertEqual(len(list(t.variables)), 1) + assert len(list(t.variables)) == 1 def test_add_variable_by_dict(self): """add_variable(dict) adds all key/value pairs as variables.""" t = ecf.Task("t1") t.add_variable({"A": "1", "B": "2"}) - self.assertEqual(len(list(t.variables)), 2) + assert len(list(t.variables)) == 2 def test_add_variable_fluent_chain(self): """Multiple add_variable calls can be chained.""" t = ecf.Task("t1") t.add_variable("A", "1").add_variable("B", "2").add_variable("C", "3") - self.assertEqual(len(list(t.variables)), 3) + assert len(list(t.variables)) == 3 def test_delete_variable_by_name(self): """delete_variable(str) removes the named variable.""" @@ -743,29 +741,29 @@ def test_delete_variable_by_name(self): t.add_variable("BAZ", "qux") t.delete_variable("FOO") names = [v.name() for v in t.variables] - self.assertNotIn("FOO", names) - self.assertIn("BAZ", names) + assert "FOO" not in names + assert "BAZ" in names def test_delete_variable_empty_string_removes_all(self): """delete_variable('') removes all variables.""" t = ecf.Task("t1") t.add_variable("A", "1").add_variable("B", "2") t.delete_variable("") - self.assertEqual(len(list(t.variables)), 0) + assert len(list(t.variables)) == 0 def test_find_variable_found(self): """find_variable returns the variable when found.""" t = ecf.Task("t1") t.add_variable("MYVAR", "hello") v = t.find_variable("MYVAR") - self.assertFalse(v.empty()) - self.assertEqual(v.name(), "MYVAR") + assert not v.empty() + assert v.name() == "MYVAR" def test_find_variable_not_found_returns_empty(self): """find_variable returns an empty object when not found.""" t = ecf.Task("t1") v = t.find_variable("NONEXISTENT") - self.assertTrue(v.empty()) + assert v.empty() def test_find_parent_variable_found_in_parent(self): """find_parent_variable traverses the hierarchy to find a variable.""" @@ -774,13 +772,13 @@ def test_find_parent_variable_found_in_parent(self): s.add_variable("SUITE_VAR", "sv") t = s.add_task("t1") v = t.find_parent_variable("SUITE_VAR") - self.assertFalse(v.empty()) + assert not v.empty() def test_find_parent_variable_not_found_returns_empty(self): """find_parent_variable returns empty object for nonexistent variable.""" t = ecf.Task("t1") v = t.find_parent_variable("NONEXISTENT") - self.assertTrue(v.empty()) + assert v.empty() def test_find_parent_variable_sub_value_found(self): """find_parent_variable_sub_value returns the value string.""" @@ -789,26 +787,26 @@ def test_find_parent_variable_sub_value_found(self): s.add_variable("MYKEY", "hello") t = s.add_task("t1") result = t.find_parent_variable_sub_value("MYKEY") - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_find_parent_variable_sub_value_not_found_returns_empty_str(self): """find_parent_variable_sub_value returns empty string for nonexistent key.""" t = ecf.Task("t1") result = t.find_parent_variable_sub_value("NO_SUCH_KEY") - self.assertEqual(result, "") + assert result == "" def test_variables_property_empty_initially(self): """variables property returns empty iterable when no variables added.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.variables)), 0) + assert len(list(t.variables)) == 0 def test_variables_property_after_add(self): """variables property returns all added variables.""" t = ecf.Task("t1") t.add_variable("A", "1").add_variable("B", "2") names = [v.name() for v in t.variables] - self.assertIn("A", names) - self.assertIn("B", names) + assert "A" in names + assert "B" in names # ------------------------------------------------------------------ # Events: add_event / delete_event / find_event @@ -818,35 +816,35 @@ def test_add_event_by_object(self): """add_event(Event) adds an event and returns self.""" t = ecf.Task("t1") ret = t.add_event(ecf.Event("done")) - self.assertIs(ret, t) - self.assertEqual(len(list(t.events)), 1) + assert ret is t + assert len(list(t.events)) == 1 def test_add_event_by_int(self): """add_event(int) adds an event by number.""" t = ecf.Task("t1") ret = t.add_event(1) - self.assertIs(ret, t) - self.assertEqual(len(list(t.events)), 1) + assert ret is t + assert len(list(t.events)) == 1 def test_add_event_by_int_and_name(self): """add_event(int, str) adds an event by number and name.""" t = ecf.Task("t1") ret = t.add_event(10, "myevent") - self.assertIs(ret, t) - self.assertEqual(len(list(t.events)), 1) + assert ret is t + assert len(list(t.events)) == 1 def test_add_event_by_str(self): """add_event(str) adds an event by name.""" t = ecf.Task("t1") ret = t.add_event("fred") - self.assertIs(ret, t) - self.assertEqual(len(list(t.events)), 1) + assert ret is t + assert len(list(t.events)) == 1 def test_add_multiple_events(self): """Multiple add_event calls accumulate events.""" t = ecf.Task("t1") t.add_event(ecf.Event(1)).add_event(2).add_event(10, "e2").add_event("fred") - self.assertEqual(len(list(t.events)), 4) + assert len(list(t.events)) == 4 def test_delete_event_by_name(self): """delete_event(str) removes the named event.""" @@ -854,32 +852,32 @@ def test_delete_event_by_name(self): t.add_event("done").add_event("started") t.delete_event("done") names = [e.name() for e in t.events] - self.assertNotIn("done", names) + assert "done" not in names def test_delete_event_empty_string_removes_all(self): """delete_event('') removes all events.""" t = ecf.Task("t1") t.add_event("e1").add_event("e2") t.delete_event("") - self.assertEqual(len(list(t.events)), 0) + assert len(list(t.events)) == 0 def test_find_event_found(self): """find_event returns the event when found.""" t = ecf.Task("t1") t.add_event("done") e = t.find_event("done") - self.assertFalse(e.empty()) + assert not e.empty() def test_find_event_not_found_returns_empty(self): """find_event returns an empty event when not found.""" t = ecf.Task("t1") e = t.find_event("nonexistent") - self.assertTrue(e.empty()) + assert e.empty() def test_events_property_empty_initially(self): """events property returns empty iterable when no events added.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.events)), 0) + assert len(list(t.events)) == 0 # ------------------------------------------------------------------ # Meters: add_meter / delete_meter / find_meter @@ -889,28 +887,28 @@ def test_add_meter_by_object(self): """add_meter(Meter) adds a meter and returns self.""" t = ecf.Task("t1") ret = t.add_meter(ecf.Meter("m1", 0, 100)) - self.assertIs(ret, t) - self.assertEqual(len(list(t.meters)), 1) + assert ret is t + assert len(list(t.meters)) == 1 def test_add_meter_by_name_min_max_color(self): """add_meter(str, int, int, int) adds meter with color change.""" t = ecf.Task("t1") ret = t.add_meter("progress", 0, 100, 50) - self.assertIs(ret, t) - self.assertEqual(len(list(t.meters)), 1) + assert ret is t + assert len(list(t.meters)) == 1 def test_add_meter_by_name_min_max(self): """add_meter(str, int, int) adds meter without explicit color.""" t = ecf.Task("t1") ret = t.add_meter("progress", 0, 100) - self.assertIs(ret, t) - self.assertEqual(len(list(t.meters)), 1) + assert ret is t + assert len(list(t.meters)) == 1 def test_add_multiple_meters(self): """Multiple meters can be added.""" t = ecf.Task("t1") t.add_meter(ecf.Meter("m1", 0, 100)).add_meter("m2", 0, 50) - self.assertEqual(len(list(t.meters)), 2) + assert len(list(t.meters)) == 2 def test_delete_meter_by_name(self): """delete_meter(str) removes the named meter.""" @@ -918,34 +916,34 @@ def test_delete_meter_by_name(self): t.add_meter("m1", 0, 100).add_meter("m2", 0, 50) t.delete_meter("m1") names = [m.name() for m in t.meters] - self.assertNotIn("m1", names) - self.assertIn("m2", names) + assert "m1" not in names + assert "m2" in names def test_delete_meter_empty_string_removes_all(self): """delete_meter('') removes all meters.""" t = ecf.Task("t1") t.add_meter("m1", 0, 100).add_meter("m2", 0, 50) t.delete_meter("") - self.assertEqual(len(list(t.meters)), 0) + assert len(list(t.meters)) == 0 def test_find_meter_found(self): """find_meter returns the meter when found.""" t = ecf.Task("t1") t.add_meter("progress", 0, 100) m = t.find_meter("progress") - self.assertFalse(m.empty()) - self.assertEqual(m.name(), "progress") + assert not m.empty() + assert m.name() == "progress" def test_find_meter_not_found_returns_empty(self): """find_meter returns an empty meter when not found.""" t = ecf.Task("t1") m = t.find_meter("nonexistent") - self.assertTrue(m.empty()) + assert m.empty() def test_meters_property_empty_initially(self): """meters property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.meters)), 0) + assert len(list(t.meters)) == 0 # ------------------------------------------------------------------ # Labels: add_label / delete_label / find_label @@ -955,21 +953,21 @@ def test_add_label_by_name_value(self): """add_label(str, str) adds a label and returns self.""" t = ecf.Task("t1") ret = t.add_label("info", "some text") - self.assertIs(ret, t) - self.assertEqual(len(list(t.labels)), 1) + assert ret is t + assert len(list(t.labels)) == 1 def test_add_label_by_object(self): """add_label(Label) adds a label object.""" t = ecf.Task("t1") ret = t.add_label(ecf.Label("lbl", "val")) - self.assertIs(ret, t) - self.assertEqual(len(list(t.labels)), 1) + assert ret is t + assert len(list(t.labels)) == 1 def test_add_multiple_labels(self): """Multiple labels can be added.""" t = ecf.Task("t1") t.add_label("l1", "v1").add_label("l2", "v2") - self.assertEqual(len(list(t.labels)), 2) + assert len(list(t.labels)) == 2 def test_delete_label_by_name(self): """delete_label(str) removes the named label.""" @@ -977,34 +975,34 @@ def test_delete_label_by_name(self): t.add_label("l1", "v1").add_label("l2", "v2") t.delete_label("l1") names = [lb.name() for lb in t.labels] - self.assertNotIn("l1", names) - self.assertIn("l2", names) + assert "l1" not in names + assert "l2" in names def test_delete_label_empty_string_removes_all(self): """delete_label('') removes all labels.""" t = ecf.Task("t1") t.add_label("l1", "v1").add_label("l2", "v2") t.delete_label("") - self.assertEqual(len(list(t.labels)), 0) + assert len(list(t.labels)) == 0 def test_find_label_found(self): """find_label returns the label when found.""" t = ecf.Task("t1") t.add_label("info", "text") lb = t.find_label("info") - self.assertFalse(lb.empty()) - self.assertEqual(lb.name(), "info") + assert not lb.empty() + assert lb.name() == "info" def test_find_label_not_found_returns_empty(self): """find_label returns an empty label when not found.""" t = ecf.Task("t1") lb = t.find_label("nonexistent") - self.assertTrue(lb.empty()) + assert lb.empty() def test_labels_property_empty_initially(self): """labels property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.labels)), 0) + assert len(list(t.labels)) == 0 # ------------------------------------------------------------------ # Limits: add_limit / delete_limit / find_limit @@ -1014,21 +1012,21 @@ def test_add_limit_by_name_max(self): """add_limit(str, int) adds a limit and returns self.""" t = ecf.Task("t1") ret = t.add_limit("lim1", 5) - self.assertIs(ret, t) - self.assertEqual(len(list(t.limits)), 1) + assert ret is t + assert len(list(t.limits)) == 1 def test_add_limit_by_object(self): """add_limit(Limit) adds a limit object.""" t = ecf.Task("t1") ret = t.add_limit(ecf.Limit("lim1", 5)) - self.assertIs(ret, t) - self.assertEqual(len(list(t.limits)), 1) + assert ret is t + assert len(list(t.limits)) == 1 def test_add_multiple_limits(self): """Multiple limits can be added.""" t = ecf.Task("t1") t.add_limit("l1", 5).add_limit("l2", 10) - self.assertEqual(len(list(t.limits)), 2) + assert len(list(t.limits)) == 2 def test_delete_limit_by_name(self): """delete_limit(str) removes the named limit.""" @@ -1036,32 +1034,32 @@ def test_delete_limit_by_name(self): t.add_limit("l1", 5).add_limit("l2", 10) t.delete_limit("l1") names = [lim.name() for lim in t.limits] - self.assertNotIn("l1", names) + assert "l1" not in names def test_delete_limit_empty_string_removes_all(self): """delete_limit('') removes all limits.""" t = ecf.Task("t1") t.add_limit("l1", 5).add_limit("l2", 10) t.delete_limit("") - self.assertEqual(len(list(t.limits)), 0) + assert len(list(t.limits)) == 0 def test_find_limit_found(self): """find_limit returns a non-None limit ptr when found.""" t = ecf.Task("t1") t.add_limit("lim1", 5) lim = t.find_limit("lim1") - self.assertIsNotNone(lim) + assert lim is not None def test_find_limit_not_found_returns_none(self): """find_limit returns None when not found.""" t = ecf.Task("t1") lim = t.find_limit("nonexistent") - self.assertIsNone(lim) + assert lim is None def test_limits_property_empty_initially(self): """limits property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.limits)), 0) + assert len(list(t.limits)) == 0 # ------------------------------------------------------------------ # Inlimits: add_inlimit / delete_inlimit @@ -1071,34 +1069,34 @@ def test_add_inlimit_by_params(self): """add_inlimit(str, str, int, bool) adds an inlimit.""" s = ecf.Suite("s1") ret = s.add_inlimit("lim1", "/s1", 1, False) - self.assertIs(ret, s) - self.assertEqual(len(list(s.inlimits)), 1) + assert ret is s + assert len(list(s.inlimits)) == 1 def test_add_inlimit_by_object(self): """add_inlimit(InLimit) adds an inlimit object.""" s = ecf.Suite("s1") ret = s.add_inlimit(ecf.InLimit("lim1", "/s1", 2)) - self.assertIs(ret, s) - self.assertEqual(len(list(s.inlimits)), 1) + assert ret is s + assert len(list(s.inlimits)) == 1 def test_delete_inlimit_by_name(self): """delete_inlimit(str) removes the named inlimit.""" s = ecf.Suite("s1") s.add_inlimit("lim1", "/s1", 1).add_inlimit("lim2", "/s1", 1) s.delete_inlimit("lim1") - self.assertEqual(len(list(s.inlimits)), 1) + assert len(list(s.inlimits)) == 1 def test_delete_inlimit_empty_string_removes_all(self): """delete_inlimit('') removes all inlimits.""" s = ecf.Suite("s1") s.add_inlimit("lim1", "/s1", 1).add_inlimit("lim2", "/s1", 1) s.delete_inlimit("") - self.assertEqual(len(list(s.inlimits)), 0) + assert len(list(s.inlimits)) == 0 def test_inlimits_property_empty_initially(self): """inlimits property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.inlimits)), 0) + assert len(list(t.inlimits)) == 0 # ------------------------------------------------------------------ # Queues: add_queue / delete_queue / find_queue @@ -1108,21 +1106,21 @@ def test_add_queue_by_object(self): """add_queue(QueueAttr) adds a queue and returns self.""" t = ecf.Task("t1") ret = t.add_queue(ecf.Queue("q1", ["001", "002"])) - self.assertIs(ret, t) - self.assertEqual(len(list(t.queues)), 1) + assert ret is t + assert len(list(t.queues)) == 1 def test_add_queue_by_name_and_list(self): """add_queue(str, list) adds a queue from name and list.""" t = ecf.Task("t1") ret = t.add_queue("q1", ["001", "002"]) - self.assertIs(ret, t) - self.assertEqual(len(list(t.queues)), 1) + assert ret is t + assert len(list(t.queues)) == 1 def test_add_multiple_queues(self): """Multiple queues can be added.""" t = ecf.Task("t1") t.add_queue("q1", ["a"]).add_queue("q2", ["b"]) - self.assertEqual(len(list(t.queues)), 2) + assert len(list(t.queues)) == 2 def test_delete_queue_by_name(self): """delete_queue(str) removes the named queue.""" @@ -1130,32 +1128,32 @@ def test_delete_queue_by_name(self): t.add_queue("q1", ["a"]).add_queue("q2", ["b"]) t.delete_queue("q1") names = [q.name() for q in t.queues] - self.assertNotIn("q1", names) + assert "q1" not in names def test_delete_queue_empty_string_removes_all(self): """delete_queue('') removes all queues.""" t = ecf.Task("t1") t.add_queue("q1", ["a"]).add_queue("q2", ["b"]) t.delete_queue("") - self.assertEqual(len(list(t.queues)), 0) + assert len(list(t.queues)) == 0 def test_find_queue_found(self): """find_queue returns the queue when found.""" t = ecf.Task("t1") t.add_queue("q1", ["a"]) q = t.find_queue("q1") - self.assertFalse(q.empty()) + assert not q.empty() def test_find_queue_not_found_returns_empty(self): """find_queue returns an empty queue when not found.""" t = ecf.Task("t1") q = t.find_queue("nonexistent") - self.assertTrue(q.empty()) + assert q.empty() def test_queues_property_empty_initially(self): """queues property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.queues)), 0) + assert len(list(t.queues)) == 0 # ------------------------------------------------------------------ # Generics: add_generic / delete_generic / find_generic @@ -1165,15 +1163,15 @@ def test_add_generic_by_object(self): """add_generic(GenericAttr) adds a generic attribute.""" t = ecf.Task("t1") ret = t.add_generic(ecf.Generic("gen1", ["a", "b"])) - self.assertIs(ret, t) - self.assertEqual(len(list(t.generics)), 1) + assert ret is t + assert len(list(t.generics)) == 1 def test_add_generic_by_name_and_list(self): """add_generic(str, list) adds a generic from name and list.""" t = ecf.Task("t1") ret = t.add_generic("gen1", ["a", "b"]) - self.assertIs(ret, t) - self.assertEqual(len(list(t.generics)), 1) + assert ret is t + assert len(list(t.generics)) == 1 def test_delete_generic_by_name(self): """delete_generic(str) removes the named generic.""" @@ -1181,32 +1179,32 @@ def test_delete_generic_by_name(self): t.add_generic("gen1", ["a"]).add_generic("gen2", ["b"]) t.delete_generic("gen1") names = [g.name() for g in t.generics] - self.assertNotIn("gen1", names) + assert "gen1" not in names def test_delete_generic_empty_string_removes_all(self): """delete_generic('') removes all generics.""" t = ecf.Task("t1") t.add_generic("gen1", ["a"]).add_generic("gen2", ["b"]) t.delete_generic("") - self.assertEqual(len(list(t.generics)), 0) + assert len(list(t.generics)) == 0 def test_find_generic_found(self): """find_generic returns the generic when found.""" t = ecf.Task("t1") t.add_generic("gen1", ["a"]) g = t.find_generic("gen1") - self.assertFalse(g.empty()) + assert not g.empty() def test_find_generic_not_found_returns_empty(self): """find_generic returns an empty generic when not found.""" t = ecf.Task("t1") g = t.find_generic("nonexistent") - self.assertTrue(g.empty()) + assert g.empty() def test_generics_property_empty_initially(self): """generics property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.generics)), 0) + assert len(list(t.generics)) == 0 # ------------------------------------------------------------------ # Aviso / Mirror @@ -1224,13 +1222,13 @@ def test_add_aviso_returns_self(self): "schema", ) ret = t.add_aviso(attr) - self.assertIs(ret, t) - self.assertEqual(len(list(t.avisos)), 1) + assert ret is t + assert len(list(t.avisos)) == 1 def test_avisos_property_empty_initially(self): """avisos property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.avisos)), 0) + assert len(list(t.avisos)) == 0 def test_add_mirror_returns_self(self): """add_mirror(MirrorAttr) adds the mirror and returns self.""" @@ -1239,13 +1237,13 @@ def test_add_mirror_returns_self(self): "mir1", "/remote/path", "host", "7141", "120", True, "auth_key" ) ret = t.add_mirror(attr) - self.assertIs(ret, t) - self.assertEqual(len(list(t.mirrors)), 1) + assert ret is t + assert len(list(t.mirrors)) == 1 def test_mirrors_property_empty_initially(self): """mirrors property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.mirrors)), 0) + assert len(list(t.mirrors)) == 0 # ------------------------------------------------------------------ # Times: add_time / delete_time @@ -1255,42 +1253,42 @@ def test_add_time_by_hour_minute(self): """add_time(int, int) adds a time and returns self.""" t = ecf.Task("t1") ret = t.add_time(10, 30) - self.assertIs(ret, t) - self.assertEqual(len(list(t.times)), 1) + assert ret is t + assert len(list(t.times)) == 1 def test_add_time_by_hour_minute_relative(self): """add_time(int, int, bool) adds a relative time.""" t = ecf.Task("t1") ret = t.add_time(10, 30, True) - self.assertIs(ret, t) - self.assertEqual(len(list(t.times)), 1) + assert ret is t + assert len(list(t.times)) == 1 def test_add_time_by_str(self): """add_time(str) adds a time from a string representation.""" t = ecf.Task("t1") ret = t.add_time("10:30") - self.assertIs(ret, t) - self.assertEqual(len(list(t.times)), 1) + assert ret is t + assert len(list(t.times)) == 1 def test_add_time_by_object(self): """add_time(TimeAttr) adds a time from an object.""" t = ecf.Task("t1") ret = t.add_time(ecf.Time(10, 30)) - self.assertIs(ret, t) - self.assertEqual(len(list(t.times)), 1) + assert ret is t + assert len(list(t.times)) == 1 def test_add_multiple_times(self): """Multiple times can be added.""" t = ecf.Task("t1") t.add_time(10, 0).add_time(14, 0) - self.assertEqual(len(list(t.times)), 2) + assert len(list(t.times)) == 2 def test_delete_time_by_str(self): """delete_time(str) removes a time by string.""" t = ecf.Task("t1") t.add_time("10:30") t.delete_time("10:30") - self.assertEqual(len(list(t.times)), 0) + assert len(list(t.times)) == 0 def test_delete_time_by_object(self): """delete_time(TimeAttr) removes a time by object.""" @@ -1299,12 +1297,12 @@ def test_delete_time_by_object(self): times_copy = list(t.times) for time_attr in times_copy: t.delete_time(time_attr) - self.assertEqual(len(list(t.times)), 0) + assert len(list(t.times)) == 0 def test_times_property_empty_initially(self): """times property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.times)), 0) + assert len(list(t.times)) == 0 # ------------------------------------------------------------------ # Todays: add_today / delete_today @@ -1314,36 +1312,36 @@ def test_add_today_by_hour_minute(self): """add_today(int, int) adds a today and returns self.""" t = ecf.Task("t1") ret = t.add_today(10, 30) - self.assertIs(ret, t) - self.assertEqual(len(list(t.todays)), 1) + assert ret is t + assert len(list(t.todays)) == 1 def test_add_today_by_hour_minute_relative(self): """add_today(int, int, bool) adds a relative today.""" t = ecf.Task("t1") ret = t.add_today(10, 30, True) - self.assertIs(ret, t) - self.assertEqual(len(list(t.todays)), 1) + assert ret is t + assert len(list(t.todays)) == 1 def test_add_today_by_str(self): """add_today(str) adds a today from a string.""" t = ecf.Task("t1") ret = t.add_today("10:30") - self.assertIs(ret, t) - self.assertEqual(len(list(t.todays)), 1) + assert ret is t + assert len(list(t.todays)) == 1 def test_add_today_by_object(self): """add_today(TodayAttr) adds a today from an object.""" t = ecf.Task("t1") ret = t.add_today(ecf.Today(10, 30)) - self.assertIs(ret, t) - self.assertEqual(len(list(t.todays)), 1) + assert ret is t + assert len(list(t.todays)) == 1 def test_delete_today_by_str(self): """delete_today(str) removes a today by string.""" t = ecf.Task("t1") t.add_today("10:30") t.delete_today("10:30") - self.assertEqual(len(list(t.todays)), 0) + assert len(list(t.todays)) == 0 def test_delete_today_by_object(self): """delete_today(TodayAttr) removes a today by object.""" @@ -1352,12 +1350,12 @@ def test_delete_today_by_object(self): todays_copy = list(t.todays) for today_attr in todays_copy: t.delete_today(today_attr) - self.assertEqual(len(list(t.todays)), 0) + assert len(list(t.todays)) == 0 def test_todays_property_empty_initially(self): """todays property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.todays)), 0) + assert len(list(t.todays)) == 0 # ------------------------------------------------------------------ # Dates: add_date / delete_date @@ -1367,21 +1365,21 @@ def test_add_date_by_day_month_year(self): """add_date(int, int, int) adds a date and returns self.""" t = ecf.Task("t1") ret = t.add_date(1, 6, 2025) - self.assertIs(ret, t) - self.assertEqual(len(list(t.dates)), 1) + assert ret is t + assert len(list(t.dates)) == 1 def test_add_date_by_object(self): """add_date(DateAttr) adds a date from an object.""" t = ecf.Task("t1") ret = t.add_date(ecf.Date(1, 6, 2025)) - self.assertIs(ret, t) - self.assertEqual(len(list(t.dates)), 1) + assert ret is t + assert len(list(t.dates)) == 1 def test_add_multiple_dates(self): """Multiple dates can be added.""" t = ecf.Task("t1") t.add_date(1, 6, 2025).add_date(2, 6, 2025) - self.assertEqual(len(list(t.dates)), 2) + assert len(list(t.dates)) == 2 def test_delete_date_by_object(self): """delete_date(DateAttr) removes a date by object.""" @@ -1390,19 +1388,19 @@ def test_delete_date_by_object(self): dates_copy = list(t.dates) for d in dates_copy: t.delete_date(d) - self.assertEqual(len(list(t.dates)), 0) + assert len(list(t.dates)) == 0 def test_delete_date_by_str(self): """delete_date('') removes all dates.""" t = ecf.Task("t1") t.add_date(1, 6, 2025).add_date(2, 6, 2025) t.delete_date("") - self.assertEqual(len(list(t.dates)), 0) + assert len(list(t.dates)) == 0 def test_dates_property_empty_initially(self): """dates property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.dates)), 0) + assert len(list(t.dates)) == 0 # ------------------------------------------------------------------ # Days: add_day / delete_day @@ -1412,28 +1410,28 @@ def test_add_day_by_enum(self): """add_day(Day_t enum) adds a day and returns self.""" t = ecf.Task("t1") ret = t.add_day(ecf.Days.monday) - self.assertIs(ret, t) - self.assertEqual(len(list(t.days)), 1) + assert ret is t + assert len(list(t.days)) == 1 def test_add_day_by_str(self): """add_day(str) adds a day from a string.""" t = ecf.Task("t1") ret = t.add_day("tuesday") - self.assertIs(ret, t) - self.assertEqual(len(list(t.days)), 1) + assert ret is t + assert len(list(t.days)) == 1 def test_add_day_by_object(self): """add_day(DayAttr) adds a day from an object.""" t = ecf.Task("t1") ret = t.add_day(ecf.Day(ecf.Days.wednesday)) - self.assertIs(ret, t) - self.assertEqual(len(list(t.days)), 1) + assert ret is t + assert len(list(t.days)) == 1 def test_add_multiple_days(self): """Multiple days can be added.""" t = ecf.Task("t1") t.add_day("monday").add_day("tuesday") - self.assertEqual(len(list(t.days)), 2) + assert len(list(t.days)) == 2 def test_delete_day_by_object(self): """delete_day(DayAttr) removes a day by object.""" @@ -1442,19 +1440,19 @@ def test_delete_day_by_object(self): days_copy = list(t.days) for d in days_copy: t.delete_day(d) - self.assertEqual(len(list(t.days)), 0) + assert len(list(t.days)) == 0 def test_delete_day_by_str(self): """delete_day('') removes all days.""" t = ecf.Task("t1") t.add_day("monday").add_day("tuesday") t.delete_day("") - self.assertEqual(len(list(t.days)), 0) + assert len(list(t.days)) == 0 def test_days_property_empty_initially(self): """days property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.days)), 0) + assert len(list(t.days)) == 0 # ------------------------------------------------------------------ # Crons: add_cron / delete_cron @@ -1467,8 +1465,8 @@ def test_add_cron_by_object(self): cron.set_week_days([0, 1, 2, 3, 4, 5, 6]) cron.set_time_series("10:00") ret = t.add_cron(cron) - self.assertIs(ret, t) - self.assertEqual(len(list(t.crons)), 1) + assert ret is t + assert len(list(t.crons)) == 1 def test_delete_cron_by_object(self): """delete_cron(CronAttr) removes a cron by object.""" @@ -1480,7 +1478,7 @@ def test_delete_cron_by_object(self): crons_copy = list(t.crons) for c in crons_copy: t.delete_cron(c) - self.assertEqual(len(list(t.crons)), 0) + assert len(list(t.crons)) == 0 def test_delete_cron_by_str(self): """delete_cron('') removes all crons.""" @@ -1490,12 +1488,12 @@ def test_delete_cron_by_str(self): cron.set_time_series("10:00") t.add_cron(cron) t.delete_cron("") - self.assertEqual(len(list(t.crons)), 0) + assert len(list(t.crons)) == 0 def test_crons_property_empty_initially(self): """crons property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.crons)), 0) + assert len(list(t.crons)) == 0 # ------------------------------------------------------------------ # Zombies: add_zombie / delete_zombie @@ -1506,8 +1504,8 @@ def test_add_zombie_by_object(self): t = ecf.Task("t1") za = ecf.ZombieAttr(ecf.ZombieType.ecf, [], ecf.ZombieUserActionType.block) ret = t.add_zombie(za) - self.assertIs(ret, t) - self.assertEqual(len(list(t.zombies)), 1) + assert ret is t + assert len(list(t.zombies)) == 1 def test_delete_zombie_by_str_ecf(self): """delete_zombie('ecf') removes the ecf-type zombie.""" @@ -1515,7 +1513,7 @@ def test_delete_zombie_by_str_ecf(self): za = ecf.ZombieAttr(ecf.ZombieType.ecf, [], ecf.ZombieUserActionType.block) t.add_zombie(za) t.delete_zombie("ecf") - self.assertEqual(len(list(t.zombies)), 0) + assert len(list(t.zombies)) == 0 def test_delete_zombie_by_str_user(self): """delete_zombie('user') removes the user-type zombie.""" @@ -1523,7 +1521,7 @@ def test_delete_zombie_by_str_user(self): za = ecf.ZombieAttr(ecf.ZombieType.user, [], ecf.ZombieUserActionType.block) t.add_zombie(za) t.delete_zombie("user") - self.assertEqual(len(list(t.zombies)), 0) + assert len(list(t.zombies)) == 0 def test_delete_zombie_empty_string_removes_all(self): """delete_zombie('') removes all zombies.""" @@ -1532,7 +1530,7 @@ def test_delete_zombie_empty_string_removes_all(self): za2 = ecf.ZombieAttr(ecf.ZombieType.user, [], ecf.ZombieUserActionType.block) t.add_zombie(za1).add_zombie(za2) t.delete_zombie("") - self.assertEqual(len(list(t.zombies)), 0) + assert len(list(t.zombies)) == 0 def test_delete_zombie_by_enum(self): """delete_zombie(ZombieType) removes zombie by enum value.""" @@ -1540,12 +1538,12 @@ def test_delete_zombie_by_enum(self): za = ecf.ZombieAttr(ecf.ZombieType.ecf, [], ecf.ZombieUserActionType.block) t.add_zombie(za) t.delete_zombie(ecf.ZombieType.ecf) - self.assertEqual(len(list(t.zombies)), 0) + assert len(list(t.zombies)) == 0 def test_zombies_property_empty_initially(self): """zombies property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.zombies)), 0) + assert len(list(t.zombies)) == 0 # ------------------------------------------------------------------ # Repeat: add_repeat / delete_repeat / get_repeat @@ -1555,14 +1553,14 @@ def test_add_repeat_integer(self): """add_repeat(RepeatInteger) adds a repeat and returns self.""" t = ecf.Task("t1") ret = t.add_repeat(ecf.RepeatInteger("count", 0, 10, 1)) - self.assertIs(ret, t) - self.assertFalse(t.get_repeat().empty()) + assert ret is t + assert not t.get_repeat().empty() def test_add_repeat_date(self): """add_repeat(RepeatDate) adds a date repeat.""" t = ecf.Task("t1") t.add_repeat(ecf.RepeatDate("date", 20250101, 20251231, 1)) - self.assertFalse(t.get_repeat().empty()) + assert not t.get_repeat().empty() def test_add_repeat_datetime(self): """add_repeat(RepeatDateTime) adds a datetime repeat.""" @@ -1570,43 +1568,43 @@ def test_add_repeat_datetime(self): t.add_repeat( ecf.RepeatDateTime("dt", "20250101T000000", "20250110T000000", "24:00:00") ) - self.assertFalse(t.get_repeat().empty()) + assert not t.get_repeat().empty() def test_add_repeat_date_list(self): """add_repeat(RepeatDateList) adds a date list repeat.""" t = ecf.Task("t1") t.add_repeat(ecf.RepeatDateList("dl", [20250101, 20250201])) - self.assertFalse(t.get_repeat().empty()) + assert not t.get_repeat().empty() def test_add_repeat_string(self): """add_repeat(RepeatString) adds a string repeat.""" t = ecf.Task("t1") t.add_repeat(ecf.RepeatString("rs", ["alpha", "beta"])) - self.assertFalse(t.get_repeat().empty()) + assert not t.get_repeat().empty() def test_add_repeat_enumerated(self): """add_repeat(RepeatEnumerated) adds an enumerated repeat.""" t = ecf.Task("t1") t.add_repeat(ecf.RepeatEnumerated("re", ["red", "green", "blue"])) - self.assertFalse(t.get_repeat().empty()) + assert not t.get_repeat().empty() def test_add_repeat_day(self): """add_repeat(RepeatDay) adds a day repeat.""" t = ecf.Task("t1") t.add_repeat(ecf.RepeatDay(1)) - self.assertFalse(t.get_repeat().empty()) + assert not t.get_repeat().empty() def test_delete_repeat(self): """delete_repeat() removes the repeat.""" t = ecf.Task("t1") t.add_repeat(ecf.RepeatInteger("count", 0, 10, 1)) t.delete_repeat() - self.assertTrue(t.get_repeat().empty()) + assert t.get_repeat().empty() def test_get_repeat_empty_initially(self): """get_repeat() returns an empty Repeat when none is set.""" t = ecf.Task("t1") - self.assertTrue(t.get_repeat().empty()) + assert t.get_repeat().empty() # ------------------------------------------------------------------ # Defstatus: add_defstatus / get_defstatus @@ -1616,20 +1614,20 @@ def test_add_defstatus_by_dstate(self): """add_defstatus(DState) sets the defstatus and returns self.""" t = ecf.Task("t1") ret = t.add_defstatus(ecf.DState.complete) - self.assertIs(ret, t) - self.assertEqual(t.get_defstatus(), ecf.DState.complete) + assert ret is t + assert t.get_defstatus() == ecf.DState.complete def test_add_defstatus_by_defstatus_object(self): """add_defstatus(Defstatus) sets the defstatus from a Defstatus object.""" t = ecf.Task("t1") ret = t.add_defstatus(ecf.Defstatus(ecf.DState.suspended)) - self.assertIs(ret, t) - self.assertEqual(t.get_defstatus(), ecf.DState.suspended) + assert ret is t + assert t.get_defstatus() == ecf.DState.suspended def test_get_defstatus_default_is_queued(self): """A fresh task has defstatus == queued.""" t = ecf.Task("t1") - self.assertEqual(t.get_defstatus(), ecf.DState.queued) + assert t.get_defstatus() == ecf.DState.queued # ------------------------------------------------------------------ # Late: add_late / get_late @@ -1643,7 +1641,7 @@ def test_add_late_returns_self(self): late.active(ecf.TimeSlot(0, 30)) late.complete(ecf.TimeSlot(16, 0), True) ret = t.add_late(late) - self.assertIs(ret, t) + assert ret is t def test_get_late_returns_late_attr(self): """get_late() returns the LateAttr after it is added.""" @@ -1652,12 +1650,12 @@ def test_get_late_returns_late_attr(self): late.submitted(ecf.TimeSlot(0, 15)) t.add_late(late) result = t.get_late() - self.assertIsNotNone(result) + assert result is not None def test_get_late_returns_none_when_not_set(self): """get_late() returns None when no late attribute is set.""" t = ecf.Task("t1") - self.assertIsNone(t.get_late()) + assert t.get_late() is None # ------------------------------------------------------------------ # Autocancel: add_autocancel / get_autocancel @@ -1667,35 +1665,35 @@ def test_add_autocancel_by_days(self): """add_autocancel(int) adds autocancel by number of days.""" t = ecf.Task("t1") ret = t.add_autocancel(2) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autocancel()) + assert ret is t + assert t.get_autocancel() is not None def test_add_autocancel_by_hour_min_relative(self): """add_autocancel(int, int, bool) adds autocancel by time+relative.""" t = ecf.Task("t1") ret = t.add_autocancel(1, 30, True) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autocancel()) + assert ret is t + assert t.get_autocancel() is not None def test_add_autocancel_by_timeslot(self): """add_autocancel(TimeSlot, bool) adds autocancel by TimeSlot.""" t = ecf.Task("t1") ret = t.add_autocancel(ecf.TimeSlot(1, 30), True) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autocancel()) + assert ret is t + assert t.get_autocancel() is not None def test_add_autocancel_by_object(self): """add_autocancel(AutoCancelAttr) adds autocancel from object.""" t = ecf.Task("t1") ac = ecf.Autocancel(2) ret = t.add_autocancel(ac) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autocancel()) + assert ret is t + assert t.get_autocancel() is not None def test_get_autocancel_returns_none_when_not_set(self): """get_autocancel() returns None when not set.""" t = ecf.Task("t1") - self.assertIsNone(t.get_autocancel()) + assert t.get_autocancel() is None # ------------------------------------------------------------------ # Autoarchive: add_autoarchive / get_autoarchive @@ -1705,34 +1703,34 @@ def test_add_autoarchive_by_days(self): """add_autoarchive(int) adds autoarchive by number of days.""" t = ecf.Task("t1") ret = t.add_autoarchive(2) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autoarchive()) + assert ret is t + assert t.get_autoarchive() is not None def test_add_autoarchive_by_days_idle(self): """add_autoarchive(int, bool) adds autoarchive with idle flag.""" t = ecf.Task("t1") ret = t.add_autoarchive(2, True) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autoarchive()) + assert ret is t + assert t.get_autoarchive() is not None def test_add_autoarchive_by_hour_min_relative_idle(self): """add_autoarchive(int, int, bool, bool) adds by params.""" t = ecf.Task("t1") ret = t.add_autoarchive(1, 30, True, False) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autoarchive()) + assert ret is t + assert t.get_autoarchive() is not None def test_add_autoarchive_by_timeslot(self): """add_autoarchive(TimeSlot, bool, bool) adds by TimeSlot.""" t = ecf.Task("t1") ret = t.add_autoarchive(ecf.TimeSlot(1, 30), True, False) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autoarchive()) + assert ret is t + assert t.get_autoarchive() is not None def test_get_autoarchive_returns_none_when_not_set(self): """get_autoarchive() returns None when not set.""" t = ecf.Task("t1") - self.assertIsNone(t.get_autoarchive()) + assert t.get_autoarchive() is None # ------------------------------------------------------------------ # Autorestore: add_autorestore / get_autorestore @@ -1743,20 +1741,20 @@ def test_add_autorestore_by_object(self): t = ecf.Task("t1") ar = ecf.Autorestore(["/s1"]) ret = t.add_autorestore(ar) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autorestore()) + assert ret is t + assert t.get_autorestore() is not None def test_add_autorestore_by_list(self): """add_autorestore(list) adds autorestore from a list of paths.""" t = ecf.Task("t1") ret = t.add_autorestore(["/s1", "/s2"]) - self.assertIs(ret, t) - self.assertIsNotNone(t.get_autorestore()) + assert ret is t + assert t.get_autorestore() is not None def test_get_autorestore_returns_none_when_not_set(self): """get_autorestore() returns None when not set.""" t = ecf.Task("t1") - self.assertIsNone(t.get_autorestore()) + assert t.get_autorestore() is None # ------------------------------------------------------------------ # State: get_state / get_dstate / get_state_change_time @@ -1766,7 +1764,7 @@ def test_get_state_fresh_task_returns_state(self): """A fresh task returns a State enum value from get_state().""" t = ecf.Task("t1") state = t.get_state() - self.assertIsInstance(state, ecf.State) + assert isinstance(state, ecf.State) def test_get_state_in_defs_is_unknown(self): """A task just added to a defs has state unknown (no begin() called).""" @@ -1774,14 +1772,14 @@ def test_get_state_in_defs_is_unknown(self): s = d.add_suite("s1") t = s.add_task("t1") # State is unknown until begin() is called on the defs - self.assertEqual(t.get_state(), ecf.State.unknown) + assert t.get_state() == ecf.State.unknown def test_get_dstate_in_defs_is_unknown(self): """A task just added to a defs has dstate unknown (no begin() called).""" d = ecf.Defs() s = d.add_suite("s1") t = s.add_task("t1") - self.assertEqual(t.get_dstate(), ecf.DState.unknown) + assert t.get_dstate() == ecf.DState.unknown def test_get_state_change_time_default_format(self): """get_state_change_time() returns a string with default iso_extended format.""" @@ -1789,7 +1787,7 @@ def test_get_state_change_time_default_format(self): s = d.add_suite("s1") t = s.add_task("t1") result = t.get_state_change_time() - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_get_state_change_time_iso_extended(self): """get_state_change_time('iso_extended') returns iso_extended format.""" @@ -1797,7 +1795,7 @@ def test_get_state_change_time_iso_extended(self): s = d.add_suite("s1") t = s.add_task("t1") result = t.get_state_change_time("iso_extended") - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_get_state_change_time_iso(self): """get_state_change_time('iso') returns iso format.""" @@ -1805,7 +1803,7 @@ def test_get_state_change_time_iso(self): s = d.add_suite("s1") t = s.add_task("t1") result = t.get_state_change_time("iso") - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_get_state_change_time_simple(self): """get_state_change_time('simple') returns simple format.""" @@ -1813,12 +1811,12 @@ def test_get_state_change_time_simple(self): s = d.add_suite("s1") t = s.add_task("t1") result = t.get_state_change_time("simple") - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_is_suspended_false_by_default(self): """A fresh task is not suspended.""" t = ecf.Task("t1") - self.assertFalse(t.is_suspended()) + assert not t.is_suspended() # ------------------------------------------------------------------ # get_trigger / get_complete / get_defs / get_parent @@ -1827,41 +1825,41 @@ def test_is_suspended_false_by_default(self): def test_get_trigger_returns_none_when_not_set(self): """get_trigger() returns None when no trigger is set.""" t = ecf.Task("t1") - self.assertIsNone(t.get_trigger()) + assert t.get_trigger() is None def test_get_trigger_returns_expression_when_set(self): """get_trigger() returns an Expression when trigger is set.""" t = ecf.Task("t1") t.add_trigger("t2 == complete") - self.assertIsNotNone(t.get_trigger()) + assert t.get_trigger() is not None def test_get_complete_returns_none_when_not_set(self): """get_complete() returns None when no complete is set.""" t = ecf.Task("t1") - self.assertIsNone(t.get_complete()) + assert t.get_complete() is None def test_get_complete_returns_expression_when_set(self): """get_complete() returns an Expression when complete is set.""" t = ecf.Task("t1") t.add_complete("t2 == complete") - self.assertIsNotNone(t.get_complete()) + assert t.get_complete() is not None def test_get_defs_returns_none_for_detached_node(self): """get_defs() returns None when node is not in a Defs.""" t = ecf.Task("t1") - self.assertIsNone(t.get_defs()) + assert t.get_defs() is None def test_get_defs_returns_defs_when_in_defs(self): """get_defs() returns the Defs when node is inside one.""" d = ecf.Defs() s = d.add_suite("s1") t = s.add_task("t1") - self.assertIsNotNone(t.get_defs()) + assert t.get_defs() is not None def test_get_parent_returns_none_for_detached(self): """get_parent() returns None when node has no parent.""" t = ecf.Task("t1") - self.assertIsNone(t.get_parent()) + assert t.get_parent() is None def test_get_parent_returns_parent_node(self): """get_parent() returns the parent suite.""" @@ -1869,8 +1867,8 @@ def test_get_parent_returns_parent_node(self): s = d.add_suite("s1") t = s.add_task("t1") parent = t.get_parent() - self.assertIsNotNone(parent) - self.assertEqual(parent.name(), "s1") + assert parent is not None + assert parent.name() == "s1" # ------------------------------------------------------------------ # get_all_nodes @@ -1881,7 +1879,7 @@ def test_get_all_nodes_includes_self_for_task(self): t = ecf.Task("t1") nodes = t.get_all_nodes() # get_all_nodes includes the node itself - self.assertGreaterEqual(len(nodes), 1) + assert len(nodes) >= 1 def test_get_all_nodes_returns_children_for_suite(self): """get_all_nodes() on a Suite returns all immediate task children.""" @@ -1891,7 +1889,7 @@ def test_get_all_nodes_returns_children_for_suite(self): s.add_task("t2") nodes = s.get_all_nodes() # get_all_nodes returns immediate children only - self.assertGreater(len(nodes), 0) + assert len(nodes) > 0 # ------------------------------------------------------------------ # get_flag @@ -1901,8 +1899,8 @@ def test_get_flag_returns_flag_object(self): """get_flag() returns a Flag object.""" t = ecf.Task("t1") flag = t.get_flag() - self.assertIsNotNone(flag) - self.assertIsInstance(flag, ecf.Flag) + assert flag is not None + assert isinstance(flag, ecf.Flag) # ------------------------------------------------------------------ # has_time_dependencies @@ -1911,25 +1909,25 @@ def test_get_flag_returns_flag_object(self): def test_has_time_dependencies_false_by_default(self): """has_time_dependencies() is False when no time attrs are set.""" t = ecf.Task("t1") - self.assertFalse(t.has_time_dependencies()) + assert not t.has_time_dependencies() def test_has_time_dependencies_true_after_add_time(self): """has_time_dependencies() is True after a time is added.""" t = ecf.Task("t1") t.add_time(10, 0) - self.assertTrue(t.has_time_dependencies()) + assert t.has_time_dependencies() def test_has_time_dependencies_true_after_add_today(self): """has_time_dependencies() is True after a today is added.""" t = ecf.Task("t1") t.add_today(10, 0) - self.assertTrue(t.has_time_dependencies()) + assert t.has_time_dependencies() def test_has_time_dependencies_true_after_add_date(self): """has_time_dependencies() is True after a date is added.""" t = ecf.Task("t1") t.add_date(1, 1, 2025) - self.assertTrue(t.has_time_dependencies()) + assert t.has_time_dependencies() # ------------------------------------------------------------------ # get_generated_variables @@ -1942,7 +1940,7 @@ def test_get_generated_variables_returns_list(self): t = s.add_task("t1") t.update_generated_variables() result = t.get_generated_variables() - self.assertIsInstance(result, list) + assert isinstance(result, list) def test_get_generated_variables_with_variable_list(self): """get_generated_variables(VariableList) fills the list in-place.""" @@ -1953,7 +1951,7 @@ def test_get_generated_variables_with_variable_list(self): vl = ecf.VariableList() t.get_generated_variables(vl) # VariableList should have been populated - self.assertIsNotNone(vl) + assert vl is not None # ------------------------------------------------------------------ # find_gen_variable @@ -1963,7 +1961,7 @@ def test_find_gen_variable_not_found_returns_empty(self): """find_gen_variable returns empty object for nonexistent variable.""" t = ecf.Task("t1") v = t.find_gen_variable("NONEXISTENT") - self.assertTrue(v.empty()) + assert v.empty() # ------------------------------------------------------------------ # sort_attributes @@ -1999,7 +1997,7 @@ def test_sort_attributes_by_str_name(self): def test_sort_attributes_invalid_str_raises(self): """sort_attributes(invalid_str, ...) raises RuntimeError.""" t = ecf.Task("t1") - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): t.sort_attributes("invalid_attr_type", True, []) # ------------------------------------------------------------------ @@ -2012,7 +2010,7 @@ def test_find_node_up_the_tree_finds_self(self): s = d.add_suite("s1") t = s.add_task("t1") found = t.find_node_up_the_tree("t1") - self.assertIsNotNone(found) + assert found is not None def test_find_node_up_the_tree_not_found_returns_none(self): """find_node_up_the_tree returns None for a name that doesn't exist.""" @@ -2020,7 +2018,7 @@ def test_find_node_up_the_tree_not_found_returns_none(self): s = d.add_suite("s1") t = s.add_task("t1") found = t.find_node_up_the_tree("nonexistent_xyz") - self.assertIsNone(found) + assert found is None # ------------------------------------------------------------------ # Verifies property @@ -2029,14 +2027,10 @@ def test_find_node_up_the_tree_not_found_returns_none(self): def test_verifies_property_empty_initially(self): """verifies property returns empty iterable initially.""" t = ecf.Task("t1") - self.assertEqual(len(list(t.verifies)), 0) + assert len(list(t.verifies)) == 0 def test_add_verify_and_check_verifies_property(self): """add_verify adds a verify attribute visible via verifies property.""" t = ecf.Task("t1") t.add_verify(ecf.Verify(ecf.State.complete, 1)) - self.assertEqual(len(list(t.verifies)), 1) - - -if __name__ == "__main__": - unittest.main(verbosity=2) + assert len(list(t.verifies)) == 1 diff --git a/libs/pyext/test/py_u_TestExportNodeAttr.py b/libs/pyext/test/py_u_TestExportNodeAttr.py index 82a8b3c06..c3e91b7b4 100644 --- a/libs/pyext/test/py_u_TestExportNodeAttr.py +++ b/libs/pyext/test/py_u_TestExportNodeAttr.py @@ -12,17 +12,17 @@ from os import environ import ecflow as ecf -import unittest from typing import List, Dict +import pytest def _is_running_test_on(prefix): """Helper to detect if running on a CI runner with a name starting with the given prefix.""" - envvar = environ.get('RUNNER_NAME') + envvar = environ.get("RUNNER_NAME") return envvar is not None and envvar.startswith(prefix) -class TestTrigger(unittest.TestCase): +class TestTrigger: """Tests for py::class_ as exposed in ExportNodeAttr.cpp. Exposed API @@ -49,17 +49,17 @@ class TestTrigger(unittest.TestCase): def test_create_from_str_simple(self): """Plain expression string is stored verbatim (FIRST join type, no prefix).""" t = ecf.Trigger("a == b") - self.assertEqual(t.get_expression(), "a == b") + assert t.get_expression() == "a == b" def test_create_from_str_compound(self): """A multi-token string is stored verbatim; it is NOT split or reparsed.""" t = ecf.Trigger("a == b OR c == d") - self.assertEqual(t.get_expression(), "a == b OR c == d") + assert t.get_expression() == "a == b OR c == d" def test_create_from_str_empty(self): """An empty string is accepted and stored as an empty expression.""" t = ecf.Trigger("") - self.assertEqual(t.get_expression(), "") + assert t.get_expression() == "" # ------------------------------------------------------------------ # Constructor: Trigger(str, bool) @@ -68,12 +68,12 @@ def test_create_from_str_empty(self): def test_create_from_str_and_bool_true_produces_and_prefix(self): """Trigger(str, True) marks the part as AND-type; compose_expression prepends ' AND '.""" t = ecf.Trigger("a == b", True) - self.assertEqual(t.get_expression(), " AND a == b") + assert t.get_expression() == " AND a == b" def test_create_from_str_and_bool_false_produces_or_prefix(self): """Trigger(str, False) marks the part as OR-type; compose_expression prepends ' OR '.""" t = ecf.Trigger("a == b", False) - self.assertEqual(t.get_expression(), " OR a == b") + assert t.get_expression() == " OR a == b" # ------------------------------------------------------------------ # Constructor: Trigger(PartExpression) @@ -82,22 +82,22 @@ def test_create_from_str_and_bool_false_produces_or_prefix(self): def test_create_from_part_expression_first_type(self): """PartExpression with default (FIRST) join type produces no prefix.""" t = ecf.Trigger(ecf.PartExpression("a == b")) - self.assertEqual(t.get_expression(), "a == b") + assert t.get_expression() == "a == b" def test_create_from_part_expression_and_type(self): """PartExpression(str, True) is AND-type; Trigger inherits the ' AND ' prefix.""" t = ecf.Trigger(ecf.PartExpression("a == b", True)) - self.assertEqual(t.get_expression(), " AND a == b") + assert t.get_expression() == " AND a == b" def test_create_from_part_expression_or_type(self): """PartExpression(str, False) is OR-type; Trigger inherits the ' OR ' prefix.""" t = ecf.Trigger(ecf.PartExpression("a == b", False)) - self.assertEqual(t.get_expression(), " OR a == b") + assert t.get_expression() == " OR a == b" def test_create_from_part_expression_compound(self): """A compound string inside a PartExpression is stored verbatim.""" t = ecf.Trigger(ecf.PartExpression("a == b OR c == d")) - self.assertEqual(t.get_expression(), "a == b OR c == d") + assert t.get_expression() == "a == b OR c == d" # ------------------------------------------------------------------ # Constructor: Trigger(list) -- positive cases @@ -106,32 +106,32 @@ def test_create_from_part_expression_compound(self): def test_create_from_list_empty(self): """An empty list produces an empty expression.""" t = ecf.Trigger([]) - self.assertEqual(t.get_expression(), "") + assert t.get_expression() == "" def test_create_from_list_single_expression_string(self): """A single non-name string (contains spaces/operators) is stored as-is.""" t = ecf.Trigger(["a == b"]) - self.assertEqual(t.get_expression(), "a == b") + assert t.get_expression() == "a == b" def test_create_from_list_single_valid_name_gets_complete_appended(self): """A valid ecflow node name (alphanumeric/underscore only) is auto-suffixed '== complete'.""" t = ecf.Trigger(["taskA"]) - self.assertEqual(t.get_expression(), "taskA == complete") + assert t.get_expression() == "taskA == complete" def test_create_from_list_multiple_expression_strings_are_and_joined(self): """Multiple non-name strings: first is FIRST type, subsequent are AND-joined.""" t = ecf.Trigger(["a == b", "c == d"]) - self.assertEqual(t.get_expression(), "a == b AND c == d") + assert t.get_expression() == "a == b AND c == d" def test_create_from_list_multiple_valid_names_are_and_joined(self): """Multiple valid names: each gets '== complete' suffix, joined with AND.""" t = ecf.Trigger(["taskA", "taskB"]) - self.assertEqual(t.get_expression(), "taskA == complete AND taskB == complete") + assert t.get_expression() == "taskA == complete AND taskB == complete" def test_create_from_list_three_strings_and_joined(self): """Three strings produce a chain of AND-joined parts.""" t = ecf.Trigger(["a == b", "c == d", "e == f"]) - self.assertEqual(t.get_expression(), "a == b AND c == d AND e == f") + assert t.get_expression() == "a == b AND c == d AND e == f" # ------------------------------------------------------------------ # get_expression() @@ -140,12 +140,12 @@ def test_create_from_list_three_strings_and_joined(self): def test_get_expression_returns_str(self): """get_expression() always returns a Python str.""" t = ecf.Trigger("x == active") - self.assertIsInstance(t.get_expression(), str) + assert isinstance(t.get_expression(), str) def test_get_expression_is_idempotent(self): """Calling get_expression() twice returns the same value.""" t = ecf.Trigger("a == b") - self.assertEqual(t.get_expression(), t.get_expression()) + assert t.get_expression() == t.get_expression() # ------------------------------------------------------------------ # __str__ @@ -154,12 +154,12 @@ def test_get_expression_is_idempotent(self): def test_str_equals_get_expression(self): """__str__ is bound to the same function as get_expression().""" t = ecf.Trigger("a == b") - self.assertEqual(str(t), t.get_expression()) + assert str(t) == t.get_expression() def test_str_on_compound_expression(self): """__str__ on a compound expression returns the full composed string.""" t = ecf.Trigger(["a == b", "c == d"]) - self.assertEqual(str(t), "a == b AND c == d") + assert str(t) == "a == b AND c == d" # ------------------------------------------------------------------ # __eq__ — equal cases @@ -169,31 +169,31 @@ def test_eq_identical_str_constructor(self): """Two Triggers created with the same string are equal.""" t1 = ecf.Trigger("a == b") t2 = ecf.Trigger("a == b") - self.assertEqual(t1, t2) + assert t1 == t2 def test_eq_str_and_part_expression_first_type(self): """Trigger(str) and Trigger(PartExpression(str)) with FIRST type are equal.""" t1 = ecf.Trigger("a == b") t2 = ecf.Trigger(ecf.PartExpression("a == b")) - self.assertEqual(t1, t2) + assert t1 == t2 def test_eq_reflexive(self): """A Trigger is equal to itself.""" t = ecf.Trigger("a == b") - self.assertEqual(t, t) + assert t == t def test_eq_symmetric(self): """Equality is symmetric: t1 == t2 implies t2 == t1.""" t1 = ecf.Trigger("a == b") t2 = ecf.Trigger("a == b") - self.assertTrue(t1 == t2) - self.assertTrue(t2 == t1) + assert t1 == t2 + assert t2 == t1 def test_eq_list_and_str_with_same_composed_expression(self): """Trigger from list produces the same PartExpression vector as the equivalent.""" t1 = ecf.Trigger(["a == b"]) t2 = ecf.Trigger("a == b") - self.assertEqual(t1, t2) + assert t1 == t2 # ------------------------------------------------------------------ # __eq__ / __ne__ — not-equal cases @@ -203,25 +203,25 @@ def test_ne_different_expression_strings(self): """Triggers with different expression strings are not equal.""" t1 = ecf.Trigger("a == b") t2 = ecf.Trigger("c == d") - self.assertNotEqual(t1, t2) + assert t1 != t2 def test_ne_str_vs_str_with_bool_true(self): """Trigger(str) ≠ Trigger(str, True): the internal join-type flag differs.""" t1 = ecf.Trigger("a == b") t2 = ecf.Trigger("a == b", True) - self.assertNotEqual(t1, t2) + assert t1 != t2 def test_ne_str_with_bool_true_vs_bool_false(self): """Trigger(str, True) ≠ Trigger(str, False): AND-type vs OR-type.""" t1 = ecf.Trigger("a == b", True) t2 = ecf.Trigger("a == b", False) - self.assertNotEqual(t1, t2) + assert t1 != t2 def test_ne_single_vs_multiple_parts(self): """A single-part Trigger differs from a two-part Trigger.""" t1 = ecf.Trigger("a == b") t2 = ecf.Trigger(["a == b", "c == d"]) - self.assertNotEqual(t1, t2) + assert t1 != t2 # ------------------------------------------------------------------ # __hash__ — boost.python extension types are hashable by object identity @@ -233,27 +233,27 @@ def test_is_hashable_by_identity(self): for C-extension types; hash() returns a non-zero integer derived from the object id. """ t = ecf.Trigger("a == b") - self.assertIsInstance(hash(t), int) + assert isinstance(hash(t), int) def test_can_be_inserted_in_set(self): """Because Trigger is hashable, instances can be stored in a Python set.""" t = ecf.Trigger("a == b") s = {t} - self.assertIn(t, s) + assert t in s def test_can_be_used_as_dict_key(self): """Because Trigger is hashable, instances can be used as dictionary keys.""" t = ecf.Trigger("a == b") d = {t: "value"} - self.assertEqual(d[t], "value") + assert d[t] == "value" def test_hash_is_identity_based(self): """Two equal-by-value Triggers have different hashes (identity, not value).""" t1 = ecf.Trigger("a == b") t2 = ecf.Trigger("a == b") - self.assertEqual(t1, t2) # same value - self.assertIsNot(t1, t2) # different objects - self.assertNotEqual(hash(t1), hash(t2)) # therefore different hashes + assert t1 == t2 # same value + assert t1 is not t2 # different objects + assert hash(t1) != hash(t2) # therefore different hashes # ------------------------------------------------------------------ # Negative: invalid constructor arguments (wrong Python types) @@ -261,12 +261,12 @@ def test_hash_is_identity_based(self): def test_create_from_int_raises(self): """No constructor accepts a plain integer.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Trigger(42) def test_create_from_none_raises(self): """No constructor accepts None.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Trigger(None) # ------------------------------------------------------------------ @@ -275,45 +275,47 @@ def test_create_from_none_raises(self): def test_list_with_integer_raises(self): """An integer inside the list is not a string or Node; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Trigger([42]) - @unittest.skip( - "KNOWN BUG: passing None inside a list causes a segfault in the C++ binding " - "(py::extract(None) crashes). Skip until fixed in Trigger.cpp." + @pytest.mark.skip( + reason=( + "KNOWN BUG: passing None inside a list causes a segfault in the C++ binding " + "(py::extract(None) crashes). Skip until fixed in Trigger.cpp." + ) ) def test_list_with_none_raises(self): """None inside the list should raise RuntimeError, but currently segfaults.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Trigger([None]) def test_list_with_part_expression_raises(self): """PartExpression objects are NOT accepted by the list constructor; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Trigger([ecf.PartExpression("a == b")]) def test_list_with_mixed_str_and_part_expression_raises(self): """A PartExpression anywhere in the list triggers RuntimeError, even after a valid string.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Trigger(["a == b", ecf.PartExpression("c == d")]) def test_list_with_nested_list_raises(self): """A nested list inside the list is not a valid element; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Trigger([["a == b"]]) def test_list_with_float_raises(self): """A float inside the list is not a valid element; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Trigger([3.14]) def test_list_with_bool_raises(self): """A bare bool inside the list (no associated expression) raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Trigger([True]) -class TestComplete(unittest.TestCase): +class TestComplete: """Tests for py::class_ as exposed in ExportNodeAttr.cpp. Complete shares the same implementation pattern as Trigger (both wrap a @@ -344,17 +346,17 @@ class TestComplete(unittest.TestCase): def test_create_from_str_simple(self): """Plain expression string is stored verbatim (FIRST join type, no prefix).""" c = ecf.Complete("a == b") - self.assertEqual(c.get_expression(), "a == b") + assert c.get_expression() == "a == b" def test_create_from_str_compound(self): """A multi-token string is stored verbatim; it is NOT split or reparsed.""" c = ecf.Complete("a == b OR c == d") - self.assertEqual(c.get_expression(), "a == b OR c == d") + assert c.get_expression() == "a == b OR c == d" def test_create_from_str_empty(self): """An empty string is accepted and stored as an empty expression.""" c = ecf.Complete("") - self.assertEqual(c.get_expression(), "") + assert c.get_expression() == "" # ------------------------------------------------------------------ # Constructor: Complete(str, bool) @@ -363,12 +365,12 @@ def test_create_from_str_empty(self): def test_create_from_str_and_bool_true_produces_and_prefix(self): """Complete(str, True) marks the part as AND-type; compose_expression prepends ' AND '.""" c = ecf.Complete("a == b", True) - self.assertEqual(c.get_expression(), " AND a == b") + assert c.get_expression() == " AND a == b" def test_create_from_str_and_bool_false_produces_or_prefix(self): """Complete(str, False) marks the part as OR-type; compose_expression prepends ' OR '.""" c = ecf.Complete("a == b", False) - self.assertEqual(c.get_expression(), " OR a == b") + assert c.get_expression() == " OR a == b" # ------------------------------------------------------------------ # Constructor: Complete(PartExpression) @@ -377,22 +379,22 @@ def test_create_from_str_and_bool_false_produces_or_prefix(self): def test_create_from_part_expression_first_type(self): """PartExpression with default (FIRST) join type produces no prefix.""" c = ecf.Complete(ecf.PartExpression("a == b")) - self.assertEqual(c.get_expression(), "a == b") + assert c.get_expression() == "a == b" def test_create_from_part_expression_and_type(self): """PartExpression(str, True) is AND-type; Complete inherits the ' AND ' prefix.""" c = ecf.Complete(ecf.PartExpression("a == b", True)) - self.assertEqual(c.get_expression(), " AND a == b") + assert c.get_expression() == " AND a == b" def test_create_from_part_expression_or_type(self): """PartExpression(str, False) is OR-type; Complete inherits the ' OR ' prefix.""" c = ecf.Complete(ecf.PartExpression("a == b", False)) - self.assertEqual(c.get_expression(), " OR a == b") + assert c.get_expression() == " OR a == b" def test_create_from_part_expression_compound(self): """A compound string inside a PartExpression is stored verbatim.""" c = ecf.Complete(ecf.PartExpression("a == b OR c == d")) - self.assertEqual(c.get_expression(), "a == b OR c == d") + assert c.get_expression() == "a == b OR c == d" # ------------------------------------------------------------------ # Constructor: Complete(list) -- positive cases @@ -401,32 +403,32 @@ def test_create_from_part_expression_compound(self): def test_create_from_list_empty(self): """An empty list produces an empty expression.""" c = ecf.Complete([]) - self.assertEqual(c.get_expression(), "") + assert c.get_expression() == "" def test_create_from_list_single_expression_string(self): """A single non-name string (contains spaces/operators) is stored as-is.""" c = ecf.Complete(["a == b"]) - self.assertEqual(c.get_expression(), "a == b") + assert c.get_expression() == "a == b" def test_create_from_list_single_valid_name_gets_complete_appended(self): """A valid ecflow node name (alphanumeric/underscore only) is auto-suffixed '== complete'.""" c = ecf.Complete(["taskA"]) - self.assertEqual(c.get_expression(), "taskA == complete") + assert c.get_expression() == "taskA == complete" def test_create_from_list_multiple_expression_strings_are_and_joined(self): """Multiple non-name strings: first is FIRST type, subsequent are AND-joined.""" c = ecf.Complete(["a == b", "c == d"]) - self.assertEqual(c.get_expression(), "a == b AND c == d") + assert c.get_expression() == "a == b AND c == d" def test_create_from_list_multiple_valid_names_are_and_joined(self): """Multiple valid names: each gets '== complete' suffix, joined with AND.""" c = ecf.Complete(["taskA", "taskB"]) - self.assertEqual(c.get_expression(), "taskA == complete AND taskB == complete") + assert c.get_expression() == "taskA == complete AND taskB == complete" def test_create_from_list_three_strings_and_joined(self): """Three strings produce a chain of AND-joined parts.""" c = ecf.Complete(["a == b", "c == d", "e == f"]) - self.assertEqual(c.get_expression(), "a == b AND c == d AND e == f") + assert c.get_expression() == "a == b AND c == d AND e == f" # ------------------------------------------------------------------ # get_expression() @@ -435,12 +437,12 @@ def test_create_from_list_three_strings_and_joined(self): def test_get_expression_returns_str(self): """get_expression() always returns a Python str.""" c = ecf.Complete("x == active") - self.assertIsInstance(c.get_expression(), str) + assert isinstance(c.get_expression(), str) def test_get_expression_is_idempotent(self): """Calling get_expression() twice returns the same value.""" c = ecf.Complete("a == b") - self.assertEqual(c.get_expression(), c.get_expression()) + assert c.get_expression() == c.get_expression() # ------------------------------------------------------------------ # __str__ @@ -449,12 +451,12 @@ def test_get_expression_is_idempotent(self): def test_str_equals_get_expression(self): """__str__ is bound to the same function as get_expression().""" c = ecf.Complete("a == b") - self.assertEqual(str(c), c.get_expression()) + assert str(c) == c.get_expression() def test_str_on_compound_expression(self): """__str__ on a compound expression returns the full composed string.""" c = ecf.Complete(["a == b", "c == d"]) - self.assertEqual(str(c), "a == b AND c == d") + assert str(c) == "a == b AND c == d" # ------------------------------------------------------------------ # __eq__ — equal cases @@ -464,31 +466,31 @@ def test_eq_identical_str_constructor(self): """Two Completes created with the same string are equal.""" c1 = ecf.Complete("a == b") c2 = ecf.Complete("a == b") - self.assertEqual(c1, c2) + assert c1 == c2 def test_eq_str_and_part_expression_first_type(self): """Complete(str) and Complete(PartExpression(str)) with FIRST type are equal.""" c1 = ecf.Complete("a == b") c2 = ecf.Complete(ecf.PartExpression("a == b")) - self.assertEqual(c1, c2) + assert c1 == c2 def test_eq_reflexive(self): """A Complete is equal to itself.""" c = ecf.Complete("a == b") - self.assertEqual(c, c) + assert c == c def test_eq_symmetric(self): """Equality is symmetric: c1 == c2 implies c2 == c1.""" c1 = ecf.Complete("a == b") c2 = ecf.Complete("a == b") - self.assertTrue(c1 == c2) - self.assertTrue(c2 == c1) + assert c1 == c2 + assert c2 == c1 def test_eq_list_and_str_with_same_composed_expression(self): """Complete from list produces the same PartExpression vector as the equivalent.""" c1 = ecf.Complete(["a == b"]) c2 = ecf.Complete("a == b") - self.assertEqual(c1, c2) + assert c1 == c2 # ------------------------------------------------------------------ # __eq__ / __ne__ — not-equal cases @@ -498,25 +500,25 @@ def test_ne_different_expression_strings(self): """Completes with different expression strings are not equal.""" c1 = ecf.Complete("a == b") c2 = ecf.Complete("c == d") - self.assertNotEqual(c1, c2) + assert c1 != c2 def test_ne_str_vs_str_with_bool_true(self): """Complete(str) ≠ Complete(str, True): the internal join-type flag differs.""" c1 = ecf.Complete("a == b") c2 = ecf.Complete("a == b", True) - self.assertNotEqual(c1, c2) + assert c1 != c2 def test_ne_str_with_bool_true_vs_bool_false(self): """Complete(str, True) ≠ Complete(str, False): AND-type vs OR-type.""" c1 = ecf.Complete("a == b", True) c2 = ecf.Complete("a == b", False) - self.assertNotEqual(c1, c2) + assert c1 != c2 def test_ne_single_vs_multiple_parts(self): """A single-part Complete differs from a two-part Complete.""" c1 = ecf.Complete("a == b") c2 = ecf.Complete(["a == b", "c == d"]) - self.assertNotEqual(c1, c2) + assert c1 != c2 # ------------------------------------------------------------------ # Complete is a distinct type from Trigger @@ -526,8 +528,8 @@ def test_complete_and_trigger_are_different_types(self): """Complete and Trigger with identical expressions are different Python types.""" c = ecf.Complete("a == b") t = ecf.Trigger("a == b") - self.assertNotIsInstance(c, type(t)) - self.assertNotIsInstance(t, type(c)) + assert not isinstance(c, type(t)) + assert not isinstance(t, type(c)) def test_complete_and_trigger_are_not_equal(self): """Complete and Trigger cannot be compared with ==; they are unrelated types.""" @@ -536,7 +538,7 @@ def test_complete_and_trigger_are_not_equal(self): # boost.python __eq__ is only defined between objects of the same C++ type, # so comparing across types returns NotImplemented and Python falls back to # identity comparison, which is False for distinct objects. - self.assertFalse(c == t) + assert not c == t # ------------------------------------------------------------------ # __hash__ — boost.python extension types are hashable by object identity @@ -546,27 +548,27 @@ def test_is_hashable_by_identity(self): """boost.python extension types keep identity-based tp_hash even when __eq__ is defined. hash() returns a non-zero integer derived from the object id.""" c = ecf.Complete("a == b") - self.assertIsInstance(hash(c), int) + assert isinstance(hash(c), int) def test_can_be_inserted_in_set(self): """Because Complete is hashable, instances can be stored in a Python set.""" c = ecf.Complete("a == b") s = {c} - self.assertIn(c, s) + assert c in s def test_can_be_used_as_dict_key(self): """Because Complete is hashable, instances can be used as dictionary keys.""" c = ecf.Complete("a == b") d = {c: "value"} - self.assertEqual(d[c], "value") + assert d[c] == "value" def test_hash_is_identity_based(self): """Two equal-by-value Completes have different hashes (identity, not value).""" c1 = ecf.Complete("a == b") c2 = ecf.Complete("a == b") - self.assertEqual(c1, c2) - self.assertIsNot(c1, c2) - self.assertNotEqual(hash(c1), hash(c2)) + assert c1 == c2 + assert c1 is not c2 + assert hash(c1) != hash(c2) # ------------------------------------------------------------------ # Negative: invalid constructor arguments (wrong Python types) @@ -574,12 +576,12 @@ def test_hash_is_identity_based(self): def test_create_from_int_raises(self): """No constructor accepts a plain integer.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Complete(42) def test_create_from_none_raises(self): """No constructor accepts None.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Complete(None) # ------------------------------------------------------------------ @@ -588,45 +590,47 @@ def test_create_from_none_raises(self): def test_list_with_integer_raises(self): """An integer inside the list is not a string or Node; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Complete([42]) - @unittest.skip( - "KNOWN BUG: passing None inside a list causes a segfault in the C++ binding " - "(py::extract(None) crashes). Skip until fixed in Trigger.cpp." + @pytest.mark.skip( + reason=( + "KNOWN BUG: passing None inside a list causes a segfault in the C++ binding " + "(py::extract(None) crashes). Skip until fixed in Trigger.cpp." + ) ) def test_list_with_none_raises(self): """None inside the list should raise RuntimeError, but currently segfaults.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Complete([None]) def test_list_with_part_expression_raises(self): """PartExpression objects are NOT accepted by the list constructor; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Complete([ecf.PartExpression("a == b")]) def test_list_with_mixed_str_and_part_expression_raises(self): """A PartExpression anywhere in the list triggers RuntimeError, even after a valid string.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Complete(["a == b", ecf.PartExpression("c == d")]) def test_list_with_nested_list_raises(self): """A nested list inside the list is not a valid element; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Complete([["a == b"]]) def test_list_with_float_raises(self): """A float inside the list is not a valid element; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Complete([3.14]) def test_list_with_bool_raises(self): """A bare bool inside the list (no associated expression) raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Complete([True]) -class TestPartExpression(unittest.TestCase): +class TestPartExpression: """Tests for py::class_ as exposed in ExportNodeAttr.cpp. Exposed API @@ -655,18 +659,18 @@ class TestPartExpression(unittest.TestCase): def test_create_from_str_stores_expression_verbatim(self): """The expression string is stored exactly as passed.""" pe = ecf.PartExpression("a == b") - self.assertEqual(pe.get_expression(), "a == b") + assert pe.get_expression() == "a == b" def test_create_from_str_empty_string(self): """An empty string is accepted.""" pe = ecf.PartExpression("") - self.assertEqual(pe.get_expression(), "") + assert pe.get_expression() == "" def test_create_from_str_is_first_type(self): """Default (FIRST) join type: and_expr() and or_expr() are both False.""" pe = ecf.PartExpression("a == b") - self.assertFalse(pe.and_expr()) - self.assertFalse(pe.or_expr()) + assert not pe.and_expr() + assert not pe.or_expr() # ------------------------------------------------------------------ # Constructor: PartExpression(str, bool) @@ -675,19 +679,19 @@ def test_create_from_str_is_first_type(self): def test_create_with_true_is_and_type(self): """PartExpression(str, True) → AND type: and_expr() True, or_expr() False.""" pe = ecf.PartExpression("a == b", True) - self.assertTrue(pe.and_expr()) - self.assertFalse(pe.or_expr()) + assert pe.and_expr() + assert not pe.or_expr() def test_create_with_false_is_or_type(self): """PartExpression(str, False) → OR type: and_expr() False, or_expr() True.""" pe = ecf.PartExpression("a == b", False) - self.assertFalse(pe.and_expr()) - self.assertTrue(pe.or_expr()) + assert not pe.and_expr() + assert pe.or_expr() def test_create_with_bool_stores_expression_verbatim(self): """Expression string is stored verbatim regardless of the bool flag.""" pe = ecf.PartExpression("x == active", True) - self.assertEqual(pe.get_expression(), "x == active") + assert pe.get_expression() == "x == active" # ------------------------------------------------------------------ # get_expression() @@ -696,12 +700,12 @@ def test_create_with_bool_stores_expression_verbatim(self): def test_get_expression_returns_str_type(self): """get_expression() always returns a Python str.""" pe = ecf.PartExpression("a == b") - self.assertIsInstance(pe.get_expression(), str) + assert isinstance(pe.get_expression(), str) def test_get_expression_is_idempotent(self): """Calling get_expression() twice returns the same value.""" pe = ecf.PartExpression("a == b") - self.assertEqual(pe.get_expression(), pe.get_expression()) + assert pe.get_expression() == pe.get_expression() # ------------------------------------------------------------------ # and_expr() / or_expr() @@ -710,27 +714,27 @@ def test_get_expression_is_idempotent(self): def test_and_expr_returns_bool_type(self): """and_expr() returns a Python bool.""" pe = ecf.PartExpression("a == b", True) - self.assertIsInstance(pe.and_expr(), bool) + assert isinstance(pe.and_expr(), bool) def test_or_expr_returns_bool_type(self): """or_expr() returns a Python bool.""" pe = ecf.PartExpression("a == b", False) - self.assertIsInstance(pe.or_expr(), bool) + assert isinstance(pe.or_expr(), bool) def test_and_and_or_are_mutually_exclusive_for_and_type(self): """For AND type exactly one of and_expr/or_expr is True.""" pe = ecf.PartExpression("a == b", True) - self.assertNotEqual(pe.and_expr(), pe.or_expr()) + assert pe.and_expr() != pe.or_expr() def test_and_and_or_are_mutually_exclusive_for_or_type(self): """For OR type exactly one of and_expr/or_expr is True.""" pe = ecf.PartExpression("a == b", False) - self.assertNotEqual(pe.and_expr(), pe.or_expr()) + assert pe.and_expr() != pe.or_expr() def test_first_type_has_neither_and_nor_or(self): """FIRST type has both and_expr() and or_expr() False.""" pe = ecf.PartExpression("a == b") - self.assertFalse(pe.and_expr() or pe.or_expr()) + assert not pe.and_expr() or pe.or_expr() # ------------------------------------------------------------------ # __eq__ — equal cases @@ -740,31 +744,31 @@ def test_eq_same_str_same_default_type(self): """Two PartExpressions with same string and default type are equal.""" pe1 = ecf.PartExpression("a == b") pe2 = ecf.PartExpression("a == b") - self.assertEqual(pe1, pe2) + assert pe1 == pe2 def test_eq_same_str_same_and_type(self): """Two AND-type PartExpressions with same string are equal.""" pe1 = ecf.PartExpression("a == b", True) pe2 = ecf.PartExpression("a == b", True) - self.assertEqual(pe1, pe2) + assert pe1 == pe2 def test_eq_same_str_same_or_type(self): """Two OR-type PartExpressions with same string are equal.""" pe1 = ecf.PartExpression("a == b", False) pe2 = ecf.PartExpression("a == b", False) - self.assertEqual(pe1, pe2) + assert pe1 == pe2 def test_eq_reflexive(self): """A PartExpression is equal to itself.""" pe = ecf.PartExpression("a == b") - self.assertEqual(pe, pe) + assert pe == pe def test_eq_symmetric(self): """Equality is symmetric.""" pe1 = ecf.PartExpression("a == b") pe2 = ecf.PartExpression("a == b") - self.assertTrue(pe1 == pe2) - self.assertTrue(pe2 == pe1) + assert pe1 == pe2 + assert pe2 == pe1 # ------------------------------------------------------------------ # __eq__ / __ne__ — not-equal cases @@ -774,25 +778,25 @@ def test_ne_different_expression_strings(self): """Different expression strings are not equal.""" pe1 = ecf.PartExpression("a == b") pe2 = ecf.PartExpression("c == d") - self.assertNotEqual(pe1, pe2) + assert pe1 != pe2 def test_ne_same_str_different_type_first_vs_and(self): """Same string but different join types (FIRST vs AND) are not equal.""" pe1 = ecf.PartExpression("a == b") pe2 = ecf.PartExpression("a == b", True) - self.assertNotEqual(pe1, pe2) + assert pe1 != pe2 def test_ne_same_str_different_type_first_vs_or(self): """Same string but different join types (FIRST vs OR) are not equal.""" pe1 = ecf.PartExpression("a == b") pe2 = ecf.PartExpression("a == b", False) - self.assertNotEqual(pe1, pe2) + assert pe1 != pe2 def test_ne_same_str_different_type_and_vs_or(self): """Same string, AND vs OR join type: not equal.""" pe1 = ecf.PartExpression("a == b", True) pe2 = ecf.PartExpression("a == b", False) - self.assertNotEqual(pe1, pe2) + assert pe1 != pe2 # ------------------------------------------------------------------ # __hash__ — boost.python extension types are hashable by object identity @@ -802,27 +806,27 @@ def test_is_hashable_by_identity(self): """boost.python extension types keep identity-based tp_hash even when __eq__ is defined. hash() returns a non-zero integer derived from the object id.""" pe = ecf.PartExpression("a == b") - self.assertIsInstance(hash(pe), int) + assert isinstance(hash(pe), int) def test_can_be_inserted_in_set(self): """Because PartExpression is hashable, instances can be stored in a Python set.""" pe = ecf.PartExpression("a == b") s = {pe} - self.assertIn(pe, s) + assert pe in s def test_can_be_used_as_dict_key(self): """Because PartExpression is hashable, instances can be used as dictionary keys.""" pe = ecf.PartExpression("a == b") d = {pe: "value"} - self.assertEqual(d[pe], "value") + assert d[pe] == "value" def test_hash_is_identity_based(self): """Two equal-by-value PartExpressions have different hashes (identity, not value).""" pe1 = ecf.PartExpression("a == b") pe2 = ecf.PartExpression("a == b") - self.assertEqual(pe1, pe2) - self.assertIsNot(pe1, pe2) - self.assertNotEqual(hash(pe1), hash(pe2)) + assert pe1 == pe2 + assert pe1 is not pe2 + assert hash(pe1) != hash(pe2) # ------------------------------------------------------------------ # Negative: invalid constructor arguments @@ -830,36 +834,36 @@ def test_hash_is_identity_based(self): def test_no_default_constructor(self): """PartExpression() with no arguments is not exposed; raises TypeError.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): ecf.PartExpression() def test_create_from_int_raises(self): """No constructor accepts a plain integer as the first argument.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.PartExpression(42) def test_create_from_none_raises(self): """No constructor accepts None as the first argument.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.PartExpression(None) def test_create_from_list_raises(self): """No constructor accepts a list as the first argument.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.PartExpression(["a == b"]) def test_create_with_string_bool_flag(self): """A string as the second argument (bool position) should raise TypeError.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.PartExpression("a == b", "yes") def test_create_with_too_many_args_raises(self): """Passing three arguments raises TypeError; no 3-argument constructor exists.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.PartExpression("a == b", True, "extra") -class TestExpression(unittest.TestCase): +class TestExpression: """Tests for py::class_ as exposed in ExportNodeAttr.cpp. Exposed API @@ -890,17 +894,17 @@ class TestExpression(unittest.TestCase): def test_create_from_str_simple(self): """Plain string is stored verbatim as a single FIRST-type part.""" e = ecf.Expression("a == b") - self.assertEqual(e.get_expression(), "a == b") + assert e.get_expression() == "a == b" def test_create_from_str_compound(self): """Compound string is stored verbatim (no splitting).""" e = ecf.Expression("a == b OR c == d") - self.assertEqual(e.get_expression(), "a == b OR c == d") + assert e.get_expression() == "a == b OR c == d" def test_create_from_str_empty(self): """An empty string is accepted and produces an empty expression.""" e = ecf.Expression("") - self.assertEqual(e.get_expression(), "") + assert e.get_expression() == "" # ------------------------------------------------------------------ # Constructor: Expression(PartExpression) @@ -909,16 +913,16 @@ def test_create_from_str_empty(self): def test_create_from_part_expression_first_type(self): """A FIRST-type PartExpression is valid as the sole (first) part.""" e = ecf.Expression(ecf.PartExpression("a == b")) - self.assertEqual(e.get_expression(), "a == b") + assert e.get_expression() == "a == b" def test_create_from_part_expression_and_type_raises(self): """An AND-type PartExpression as first part violates the ordering rule; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Expression(ecf.PartExpression("a == b", True)) def test_create_from_part_expression_or_type_raises(self): """An OR-type PartExpression as first part violates the ordering rule; raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Expression(ecf.PartExpression("a == b", False)) # ------------------------------------------------------------------ @@ -928,12 +932,12 @@ def test_create_from_part_expression_or_type_raises(self): def test_get_expression_returns_str_type(self): """get_expression() always returns a Python str.""" e = ecf.Expression("x == active") - self.assertIsInstance(e.get_expression(), str) + assert isinstance(e.get_expression(), str) def test_get_expression_is_idempotent(self): """Calling get_expression() twice returns the same value.""" e = ecf.Expression("a == b") - self.assertEqual(e.get_expression(), e.get_expression()) + assert e.get_expression() == e.get_expression() # ------------------------------------------------------------------ # __str__ @@ -942,13 +946,13 @@ def test_get_expression_is_idempotent(self): def test_str_equals_get_expression(self): """__str__ is bound to the same compose_expression function as get_expression().""" e = ecf.Expression("a == b") - self.assertEqual(str(e), e.get_expression()) + assert str(e) == e.get_expression() def test_str_after_add_reflects_combined_expression(self): """After add(), __str__ returns the full AND-joined composed string.""" e = ecf.Expression("a == b") e.add(ecf.PartExpression("c == d", True)) - self.assertEqual(str(e), "a == b AND c == d") + assert str(e) == "a == b AND c == d" # ------------------------------------------------------------------ # add() — valid usage @@ -958,26 +962,26 @@ def test_add_and_type_part_expression(self): """add() with AND-type PartExpression appends correctly.""" e = ecf.Expression("a == b") e.add(ecf.PartExpression("c == d", True)) - self.assertEqual(e.get_expression(), "a == b AND c == d") + assert e.get_expression() == "a == b AND c == d" def test_add_or_type_part_expression(self): """add() with OR-type PartExpression appends correctly.""" e = ecf.Expression("a == b") e.add(ecf.PartExpression("c == d", False)) - self.assertEqual(e.get_expression(), "a == b OR c == d") + assert e.get_expression() == "a == b OR c == d" def test_add_multiple_parts_chain(self): """Multiple add() calls chain parts in insertion order.""" e = ecf.Expression("a == b") e.add(ecf.PartExpression("c == d", True)) e.add(ecf.PartExpression("e == f", False)) - self.assertEqual(e.get_expression(), "a == b AND c == d OR e == f") + assert e.get_expression() == "a == b AND c == d OR e == f" def test_add_returns_none(self): """add() is a void function; returns None in Python.""" e = ecf.Expression("a == b") result = e.add(ecf.PartExpression("c == d", True)) - self.assertIsNone(result) + assert result is None # ------------------------------------------------------------------ # add() — invalid usage (ordering rule violations) @@ -986,7 +990,7 @@ def test_add_returns_none(self): def test_add_first_type_to_non_empty_expression_raises(self): """Adding a FIRST-type PartExpression to a non-empty Expression violates the rule.""" e = ecf.Expression("a == b") - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): e.add(ecf.PartExpression("c == d")) # FIRST type → invalid as subsequent # ------------------------------------------------------------------ @@ -996,19 +1000,19 @@ def test_add_first_type_to_non_empty_expression_raises(self): def test_add_string_raises(self): """add() expects a PartExpression, not a plain string; raises TypeError.""" e = ecf.Expression("a == b") - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): e.add("c == d") def test_add_none_raises(self): """add() does not accept None; raises TypeError.""" e = ecf.Expression("a == b") - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): e.add(None) def test_add_int_raises(self): """add() does not accept an integer; raises TypeError.""" e = ecf.Expression("a == b") - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): e.add(42) # ------------------------------------------------------------------ @@ -1019,17 +1023,17 @@ def test_parts_single_expression(self): """parts yields one PartExpression equal to the one used at construction.""" e = ecf.Expression("a == b") parts = list(e.parts) - self.assertEqual(len(parts), 1) - self.assertEqual(parts[0], ecf.PartExpression("a == b")) + assert len(parts) == 1 + assert parts[0] == ecf.PartExpression("a == b") def test_parts_after_add_yields_both_entries(self): """parts yields all appended PartExpressions in insertion order.""" e = ecf.Expression("a == b") e.add(ecf.PartExpression("c == d", True)) parts = list(e.parts) - self.assertEqual(len(parts), 2) - self.assertEqual(parts[0], ecf.PartExpression("a == b")) - self.assertEqual(parts[1], ecf.PartExpression("c == d", True)) + assert len(parts) == 2 + assert parts[0] == ecf.PartExpression("a == b") + assert parts[1] == ecf.PartExpression("c == d", True) def test_parts_after_three_adds(self): """parts yields three PartExpressions in the correct order.""" @@ -1037,20 +1041,20 @@ def test_parts_after_three_adds(self): e.add(ecf.PartExpression("c == d", True)) e.add(ecf.PartExpression("e == f", False)) parts = list(e.parts) - self.assertEqual(len(parts), 3) - self.assertEqual(parts[2], ecf.PartExpression("e == f", False)) + assert len(parts) == 3 + assert parts[2] == ecf.PartExpression("e == f", False) def test_parts_is_iterable_multiple_times(self): """The parts property can be iterated more than once without exhaustion.""" e = ecf.Expression("a == b") e.add(ecf.PartExpression("c == d", True)) - self.assertEqual(len(list(e.parts)), len(list(e.parts))) + assert len(list(e.parts)) == len(list(e.parts)) def test_parts_elements_are_part_expression_instances(self): """Each element yielded by parts is a PartExpression.""" e = ecf.Expression("a == b") for part in e.parts: - self.assertIsInstance(part, ecf.PartExpression) + assert isinstance(part, ecf.PartExpression) # ------------------------------------------------------------------ # __eq__ — equal cases @@ -1060,25 +1064,25 @@ def test_eq_identical_str_constructor(self): """Two Expressions created with the same string are equal.""" e1 = ecf.Expression("a == b") e2 = ecf.Expression("a == b") - self.assertEqual(e1, e2) + assert e1 == e2 def test_eq_str_vs_part_expression_first_type(self): """Expression(str) == Expression(PartExpression(str)) when same FIRST-type part.""" e1 = ecf.Expression("a == b") e2 = ecf.Expression(ecf.PartExpression("a == b")) - self.assertEqual(e1, e2) + assert e1 == e2 def test_eq_reflexive(self): """An Expression is equal to itself.""" e = ecf.Expression("a == b") - self.assertEqual(e, e) + assert e == e def test_eq_symmetric(self): """Equality is symmetric.""" e1 = ecf.Expression("a == b") e2 = ecf.Expression("a == b") - self.assertTrue(e1 == e2) - self.assertTrue(e2 == e1) + assert e1 == e2 + assert e2 == e1 def test_eq_after_identical_adds(self): """Expressions built identically via add() are equal.""" @@ -1086,7 +1090,7 @@ def test_eq_after_identical_adds(self): e1.add(ecf.PartExpression("c == d", True)) e2 = ecf.Expression("a == b") e2.add(ecf.PartExpression("c == d", True)) - self.assertEqual(e1, e2) + assert e1 == e2 # ------------------------------------------------------------------ # __eq__ / __ne__ — not-equal cases @@ -1096,14 +1100,14 @@ def test_ne_different_expression_strings(self): """Expressions with different strings are not equal.""" e1 = ecf.Expression("a == b") e2 = ecf.Expression("c == d") - self.assertNotEqual(e1, e2) + assert e1 != e2 def test_ne_single_vs_multi_part(self): """A one-part Expression is not equal to a two-part Expression.""" e1 = ecf.Expression("a == b") e2 = ecf.Expression("a == b") e2.add(ecf.PartExpression("c == d", True)) - self.assertNotEqual(e1, e2) + assert e1 != e2 def test_ne_different_join_types_in_second_part(self): """Expressions that differ only in the join type of a part are not equal.""" @@ -1111,7 +1115,7 @@ def test_ne_different_join_types_in_second_part(self): e1.add(ecf.PartExpression("c == d", True)) # AND e2 = ecf.Expression("a == b") e2.add(ecf.PartExpression("c == d", False)) # OR - self.assertNotEqual(e1, e2) + assert e1 != e2 # ------------------------------------------------------------------ # __hash__ — boost.python extension types are hashable by object identity @@ -1121,27 +1125,27 @@ def test_is_hashable_by_identity(self): """boost.python extension types keep identity-based tp_hash even when __eq__ is defined. hash() returns a non-zero integer derived from the object id.""" e = ecf.Expression("a == b") - self.assertIsInstance(hash(e), int) + assert isinstance(hash(e), int) def test_can_be_inserted_in_set(self): """Because Expression is hashable, instances can be stored in a Python set.""" e = ecf.Expression("a == b") s = {e} - self.assertIn(e, s) + assert e in s def test_can_be_used_as_dict_key(self): """Because Expression is hashable, instances can be used as dictionary keys.""" e = ecf.Expression("a == b") d = {e: "value"} - self.assertEqual(d[e], "value") + assert d[e] == "value" def test_hash_is_identity_based(self): """Two equal-by-value Expressions have different hashes (identity, not value).""" e1 = ecf.Expression("a == b") e2 = ecf.Expression("a == b") - self.assertEqual(e1, e2) - self.assertIsNot(e1, e2) - self.assertNotEqual(hash(e1), hash(e2)) + assert e1 == e2 + assert e1 is not e2 + assert hash(e1) != hash(e2) # ------------------------------------------------------------------ # Negative: invalid constructor arguments @@ -1149,26 +1153,26 @@ def test_hash_is_identity_based(self): def test_no_default_constructor(self): """Expression() with no arguments is not exposed; raises TypeError.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): ecf.Expression() def test_create_from_int_raises(self): """No constructor accepts a plain integer.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Expression(42) def test_create_from_none_raises(self): """No constructor accepts None.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Expression(None) def test_create_from_list_raises(self): """No constructor accepts a list.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Expression(["a == b"]) -class TestFlagType(unittest.TestCase): +class TestFlagType: """Tests for py::enum_ exposed as ecf.FlagType in ExportNodeAttr.cpp. Exposed API @@ -1276,28 +1280,23 @@ class TestFlagType(unittest.TestCase): def test_total_member_count_is_26(self): """Exactly 26 enum values are declared in the binding.""" - self.assertEqual(len(ecf.FlagType.names), 26) - self.assertEqual(len(ecf.FlagType.values), 26) + assert len(ecf.FlagType.names) == 26 + assert len(ecf.FlagType.values) == 26 def test_all_names_accessible_as_class_attributes(self): """Every declared name is accessible as an attribute of the FlagType class.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertTrue( - hasattr(ecf.FlagType, name), f"ecf.FlagType.{name} not found" - ) + assert hasattr(ecf.FlagType, name), f"ecf.FlagType.{name} not found" def test_all_names_present_in_names_dict(self): """FlagType.names contains every declared name as a key.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(name, ecf.FlagType.names) + assert name in ecf.FlagType.names def test_all_int_values_present_in_values_dict(self): """FlagType.values contains every declared integer value as a key.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertIn(expected_int, ecf.FlagType.values) + assert expected_int in ecf.FlagType.values # ------------------------------------------------------------------ # Type hierarchy @@ -1306,15 +1305,13 @@ def test_all_int_values_present_in_values_dict(self): def test_instances_are_flagtype(self): """Every enum member is an instance of ecf.FlagType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIsInstance(getattr(ecf.FlagType, name), ecf.FlagType) + assert isinstance(getattr(ecf.FlagType, name), ecf.FlagType) def test_int_conversion_works(self): """int(member) produces the C++ integer value (pybind11 enums are not int subclasses unlike Boost.Python enums, but they are still convertible to int).""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.FlagType, name)), expected) + assert int(getattr(ecf.FlagType, name)) == expected # ------------------------------------------------------------------ # str / repr / .name @@ -1323,24 +1320,21 @@ def test_int_conversion_works(self): def test_str_returns_just_the_name(self): """str(v) returns only the attribute name, not the module-qualified form.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertEqual(str(v), name) + v = getattr(ecf.FlagType, name) + assert str(v) == name def test_repr_returns_module_qualified_name(self): """repr(v) returns 'ecflow.FlagType.'.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertEqual(repr(v), f"ecflow.FlagType.{name}") + v = getattr(ecf.FlagType, name) + assert repr(v) == f"ecflow.FlagType.{name}" def test_name_attribute_equals_str(self): """v.name is the same string as str(v) for every member.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertEqual(v.name, str(v)) - self.assertEqual(v.name, name) + v = getattr(ecf.FlagType, name) + assert v.name == str(v) + assert v.name == name # ------------------------------------------------------------------ # Integer values @@ -1349,20 +1343,19 @@ def test_name_attribute_equals_str(self): def test_int_value_matches_cpp_enum(self): """int(v) matches the C++ integer value for each declared member.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertEqual(int(v), expected) + v = getattr(ecf.FlagType, name) + assert int(v) == expected def test_force_abort_is_zero(self): """force_abort is the first (lowest) enum value and equals 0.""" - self.assertEqual(int(ecf.FlagType.force_abort), 0) + assert int(ecf.FlagType.force_abort) == 0 def test_integer_values_are_non_contiguous(self): """The integer values are not a simple 0..N contiguous range (e.g. killcmd_failed=22 and status=24 are out of declaration order).""" - self.assertEqual(int(ecf.FlagType.killcmd_failed), 22) - self.assertEqual(int(ecf.FlagType.status), 24) - self.assertEqual(int(ecf.FlagType.remote_error), 25) + assert int(ecf.FlagType.killcmd_failed) == 22 + assert int(ecf.FlagType.status) == 24 + assert int(ecf.FlagType.remote_error) == 25 # ------------------------------------------------------------------ # .values and .names lookup dicts @@ -1371,30 +1364,26 @@ def test_integer_values_are_non_contiguous(self): def test_values_dict_maps_int_to_flagtype_instance(self): """FlagType.values maps each int key to a FlagType instance.""" for int_val, member in ecf.FlagType.values.items(): - with self.subTest(int_val=int_val): - self.assertIsInstance(int_val, int) - self.assertIsInstance(member, ecf.FlagType) + assert isinstance(int_val, int) + assert isinstance(member, ecf.FlagType) def test_names_dict_maps_str_to_flagtype_instance(self): """FlagType.names maps each string key to a FlagType instance.""" for name, member in ecf.FlagType.names.items(): - with self.subTest(name=name): - self.assertIsInstance(name, str) - self.assertIsInstance(member, ecf.FlagType) + assert isinstance(name, str) + assert isinstance(member, ecf.FlagType) def test_values_dict_roundtrip(self): """FlagType.values[int(v)] is the same singleton as the class attribute.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertIs(ecf.FlagType.values[int(v)], v) + v = getattr(ecf.FlagType, name) + assert ecf.FlagType.values[int(v)] is v def test_names_dict_roundtrip(self): """FlagType.names[str(v)] is the same singleton as the class attribute.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertIs(ecf.FlagType.names[str(v)], v) + v = getattr(ecf.FlagType, name) + assert ecf.FlagType.names[str(v)] is v # ------------------------------------------------------------------ # Iteration — the class itself is NOT iterable @@ -1402,21 +1391,19 @@ def test_names_dict_roundtrip(self): def test_flag_type_class_is_not_iterable(self): """list(FlagType) raises TypeError; the class object has no __iter__.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): list(ecf.FlagType) def test_all_members_reachable_via_values_dict(self): """Iterating FlagType.values.values() yields every member once.""" members = set(ecf.FlagType.values.values()) for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(getattr(ecf.FlagType, name), members) + assert getattr(ecf.FlagType, name) in members def test_all_members_reachable_via_names_dict(self): """Iterating FlagType.names.values() yields every member once.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(name, ecf.FlagType.names) + assert name in ecf.FlagType.names # ------------------------------------------------------------------ # Equality and ordering (inherited from int) @@ -1425,38 +1412,36 @@ def test_all_members_reachable_via_names_dict(self): def test_same_member_is_equal_to_itself(self): """v == v for every member (reflexive).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertEqual(v, v) + v = getattr(ecf.FlagType, name) + assert v == v def test_members_with_different_int_values_are_not_equal(self): """force_abort != user_edit because their integer values differ.""" - self.assertNotEqual(ecf.FlagType.force_abort, ecf.FlagType.user_edit) + assert ecf.FlagType.force_abort != ecf.FlagType.user_edit def test_equality_with_plain_int(self): """A FlagType value equals a plain int when the integer values match.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertEqual(v, expected) + v = getattr(ecf.FlagType, name) + assert v == expected def test_inequality_with_wrong_plain_int(self): """A FlagType value does not equal a plain int with a different value.""" - self.assertNotEqual(ecf.FlagType.force_abort, 99) + assert ecf.FlagType.force_abort != 99 def test_ordering_less_than(self): """force_abort (0) < user_edit (1) — ordering is by integer value.""" - self.assertLess(ecf.FlagType.force_abort, ecf.FlagType.user_edit) + assert ecf.FlagType.force_abort < ecf.FlagType.user_edit def test_ordering_greater_than(self): """remote_error (25) > not_set (19).""" - self.assertGreater(ecf.FlagType.remote_error, ecf.FlagType.not_set) + assert ecf.FlagType.remote_error > ecf.FlagType.not_set def test_ordering_full_sort(self): """Sorting all members by value produces a sequence of non-decreasing integers.""" sorted_vals = sorted(ecf.FlagType.values.values()) ints = [int(v) for v in sorted_vals] - self.assertEqual(ints, sorted(ints)) + assert ints == sorted(ints) # ------------------------------------------------------------------ # Hash (inherited from int: hash(v) == int(v)) @@ -1465,25 +1450,24 @@ def test_ordering_full_sort(self): def test_hash_equals_int_value(self): """hash(v) == int(v) for every member (int hash semantics).""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - v = getattr(ecf.FlagType, name) - self.assertEqual(hash(v), expected_int) + v = getattr(ecf.FlagType, name) + assert hash(v) == expected_int def test_can_be_stored_in_set(self): """FlagType members are hashable and can be stored in a Python set.""" s = set(ecf.FlagType.values.values()) - self.assertEqual(len(s), 26) + assert len(s) == 26 def test_set_deduplicates_equal_values(self): """Adding the same member twice to a set results in one entry.""" s = {ecf.FlagType.force_abort, ecf.FlagType.force_abort} - self.assertEqual(len(s), 1) + assert len(s) == 1 def test_can_be_used_as_dict_key(self): """FlagType members work as dictionary keys.""" d = {ecf.FlagType.force_abort: "fa", ecf.FlagType.user_edit: "ue"} - self.assertEqual(d[ecf.FlagType.force_abort], "fa") - self.assertEqual(d[ecf.FlagType.user_edit], "ue") + assert d[ecf.FlagType.force_abort] == "fa" + assert d[ecf.FlagType.user_edit] == "ue" # ------------------------------------------------------------------ # Construction from int @@ -1492,15 +1476,15 @@ def test_can_be_used_as_dict_key(self): def test_can_construct_from_int(self): """FlagType(n) constructs a FlagType instance from a raw integer.""" v = ecf.FlagType(0) - self.assertIsInstance(v, ecf.FlagType) - self.assertEqual(int(v), 0) + assert isinstance(v, ecf.FlagType) + assert int(v) == 0 def test_int_constructor_does_not_return_singleton(self): """FlagType(n) and the class-attribute singleton may differ in identity even though they are equal in value (boost.python does not intern them).""" from_ctor = ecf.FlagType(0) singleton = ecf.FlagType.force_abort - self.assertEqual(from_ctor, singleton) # same value + assert from_ctor == singleton # same value # identity may or may not be the same; we only assert value equality # ------------------------------------------------------------------ @@ -1509,21 +1493,21 @@ def test_int_constructor_does_not_return_singleton(self): def test_nonexistent_member_raises_attribute_error(self): """Accessing an undefined name on FlagType raises AttributeError.""" - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): _ = ecf.FlagType.nonexistent_flag def test_missing_int_key_in_values_raises_key_error(self): """Looking up an int that has no enum member raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.FlagType.values[9999] def test_missing_name_key_in_names_raises_key_error(self): """Looking up a name that has no enum member raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.FlagType.names["nonexistent_flag"] -class TestFlag(unittest.TestCase): +class TestFlag: """Tests for py::class_ exposed as ecf.Flag in ExportNodeAttr.cpp. Exposed API @@ -1587,18 +1571,17 @@ class TestFlag(unittest.TestCase): def test_default_constructor_creates_empty_flag(self): """Flag() creates an instance with no flags set.""" f = ecf.Flag() - self.assertIsInstance(f, ecf.Flag) + assert isinstance(f, ecf.Flag) def test_default_str_is_empty(self): """str(Flag()) is an empty string when no flags are set.""" - self.assertEqual(str(ecf.Flag()), "") + assert str(ecf.Flag()) == "" def test_all_flags_clear_after_construction(self): """is_set() returns False for every FlagType immediately after construction.""" f = ecf.Flag() for ft in ecf.Flag.list(): - with self.subTest(flag=str(ft)): - self.assertFalse(f.is_set(ft)) + assert not f.is_set(ft) # ------------------------------------------------------------------ # set() / is_set() @@ -1608,7 +1591,7 @@ def test_set_makes_is_set_true(self): """After set(ft), is_set(ft) returns True.""" f = ecf.Flag() f.set(ecf.FlagType.late) - self.assertTrue(f.is_set(ecf.FlagType.late)) + assert f.is_set(ecf.FlagType.late) def test_set_does_not_affect_other_flags(self): """Setting one flag leaves all others unset.""" @@ -1616,25 +1599,23 @@ def test_set_does_not_affect_other_flags(self): f.set(ecf.FlagType.late) for ft in ecf.Flag.list(): if ft != ecf.FlagType.late: - with self.subTest(flag=str(ft)): - self.assertFalse(f.is_set(ft)) + assert not f.is_set(ft) def test_set_multiple_flags_independently(self): """Multiple flags can be set independently; each is_set call reflects only that flag.""" f = ecf.Flag() f.set(ecf.FlagType.late) f.set(ecf.FlagType.zombie) - self.assertTrue(f.is_set(ecf.FlagType.late)) - self.assertTrue(f.is_set(ecf.FlagType.zombie)) - self.assertFalse(f.is_set(ecf.FlagType.killed)) + assert f.is_set(ecf.FlagType.late) + assert f.is_set(ecf.FlagType.zombie) + assert not f.is_set(ecf.FlagType.killed) def test_set_every_individual_flag(self): """set() then is_set() returns True for each of the 25 settable flags.""" for ft in ecf.Flag.list(): - with self.subTest(flag=str(ft)): - f = ecf.Flag() - f.set(ft) - self.assertTrue(f.is_set(ft)) + f = ecf.Flag() + f.set(ft) + assert f.is_set(ft) # ------------------------------------------------------------------ # clear() @@ -1644,9 +1625,9 @@ def test_clear_removes_set_flag(self): """clear(ft) makes is_set(ft) return False after it was True.""" f = ecf.Flag() f.set(ecf.FlagType.late) - self.assertTrue(f.is_set(ecf.FlagType.late)) + assert f.is_set(ecf.FlagType.late) f.clear(ecf.FlagType.late) - self.assertFalse(f.is_set(ecf.FlagType.late)) + assert not f.is_set(ecf.FlagType.late) def test_clear_does_not_affect_other_set_flags(self): """Clearing one flag leaves other previously set flags intact.""" @@ -1654,14 +1635,14 @@ def test_clear_does_not_affect_other_set_flags(self): f.set(ecf.FlagType.late) f.set(ecf.FlagType.zombie) f.clear(ecf.FlagType.late) - self.assertFalse(f.is_set(ecf.FlagType.late)) - self.assertTrue(f.is_set(ecf.FlagType.zombie)) + assert not f.is_set(ecf.FlagType.late) + assert f.is_set(ecf.FlagType.zombie) def test_clear_on_already_clear_flag_is_idempotent(self): """Clearing a flag that is already clear does not raise and leaves state unchanged.""" f = ecf.Flag() f.clear(ecf.FlagType.late) # already clear — must not raise - self.assertFalse(f.is_set(ecf.FlagType.late)) + assert not f.is_set(ecf.FlagType.late) # ------------------------------------------------------------------ # reset() @@ -1675,21 +1656,20 @@ def test_reset_clears_all_flags(self): f.set(ecf.FlagType.message) f.reset() for ft in ecf.Flag.list(): - with self.subTest(flag=str(ft)): - self.assertFalse(f.is_set(ft)) + assert not f.is_set(ft) def test_reset_makes_str_empty(self): """After reset(), str(f) returns the empty string.""" f = ecf.Flag() f.set(ecf.FlagType.late) f.reset() - self.assertEqual(str(f), "") + assert str(f) == "" def test_reset_on_empty_flag_is_idempotent(self): """reset() on an already-empty Flag does not raise.""" f = ecf.Flag() f.reset() # no-op, must not raise - self.assertEqual(str(f), "") + assert str(f) == "" # ------------------------------------------------------------------ # __str__ @@ -1698,10 +1678,9 @@ def test_reset_on_empty_flag_is_idempotent(self): def test_str_single_flag_matches_display_name(self): """str(f) with one set flag returns the C++ display name for that flag.""" for attr_name, display_name in self.TYPE_TO_STRING.items(): - with self.subTest(flag=attr_name): - f = ecf.Flag() - f.set(getattr(ecf.FlagType, attr_name)) - self.assertEqual(str(f), display_name) + f = ecf.Flag() + f.set(getattr(ecf.FlagType, attr_name)) + assert str(f) == display_name def test_str_two_flags_are_comma_separated(self): """str(f) with two set flags returns both display names separated by a comma.""" @@ -1709,13 +1688,13 @@ def test_str_two_flags_are_comma_separated(self): f.set(ecf.FlagType.late) f.set(ecf.FlagType.zombie) s = str(f) - self.assertIn("late", s) - self.assertIn("zombie", s) - self.assertIn(",", s) + assert "late" in s + assert "zombie" in s + assert "," in s def test_str_returns_str_type(self): """str(Flag()) always returns a Python str.""" - self.assertIsInstance(str(ecf.Flag()), str) + assert isinstance(str(ecf.Flag()), str) # ------------------------------------------------------------------ # __eq__ — value equality (compares which flags are set) @@ -1723,41 +1702,41 @@ def test_str_returns_str_type(self): def test_eq_two_empty_flags(self): """Two default-constructed Flags are equal.""" - self.assertEqual(ecf.Flag(), ecf.Flag()) + assert ecf.Flag() == ecf.Flag() def test_eq_same_flags_set(self): """Two Flags with the same flags set are equal.""" f1, f2 = ecf.Flag(), ecf.Flag() f1.set(ecf.FlagType.late) f2.set(ecf.FlagType.late) - self.assertEqual(f1, f2) + assert f1 == f2 def test_eq_reflexive(self): """A Flag is equal to itself.""" f = ecf.Flag() f.set(ecf.FlagType.late) - self.assertEqual(f, f) + assert f == f def test_eq_symmetric(self): """Equality is symmetric.""" f1, f2 = ecf.Flag(), ecf.Flag() f1.set(ecf.FlagType.late) f2.set(ecf.FlagType.late) - self.assertTrue(f1 == f2) - self.assertTrue(f2 == f1) + assert f1 == f2 + assert f2 == f1 def test_ne_different_flags_set(self): """Flags with different sets of flags are not equal.""" f1, f2 = ecf.Flag(), ecf.Flag() f1.set(ecf.FlagType.late) f2.set(ecf.FlagType.zombie) - self.assertNotEqual(f1, f2) + assert f1 != f2 def test_ne_empty_vs_nonempty(self): """An empty Flag is not equal to one with a flag set.""" f1, f2 = ecf.Flag(), ecf.Flag() f2.set(ecf.FlagType.late) - self.assertNotEqual(f1, f2) + assert f1 != f2 # ------------------------------------------------------------------ # __hash__ — identity-based (boost.python extension type) @@ -1766,22 +1745,22 @@ def test_ne_empty_vs_nonempty(self): def test_is_hashable(self): """Flag is hashable (boost.python identity-based hash).""" f = ecf.Flag() - self.assertIsInstance(hash(f), int) + assert isinstance(hash(f), int) def test_hash_is_identity_based_not_value_based(self): """Two value-equal Flags may have different hashes (identity, not content).""" f1, f2 = ecf.Flag(), ecf.Flag() f1.set(ecf.FlagType.late) f2.set(ecf.FlagType.late) - self.assertEqual(f1, f2) # same value - self.assertIsNot(f1, f2) # different objects - self.assertNotEqual(hash(f1), hash(f2)) # different hashes + assert f1 == f2 # same value + assert f1 is not f2 # different objects + assert hash(f1) != hash(f2) # different hashes def test_can_be_used_as_dict_key(self): """Flag instances can be used as dictionary keys.""" f = ecf.Flag() d = {f: "value"} - self.assertEqual(d[f], "value") + assert d[f] == "value" # ------------------------------------------------------------------ # Flag.list() static method → FlagTypeVec @@ -1789,34 +1768,33 @@ def test_can_be_used_as_dict_key(self): def test_list_returns_flag_type_vec(self): """Flag.list() returns a FlagTypeVec instance.""" - self.assertIsInstance(ecf.Flag.list(), ecf.FlagTypeVec) + assert isinstance(ecf.Flag.list(), ecf.FlagTypeVec) def test_list_has_25_elements(self): """Flag.list() contains exactly 25 elements (not_set is excluded).""" - self.assertEqual(len(ecf.Flag.list()), 25) + assert len(ecf.Flag.list()) == 25 def test_list_excludes_not_set(self): """FlagType.not_set is intentionally absent from Flag.list().""" - self.assertNotIn(ecf.FlagType.not_set, ecf.Flag.list()) + assert ecf.FlagType.not_set not in ecf.Flag.list() def test_list_contains_all_settable_types(self): """Flag.list() contains every FlagType except not_set.""" fl = ecf.Flag.list() for name in self.TYPE_TO_STRING: - with self.subTest(flag=name): - self.assertIn(getattr(ecf.FlagType, name), fl) + assert getattr(ecf.FlagType, name) in fl def test_list_elements_are_flag_type_instances(self): """Every element of Flag.list() is a FlagType instance.""" for ft in ecf.Flag.list(): - self.assertIsInstance(ft, ecf.FlagType) + assert isinstance(ft, ecf.FlagType) def test_list_each_call_produces_independent_copy(self): """Flag.list() returns a new FlagTypeVec on each call (not a shared reference).""" fl1 = ecf.Flag.list() fl2 = ecf.Flag.list() - self.assertIsNot(fl1, fl2) - self.assertEqual(len(fl1), len(fl2)) + assert fl1 is not fl2 + assert len(fl1) == len(fl2) # ------------------------------------------------------------------ # Flag.type_to_string() static method @@ -1825,14 +1803,13 @@ def test_list_each_call_produces_independent_copy(self): def test_type_to_string_returns_str(self): """type_to_string() always returns a Python str.""" result = ecf.Flag.type_to_string(ecf.FlagType.late) - self.assertIsInstance(result, str) + assert isinstance(result, str) def test_type_to_string_known_display_names(self): """type_to_string() returns the C++ display name, which may differ from the Python attr name.""" for attr_name, display_name in self.TYPE_TO_STRING.items(): - with self.subTest(flag=attr_name): - ft = getattr(ecf.FlagType, attr_name) - self.assertEqual(ecf.Flag.type_to_string(ft), display_name) + ft = getattr(ecf.FlagType, attr_name) + assert ecf.Flag.type_to_string(ft) == display_name def test_type_to_string_differs_from_flag_type_str_for_renamed_flags(self): """For the 5 renamed flags, type_to_string() differs from str(FlagType).""" @@ -1844,10 +1821,9 @@ def test_type_to_string_differs_from_flag_type_str_for_renamed_flags(self): "wait": "task_waiting", } for attr_name, display_name in renamed.items(): - with self.subTest(flag=attr_name): - ft = getattr(ecf.FlagType, attr_name) - self.assertNotEqual(str(ft), display_name) - self.assertEqual(ecf.Flag.type_to_string(ft), display_name) + ft = getattr(ecf.FlagType, attr_name) + assert str(ft) != display_name + assert ecf.Flag.type_to_string(ft) == display_name def test_type_to_string_matches_flag_str_for_unrenamed_flags(self): """For the other 20 flags, type_to_string() matches str(FlagType).""" @@ -1855,15 +1831,14 @@ def test_type_to_string_matches_flag_str_for_unrenamed_flags(self): for attr_name, display_name in self.TYPE_TO_STRING.items(): if attr_name in renamed_attrs: continue - with self.subTest(flag=attr_name): - ft = getattr(ecf.FlagType, attr_name) - self.assertEqual(ecf.Flag.type_to_string(ft), str(ft)) + ft = getattr(ecf.FlagType, attr_name) + assert ecf.Flag.type_to_string(ft) == str(ft) def test_type_to_string_for_not_set(self): """type_to_string() also works for not_set even though it is excluded from list().""" result = ecf.Flag.type_to_string(ecf.FlagType.not_set) - self.assertIsInstance(result, str) - self.assertEqual(result, "not_set") + assert isinstance(result, str) + assert result == "not_set" # ------------------------------------------------------------------ # Negative: wrong argument types @@ -1872,35 +1847,35 @@ def test_type_to_string_for_not_set(self): def test_is_set_with_string_raises(self): """is_set() accepts only FlagType, not a plain string.""" f = ecf.Flag() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): f.is_set("late") def test_is_set_with_plain_int_raises(self): """is_set() accepts only FlagType, not a raw integer.""" f = ecf.Flag() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): f.is_set(7) # 7 is the int value of 'late' but not a FlagType def test_set_with_string_raises(self): """set() accepts only FlagType, not a plain string.""" f = ecf.Flag() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): f.set("late") def test_set_with_plain_int_raises(self): """set() accepts only FlagType, not a raw integer.""" f = ecf.Flag() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): f.set(999) def test_clear_with_string_raises(self): """clear() accepts only FlagType, not a plain string.""" f = ecf.Flag() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): f.clear("late") -class TestFlagTypeVec(unittest.TestCase): +class TestFlagTypeVec: """Tests for py::class_> exposed as ecf.FlagTypeVec. FlagTypeVec is the return type of Flag.list(). It is exposed via @@ -1920,7 +1895,7 @@ class TestFlagTypeVec(unittest.TestCase): v in fv -- __contains__ -- membership test using FlagType equality """ - def setUp(self): + def setup_method(self): self.fl = ecf.Flag.list() # canonical 25-element FlagTypeVec # ------------------------------------------------------------------ @@ -1929,7 +1904,7 @@ def setUp(self): def test_len_is_25(self): """len(FlagTypeVec) from Flag.list() is 25.""" - self.assertEqual(len(self.fl), 25) + assert len(self.fl) == 25 # ------------------------------------------------------------------ # Indexing @@ -1937,27 +1912,27 @@ def test_len_is_25(self): def test_positive_index_returns_flag_type(self): """Positive indexing returns a FlagType instance.""" - self.assertIsInstance(self.fl[0], ecf.FlagType) + assert isinstance(self.fl[0], ecf.FlagType) def test_first_element_is_force_abort(self): """The first element corresponds to the first C++ enum value (force_abort).""" - self.assertEqual(self.fl[0], ecf.FlagType.force_abort) + assert self.fl[0] == ecf.FlagType.force_abort def test_last_element_via_positive_index(self): """The element at index len-1 is remote_error (last declared type).""" - self.assertEqual(self.fl[24], ecf.FlagType.remote_error) + assert self.fl[24] == ecf.FlagType.remote_error def test_negative_index_minus_one_is_last(self): """fl[-1] resolves to the last element (remote_error).""" - self.assertEqual(self.fl[-1], ecf.FlagType.remote_error) + assert self.fl[-1] == ecf.FlagType.remote_error def test_negative_index_minus_two_is_second_to_last(self): """fl[-2] resolves to the penultimate element (checkpt_error).""" - self.assertEqual(self.fl[-2], ecf.FlagType.checkpt_error) + assert self.fl[-2] == ecf.FlagType.checkpt_error def test_out_of_range_index_raises(self): """Indexing beyond the length raises IndexError.""" - with self.assertRaises((IndexError, RuntimeError)): + with pytest.raises((IndexError, RuntimeError)): _ = self.fl[25] # ------------------------------------------------------------------ @@ -1967,19 +1942,19 @@ def test_out_of_range_index_raises(self): def test_iterable_yields_25_elements(self): """Iterating the FlagTypeVec yields exactly 25 FlagType values.""" items = list(self.fl) - self.assertEqual(len(items), 25) + assert len(items) == 25 def test_iterated_elements_are_flag_type(self): """Every element yielded by iteration is a FlagType instance.""" for ft in self.fl: - self.assertIsInstance(ft, ecf.FlagType) + assert isinstance(ft, ecf.FlagType) def test_iteration_covers_all_settable_types(self): """Iterating the FlagTypeVec covers all 25 settable FlagType values.""" iterated = list(self.fl) expected_ints = set(range(26)) - {19} # all except not_set (19) actual_ints = {int(ft) for ft in iterated} - self.assertEqual(actual_ints, expected_ints) + assert actual_ints == expected_ints # ------------------------------------------------------------------ # __contains__ @@ -1990,12 +1965,11 @@ def test_contains_settable_flag_type(self): for ft in ecf.FlagType.names.values(): if ft == ecf.FlagType.not_set: continue - with self.subTest(flag=str(ft)): - self.assertIn(ft, self.fl) + assert ft in self.fl def test_not_set_is_not_in_list(self): """FlagType.not_set is not contained in Flag.list().""" - self.assertNotIn(ecf.FlagType.not_set, self.fl) + assert ecf.FlagType.not_set not in self.fl # ------------------------------------------------------------------ # Direct construction @@ -2004,20 +1978,20 @@ def test_not_set_is_not_in_list(self): def test_direct_constructor_creates_empty_vec(self): """FlagTypeVec() constructs an empty vector directly.""" fv = ecf.FlagTypeVec() - self.assertIsInstance(fv, ecf.FlagTypeVec) - self.assertEqual(len(fv), 0) + assert isinstance(fv, ecf.FlagTypeVec) + assert len(fv) == 0 def test_direct_constructor_contains_nothing(self): """A directly constructed FlagTypeVec contains no FlagType values.""" fv = ecf.FlagTypeVec() - self.assertNotIn(ecf.FlagType.force_abort, fv) + assert ecf.FlagType.force_abort not in fv def test_direct_constructor_iterates_empty(self): """Iterating a directly constructed FlagTypeVec yields no items.""" - self.assertEqual(list(ecf.FlagTypeVec()), []) + assert list(ecf.FlagTypeVec()) == [] -class TestJobCreationCtrl(unittest.TestCase): +class TestJobCreationCtrl: """Tests for py::class_ exposed as ecf.JobCreationCtrl. Exposed API @@ -2050,23 +2024,23 @@ class TestJobCreationCtrl(unittest.TestCase): def test_constructor_creates_instance(self): """JobCreationCtrl() constructs an instance of the correct type.""" jc = ecf.JobCreationCtrl() - self.assertIsInstance(jc, ecf.JobCreationCtrl) + assert isinstance(jc, ecf.JobCreationCtrl) def test_initial_dir_is_empty_string(self): """get_dir_for_job_creation() returns '' immediately after construction.""" jc = ecf.JobCreationCtrl() - self.assertEqual(jc.get_dir_for_job_creation(), "") + assert jc.get_dir_for_job_creation() == "" def test_initial_error_msg_is_empty_string(self): """get_error_msg() returns '' immediately after construction.""" jc = ecf.JobCreationCtrl() - self.assertEqual(jc.get_error_msg(), "") + assert jc.get_error_msg() == "" def test_initial_dir_and_error_msg_return_str(self): """Both getters return Python str objects from a fresh instance.""" jc = ecf.JobCreationCtrl() - self.assertIsInstance(jc.get_dir_for_job_creation(), str) - self.assertIsInstance(jc.get_error_msg(), str) + assert isinstance(jc.get_dir_for_job_creation(), str) + assert isinstance(jc.get_error_msg(), str) # ------------------------------------------------------------------ # set_node_path() @@ -2076,7 +2050,7 @@ def test_set_node_path_returns_none(self): """set_node_path() is a void method; returns None.""" jc = ecf.JobCreationCtrl() result = jc.set_node_path("/suite/family/task") - self.assertIsNone(result) + assert result is None def test_set_node_path_accepts_absolute_path(self): """set_node_path() accepts a well-formed absolute path without raising.""" @@ -2096,33 +2070,33 @@ def test_set_dir_returns_none(self): """set_dir_for_job_creation() is a void method; returns None.""" jc = ecf.JobCreationCtrl() result = jc.set_dir_for_job_creation("/tmp/jobs") - self.assertIsNone(result) + assert result is None def test_get_dir_reflects_set_dir(self): """get_dir_for_job_creation() returns exactly what was passed to set_dir_for_job_creation().""" jc = ecf.JobCreationCtrl() jc.set_dir_for_job_creation("/tmp/ecflow_test_jobs") - self.assertEqual(jc.get_dir_for_job_creation(), "/tmp/ecflow_test_jobs") + assert jc.get_dir_for_job_creation() == "/tmp/ecflow_test_jobs" def test_get_dir_returns_str(self): """get_dir_for_job_creation() always returns a Python str.""" jc = ecf.JobCreationCtrl() jc.set_dir_for_job_creation("/tmp/jobs") - self.assertIsInstance(jc.get_dir_for_job_creation(), str) + assert isinstance(jc.get_dir_for_job_creation(), str) def test_set_dir_empty_string_clears_dir(self): """set_dir_for_job_creation('') stores an empty string.""" jc = ecf.JobCreationCtrl() jc.set_dir_for_job_creation("/tmp/jobs") jc.set_dir_for_job_creation("") - self.assertEqual(jc.get_dir_for_job_creation(), "") + assert jc.get_dir_for_job_creation() == "" def test_set_dir_overwrites_previous_value(self): """A second call to set_dir_for_job_creation() replaces the first value.""" jc = ecf.JobCreationCtrl() jc.set_dir_for_job_creation("/tmp/first") jc.set_dir_for_job_creation("/tmp/second") - self.assertEqual(jc.get_dir_for_job_creation(), "/tmp/second") + assert jc.get_dir_for_job_creation() == "/tmp/second" # ------------------------------------------------------------------ # set_verbose() / is_verbose() @@ -2132,55 +2106,55 @@ def test_set_verbose_true_returns_none(self): """set_verbose(True) is a void method; returns None.""" jc = ecf.JobCreationCtrl() result = jc.set_verbose(True) - self.assertIsNone(result) - self.assertEqual(jc.is_verbose(), True) + assert result is None + assert jc.is_verbose() is True def test_set_verbose_false_returns_none(self): """set_verbose(False) is a void method; returns None.""" jc = ecf.JobCreationCtrl() result = jc.set_verbose(False) - self.assertIsNone(result) - self.assertEqual(jc.is_verbose(), False) + assert result is None + assert jc.is_verbose() is False def test_set_verbose_toggle(self): """set_verbose() can be called multiple times without raising.""" jc = ecf.JobCreationCtrl() jc.set_verbose(True) - self.assertEqual(jc.is_verbose(), True) + assert jc.is_verbose() is True jc.set_verbose(False) - self.assertEqual(jc.is_verbose(), False) + assert jc.is_verbose() is False jc.set_verbose(True) # must not raise - self.assertEqual(jc.is_verbose(), True) + assert jc.is_verbose() is True def test_is_verbose_initially_false(self): """is_verbose() returns False immediately after construction.""" jc = ecf.JobCreationCtrl() - self.assertFalse(jc.is_verbose()) + assert not jc.is_verbose() def test_is_verbose_returns_bool(self): """is_verbose() returns a Python bool.""" jc = ecf.JobCreationCtrl() - self.assertIsInstance(jc.is_verbose(), bool) + assert isinstance(jc.is_verbose(), bool) def test_is_verbose_true_after_set_verbose_true(self): """is_verbose() returns True after set_verbose(True).""" jc = ecf.JobCreationCtrl() jc.set_verbose(True) - self.assertTrue(jc.is_verbose()) + assert jc.is_verbose() def test_is_verbose_false_after_set_verbose_false(self): """is_verbose() returns False after set_verbose(False).""" jc = ecf.JobCreationCtrl() jc.set_verbose(True) jc.set_verbose(False) - self.assertFalse(jc.is_verbose()) + assert not jc.is_verbose() def test_is_verbose_reflects_each_toggle(self): """is_verbose() tracks every set_verbose() call correctly.""" jc = ecf.JobCreationCtrl() for expected in [True, False, True, False]: jc.set_verbose(expected) - self.assertEqual(jc.is_verbose(), expected) + assert jc.is_verbose() == expected # ------------------------------------------------------------------ # generate_temp_dir() @@ -2190,21 +2164,21 @@ def test_generate_temp_dir_returns_none(self): """generate_temp_dir() is a void method; returns None.""" jc = ecf.JobCreationCtrl() result = jc.generate_temp_dir() - self.assertIsNone(result) + assert result is None def test_generate_temp_dir_sets_non_empty_dir(self): """After generate_temp_dir(), get_dir_for_job_creation() returns a non-empty path.""" jc = ecf.JobCreationCtrl() jc.generate_temp_dir() - self.assertNotEqual(jc.get_dir_for_job_creation(), "") + assert jc.get_dir_for_job_creation() != "" def test_generate_temp_dir_overrides_explicit_dir(self): """generate_temp_dir() replaces any previously set directory.""" jc = ecf.JobCreationCtrl() jc.set_dir_for_job_creation("/tmp/explicit") jc.generate_temp_dir() - self.assertNotEqual(jc.get_dir_for_job_creation(), "/tmp/explicit") - self.assertNotEqual(jc.get_dir_for_job_creation(), "") + assert jc.get_dir_for_job_creation() != "/tmp/explicit" + assert jc.get_dir_for_job_creation() != "" def test_generate_temp_dir_is_idempotent_path(self): """Calling generate_temp_dir() twice produces the same temp directory path.""" @@ -2213,13 +2187,13 @@ def test_generate_temp_dir_is_idempotent_path(self): d1 = jc.get_dir_for_job_creation() jc.generate_temp_dir() d2 = jc.get_dir_for_job_creation() - self.assertEqual(d1, d2) + assert d1 == d2 def test_generate_temp_dir_path_is_str(self): """The directory set by generate_temp_dir() is a Python str.""" jc = ecf.JobCreationCtrl() jc.generate_temp_dir() - self.assertIsInstance(jc.get_dir_for_job_creation(), str) + assert isinstance(jc.get_dir_for_job_creation(), str) # ------------------------------------------------------------------ # get_error_msg() @@ -2228,12 +2202,12 @@ def test_generate_temp_dir_path_is_str(self): def test_get_error_msg_returns_str(self): """get_error_msg() always returns a Python str.""" jc = ecf.JobCreationCtrl() - self.assertIsInstance(jc.get_error_msg(), str) + assert isinstance(jc.get_error_msg(), str) def test_get_error_msg_is_initially_empty(self): """A freshly constructed JobCreationCtrl has no error message.""" jc = ecf.JobCreationCtrl() - self.assertEqual(jc.get_error_msg(), "") + assert jc.get_error_msg() == "" # ------------------------------------------------------------------ # __eq__ — identity-based (no value equality binding) @@ -2242,7 +2216,7 @@ def test_get_error_msg_is_initially_empty(self): def test_eq_reflexive(self): """An instance is equal to itself (identity).""" jc = ecf.JobCreationCtrl() - self.assertEqual(jc, jc) + assert jc == jc def test_eq_two_instances_with_same_state_are_not_equal(self): """Two distinct instances with the same configuration are NOT equal @@ -2251,13 +2225,13 @@ def test_eq_two_instances_with_same_state_are_not_equal(self): jc2 = ecf.JobCreationCtrl() jc1.set_dir_for_job_creation("/tmp/x") jc2.set_dir_for_job_creation("/tmp/x") - self.assertNotEqual(jc1, jc2) + assert jc1 != jc2 def test_ne_different_instances(self): """Two default-constructed instances are not equal (different objects).""" jc1 = ecf.JobCreationCtrl() jc2 = ecf.JobCreationCtrl() - self.assertNotEqual(jc1, jc2) + assert jc1 != jc2 # ------------------------------------------------------------------ # __hash__ — identity-based @@ -2266,19 +2240,19 @@ def test_ne_different_instances(self): def test_is_hashable(self): """JobCreationCtrl is hashable (boost.python identity-based hash).""" jc = ecf.JobCreationCtrl() - self.assertIsInstance(hash(jc), int) + assert isinstance(hash(jc), int) def test_can_be_used_as_dict_key(self): """A JobCreationCtrl instance can be used as a dictionary key.""" jc = ecf.JobCreationCtrl() d = {jc: "value"} - self.assertEqual(d[jc], "value") + assert d[jc] == "value" def test_can_be_stored_in_set(self): """A JobCreationCtrl instance can be stored in a Python set.""" jc = ecf.JobCreationCtrl() s = {jc} - self.assertIn(jc, s) + assert jc in s # ------------------------------------------------------------------ # boost::noncopyable @@ -2289,7 +2263,7 @@ def test_copy_raises(self): import copy jc = ecf.JobCreationCtrl() - with self.assertRaises((RuntimeError, TypeError)): + with pytest.raises((RuntimeError, TypeError)): copy.copy(jc) # ------------------------------------------------------------------ @@ -2299,50 +2273,49 @@ def test_copy_raises(self): def test_set_node_path_with_int_raises(self): """set_node_path() requires a str; passing int raises ArgumentError.""" jc = ecf.JobCreationCtrl() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): jc.set_node_path(42) def test_set_node_path_with_none_raises(self): """set_node_path() requires a str; passing None raises ArgumentError.""" jc = ecf.JobCreationCtrl() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): jc.set_node_path(None) def test_set_dir_with_int_raises(self): """set_dir_for_job_creation() requires a str; passing int raises ArgumentError.""" jc = ecf.JobCreationCtrl() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): jc.set_dir_for_job_creation(42) def test_set_dir_with_none_raises(self): """set_dir_for_job_creation() requires a str; passing None raises ArgumentError.""" jc = ecf.JobCreationCtrl() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): jc.set_dir_for_job_creation(None) def test_set_verbose_with_string_raises(self): """set_verbose() requires a bool; passing a plain string raises ArgumentError.""" jc = ecf.JobCreationCtrl() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): jc.set_verbose("true") def test_set_verbose_accepts_none_as_false(self): """boost.python converts None to C++ bool(false); set_verbose(None) is accepted.""" jc = ecf.JobCreationCtrl() jc.set_verbose(None) # must not raise — None is treated as Fals - self.assertEqual(jc.is_verbose(), False) + assert jc.is_verbose() is False def test_set_verbose_accepts_int_as_bool(self): """boost.python converts any Python int to C++ bool (0→False, non-zero→True). Only plain strings are rejected; all integers are silently accepted.""" jc = ecf.JobCreationCtrl() for val in [0, 1, 2, -1, 42, -42, 100]: - with self.subTest(val=val): - jc.set_verbose(val) # must not raise - self.assertEqual(jc.is_verbose(), bool(val)) # 0→False, non-zero→True + jc.set_verbose(val) # must not raise + assert jc.is_verbose() == bool(val) # 0→False, non-zero→True -class TestZombieType(unittest.TestCase): +class TestZombieType: """Tests for py::enum_ exposed as ecf.ZombieType. Exposed API @@ -2407,29 +2380,23 @@ class TestZombieType(unittest.TestCase): def test_total_member_count_is_6(self): """Exactly 6 ZombieType values are declared in the binding.""" - self.assertEqual(len(ecf.ZombieType.names), 6) - self.assertEqual(len(ecf.ZombieType.values), 6) + assert len(ecf.ZombieType.names) == 6 + assert len(ecf.ZombieType.values) == 6 def test_all_names_accessible_as_class_attributes(self): """Every declared name is accessible as an attribute of ZombieType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertTrue( - hasattr(ecf.ZombieType, name), - f"ecf.ZombieType.{name} not found", - ) + assert hasattr(ecf.ZombieType, name), f"ecf.ZombieType.{name} not found" def test_all_names_present_in_names_dict(self): """ZombieType.names contains every declared name as a key.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(name, ecf.ZombieType.names) + assert name in ecf.ZombieType.names def test_all_int_values_present_in_values_dict(self): """ZombieType.values contains every declared integer value as a key.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertIn(expected_int, ecf.ZombieType.values) + assert expected_int in ecf.ZombieType.values # ------------------------------------------------------------------ # Type hierarchy @@ -2438,15 +2405,13 @@ def test_all_int_values_present_in_values_dict(self): def test_instances_are_zombie_type(self): """Every enum member is an instance of ecf.ZombieType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIsInstance(getattr(ecf.ZombieType, name), ecf.ZombieType) + assert isinstance(getattr(ecf.ZombieType, name), ecf.ZombieType) def test_int_conversion_works(self): """int(member) produces the C++ integer value (pybind11 enums are not int subclasses unlike Boost.Python enums, but they are still convertible to int).""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.ZombieType, name)), expected) + assert int(getattr(ecf.ZombieType, name)) == expected # ------------------------------------------------------------------ # str / repr / .name @@ -2455,25 +2420,19 @@ def test_int_conversion_works(self): def test_str_returns_just_the_name(self): """str(v) returns only the attribute name, not the module-qualified form.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual(str(getattr(ecf.ZombieType, name)), name) + assert str(getattr(ecf.ZombieType, name)) == name def test_repr_returns_module_qualified_name(self): """repr(v) returns 'ecflow.ZombieType.'.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual( - repr(getattr(ecf.ZombieType, name)), - f"ecflow.ZombieType.{name}", - ) + assert repr(getattr(ecf.ZombieType, name)) == f"ecflow.ZombieType.{name}" def test_name_attribute_equals_str(self): """v.name is the same string as str(v) for every member.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieType, name) - self.assertEqual(v.name, name) - self.assertEqual(v.name, str(v)) + v = getattr(ecf.ZombieType, name) + assert v.name == name + assert v.name == str(v) # ------------------------------------------------------------------ # Integer values @@ -2482,19 +2441,16 @@ def test_name_attribute_equals_str(self): def test_int_value_matches_cpp_enum(self): """int(v) matches the documented C++ integer value for each member.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.ZombieType, name)), expected) + assert int(getattr(ecf.ZombieType, name)) == expected def test_user_is_zero(self): """user has the smallest (zero) integer value even though it is not first in the binding.""" - self.assertEqual(int(ecf.ZombieType.user), 0) + assert int(ecf.ZombieType.user) == 0 def test_declaration_order_differs_from_numeric_order(self): """ecf_pid_passwd (4) > ecf_passwd (3) even though ecf_pid_passwd is declared first.""" - self.assertGreater( - int(ecf.ZombieType.ecf_pid_passwd), int(ecf.ZombieType.ecf_passwd) - ) - self.assertGreater(int(ecf.ZombieType.path), int(ecf.ZombieType.ecf_pid_passwd)) + assert int(ecf.ZombieType.ecf_pid_passwd) > int(ecf.ZombieType.ecf_passwd) + assert int(ecf.ZombieType.path) > int(ecf.ZombieType.ecf_pid_passwd) # ------------------------------------------------------------------ # .values and .names lookup dicts @@ -2503,30 +2459,26 @@ def test_declaration_order_differs_from_numeric_order(self): def test_values_dict_maps_int_to_zombie_type(self): """ZombieType.values maps each int key to a ZombieType instance.""" for int_val, member in ecf.ZombieType.values.items(): - with self.subTest(int_val=int_val): - self.assertIsInstance(int_val, int) - self.assertIsInstance(member, ecf.ZombieType) + assert isinstance(int_val, int) + assert isinstance(member, ecf.ZombieType) def test_names_dict_maps_str_to_zombie_type(self): """ZombieType.names maps each string key to a ZombieType instance.""" for name, member in ecf.ZombieType.names.items(): - with self.subTest(name=name): - self.assertIsInstance(name, str) - self.assertIsInstance(member, ecf.ZombieType) + assert isinstance(name, str) + assert isinstance(member, ecf.ZombieType) def test_values_dict_roundtrip(self): """ZombieType.values[int(v)] is the same singleton as the class attribute.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieType, name) - self.assertIs(ecf.ZombieType.values[int(v)], v) + v = getattr(ecf.ZombieType, name) + assert ecf.ZombieType.values[int(v)] is v def test_names_dict_roundtrip(self): """ZombieType.names[str(v)] is the same singleton as the class attribute.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieType, name) - self.assertIs(ecf.ZombieType.names[str(v)], v) + v = getattr(ecf.ZombieType, name) + assert ecf.ZombieType.names[str(v)] is v # ------------------------------------------------------------------ # Iteration — the class itself is NOT iterable @@ -2534,15 +2486,14 @@ def test_names_dict_roundtrip(self): def test_zombie_type_class_is_not_iterable(self): """list(ZombieType) raises TypeError; the class object has no __iter__.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): list(ecf.ZombieType) def test_all_members_reachable_via_values_dict(self): """Iterating ZombieType.values.values() yields every member once.""" members = set(ecf.ZombieType.values.values()) for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(getattr(ecf.ZombieType, name), members) + assert getattr(ecf.ZombieType, name) in members # ------------------------------------------------------------------ # Equality and ordering (inherited from int) @@ -2551,41 +2502,37 @@ def test_all_members_reachable_via_values_dict(self): def test_same_member_is_equal_to_itself(self): """v == v for every member (reflexive).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieType, name) - self.assertEqual(v, v) + v = getattr(ecf.ZombieType, name) + assert v == v def test_members_with_different_int_values_are_not_equal(self): """ecf != user because their integer values differ.""" - self.assertNotEqual(ecf.ZombieType.ecf, ecf.ZombieType.user) + assert ecf.ZombieType.ecf != ecf.ZombieType.user def test_equality_with_plain_int(self): """A ZombieType value equals a plain int with the matching integer value.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(getattr(ecf.ZombieType, name), expected) + assert getattr(ecf.ZombieType, name) == expected def test_ordering_user_is_smallest(self): """user (0) is less than every other ZombieType value.""" for name in self.ALL_NAMES: if name == "user": continue - with self.subTest(name=name): - self.assertLess(ecf.ZombieType.user, getattr(ecf.ZombieType, name)) + assert ecf.ZombieType.user < getattr(ecf.ZombieType, name) def test_ordering_path_is_largest(self): """path (5) is greater than every other ZombieType value.""" for name in self.ALL_NAMES: if name == "path": continue - with self.subTest(name=name): - self.assertGreater(ecf.ZombieType.path, getattr(ecf.ZombieType, name)) + assert ecf.ZombieType.path > getattr(ecf.ZombieType, name) def test_full_sort_produces_non_decreasing_int_sequence(self): """Sorting all members by value yields a non-decreasing integer sequence.""" sorted_vals = sorted(ecf.ZombieType.values.values()) ints = [int(v) for v in sorted_vals] - self.assertEqual(ints, sorted(ints)) + assert ints == sorted(ints) # ------------------------------------------------------------------ # Hash (inherited from int: hash(v) == int(v)) @@ -2594,18 +2541,17 @@ def test_full_sort_produces_non_decreasing_int_sequence(self): def test_hash_equals_int_value(self): """hash(v) == int(v) for every member.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(hash(getattr(ecf.ZombieType, name)), expected_int) + assert hash(getattr(ecf.ZombieType, name)) == expected_int def test_can_be_stored_in_set(self): """ZombieType members are hashable and can be stored in a Python set.""" s = set(ecf.ZombieType.values.values()) - self.assertEqual(len(s), 6) + assert len(s) == 6 def test_set_deduplicates_equal_values(self): """Adding the same member twice produces one entry in a set.""" s = {ecf.ZombieType.ecf, ecf.ZombieType.ecf} - self.assertEqual(len(s), 1) + assert len(s) == 1 def test_can_be_used_as_dict_key(self): """ZombieType members work as dictionary keys.""" @@ -2613,8 +2559,8 @@ def test_can_be_used_as_dict_key(self): ecf.ZombieType.ecf: "ecf_zombie", ecf.ZombieType.user: "user_zombie", } - self.assertEqual(d[ecf.ZombieType.ecf], "ecf_zombie") - self.assertEqual(d[ecf.ZombieType.user], "user_zombie") + assert d[ecf.ZombieType.ecf] == "ecf_zombie" + assert d[ecf.ZombieType.user] == "user_zombie" # ------------------------------------------------------------------ # Construction from int @@ -2623,16 +2569,15 @@ def test_can_be_used_as_dict_key(self): def test_can_construct_from_int(self): """ZombieType(n) constructs a ZombieType instance from a raw integer.""" v = ecf.ZombieType(1) - self.assertIsInstance(v, ecf.ZombieType) - self.assertEqual(int(v), 1) + assert isinstance(v, ecf.ZombieType) + assert int(v) == 1 def test_int_constructor_value_equals_class_attribute(self): """ZombieType(n) produces a value equal to the corresponding class attribute.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - from_ctor = ecf.ZombieType(expected_int) - singleton = getattr(ecf.ZombieType, name) - self.assertEqual(from_ctor, singleton) + from_ctor = ecf.ZombieType(expected_int) + singleton = getattr(ecf.ZombieType, name) + assert from_ctor == singleton # ------------------------------------------------------------------ # Negative cases @@ -2640,21 +2585,21 @@ def test_int_constructor_value_equals_class_attribute(self): def test_nonexistent_member_raises_attribute_error(self): """Accessing an undefined name on ZombieType raises AttributeError.""" - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): _ = ecf.ZombieType.nonexistent_zombie_type def test_missing_int_key_in_values_raises_key_error(self): """Looking up an int that has no member in values raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.ZombieType.values[9999] def test_missing_name_key_in_names_raises_key_error(self): """Looking up a name that has no member in names raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.ZombieType.names["nonexistent_zombie_type"] -class TestZombieUserActionType(unittest.TestCase): +class TestZombieUserActionType: """Tests for py::enum_ exposed as ecf.ZombieUserActionType. Exposed API @@ -2707,26 +2652,23 @@ class TestZombieUserActionType(unittest.TestCase): def test_total_member_count_is_6(self): """Exactly 6 ZombieUserActionType values are declared.""" - self.assertEqual(len(ecf.ZombieUserActionType.names), 6) - self.assertEqual(len(ecf.ZombieUserActionType.values), 6) + assert len(ecf.ZombieUserActionType.names) == 6 + assert len(ecf.ZombieUserActionType.values) == 6 def test_all_names_accessible_as_class_attributes(self): """Every declared name is accessible on ZombieUserActionType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertTrue(hasattr(ecf.ZombieUserActionType, name)) + assert hasattr(ecf.ZombieUserActionType, name) def test_all_names_present_in_names_dict(self): """ZombieUserActionType.names contains every declared name.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(name, ecf.ZombieUserActionType.names) + assert name in ecf.ZombieUserActionType.names def test_all_int_values_present_in_values_dict(self): """ZombieUserActionType.values contains every declared integer value.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertIn(expected_int, ecf.ZombieUserActionType.values) + assert expected_int in ecf.ZombieUserActionType.values # ------------------------------------------------------------------ # Type hierarchy @@ -2735,17 +2677,15 @@ def test_all_int_values_present_in_values_dict(self): def test_instances_are_zombie_user_action_type(self): """Every member is an instance of ecf.ZombieUserActionType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIsInstance( - getattr(ecf.ZombieUserActionType, name), ecf.ZombieUserActionType - ) + assert isinstance( + getattr(ecf.ZombieUserActionType, name), ecf.ZombieUserActionType + ) def test_int_conversion_works(self): """int(member) produces the C++ integer value (pybind11 enums are not int subclasses unlike Boost.Python enums, but they are still convertible to int).""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.ZombieUserActionType, name)), expected) + assert int(getattr(ecf.ZombieUserActionType, name)) == expected # ------------------------------------------------------------------ # str / repr / .name @@ -2754,24 +2694,21 @@ def test_int_conversion_works(self): def test_str_returns_just_the_name(self): """str(v) returns only the bare attribute name.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual(str(getattr(ecf.ZombieUserActionType, name)), name) + assert str(getattr(ecf.ZombieUserActionType, name)) == name def test_repr_returns_module_qualified_name(self): """repr(v) returns 'ecflow.ZombieUserActionType.'.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual( - repr(getattr(ecf.ZombieUserActionType, name)), - f"ecflow.ZombieUserActionType.{name}", - ) + assert ( + repr(getattr(ecf.ZombieUserActionType, name)) + == f"ecflow.ZombieUserActionType.{name}" + ) def test_name_attribute_equals_str(self): """v.name is the same string as str(v).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieUserActionType, name) - self.assertEqual(v.name, name) + v = getattr(ecf.ZombieUserActionType, name) + assert v.name == name # ------------------------------------------------------------------ # Integer values @@ -2780,18 +2717,16 @@ def test_name_attribute_equals_str(self): def test_int_value_matches_cpp_enum(self): """int(v) matches the documented C++ integer for each member.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.ZombieUserActionType, name)), expected) + assert int(getattr(ecf.ZombieUserActionType, name)) == expected def test_fob_is_zero(self): """fob has the lowest integer value (0).""" - self.assertEqual(int(ecf.ZombieUserActionType.fob), 0) + assert int(ecf.ZombieUserActionType.fob) == 0 def test_declaration_order_differs_from_numeric_order(self): """remove (3) is declared before adopt (2) but has a higher int value.""" - self.assertGreater( - int(ecf.ZombieUserActionType.remove), - int(ecf.ZombieUserActionType.adopt), + assert int(ecf.ZombieUserActionType.remove) > int( + ecf.ZombieUserActionType.adopt ) # ------------------------------------------------------------------ @@ -2801,16 +2736,14 @@ def test_declaration_order_differs_from_numeric_order(self): def test_values_dict_roundtrip(self): """ZombieUserActionType.values[int(v)] is the same singleton as the class attr.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieUserActionType, name) - self.assertIs(ecf.ZombieUserActionType.values[int(v)], v) + v = getattr(ecf.ZombieUserActionType, name) + assert ecf.ZombieUserActionType.values[int(v)] is v def test_names_dict_roundtrip(self): """ZombieUserActionType.names[str(v)] is the same singleton as the class attr.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieUserActionType, name) - self.assertIs(ecf.ZombieUserActionType.names[str(v)], v) + v = getattr(ecf.ZombieUserActionType, name) + assert ecf.ZombieUserActionType.names[str(v)] is v # ------------------------------------------------------------------ # Iteration @@ -2818,15 +2751,14 @@ def test_names_dict_roundtrip(self): def test_class_is_not_iterable(self): """list(ZombieUserActionType) raises TypeError.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): list(ecf.ZombieUserActionType) def test_all_members_reachable_via_values_dict(self): """Every member appears in ZombieUserActionType.values.values().""" members = set(ecf.ZombieUserActionType.values.values()) for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(getattr(ecf.ZombieUserActionType, name), members) + assert getattr(ecf.ZombieUserActionType, name) in members # ------------------------------------------------------------------ # Equality and ordering @@ -2835,47 +2767,41 @@ def test_all_members_reachable_via_values_dict(self): def test_same_member_is_equal_to_itself(self): """v == v for every member (reflexive).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ZombieUserActionType, name) - self.assertEqual(v, v) + v = getattr(ecf.ZombieUserActionType, name) + assert v == v def test_different_members_are_not_equal(self): """fob != kill because their integer values differ.""" - self.assertNotEqual(ecf.ZombieUserActionType.fob, ecf.ZombieUserActionType.kill) + assert ecf.ZombieUserActionType.fob != ecf.ZombieUserActionType.kill def test_equality_with_plain_int(self): """A ZombieUserActionType value equals a plain int with the same value.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(getattr(ecf.ZombieUserActionType, name), expected) + assert getattr(ecf.ZombieUserActionType, name) == expected def test_ordering_fob_is_smallest(self): """fob (0) is less than every other ZombieUserActionType value.""" for name in self.ALL_NAMES: if name == "fob": continue - with self.subTest(name=name): - self.assertLess( - ecf.ZombieUserActionType.fob, - getattr(ecf.ZombieUserActionType, name), - ) + assert ecf.ZombieUserActionType.fob < getattr( + ecf.ZombieUserActionType, name + ) def test_ordering_kill_is_largest(self): """kill (5) is greater than every other ZombieUserActionType value.""" for name in self.ALL_NAMES: if name == "kill": continue - with self.subTest(name=name): - self.assertGreater( - ecf.ZombieUserActionType.kill, - getattr(ecf.ZombieUserActionType, name), - ) + assert ecf.ZombieUserActionType.kill > getattr( + ecf.ZombieUserActionType, name + ) def test_full_sort_produces_non_decreasing_sequence(self): """Sorting all members by value yields a non-decreasing integer sequence.""" sorted_vals = sorted(ecf.ZombieUserActionType.values.values()) ints = [int(v) for v in sorted_vals] - self.assertEqual(ints, sorted(ints)) + assert ints == sorted(ints) # ------------------------------------------------------------------ # Hash @@ -2884,21 +2810,18 @@ def test_full_sort_produces_non_decreasing_sequence(self): def test_hash_equals_int_value(self): """hash(v) == int(v) for every member.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual( - hash(getattr(ecf.ZombieUserActionType, name)), expected_int - ) + assert hash(getattr(ecf.ZombieUserActionType, name)) == expected_int def test_can_be_stored_in_set(self): """All 6 members can be stored in a Python set.""" s = set(ecf.ZombieUserActionType.values.values()) - self.assertEqual(len(s), 6) + assert len(s) == 6 def test_can_be_used_as_dict_key(self): """ZombieUserActionType members work as dictionary keys.""" d = {ecf.ZombieUserActionType.fob: "fob", ecf.ZombieUserActionType.kill: "kill"} - self.assertEqual(d[ecf.ZombieUserActionType.fob], "fob") - self.assertEqual(d[ecf.ZombieUserActionType.kill], "kill") + assert d[ecf.ZombieUserActionType.fob] == "fob" + assert d[ecf.ZombieUserActionType.kill] == "kill" # ------------------------------------------------------------------ # Construction from int @@ -2907,11 +2830,9 @@ def test_can_be_used_as_dict_key(self): def test_int_constructor_value_equals_class_attribute(self): """ZombieUserActionType(n) produces a value equal to the corresponding class attr.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual( - ecf.ZombieUserActionType(expected_int), - getattr(ecf.ZombieUserActionType, name), - ) + assert ecf.ZombieUserActionType(expected_int) == getattr( + ecf.ZombieUserActionType, name + ) # ------------------------------------------------------------------ # Negative cases @@ -2919,21 +2840,21 @@ def test_int_constructor_value_equals_class_attribute(self): def test_nonexistent_member_raises_attribute_error(self): """Accessing an undefined name raises AttributeError.""" - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): _ = ecf.ZombieUserActionType.nonexistent def test_missing_int_key_in_values_raises_key_error(self): """Looking up an unknown int in values raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.ZombieUserActionType.values[9999] def test_missing_name_key_in_names_raises_key_error(self): """Looking up an unknown name in names raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.ZombieUserActionType.names["nonexistent"] -class TestChildCmdType(unittest.TestCase): +class TestChildCmdType: """Tests for py::enum_ exposed as ecf.ChildCmdType. Exposed API @@ -2997,26 +2918,23 @@ class TestChildCmdType(unittest.TestCase): def test_total_member_count_is_8(self): """Exactly 8 ChildCmdType values are declared.""" - self.assertEqual(len(ecf.ChildCmdType.names), 8) - self.assertEqual(len(ecf.ChildCmdType.values), 8) + assert len(ecf.ChildCmdType.names) == 8 + assert len(ecf.ChildCmdType.values) == 8 def test_all_names_accessible_as_class_attributes(self): """Every declared name is accessible on ChildCmdType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertTrue(hasattr(ecf.ChildCmdType, name)) + assert hasattr(ecf.ChildCmdType, name) def test_all_names_present_in_names_dict(self): """ChildCmdType.names contains every declared name.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(name, ecf.ChildCmdType.names) + assert name in ecf.ChildCmdType.names def test_all_int_values_present_in_values_dict(self): """ChildCmdType.values contains every declared integer value.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertIn(expected_int, ecf.ChildCmdType.values) + assert expected_int in ecf.ChildCmdType.values # ------------------------------------------------------------------ # Type hierarchy @@ -3025,15 +2943,13 @@ def test_all_int_values_present_in_values_dict(self): def test_instances_are_child_cmd_type(self): """Every member is an instance of ecf.ChildCmdType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIsInstance(getattr(ecf.ChildCmdType, name), ecf.ChildCmdType) + assert isinstance(getattr(ecf.ChildCmdType, name), ecf.ChildCmdType) def test_int_conversion_works(self): """int(member) produces the C++ integer value (pybind11 enums are not int subclasses unlike Boost.Python enums, but they are still convertible to int).""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.ChildCmdType, name)), expected) + assert int(getattr(ecf.ChildCmdType, name)) == expected # ------------------------------------------------------------------ # str / repr / .name @@ -3042,24 +2958,20 @@ def test_int_conversion_works(self): def test_str_returns_just_the_name(self): """str(v) returns only the bare attribute name.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual(str(getattr(ecf.ChildCmdType, name)), name) + assert str(getattr(ecf.ChildCmdType, name)) == name def test_repr_returns_module_qualified_name(self): """repr(v) returns 'ecflow.ChildCmdType.'.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual( - repr(getattr(ecf.ChildCmdType, name)), - f"ecflow.ChildCmdType.{name}", - ) + assert ( + repr(getattr(ecf.ChildCmdType, name)) == f"ecflow.ChildCmdType.{name}" + ) def test_name_attribute_equals_str(self): """v.name is the same string as str(v).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ChildCmdType, name) - self.assertEqual(v.name, name) + v = getattr(ecf.ChildCmdType, name) + assert v.name == name # ------------------------------------------------------------------ # Integer values — contiguous 0–7, declaration order = numeric order @@ -3068,28 +2980,27 @@ def test_name_attribute_equals_str(self): def test_int_value_matches_cpp_enum(self): """int(v) matches the documented C++ integer for each member.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.ChildCmdType, name)), expected) + assert int(getattr(ecf.ChildCmdType, name)) == expected def test_init_is_zero(self): """init has integer value 0 (first and smallest).""" - self.assertEqual(int(ecf.ChildCmdType.init), 0) + assert int(ecf.ChildCmdType.init) == 0 def test_complete_is_seven(self): """complete has integer value 7 (last and largest).""" - self.assertEqual(int(ecf.ChildCmdType.complete), 7) + assert int(ecf.ChildCmdType.complete) == 7 def test_values_are_contiguous_zero_to_seven(self): """The integer values form an unbroken sequence 0–7.""" all_ints = sorted(int(v) for v in ecf.ChildCmdType.values.values()) - self.assertEqual(all_ints, list(range(8))) + assert all_ints == list(range(8)) def test_declaration_order_matches_numeric_order(self): """For ChildCmdType, declaration order equals numeric order (no surprises).""" prev_int = -1 for name in self.ALL_NAMES: cur_int = int(getattr(ecf.ChildCmdType, name)) - self.assertGreater(cur_int, prev_int) + assert cur_int > prev_int prev_int = cur_int # ------------------------------------------------------------------ @@ -3099,16 +3010,14 @@ def test_declaration_order_matches_numeric_order(self): def test_values_dict_roundtrip(self): """ChildCmdType.values[int(v)] is the same singleton as the class attr.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ChildCmdType, name) - self.assertIs(ecf.ChildCmdType.values[int(v)], v) + v = getattr(ecf.ChildCmdType, name) + assert ecf.ChildCmdType.values[int(v)] is v def test_names_dict_roundtrip(self): """ChildCmdType.names[str(v)] is the same singleton as the class attr.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ChildCmdType, name) - self.assertIs(ecf.ChildCmdType.names[str(v)], v) + v = getattr(ecf.ChildCmdType, name) + assert ecf.ChildCmdType.names[str(v)] is v # ------------------------------------------------------------------ # Iteration @@ -3116,15 +3025,14 @@ def test_names_dict_roundtrip(self): def test_class_is_not_iterable(self): """list(ChildCmdType) raises TypeError.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): list(ecf.ChildCmdType) def test_all_members_reachable_via_values_dict(self): """Every member appears in ChildCmdType.values.values().""" members = set(ecf.ChildCmdType.values.values()) for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(getattr(ecf.ChildCmdType, name), members) + assert getattr(ecf.ChildCmdType, name) in members # ------------------------------------------------------------------ # Equality and ordering @@ -3133,43 +3041,37 @@ def test_all_members_reachable_via_values_dict(self): def test_same_member_is_equal_to_itself(self): """v == v for every member (reflexive).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.ChildCmdType, name) - self.assertEqual(v, v) + v = getattr(ecf.ChildCmdType, name) + assert v == v def test_different_members_are_not_equal(self): """init != complete because their integer values differ.""" - self.assertNotEqual(ecf.ChildCmdType.init, ecf.ChildCmdType.complete) + assert ecf.ChildCmdType.init != ecf.ChildCmdType.complete def test_equality_with_plain_int(self): """A ChildCmdType value equals a plain int with the same value.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(getattr(ecf.ChildCmdType, name), expected) + assert getattr(ecf.ChildCmdType, name) == expected def test_ordering_init_is_smallest(self): """init (0) is less than every other ChildCmdType.""" for name in self.ALL_NAMES: if name == "init": continue - with self.subTest(name=name): - self.assertLess(ecf.ChildCmdType.init, getattr(ecf.ChildCmdType, name)) + assert ecf.ChildCmdType.init < getattr(ecf.ChildCmdType, name) def test_ordering_complete_is_largest(self): """complete (7) is greater than every other ChildCmdType.""" for name in self.ALL_NAMES: if name == "complete": continue - with self.subTest(name=name): - self.assertGreater( - ecf.ChildCmdType.complete, getattr(ecf.ChildCmdType, name) - ) + assert ecf.ChildCmdType.complete > getattr(ecf.ChildCmdType, name) def test_full_sort_produces_non_decreasing_sequence(self): """Sorting all members by value yields a non-decreasing integer sequence.""" sorted_vals = sorted(ecf.ChildCmdType.values.values()) ints = [int(v) for v in sorted_vals] - self.assertEqual(ints, sorted(ints)) + assert ints == sorted(ints) # ------------------------------------------------------------------ # Hash @@ -3178,19 +3080,18 @@ def test_full_sort_produces_non_decreasing_sequence(self): def test_hash_equals_int_value(self): """hash(v) == int(v) for every member.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(hash(getattr(ecf.ChildCmdType, name)), expected_int) + assert hash(getattr(ecf.ChildCmdType, name)) == expected_int def test_can_be_stored_in_set(self): """All 8 members can be stored in a Python set.""" s = set(ecf.ChildCmdType.values.values()) - self.assertEqual(len(s), 8) + assert len(s) == 8 def test_can_be_used_as_dict_key(self): """ChildCmdType members work as dictionary keys.""" d = {ecf.ChildCmdType.init: "init", ecf.ChildCmdType.complete: "complete"} - self.assertEqual(d[ecf.ChildCmdType.init], "init") - self.assertEqual(d[ecf.ChildCmdType.complete], "complete") + assert d[ecf.ChildCmdType.init] == "init" + assert d[ecf.ChildCmdType.complete] == "complete" # ------------------------------------------------------------------ # Construction from int @@ -3199,11 +3100,7 @@ def test_can_be_used_as_dict_key(self): def test_int_constructor_value_equals_class_attribute(self): """ChildCmdType(n) produces a value equal to the corresponding class attr.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual( - ecf.ChildCmdType(expected_int), - getattr(ecf.ChildCmdType, name), - ) + assert ecf.ChildCmdType(expected_int) == getattr(ecf.ChildCmdType, name) # ------------------------------------------------------------------ # Negative cases @@ -3211,21 +3108,21 @@ def test_int_constructor_value_equals_class_attribute(self): def test_nonexistent_member_raises_attribute_error(self): """Accessing an undefined name raises AttributeError.""" - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): _ = ecf.ChildCmdType.nonexistent def test_missing_int_key_in_values_raises_key_error(self): """Looking up an unknown int in values raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.ChildCmdType.values[9999] def test_missing_name_key_in_names_raises_key_error(self): """Looking up an unknown name in names raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.ChildCmdType.names["nonexistent"] -class TestAttrType(unittest.TestCase): +class TestAttrType: """Tests for py::enum_ exposed as ecf.AttrType. Exposed API @@ -3279,26 +3176,23 @@ class TestAttrType(unittest.TestCase): def test_total_member_count_is_6(self): """Exactly 6 AttrType values are declared.""" - self.assertEqual(len(ecf.AttrType.names), 6) - self.assertEqual(len(ecf.AttrType.values), 6) + assert len(ecf.AttrType.names) == 6 + assert len(ecf.AttrType.values) == 6 def test_all_names_accessible_as_class_attributes(self): """Every declared name is accessible on AttrType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertTrue(hasattr(ecf.AttrType, name)) + assert hasattr(ecf.AttrType, name) def test_all_names_present_in_names_dict(self): """AttrType.names contains every declared name.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(name, ecf.AttrType.names) + assert name in ecf.AttrType.names def test_all_int_values_present_in_values_dict(self): """AttrType.values contains every declared integer value.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertIn(expected_int, ecf.AttrType.values) + assert expected_int in ecf.AttrType.values # ------------------------------------------------------------------ # Type hierarchy @@ -3307,15 +3201,13 @@ def test_all_int_values_present_in_values_dict(self): def test_instances_are_attr_type(self): """Every member is an instance of ecf.AttrType.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIsInstance(getattr(ecf.AttrType, name), ecf.AttrType) + assert isinstance(getattr(ecf.AttrType, name), ecf.AttrType) def test_int_conversion_works(self): """int(member) produces the C++ integer value (pybind11 enums are not int subclasses unlike Boost.Python enums, but they are still convertible to int).""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.AttrType, name)), expected) + assert int(getattr(ecf.AttrType, name)) == expected # ------------------------------------------------------------------ # str / repr / .name @@ -3324,24 +3216,18 @@ def test_int_conversion_works(self): def test_str_returns_just_the_name(self): """str(v) returns only the bare attribute name, including 'all'.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual(str(getattr(ecf.AttrType, name)), name) + assert str(getattr(ecf.AttrType, name)) == name def test_repr_returns_module_qualified_name(self): """repr(v) returns 'ecflow.AttrType.'.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertEqual( - repr(getattr(ecf.AttrType, name)), - f"ecflow.AttrType.{name}", - ) + assert repr(getattr(ecf.AttrType, name)) == f"ecflow.AttrType.{name}" def test_name_attribute_equals_str(self): """v.name is the same string as str(v).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.AttrType, name) - self.assertEqual(v.name, name) + v = getattr(ecf.AttrType, name) + assert v.name == name # ------------------------------------------------------------------ # Integer values — contiguous 1–6, no zero @@ -3350,31 +3236,30 @@ def test_name_attribute_equals_str(self): def test_int_value_matches_cpp_enum(self): """int(v) matches the documented C++ integer for each member.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(int(getattr(ecf.AttrType, name)), expected) + assert int(getattr(ecf.AttrType, name)) == expected def test_no_zero_value(self): """AttrType has no member with integer value 0.""" - self.assertNotIn(0, ecf.AttrType.values) + assert 0 not in ecf.AttrType.values def test_event_is_one(self): """event (1) is the minimum integer value.""" - self.assertEqual(int(ecf.AttrType.event), 1) + assert int(ecf.AttrType.event) == 1 def test_all_is_six(self): """all (6) is the maximum integer value.""" - self.assertEqual(int(ecf.AttrType.all), 6) + assert int(ecf.AttrType.all) == 6 def test_values_are_contiguous_one_to_six(self): """The integer values form an unbroken sequence 1–6.""" all_ints = sorted(int(v) for v in ecf.AttrType.values.values()) - self.assertEqual(all_ints, list(range(1, 7))) + assert all_ints == list(range(1, 7)) def test_all_attribute_is_shadow_of_builtin(self): """AttrType.all is a valid attribute even though 'all' is a Python built-in.""" v = ecf.AttrType.all - self.assertIsInstance(v, ecf.AttrType) - self.assertEqual(int(v), 6) + assert isinstance(v, ecf.AttrType) + assert int(v) == 6 # ------------------------------------------------------------------ # .values / .names round-trips @@ -3383,16 +3268,14 @@ def test_all_attribute_is_shadow_of_builtin(self): def test_values_dict_roundtrip(self): """AttrType.values[int(v)] is the same singleton as the class attr.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.AttrType, name) - self.assertIs(ecf.AttrType.values[int(v)], v) + v = getattr(ecf.AttrType, name) + assert ecf.AttrType.values[int(v)] is v def test_names_dict_roundtrip(self): """AttrType.names[str(v)] is the same singleton as the class attr.""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.AttrType, name) - self.assertIs(ecf.AttrType.names[str(v)], v) + v = getattr(ecf.AttrType, name) + assert ecf.AttrType.names[str(v)] is v # ------------------------------------------------------------------ # Iteration @@ -3400,15 +3283,14 @@ def test_names_dict_roundtrip(self): def test_class_is_not_iterable(self): """list(AttrType) raises TypeError.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): list(ecf.AttrType) def test_all_members_reachable_via_values_dict(self): """Every member appears in AttrType.values.values().""" members = set(ecf.AttrType.values.values()) for name in self.ALL_NAMES: - with self.subTest(name=name): - self.assertIn(getattr(ecf.AttrType, name), members) + assert getattr(ecf.AttrType, name) in members # ------------------------------------------------------------------ # Equality and ordering @@ -3417,41 +3299,37 @@ def test_all_members_reachable_via_values_dict(self): def test_same_member_is_equal_to_itself(self): """v == v for every member (reflexive).""" for name in self.ALL_NAMES: - with self.subTest(name=name): - v = getattr(ecf.AttrType, name) - self.assertEqual(v, v) + v = getattr(ecf.AttrType, name) + assert v == v def test_different_members_are_not_equal(self): """event != all because their integer values differ.""" - self.assertNotEqual(ecf.AttrType.event, ecf.AttrType.all) + assert ecf.AttrType.event != ecf.AttrType.all def test_equality_with_plain_int(self): """A AttrType value equals a plain int with the same value.""" for name, expected in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(getattr(ecf.AttrType, name), expected) + assert getattr(ecf.AttrType, name) == expected def test_ordering_event_is_smallest(self): """event (1) is less than every other AttrType value.""" for name in self.ALL_NAMES: if name == "event": continue - with self.subTest(name=name): - self.assertLess(ecf.AttrType.event, getattr(ecf.AttrType, name)) + assert ecf.AttrType.event < getattr(ecf.AttrType, name) def test_ordering_all_is_largest(self): """all (6) is greater than every other AttrType value.""" for name in self.ALL_NAMES: if name == "all": continue - with self.subTest(name=name): - self.assertGreater(ecf.AttrType.all, getattr(ecf.AttrType, name)) + assert ecf.AttrType.all > getattr(ecf.AttrType, name) def test_full_sort_produces_non_decreasing_sequence(self): """Sorting all members by value yields a non-decreasing integer sequence.""" sorted_vals = sorted(ecf.AttrType.values.values()) ints = [int(v) for v in sorted_vals] - self.assertEqual(ints, sorted(ints)) + assert ints == sorted(ints) # ------------------------------------------------------------------ # Hash @@ -3460,19 +3338,18 @@ def test_full_sort_produces_non_decreasing_sequence(self): def test_hash_equals_int_value(self): """hash(v) == int(v) for every member.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual(hash(getattr(ecf.AttrType, name)), expected_int) + assert hash(getattr(ecf.AttrType, name)) == expected_int def test_can_be_stored_in_set(self): """All 6 members can be stored in a Python set.""" s = set(ecf.AttrType.values.values()) - self.assertEqual(len(s), 6) + assert len(s) == 6 def test_can_be_used_as_dict_key(self): """AttrType members work as dictionary keys.""" d = {ecf.AttrType.event: "event", ecf.AttrType.all: "all"} - self.assertEqual(d[ecf.AttrType.event], "event") - self.assertEqual(d[ecf.AttrType.all], "all") + assert d[ecf.AttrType.event] == "event" + assert d[ecf.AttrType.all] == "all" # ------------------------------------------------------------------ # Construction from int @@ -3481,11 +3358,7 @@ def test_can_be_used_as_dict_key(self): def test_int_constructor_value_equals_class_attribute(self): """AttrType(n) produces a value equal to the corresponding class attr.""" for name, expected_int in self.INT_VALUES.items(): - with self.subTest(name=name): - self.assertEqual( - ecf.AttrType(expected_int), - getattr(ecf.AttrType, name), - ) + assert ecf.AttrType(expected_int) == getattr(ecf.AttrType, name) # ------------------------------------------------------------------ # Negative cases @@ -3493,26 +3366,26 @@ def test_int_constructor_value_equals_class_attribute(self): def test_nonexistent_member_raises_attribute_error(self): """Accessing an undefined name raises AttributeError.""" - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): _ = ecf.AttrType.nonexistent def test_zero_not_in_values_raises_key_error(self): """Looking up int 0 in AttrType.values raises KeyError (no zero member).""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.AttrType.values[0] def test_missing_int_key_in_values_raises_key_error(self): """Looking up an unknown int in values raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.AttrType.values[9999] def test_missing_name_key_in_names_raises_key_error(self): """Looking up an unknown name in names raises KeyError.""" - with self.assertRaises(KeyError): + with pytest.raises(KeyError): _ = ecf.AttrType.names["nonexistent"] -class TestZombieAttr(unittest.TestCase): +class TestZombieAttr: """Tests for py::class_ exposed as ecf.ZombieAttr. Exposed API @@ -3555,34 +3428,34 @@ class TestZombieAttr(unittest.TestCase): def test_three_arg_constructor_creates_instance(self): """ZombieAttr(type, cmds, action) creates a valid instance.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertIsInstance(z, ecf.ZombieAttr) + assert isinstance(z, ecf.ZombieAttr) def test_four_arg_constructor_creates_instance(self): """ZombieAttr(type, cmds, action, lifetime) creates a valid instance.""" z = ecf.ZombieAttr(self.ZT.user, [], self.ZA.block, 300) - self.assertIsInstance(z, ecf.ZombieAttr) + assert isinstance(z, ecf.ZombieAttr) def test_three_arg_constructor_default_lifetime_is_3600(self): """When no lifetime is given the server default of 3600 seconds is used.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertEqual(z.zombie_lifetime(), 3600) + assert z.zombie_lifetime() == 3600 def test_constructor_with_empty_child_list(self): """An empty child list is accepted; means the action applies to all commands.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertIsInstance(z, ecf.ZombieAttr) - self.assertEqual(list(z.child_cmds), []) + assert isinstance(z, ecf.ZombieAttr) + assert list(z.child_cmds) == [] def test_constructor_with_single_child_cmd(self): """A single ChildCmdType entry is accepted.""" z = ecf.ZombieAttr(self.ZT.ecf, [self.CC.init], self.ZA.fob) - self.assertEqual(list(z.child_cmds), [self.CC.init]) + assert list(z.child_cmds) == [self.CC.init] def test_constructor_with_multiple_child_cmds(self): """Multiple ChildCmdType entries are stored in insertion order.""" cmds = [self.CC.init, self.CC.event, self.CC.abort] z = ecf.ZombieAttr(self.ZT.ecf, cmds, self.ZA.fob) - self.assertEqual(list(z.child_cmds), cmds) + assert list(z.child_cmds) == cmds def test_constructor_with_all_child_cmds(self): """All eight ChildCmdType values can be passed and are preserved.""" @@ -3597,21 +3470,19 @@ def test_constructor_with_all_child_cmds(self): self.CC.complete, ] z = ecf.ZombieAttr(self.ZT.ecf, all_cmds, self.ZA.fob) - self.assertEqual(list(z.child_cmds), all_cmds) + assert list(z.child_cmds) == all_cmds def test_constructor_with_all_zombie_types(self): """Every ZombieType value is accepted by the constructor.""" for zt in ecf.ZombieType.values.values(): - with self.subTest(zombie_type=str(zt)): - z = ecf.ZombieAttr(zt, [], self.ZA.fob) - self.assertEqual(z.zombie_type(), zt) + z = ecf.ZombieAttr(zt, [], self.ZA.fob) + assert z.zombie_type() == zt def test_constructor_with_all_user_actions(self): """Every ZombieUserActionType value is accepted by the constructor.""" for act in ecf.ZombieUserActionType.values.values(): - with self.subTest(action=str(act)): - z = ecf.ZombieAttr(self.ZT.ecf, [], act) - self.assertEqual(z.user_action(), act) + z = ecf.ZombieAttr(self.ZT.ecf, [], act) + assert z.user_action() == act # ------------------------------------------------------------------ # zombie_type() @@ -3620,14 +3491,13 @@ def test_constructor_with_all_user_actions(self): def test_zombie_type_returns_zombie_type_instance(self): """zombie_type() returns a ZombieType instance.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertIsInstance(z.zombie_type(), ecf.ZombieType) + assert isinstance(z.zombie_type(), ecf.ZombieType) def test_zombie_type_reflects_constructor_argument(self): """zombie_type() returns exactly the ZombieType passed at construction.""" for zt in ecf.ZombieType.values.values(): - with self.subTest(zombie_type=str(zt)): - z = ecf.ZombieAttr(zt, [], self.ZA.fob) - self.assertEqual(z.zombie_type(), zt) + z = ecf.ZombieAttr(zt, [], self.ZA.fob) + assert z.zombie_type() == zt # ------------------------------------------------------------------ # user_action() @@ -3636,14 +3506,13 @@ def test_zombie_type_reflects_constructor_argument(self): def test_user_action_returns_zombie_user_action_type_instance(self): """user_action() returns a ZombieUserActionType instance.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertIsInstance(z.user_action(), ecf.ZombieUserActionType) + assert isinstance(z.user_action(), ecf.ZombieUserActionType) def test_user_action_reflects_constructor_argument(self): """user_action() returns exactly the action passed at construction.""" for act in ecf.ZombieUserActionType.values.values(): - with self.subTest(action=str(act)): - z = ecf.ZombieAttr(self.ZT.ecf, [], act) - self.assertEqual(z.user_action(), act) + z = ecf.ZombieAttr(self.ZT.ecf, [], act) + assert z.user_action() == act # ------------------------------------------------------------------ # zombie_lifetime() @@ -3652,27 +3521,27 @@ def test_user_action_reflects_constructor_argument(self): def test_zombie_lifetime_returns_int(self): """zombie_lifetime() returns a Python int.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, 42) - self.assertIsInstance(z.zombie_lifetime(), int) + assert isinstance(z.zombie_lifetime(), int) def test_zombie_lifetime_reflects_explicit_lifetime(self): """zombie_lifetime() reflects the lifetime passed in the four-arg constructor.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, 300) - self.assertEqual(z.zombie_lifetime(), 300) + assert z.zombie_lifetime() == 300 def test_zombie_lifetime_default_is_3600(self): """Three-arg constructor gives lifetime == 3600 (server default).""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertEqual(z.zombie_lifetime(), 3600) + assert z.zombie_lifetime() == 3600 def test_zombie_lifetime_zero_is_clamped_to_default(self): """A zero lifetime is not stored; C++ clamps it to the default 3600.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, 0) - self.assertEqual(z.zombie_lifetime(), 3600) + assert z.zombie_lifetime() == 3600 def test_zombie_lifetime_negative_is_clamped_to_default(self): """A negative lifetime is not stored; C++ clamps it to the default 3600.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, -1) - self.assertEqual(z.zombie_lifetime(), 3600) + assert z.zombie_lifetime() == 3600 # ------------------------------------------------------------------ # empty() @@ -3681,12 +3550,12 @@ def test_zombie_lifetime_negative_is_clamped_to_default(self): def test_empty_is_false_for_three_arg_constructor(self): """empty() returns False for a normally constructed ZombieAttr.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertFalse(z.empty()) + assert not z.empty() def test_empty_is_false_for_four_arg_constructor(self): """empty() returns False regardless of which constructor overload is used.""" z = ecf.ZombieAttr(self.ZT.ecf, [self.CC.init], self.ZA.fob, 120) - self.assertFalse(z.empty()) + assert not z.empty() # ------------------------------------------------------------------ # child_cmds property @@ -3696,25 +3565,24 @@ def test_child_cmds_elements_are_child_cmd_type(self): """Every element yielded by child_cmds is a ChildCmdType instance.""" z = ecf.ZombieAttr(self.ZT.ecf, [self.CC.init, self.CC.abort], self.ZA.fob) for cmd in z.child_cmds: - with self.subTest(cmd=str(cmd)): - self.assertIsInstance(cmd, ecf.ChildCmdType) + assert isinstance(cmd, ecf.ChildCmdType) def test_child_cmds_empty_when_empty_list_passed(self): """child_cmds yields no elements when an empty list was passed.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertEqual(list(z.child_cmds), []) + assert list(z.child_cmds) == [] def test_child_cmds_preserves_insertion_order(self): """child_cmds yields ChildCmdType values in the order they were passed.""" cmds = [self.CC.abort, self.CC.meter, self.CC.label] z = ecf.ZombieAttr(self.ZT.ecf, cmds, self.ZA.fob) - self.assertEqual(list(z.child_cmds), cmds) + assert list(z.child_cmds) == cmds def test_child_cmds_is_re_iterable(self): """child_cmds can be iterated more than once and yields the same results.""" cmds = [self.CC.init, self.CC.abort] z = ecf.ZombieAttr(self.ZT.ecf, cmds, self.ZA.fob) - self.assertEqual(list(z.child_cmds), list(z.child_cmds)) + assert list(z.child_cmds) == list(z.child_cmds) # ------------------------------------------------------------------ # __str__ @@ -3723,35 +3591,35 @@ def test_child_cmds_is_re_iterable(self): def test_str_returns_str_type(self): """str() always returns a Python str.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertIsInstance(str(z), str) + assert isinstance(str(z), str) def test_str_format_contains_zombie_type(self): """str() output contains the zombie type name.""" z = ecf.ZombieAttr(self.ZT.ecf_passwd, [self.CC.wait], self.ZA.remove, 120) - self.assertIn("ecf_passwd", str(z)) + assert "ecf_passwd" in str(z) def test_str_format_contains_action(self): """str() output contains the action name.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.adopt, 60) - self.assertIn("adopt", str(z)) + assert "adopt" in str(z) def test_str_format_contains_lifetime(self): """str() output contains the lifetime as a decimal integer.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, 500) - self.assertIn("500", str(z)) + assert "500" in str(z) def test_str_format_colon_separated(self): """str() follows the pattern 'zombie :::'.""" z = ecf.ZombieAttr( self.ZT.path, [self.CC.abort, self.CC.complete], self.ZA.kill, 100 ) - self.assertEqual(str(z), "zombie path:kill:abort,complete:100") + assert str(z) == "zombie path:kill:abort,complete:100" def test_str_format_no_cmds_section_when_empty(self): """When no child cmds are given the cmds section in str() is empty.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, 3600) s = str(z) - self.assertEqual(s, "zombie ecf:fob::3600") + assert s == "zombie ecf:fob::3600" # ------------------------------------------------------------------ # __eq__ / __ne__ @@ -3760,44 +3628,44 @@ def test_str_format_no_cmds_section_when_empty(self): def test_eq_reflexive(self): """A ZombieAttr is equal to itself.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertEqual(z, z) + assert z == z def test_eq_same_arguments(self): """Two ZombieAttrs created with the same arguments are equal.""" z1 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) z2 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertEqual(z1, z2) + assert z1 == z2 def test_eq_symmetric(self): """Equality is symmetric.""" z1 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) z2 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertTrue(z1 == z2) - self.assertTrue(z2 == z1) + assert z1 == z2 + assert z2 == z1 def test_ne_different_zombie_type(self): """ZombieAttrs differing only in ZombieType are not equal.""" z1 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) z2 = ecf.ZombieAttr(self.ZT.user, [], self.ZA.fob) - self.assertNotEqual(z1, z2) + assert z1 != z2 def test_ne_different_action(self): """ZombieAttrs differing only in user_action are not equal.""" z1 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) z2 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fail) - self.assertNotEqual(z1, z2) + assert z1 != z2 def test_ne_different_lifetime(self): """ZombieAttrs differing only in lifetime are not equal.""" z1 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, 100) z2 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob, 200) - self.assertNotEqual(z1, z2) + assert z1 != z2 def test_ne_different_child_cmds(self): """ZombieAttrs differing only in child_cmds are not equal.""" z1 = ecf.ZombieAttr(self.ZT.ecf, [self.CC.init], self.ZA.fob) z2 = ecf.ZombieAttr(self.ZT.ecf, [self.CC.abort], self.ZA.fob) - self.assertNotEqual(z1, z2) + assert z1 != z2 # ------------------------------------------------------------------ # __copy__ @@ -3809,7 +3677,7 @@ def test_copy_produces_equal_instance(self): orig = ecf.ZombieAttr(self.ZT.ecf_pid, [self.CC.meter], self.ZA.adopt, 60) cop = copy.copy(orig) - self.assertEqual(orig, cop) + assert orig == cop def test_copy_is_independent_object(self): """copy.copy() produces a distinct object (not the same reference).""" @@ -3817,14 +3685,14 @@ def test_copy_is_independent_object(self): orig = ecf.ZombieAttr(self.ZT.ecf_pid, [self.CC.meter], self.ZA.adopt, 60) cop = copy.copy(orig) - self.assertIsNot(orig, cop) + assert orig is not cop def test_copy_is_instance_of_zombie_attr(self): """The result of copy.copy() is a ZombieAttr.""" import copy orig = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertIsInstance(copy.copy(orig), ecf.ZombieAttr) + assert isinstance(copy.copy(orig), ecf.ZombieAttr) # ------------------------------------------------------------------ # __hash__ @@ -3833,27 +3701,27 @@ def test_copy_is_instance_of_zombie_attr(self): def test_is_hashable(self): """ZombieAttr is hashable (boost.python identity-based hash).""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertIsInstance(hash(z), int) + assert isinstance(hash(z), int) def test_hash_is_identity_based(self): """Two value-equal ZombieAttrs may have different hashes (identity, not content).""" z1 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) z2 = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) - self.assertEqual(z1, z2) - self.assertIsNot(z1, z2) - self.assertNotEqual(hash(z1), hash(z2)) + assert z1 == z2 + assert z1 is not z2 + assert hash(z1) != hash(z2) def test_can_be_stored_in_set(self): """ZombieAttr instances can be stored in a Python set.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) s = {z} - self.assertIn(z, s) + assert z in s def test_can_be_used_as_dict_key(self): """ZombieAttr instances can be used as dictionary keys.""" z = ecf.ZombieAttr(self.ZT.ecf, [], self.ZA.fob) d = {z: "value"} - self.assertEqual(d[z], "value") + assert d[z] == "value" # ------------------------------------------------------------------ # Negative: wrong constructor argument types @@ -3861,26 +3729,26 @@ def test_can_be_used_as_dict_key(self): def test_wrong_zombie_type_raises(self): """Passing a str instead of ZombieType raises ArgumentError.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.ZombieAttr("ecf", [], self.ZA.fob) def test_wrong_action_type_raises(self): """Passing a str instead of ZombieUserActionType raises ArgumentError.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.ZombieAttr(self.ZT.ecf, [], "fob") def test_wrong_child_cmd_type_in_list_raises(self): """Passing a str inside the child commands list raises TypeError.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.ZombieAttr(self.ZT.ecf, ["init"], self.ZA.fob) def test_wrong_child_cmd_int_in_list_raises(self): """Passing a plain int inside the child commands list raises TypeError.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.ZombieAttr(self.ZT.ecf, [0], self.ZA.fob) -class TestZombieVec(unittest.TestCase): +class TestZombieVec: """Tests for py::class_>("ZombieVec", ...) as exposed in ExportNodeAttr.cpp. Exposed API @@ -3918,12 +3786,12 @@ def _make_attr(self): def test_default_constructor(self): """ZombieVec() constructs an empty vector.""" zv = ecf.ZombieVec() - self.assertIsInstance(zv, ecf.ZombieVec) + assert isinstance(zv, ecf.ZombieVec) def test_default_constructor_is_empty(self): """A newly constructed ZombieVec has length 0.""" zv = ecf.ZombieVec() - self.assertEqual(len(zv), 0) + assert len(zv) == 0 # ------------------------------------------------------------------ # __len__ @@ -3933,9 +3801,9 @@ def test_len_increases_after_append(self): """len() grows by 1 for each append() call.""" zv = ecf.ZombieVec() zv.append(ecf.Zombie()) - self.assertEqual(len(zv), 1) + assert len(zv) == 1 zv.append(ecf.Zombie()) - self.assertEqual(len(zv), 2) + assert len(zv) == 2 # ------------------------------------------------------------------ # append @@ -3949,19 +3817,19 @@ def test_append_zombie(self): def test_append_wrong_type_raises(self): """append() rejects a non-Zombie argument.""" zv = ecf.ZombieVec() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): zv.append("not_a_zombie") def test_append_int_raises(self): """append() rejects a plain int.""" zv = ecf.ZombieVec() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): zv.append(42) def test_append_none_raises(self): """append() rejects None.""" zv = ecf.ZombieVec() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): zv.append(None) # ------------------------------------------------------------------ @@ -3972,18 +3840,18 @@ def test_extend_empty_list(self): """extend() with an empty list leaves the vector unchanged.""" zv = ecf.ZombieVec() zv.extend([]) - self.assertEqual(len(zv), 0) + assert len(zv) == 0 def test_extend_with_zombies(self): """extend() adds all Zombie instances from a list.""" zv = ecf.ZombieVec() zv.extend([ecf.Zombie(), ecf.Zombie(), ecf.Zombie()]) - self.assertEqual(len(zv), 3) + assert len(zv) == 3 def test_extend_wrong_element_type_raises(self): """extend() rejects a list containing non-Zombie elements.""" zv = ecf.ZombieVec() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): zv.extend(["not_a_zombie"]) # ------------------------------------------------------------------ @@ -3996,12 +3864,12 @@ def test_getitem_after_append(self): z = ecf.Zombie() zv.append(z) item = zv[0] - self.assertIsInstance(item, ecf.Zombie) + assert isinstance(item, ecf.Zombie) def test_getitem_out_of_range_raises_index_error(self): """zv[0] on an empty ZombieVec raises IndexError.""" zv = ecf.ZombieVec() - with self.assertRaises(IndexError): + with pytest.raises(IndexError): _ = zv[0] def test_getitem_negative_index(self): @@ -4010,13 +3878,13 @@ def test_getitem_negative_index(self): zv.append(ecf.Zombie()) zv.append(ecf.Zombie()) item = zv[-1] - self.assertIsInstance(item, ecf.Zombie) + assert isinstance(item, ecf.Zombie) def test_getitem_out_of_range_positive(self): """zv[100] on a short vector raises IndexError.""" zv = ecf.ZombieVec() zv.append(ecf.Zombie()) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): _ = zv[100] # ------------------------------------------------------------------ @@ -4027,21 +3895,21 @@ def test_iteration_empty(self): """Iterating over an empty ZombieVec yields zero items.""" zv = ecf.ZombieVec() count = sum(1 for _ in zv) - self.assertEqual(count, 0) + assert count == 0 def test_iteration_non_empty(self): """Iterating over a ZombieVec yields exactly len(zv) items.""" zv = ecf.ZombieVec() zv.extend([ecf.Zombie(), ecf.Zombie(), ecf.Zombie()]) items = list(zv) - self.assertEqual(len(items), 3) + assert len(items) == 3 def test_iteration_yields_zombie_instances(self): """Each item yielded by iteration is a Zombie.""" zv = ecf.ZombieVec() zv.append(ecf.Zombie()) for item in zv: - self.assertIsInstance(item, ecf.Zombie) + assert isinstance(item, ecf.Zombie) # ------------------------------------------------------------------ # __contains__ @@ -4050,14 +3918,14 @@ def test_iteration_yields_zombie_instances(self): def test_contains_false_for_empty(self): """'in' returns False for an empty ZombieVec.""" zv = ecf.ZombieVec() - self.assertNotIn(ecf.Zombie(), zv) + assert ecf.Zombie() not in zv def test_contains_true_after_append(self): """'in' returns True for the exact same object that was appended.""" zv = ecf.ZombieVec() z = ecf.Zombie() zv.append(z) - self.assertIn(z, zv) + assert z in zv # ------------------------------------------------------------------ # __eq__ (identity-based) @@ -4066,13 +3934,13 @@ def test_contains_true_after_append(self): def test_eq_same_object(self): """A ZombieVec is equal to itself.""" zv = ecf.ZombieVec() - self.assertEqual(zv, zv) + assert zv == zv def test_eq_two_empty_vecs_are_not_equal(self): """Two distinct empty ZombieVec instances are NOT equal (identity-based).""" zv1 = ecf.ZombieVec() zv2 = ecf.ZombieVec() - self.assertNotEqual(zv1, zv2) + assert zv1 != zv2 def test_eq_two_identical_content_vecs_not_equal(self): """Two ZombieVecs with the same elements are still not equal (identity).""" @@ -4081,7 +3949,7 @@ def test_eq_two_identical_content_vecs_not_equal(self): zv2 = ecf.ZombieVec() zv1.append(z) zv2.append(z) - self.assertNotEqual(zv1, zv2) + assert zv1 != zv2 # ------------------------------------------------------------------ # __hash__ @@ -4090,30 +3958,30 @@ def test_eq_two_identical_content_vecs_not_equal(self): def test_is_hashable(self): """ZombieVec is hashable.""" zv = ecf.ZombieVec() - self.assertIsInstance(hash(zv), int) + assert isinstance(hash(zv), int) def test_hash_is_identity_based(self): """Two distinct ZombieVec instances have different hashes.""" zv1 = ecf.ZombieVec() zv2 = ecf.ZombieVec() - self.assertNotEqual(hash(zv1), hash(zv2)) + assert hash(zv1) != hash(zv2) def test_same_object_same_hash(self): """The same ZombieVec object always returns the same hash.""" zv = ecf.ZombieVec() - self.assertEqual(hash(zv), hash(zv)) + assert hash(zv) == hash(zv) def test_can_be_used_in_set(self): """ZombieVec instances can be stored in a Python set.""" zv = ecf.ZombieVec() s = {zv} - self.assertIn(zv, s) + assert zv in s def test_can_be_used_as_dict_key(self): """ZombieVec instances can be used as dictionary keys.""" zv = ecf.ZombieVec() d = {zv: "val"} - self.assertEqual(d[zv], "val") + assert d[zv] == "val" # ------------------------------------------------------------------ # copy / pickle (not supported) @@ -4124,11 +3992,11 @@ def test_copy_raises(self): import copy zv = ecf.ZombieVec() - with self.assertRaises((RuntimeError, TypeError)): + with pytest.raises((RuntimeError, TypeError)): copy.copy(zv) -class TestZombie(unittest.TestCase): +class TestZombie: """Tests for py::class_ exposed as ecf.Zombie in ExportNodeAttr.cpp. Zombie represents a zombie process entry stored by the ecFlow server. @@ -4172,7 +4040,7 @@ class TestZombie(unittest.TestCase): __copy__ -- copy.copy() returns a value-equal, identity-distinct instance """ - def setUp(self): + def setup_method(self): self.z = ecf.Zombie() # ------------------------------------------------------------------ @@ -4181,11 +4049,11 @@ def setUp(self): def test_default_constructor_creates_instance(self): """Zombie() creates a Zombie instance.""" - self.assertIsInstance(ecf.Zombie(), ecf.Zombie) + assert isinstance(ecf.Zombie(), ecf.Zombie) def test_default_constructor_is_empty(self): """A default-constructed Zombie is empty (path_to_task is '').""" - self.assertTrue(ecf.Zombie().empty()) + assert ecf.Zombie().empty() # ------------------------------------------------------------------ # empty @@ -4193,7 +4061,7 @@ def test_default_constructor_is_empty(self): def test_empty_true_on_default(self): """empty() returns True for the default-constructed Zombie.""" - self.assertTrue(self.z.empty()) + assert self.z.empty() # ------------------------------------------------------------------ # manual_user_action @@ -4201,11 +4069,11 @@ def test_empty_true_on_default(self): def test_manual_user_action_false_on_default(self): """manual_user_action() is False on a default Zombie (no action set).""" - self.assertFalse(self.z.manual_user_action()) + assert not self.z.manual_user_action() def test_manual_user_action_returns_bool(self): """manual_user_action() returns a bool.""" - self.assertIsInstance(self.z.manual_user_action(), bool) + assert isinstance(self.z.manual_user_action(), bool) # ------------------------------------------------------------------ # Action predicate methods (fob/fail/adopt/block/remove/kill) @@ -4213,27 +4081,27 @@ def test_manual_user_action_returns_bool(self): def test_fob_false_on_default(self): """fob() is False on a default Zombie.""" - self.assertFalse(self.z.fob()) + assert not self.z.fob() def test_fail_false_on_default(self): """fail() is False on a default Zombie.""" - self.assertFalse(self.z.fail()) + assert not self.z.fail() def test_adopt_false_on_default(self): """adopt() is False on a default Zombie.""" - self.assertFalse(self.z.adopt()) + assert not self.z.adopt() def test_block_true_on_default(self): """block() is True on a default Zombie (default action is BLOCK).""" - self.assertTrue(self.z.block()) + assert self.z.block() def test_remove_false_on_default(self): """remove() is False on a default Zombie.""" - self.assertFalse(self.z.remove()) + assert not self.z.remove() def test_kill_false_on_default(self): """kill() is False on a default Zombie.""" - self.assertFalse(self.z.kill()) + assert not self.z.kill() def test_exactly_one_action_predicate_is_true(self): """Exactly one of fob/fail/adopt/block/remove/kill is True at a time.""" @@ -4245,7 +4113,7 @@ def test_exactly_one_action_predicate_is_true(self): self.z.remove(), self.z.kill(), ] - self.assertEqual(predicates.count(True), 1) + assert predicates.count(True) == 1 # ------------------------------------------------------------------ # type / type_str @@ -4253,23 +4121,23 @@ def test_exactly_one_action_predicate_is_true(self): def test_type_returns_zombie_type(self): """type() returns a ZombieType instance.""" - self.assertIsInstance(self.z.type(), ecf.ZombieType) + assert isinstance(self.z.type(), ecf.ZombieType) def test_type_is_user_on_default(self): """A default Zombie has type ZombieType.user.""" - self.assertEqual(self.z.type(), ecf.ZombieType.user) + assert self.z.type() == ecf.ZombieType.user def test_type_str_returns_str(self): """type_str() returns a str.""" - self.assertIsInstance(self.z.type_str(), str) + assert isinstance(self.z.type_str(), str) def test_type_str_is_user_on_default(self): """type_str() returns 'user' for the default Zombie.""" - self.assertEqual(self.z.type_str(), "user") + assert self.z.type_str() == "user" def test_type_str_consistent_with_type(self): """type_str() is the lowercase name of type().""" - self.assertEqual(self.z.type_str(), str(self.z.type())) + assert self.z.type_str() == str(self.z.type()) # ------------------------------------------------------------------ # last_child_cmd @@ -4277,11 +4145,11 @@ def test_type_str_consistent_with_type(self): def test_last_child_cmd_returns_child_cmd_type(self): """last_child_cmd() returns a ChildCmdType instance.""" - self.assertIsInstance(self.z.last_child_cmd(), ecf.ChildCmdType) + assert isinstance(self.z.last_child_cmd(), ecf.ChildCmdType) def test_last_child_cmd_is_init_on_default(self): """A default Zombie has last_child_cmd ChildCmdType.init.""" - self.assertEqual(self.z.last_child_cmd(), ecf.ChildCmdType.init) + assert self.z.last_child_cmd() == ecf.ChildCmdType.init # ------------------------------------------------------------------ # attr @@ -4289,7 +4157,7 @@ def test_last_child_cmd_is_init_on_default(self): def test_attr_returns_zombie_attr(self): """attr() returns a ZombieAttr instance.""" - self.assertIsInstance(self.z.attr(), ecf.ZombieAttr) + assert isinstance(self.z.attr(), ecf.ZombieAttr) # ------------------------------------------------------------------ # calls @@ -4297,11 +4165,11 @@ def test_attr_returns_zombie_attr(self): def test_calls_returns_int(self): """calls() returns an int.""" - self.assertIsInstance(self.z.calls(), int) + assert isinstance(self.z.calls(), int) def test_calls_zero_on_default(self): """calls() is 0 on a default Zombie (no server contacts yet).""" - self.assertEqual(self.z.calls(), 0) + assert self.z.calls() == 0 # ------------------------------------------------------------------ # String-valued accessors @@ -4309,43 +4177,43 @@ def test_calls_zero_on_default(self): def test_jobs_password_returns_str(self): """jobs_password() returns a str.""" - self.assertIsInstance(self.z.jobs_password(), str) + assert isinstance(self.z.jobs_password(), str) def test_jobs_password_empty_on_default(self): """jobs_password() is empty on a default Zombie.""" - self.assertEqual(self.z.jobs_password(), "") + assert self.z.jobs_password() == "" def test_path_to_task_returns_str(self): """path_to_task() returns a str.""" - self.assertIsInstance(self.z.path_to_task(), str) + assert isinstance(self.z.path_to_task(), str) def test_path_to_task_empty_on_default(self): """path_to_task() is empty on a default Zombie.""" - self.assertEqual(self.z.path_to_task(), "") + assert self.z.path_to_task() == "" def test_process_or_remote_id_returns_str(self): """process_or_remote_id() returns a str.""" - self.assertIsInstance(self.z.process_or_remote_id(), str) + assert isinstance(self.z.process_or_remote_id(), str) def test_process_or_remote_id_empty_on_default(self): """process_or_remote_id() is empty on a default Zombie.""" - self.assertEqual(self.z.process_or_remote_id(), "") + assert self.z.process_or_remote_id() == "" def test_user_cmd_returns_str(self): """user_cmd() returns a str.""" - self.assertIsInstance(self.z.user_cmd(), str) + assert isinstance(self.z.user_cmd(), str) def test_user_cmd_empty_on_default(self): """user_cmd() is empty on a default Zombie (no user command).""" - self.assertEqual(self.z.user_cmd(), "") + assert self.z.user_cmd() == "" def test_host_returns_str(self): """host() returns a str.""" - self.assertIsInstance(self.z.host(), str) + assert isinstance(self.z.host(), str) def test_host_empty_on_default(self): """host() is empty on a default Zombie.""" - self.assertEqual(self.z.host(), "") + assert self.z.host() == "" # ------------------------------------------------------------------ # Integer-valued accessors @@ -4353,27 +4221,27 @@ def test_host_empty_on_default(self): def test_try_no_returns_int(self): """try_no() returns an int.""" - self.assertIsInstance(self.z.try_no(), int) + assert isinstance(self.z.try_no(), int) def test_try_no_zero_on_default(self): """try_no() is 0 on a default Zombie.""" - self.assertEqual(self.z.try_no(), 0) + assert self.z.try_no() == 0 def test_duration_returns_int(self): """duration() returns an int.""" - self.assertIsInstance(self.z.duration(), int) + assert isinstance(self.z.duration(), int) def test_duration_zero_on_default(self): """duration() is 0 on a default Zombie (just created).""" - self.assertEqual(self.z.duration(), 0) + assert self.z.duration() == 0 def test_allowed_age_returns_int(self): """allowed_age() returns an int.""" - self.assertIsInstance(self.z.allowed_age(), int) + assert isinstance(self.z.allowed_age(), int) def test_allowed_age_zero_on_default(self): """allowed_age() is 0 on a default (empty) Zombie.""" - self.assertEqual(self.z.allowed_age(), 0) + assert self.z.allowed_age() == 0 # ------------------------------------------------------------------ # user_action / user_action_str @@ -4381,23 +4249,23 @@ def test_allowed_age_zero_on_default(self): def test_user_action_returns_zombie_user_action_type(self): """user_action() returns a ZombieUserActionType instance.""" - self.assertIsInstance(self.z.user_action(), ecf.ZombieUserActionType) + assert isinstance(self.z.user_action(), ecf.ZombieUserActionType) def test_user_action_is_block_on_default(self): """user_action() is ZombieUserActionType.block on a default Zombie.""" - self.assertEqual(self.z.user_action(), ecf.ZombieUserActionType.block) + assert self.z.user_action() == ecf.ZombieUserActionType.block def test_user_action_str_returns_str(self): """user_action_str() returns a str.""" - self.assertIsInstance(self.z.user_action_str(), str) + assert isinstance(self.z.user_action_str(), str) def test_user_action_str_nonempty_on_default(self): """user_action_str() is non-empty on a default Zombie.""" - self.assertNotEqual(self.z.user_action_str(), "") + assert self.z.user_action_str() != "" def test_user_action_str_contains_block_on_default(self): """user_action_str() contains 'block' for the default action.""" - self.assertIn("block", self.z.user_action_str()) + assert "block" in self.z.user_action_str() # ------------------------------------------------------------------ # __str__ @@ -4405,15 +4273,15 @@ def test_user_action_str_contains_block_on_default(self): def test_str_returns_str(self): """str(Zombie()) returns a str.""" - self.assertIsInstance(str(self.z), str) + assert isinstance(str(self.z), str) def test_str_nonempty(self): """str(Zombie()) is non-empty.""" - self.assertGreater(len(str(self.z)), 0) + assert len(str(self.z)) > 0 def test_str_contains_type(self): """str(Zombie()) contains the zombie type.""" - self.assertIn(self.z.type_str(), str(self.z)) + assert self.z.type_str() in str(self.z) # ------------------------------------------------------------------ # __copy__ @@ -4421,26 +4289,26 @@ def test_str_contains_type(self): def test_copy_returns_zombie(self): """copy.copy(Zombie()) returns a Zombie.""" - self.assertIsInstance(copy.copy(self.z), ecf.Zombie) + assert isinstance(copy.copy(self.z), ecf.Zombie) def test_copy_is_independent(self): """copy.copy(Zombie()) returns a distinct object.""" - self.assertIsNot(copy.copy(self.z), self.z) + assert copy.copy(self.z) is not self.z def test_copy_preserves_empty(self): """copy.copy preserves the empty() value.""" - self.assertEqual(copy.copy(self.z).empty(), self.z.empty()) + assert copy.copy(self.z).empty() == self.z.empty() def test_copy_preserves_type(self): """copy.copy preserves the type().""" - self.assertEqual(copy.copy(self.z).type(), self.z.type()) + assert copy.copy(self.z).type() == self.z.type() def test_copy_preserves_str(self): """copy.copy preserves str().""" - self.assertEqual(str(copy.copy(self.z)), str(self.z)) + assert str(copy.copy(self.z)) == str(self.z) -class TestVariable(unittest.TestCase): +class TestVariable: """Tests for py::class_ as exposed in ExportNodeAttr.cpp. Exposed API @@ -4477,33 +4345,33 @@ class TestVariable(unittest.TestCase): def test_create_stores_name_and_value(self): """Variable(name, value) stores both fields correctly.""" v = ecf.Variable("MY_VAR", "hello") - self.assertEqual(v.name(), "MY_VAR") - self.assertEqual(v.value(), "hello") + assert v.name() == "MY_VAR" + assert v.value() == "hello" def test_create_with_empty_value(self): """An empty string is a valid value.""" v = ecf.Variable("X", "") - self.assertEqual(v.name(), "X") - self.assertEqual(v.value(), "") + assert v.name() == "X" + assert v.value() == "" def test_create_with_value_containing_spaces(self): """Values may contain spaces and special characters.""" v = ecf.Variable("VAR", "some value with spaces") - self.assertEqual(v.value(), "some value with spaces") + assert v.value() == "some value with spaces" def test_create_with_dotted_name(self): """Variable names may contain dots in non-leading positions.""" v = ecf.Variable("ECF_SUITE.NAME", "val") - self.assertEqual(v.name(), "ECF_SUITE.NAME") + assert v.name() == "ECF_SUITE.NAME" def test_create_with_underscore_first_char(self): """Underscore is a valid first character for a variable name.""" v = ecf.Variable("_MY_VAR", "val") - self.assertEqual(v.name(), "_MY_VAR") + assert v.name() == "_MY_VAR" def test_no_default_constructor(self): """Variable() with no arguments raises TypeError; no default constructor is exposed.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): ecf.Variable() # ------------------------------------------------------------------ @@ -4512,22 +4380,22 @@ def test_no_default_constructor(self): def test_empty_name_raises(self): """An empty name string raises RuntimeError.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Variable("", "val") def test_name_with_leading_space_raises(self): """A name starting with a space is rejected by C++ validation.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Variable(" LEADING_SPACE", "val") def test_name_with_internal_space_raises(self): """A name containing an internal space is rejected.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Variable("HAS SPACE", "val") def test_name_with_dash_raises(self): """A name containing a dash is rejected.""" - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ecf.Variable("HAS-DASH", "val") # ------------------------------------------------------------------ @@ -4536,12 +4404,12 @@ def test_name_with_dash_raises(self): def test_create_from_int_name_raises(self): """No constructor accepts an integer as the name.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Variable(42, "val") def test_create_from_none_name_raises(self): """No constructor accepts None as the name.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Variable(None, "val") # ------------------------------------------------------------------ @@ -4551,57 +4419,57 @@ def test_create_from_none_name_raises(self): def test_create_from_int_value_accepted(self): """Variable(name, int) is accepted; the integer is stored as its decimal string.""" v = ecf.Variable("CNT", 42) - self.assertIsInstance(v, ecf.Variable) + assert isinstance(v, ecf.Variable) def test_int_value_stored_as_decimal_string(self): """The integer 42 is stored as the string '42'.""" - self.assertEqual(ecf.Variable("CNT", 42).value(), "42") + assert ecf.Variable("CNT", 42).value() == "42" def test_int_value_zero_stored_as_string(self): """Zero is stored as '0'.""" - self.assertEqual(ecf.Variable("CNT", 0).value(), "0") + assert ecf.Variable("CNT", 0).value() == "0" def test_int_value_negative_stored_as_string(self): """Negative integers are stored as their decimal string representation.""" - self.assertEqual(ecf.Variable("CNT", -5).value(), "-5") + assert ecf.Variable("CNT", -5).value() == "-5" def test_int_value_name_preserved(self): """Variable(name, int) stores the name correctly.""" - self.assertEqual(ecf.Variable("MY_COUNT", 7).name(), "MY_COUNT") + assert ecf.Variable("MY_COUNT", 7).name() == "MY_COUNT" def test_int_ctor_equal_to_str_ctor(self): """Variable('X', 42) is equal to Variable('X', '42') since both store '42'.""" - self.assertEqual(ecf.Variable("X", 42), ecf.Variable("X", "42")) + assert ecf.Variable("X", 42) == ecf.Variable("X", "42") def test_create_from_none_value_raises(self): """No constructor accepts None as the value.""" - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): ecf.Variable("VAR", None) def test_int_value_above_int32_max_accepted(self): """Values above 2^31-1 (C++ int max) are accepted without overflow or TypeError.""" v = ecf.Variable("BIG", 2**31) - self.assertEqual(v.value(), "2147483648") + assert v.value() == "2147483648" def test_int_value_below_int32_min_accepted(self): """Values below -2^31 (C++ int min) are accepted without overflow or TypeError.""" v = ecf.Variable("BIG", -(2**31) - 1) - self.assertEqual(v.value(), "-2147483649") + assert v.value() == "-2147483649" def test_int_value_large_positive(self): """A large Python integer (10**12) is stored as its full decimal string.""" v = ecf.Variable("COUNTER", 10**12) - self.assertEqual(v.value(), "1000000000000") + assert v.value() == "1000000000000" def test_int_value_large_negative(self): """A large negative Python integer (-10**12) is stored as its full decimal string.""" v = ecf.Variable("COUNTER", -(10**12)) - self.assertEqual(v.value(), "-1000000000000") + assert v.value() == "-1000000000000" def test_int_value_int64_max(self): """2^63-1 (int64 max) is stored as its full decimal string.""" v = ecf.Variable("X", 2**63 - 1) - self.assertEqual(v.value(), "9223372036854775807") + assert v.value() == "9223372036854775807" # ------------------------------------------------------------------ # name() @@ -4609,11 +4477,11 @@ def test_int_value_int64_max(self): def test_name_returns_str(self): """name() always returns a Python str.""" - self.assertIsInstance(ecf.Variable("VAR", "val").name(), str) + assert isinstance(ecf.Variable("VAR", "val").name(), str) def test_name_returns_correct_value(self): """name() returns exactly the string passed at construction.""" - self.assertEqual(ecf.Variable("ECFLOW_PATH", "val").name(), "ECFLOW_PATH") + assert ecf.Variable("ECFLOW_PATH", "val").name() == "ECFLOW_PATH" # ------------------------------------------------------------------ # value() @@ -4621,15 +4489,15 @@ def test_name_returns_correct_value(self): def test_value_returns_str(self): """value() always returns a Python str.""" - self.assertIsInstance(ecf.Variable("VAR", "hello").value(), str) + assert isinstance(ecf.Variable("VAR", "hello").value(), str) def test_value_returns_correct_value(self): """value() returns exactly the string passed at construction.""" - self.assertEqual(ecf.Variable("VAR", "my_value").value(), "my_value") + assert ecf.Variable("VAR", "my_value").value() == "my_value" def test_value_empty_string_round_trips(self): """An empty value string round-trips through construction and value().""" - self.assertEqual(ecf.Variable("VAR", "").value(), "") + assert ecf.Variable("VAR", "").value() == "" # ------------------------------------------------------------------ # empty() @@ -4637,20 +4505,20 @@ def test_value_empty_string_round_trips(self): def test_empty_returns_false_for_normal_variable(self): """empty() returns False for every Python-constructed Variable.""" - self.assertFalse(ecf.Variable("VAR", "val").empty()) + assert not ecf.Variable("VAR", "val").empty() def test_empty_returns_false_when_value_is_empty_string(self): """empty() is still False even when the value is an empty string.""" - self.assertFalse(ecf.Variable("VAR", "").empty()) + assert not ecf.Variable("VAR", "").empty() def test_empty_returns_bool(self): """empty() returns a Python bool.""" - self.assertIsInstance(ecf.Variable("VAR", "val").empty(), bool) + assert isinstance(ecf.Variable("VAR", "val").empty(), bool) def test_empty_returns_true_when_result_of_a_failed_find(self): task = ecf.Task("task") v = task.find_variable("NONEXISTENT") - self.assertTrue(v.empty()) + assert v.empty() # ------------------------------------------------------------------ # __str__ @@ -4659,21 +4527,21 @@ def test_empty_returns_true_when_result_of_a_failed_find(self): def test_str_format_is_edit_name_quoted_value(self): """str() follows the format \"edit ''\".""" v = ecf.Variable("MY_VAR", "hello") - self.assertEqual(str(v), "edit MY_VAR 'hello'") + assert str(v) == "edit MY_VAR 'hello'" def test_str_with_empty_value(self): """str() with an empty value wraps the empty string in single quotes.""" v = ecf.Variable("X", "") - self.assertEqual(str(v), "edit X ''") + assert str(v) == "edit X ''" def test_str_with_value_containing_spaces(self): """str() preserves spaces in the value inside the single-quoted section.""" v = ecf.Variable("VAR", "some value") - self.assertEqual(str(v), "edit VAR 'some value'") + assert str(v) == "edit VAR 'some value'" def test_str_returns_str_type(self): """str() always returns a Python str.""" - self.assertIsInstance(str(ecf.Variable("VAR", "val")), str) + assert isinstance(str(ecf.Variable("VAR", "val")), str) # ------------------------------------------------------------------ # __eq__ / __ne__ — value-based (name AND value must match) @@ -4681,31 +4549,31 @@ def test_str_returns_str_type(self): def test_eq_same_name_and_value(self): """Two Variables with identical name and value are equal.""" - self.assertEqual(ecf.Variable("VAR", "val"), ecf.Variable("VAR", "val")) + assert ecf.Variable("VAR", "val") == ecf.Variable("VAR", "val") def test_eq_reflexive(self): """A Variable is equal to itself.""" v = ecf.Variable("VAR", "val") - self.assertEqual(v, v) + assert v == v def test_eq_symmetric(self): """Equality is symmetric.""" v1 = ecf.Variable("VAR", "val") v2 = ecf.Variable("VAR", "val") - self.assertTrue(v1 == v2) - self.assertTrue(v2 == v1) + assert v1 == v2 + assert v2 == v1 def test_ne_different_value_same_name(self): """Different values with the same name are not equal.""" - self.assertNotEqual(ecf.Variable("VAR", "hello"), ecf.Variable("VAR", "world")) + assert ecf.Variable("VAR", "hello") != ecf.Variable("VAR", "world") def test_ne_different_name_same_value(self): """Different names with the same value are not equal.""" - self.assertNotEqual(ecf.Variable("A", "val"), ecf.Variable("B", "val")) + assert ecf.Variable("A", "val") != ecf.Variable("B", "val") def test_ne_both_differ(self): """Different name and different value are not equal.""" - self.assertNotEqual(ecf.Variable("A", "1"), ecf.Variable("B", "2")) + assert ecf.Variable("A", "1") != ecf.Variable("B", "2") # ------------------------------------------------------------------ # __lt__ — name-based ordering only @@ -4713,21 +4581,21 @@ def test_ne_both_differ(self): def test_lt_alphabetically_earlier_name_is_less(self): """Variable with an alphabetically earlier name is less than one with a later name.""" - self.assertLess(ecf.Variable("A", "z"), ecf.Variable("B", "a")) + assert ecf.Variable("A", "z") < ecf.Variable("B", "a") def test_lt_alphabetically_later_name_is_not_less(self): """Variable with a later name is NOT less than one with an earlier name.""" - self.assertFalse(ecf.Variable("B", "a") < ecf.Variable("A", "z")) + assert not ecf.Variable("B", "a") < ecf.Variable("A", "z") def test_lt_same_name_any_value_is_not_less(self): """Two Variables with the same name are never in a less-than relationship, regardless of their values (ordering is name-based only).""" - self.assertFalse(ecf.Variable("X", "aaa") < ecf.Variable("X", "zzz")) - self.assertFalse(ecf.Variable("X", "zzz") < ecf.Variable("X", "aaa")) + assert not ecf.Variable("X", "aaa") < ecf.Variable("X", "zzz") + assert not ecf.Variable("X", "zzz") < ecf.Variable("X", "aaa") def test_lt_same_name_same_value_not_less(self): """A Variable is not strictly less than an equal Variable.""" - self.assertFalse(ecf.Variable("X", "v") < ecf.Variable("X", "v")) + assert not ecf.Variable("X", "v") < ecf.Variable("X", "v") def test_lt_can_be_used_to_sort(self): """sorted() on a list of Variables produces alphabetical-by-name order.""" @@ -4737,7 +4605,7 @@ def test_lt_can_be_used_to_sort(self): ecf.Variable("MIDDLE", "m"), ] sorted_names = [v.name() for v in sorted(variables, key=lambda x: x.name())] - self.assertEqual(sorted_names, ["ALPHA", "MIDDLE", "ZEBRA"]) + assert sorted_names == ["ALPHA", "MIDDLE", "ZEBRA"] # ------------------------------------------------------------------ # __copy__ @@ -4749,7 +4617,7 @@ def test_copy_produces_equal_instance(self): v = ecf.Variable("VAR", "hello") c = copy.copy(v) - self.assertEqual(v, c) + assert v == c def test_copy_is_distinct_object(self): """copy.copy() produces a distinct Python object.""" @@ -4757,13 +4625,13 @@ def test_copy_is_distinct_object(self): v = ecf.Variable("VAR", "hello") c = copy.copy(v) - self.assertIsNot(v, c) + assert v is not c def test_copy_is_variable_instance(self): """The result of copy.copy() is a Variable.""" import copy - self.assertIsInstance(copy.copy(ecf.Variable("VAR", "val")), ecf.Variable) + assert isinstance(copy.copy(ecf.Variable("VAR", "val")), ecf.Variable) def test_copy_name_and_value_match(self): """The copied Variable has the same name() and value() as the original.""" @@ -4771,8 +4639,8 @@ def test_copy_name_and_value_match(self): v = ecf.Variable("MY_NAME", "MY_VALUE") c = copy.copy(v) - self.assertEqual(c.name(), v.name()) - self.assertEqual(c.value(), v.value()) + assert c.name() == v.name() + assert c.value() == v.value() # ------------------------------------------------------------------ # __hash__ — identity-based (boost.python extension type) @@ -4781,32 +4649,32 @@ def test_copy_name_and_value_match(self): def test_is_hashable(self): """Variable is hashable; hash() returns an int.""" v = ecf.Variable("VAR", "val") - self.assertIsInstance(hash(v), int) + assert isinstance(hash(v), int) def test_hash_is_identity_based(self): """Two value-equal Variables have different hashes (identity, not value).""" v1 = ecf.Variable("VAR", "val") v2 = ecf.Variable("VAR", "val") - self.assertEqual(v1, v2) # same value - self.assertIsNot(v1, v2) # different objects - self.assertNotEqual(hash(v1), hash(v2)) # therefore different hashes + assert v1 == v2 # same value + assert v1 is not v2 # different objects + assert hash(v1) != hash(v2) # therefore different hashes def test_same_object_same_hash(self): """The same Variable object always returns the same hash.""" v = ecf.Variable("VAR", "val") - self.assertEqual(hash(v), hash(v)) + assert hash(v) == hash(v) def test_can_be_stored_in_set(self): """Variable instances can be stored in a Python set.""" v = ecf.Variable("VAR", "val") s = {v} - self.assertIn(v, s) + assert v in s def test_can_be_used_as_dict_key(self): """Variable instances can be used as dictionary keys.""" v = ecf.Variable("VAR", "val") d = {v: "entry"} - self.assertEqual(d[v], "entry") + assert d[v] == "entry" # ------------------------------------------------------------------ # py::dynamic_attr() -- arbitrary Python attributes @@ -4816,17 +4684,17 @@ def test_supports_dynamic_attribute(self): """Variable supports setting arbitrary Python attributes (py::dynamic_attr()).""" v = ecf.Variable("VAR", "val") v.my_custom = "sentinel" - self.assertEqual(v.my_custom, "sentinel") + assert v.my_custom == "sentinel" def test_dynamic_attribute_deletion(self): """A dynamic attribute can be deleted from a Variable instance.""" v = ecf.Variable("VAR", "val") v.tag = 99 del v.tag - self.assertFalse(hasattr(v, "tag")) + assert not hasattr(v, "tag") -class TestVariableList(unittest.TestCase): +class TestVariableList: """Tests for py::class_>("VariableList", ...) as exposed in ExportNodeAttr.cpp. VariableList is exposed via vector_indexing_suite (without NoProxy=true), providing a @@ -4863,11 +4731,11 @@ class TestVariableList(unittest.TestCase): def test_default_constructor(self): """VariableList() creates an empty list.""" vl = ecf.VariableList() - self.assertIsInstance(vl, ecf.VariableList) + assert isinstance(vl, ecf.VariableList) def test_default_constructor_is_empty(self): """A newly constructed VariableList has length 0.""" - self.assertEqual(len(ecf.VariableList()), 0) + assert len(ecf.VariableList()) == 0 # ------------------------------------------------------------------ # __len__ @@ -4877,9 +4745,9 @@ def test_len_increases_after_append(self): """len() grows by 1 for each append() call.""" vl = ecf.VariableList() vl.append(ecf.Variable("A", "1")) - self.assertEqual(len(vl), 1) + assert len(vl) == 1 vl.append(ecf.Variable("B", "2")) - self.assertEqual(len(vl), 2) + assert len(vl) == 2 # ------------------------------------------------------------------ # append @@ -4893,19 +4761,19 @@ def test_append_variable(self): def test_append_wrong_type_str_raises(self): """append() rejects a plain string.""" vl = ecf.VariableList() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): vl.append("not_a_variable") def test_append_wrong_type_int_raises(self): """append() rejects a plain integer.""" vl = ecf.VariableList() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): vl.append(42) def test_append_wrong_type_none_raises(self): """append() rejects None.""" vl = ecf.VariableList() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): vl.append(None) # ------------------------------------------------------------------ @@ -4916,7 +4784,7 @@ def test_extend_empty_list(self): """extend([]) leaves the VariableList unchanged.""" vl = ecf.VariableList() vl.extend([]) - self.assertEqual(len(vl), 0) + assert len(vl) == 0 def test_extend_with_variables(self): """extend() adds all Variables from an iterable.""" @@ -4924,12 +4792,12 @@ def test_extend_with_variables(self): vl.extend( [ecf.Variable("A", "1"), ecf.Variable("B", "2"), ecf.Variable("C", "3")] ) - self.assertEqual(len(vl), 3) + assert len(vl) == 3 def test_extend_wrong_element_type_raises(self): """extend() rejects a list containing non-Variable elements.""" vl = ecf.VariableList() - with self.assertRaises((TypeError, RuntimeError)): + with pytest.raises((TypeError, RuntimeError)): vl.extend(["not_a_variable"]) # ------------------------------------------------------------------ @@ -4940,33 +4808,33 @@ def test_getitem_returns_variable(self): """vl[0] returns a Variable instance.""" vl = ecf.VariableList() vl.append(ecf.Variable("A", "1")) - self.assertIsInstance(vl[0], ecf.Variable) + assert isinstance(vl[0], ecf.Variable) def test_getitem_name_and_value_are_correct(self): """vl[0] returns the Variable with the correct name and value.""" vl = ecf.VariableList() vl.append(ecf.Variable("MY_VAR", "my_value")) - self.assertEqual(vl[0].name(), "MY_VAR") - self.assertEqual(vl[0].value(), "my_value") + assert vl[0].name() == "MY_VAR" + assert vl[0].value() == "my_value" def test_getitem_negative_index(self): """vl[-1] returns the last element.""" vl = ecf.VariableList() vl.append(ecf.Variable("A", "1")) vl.append(ecf.Variable("B", "2")) - self.assertEqual(vl[-1].name(), "B") + assert vl[-1].name() == "B" def test_getitem_out_of_range_raises(self): """Accessing an out-of-range index raises IndexError.""" vl = ecf.VariableList() - with self.assertRaises(IndexError): + with pytest.raises(IndexError): _ = vl[0] def test_getitem_large_positive_index_raises(self): """vl[99] on a short list raises IndexError.""" vl = ecf.VariableList() vl.append(ecf.Variable("A", "1")) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): _ = vl[99] # ------------------------------------------------------------------ @@ -4976,20 +4844,20 @@ def test_getitem_large_positive_index_raises(self): def test_iteration_empty(self): """Iterating over an empty VariableList yields zero items.""" vl = ecf.VariableList() - self.assertEqual(sum(1 for _ in vl), 0) + assert sum(1 for _ in vl) == 0 def test_iteration_yields_correct_count(self): """Iterating yields exactly len(vl) items.""" vl = ecf.VariableList() vl.extend([ecf.Variable("A", "1"), ecf.Variable("B", "2")]) - self.assertEqual(len(list(vl)), 2) + assert len(list(vl)) == 2 def test_iteration_yields_variable_instances(self): """Every item yielded by iteration is a Variable.""" vl = ecf.VariableList() vl.append(ecf.Variable("V", "val")) for item in vl: - self.assertIsInstance(item, ecf.Variable) + assert isinstance(item, ecf.Variable) def test_iteration_preserves_insertion_order(self): """Items are yielded in insertion order.""" @@ -4998,7 +4866,7 @@ def test_iteration_preserves_insertion_order(self): [ecf.Variable("A", "1"), ecf.Variable("B", "2"), ecf.Variable("C", "3")] ) names = [v.name() for v in vl] - self.assertEqual(names, ["A", "B", "C"]) + assert names == ["A", "B", "C"] # ------------------------------------------------------------------ # __contains__ — value-based (uses Variable.__eq__) @@ -5007,26 +4875,26 @@ def test_iteration_preserves_insertion_order(self): def test_contains_false_for_empty(self): """'in' returns False on an empty VariableList.""" vl = ecf.VariableList() - self.assertNotIn(ecf.Variable("A", "1"), vl) + assert ecf.Variable("A", "1") not in vl def test_contains_true_for_value_equal_variable(self): """'in' returns True when a value-equal Variable is present, even if it is a different Python object (value-based __contains__).""" vl = ecf.VariableList() vl.append(ecf.Variable("A", "1")) - self.assertIn(ecf.Variable("A", "1"), vl) # new object, same value + assert ecf.Variable("A", "1") in vl # new object, same value def test_contains_false_for_different_value(self): """'in' returns False if the value does not match.""" vl = ecf.VariableList() vl.append(ecf.Variable("A", "1")) - self.assertNotIn(ecf.Variable("A", "DIFFERENT"), vl) + assert ecf.Variable("A", "DIFFERENT") not in vl def test_contains_false_for_different_name(self): """'in' returns False if the name does not match.""" vl = ecf.VariableList() vl.append(ecf.Variable("A", "1")) - self.assertNotIn(ecf.Variable("Z", "1"), vl) + assert ecf.Variable("Z", "1") not in vl # ------------------------------------------------------------------ # __eq__ — identity-based @@ -5035,11 +4903,11 @@ def test_contains_false_for_different_name(self): def test_eq_same_object(self): """A VariableList is equal to itself.""" vl = ecf.VariableList() - self.assertEqual(vl, vl) + assert vl == vl def test_eq_two_empty_lists_are_not_equal(self): """Two distinct empty VariableLists are NOT equal (identity-based).""" - self.assertNotEqual(ecf.VariableList(), ecf.VariableList()) + assert ecf.VariableList() != ecf.VariableList() def test_eq_two_lists_with_same_content_are_not_equal(self): """Two VariableLists with identical contents are not equal (identity, not value).""" @@ -5047,7 +4915,7 @@ def test_eq_two_lists_with_same_content_are_not_equal(self): vl2 = ecf.VariableList() vl1.append(ecf.Variable("A", "1")) vl2.append(ecf.Variable("A", "1")) - self.assertNotEqual(vl1, vl2) + assert vl1 != vl2 # ------------------------------------------------------------------ # __hash__ — identity-based @@ -5056,33 +4924,33 @@ def test_eq_two_lists_with_same_content_are_not_equal(self): def test_is_hashable(self): """VariableList is hashable; hash() returns an int.""" vl = ecf.VariableList() - self.assertIsInstance(hash(vl), int) + assert isinstance(hash(vl), int) def test_hash_is_identity_based(self): """Two distinct VariableLists have different hashes.""" vl1 = ecf.VariableList() vl2 = ecf.VariableList() - self.assertNotEqual(hash(vl1), hash(vl2)) + assert hash(vl1) != hash(vl2) def test_same_object_same_hash(self): """The same VariableList always returns the same hash.""" vl = ecf.VariableList() - self.assertEqual(hash(vl), hash(vl)) + assert hash(vl) == hash(vl) def test_can_be_stored_in_set(self): """VariableList instances can be stored in a Python set.""" vl = ecf.VariableList() s = {vl} - self.assertIn(vl, s) + assert vl in s def test_can_be_used_as_dict_key(self): """VariableList instances can be used as dictionary keys.""" vl = ecf.VariableList() d = {vl: "entry"} - self.assertEqual(d[vl], "entry") + assert d[vl] == "entry" -class TestLabel(unittest.TestCase): +class TestLabel: """Tests for py::class_