diff --git a/README.rst b/README.rst index 4ce7e00b9..8cfa3ccad 100644 --- a/README.rst +++ b/README.rst @@ -110,7 +110,7 @@ must be ``None``, other parameters can be optionally specified.: from ansys.optislang.core import Optislang osl = Optislang() - osl.shutdown() + osl.dispose() Connect to a remote optiSLang server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -125,7 +125,7 @@ related to the execution of the new optiSLang server are ignored.: host = "127.0.0.1" port = 5310 osl = Optislang(host=host, port=port) - osl.shutdown() + osl.dispose() Basic usage ~~~~~~~~~~~ @@ -137,7 +137,7 @@ Basic usage file_path = r"C:\Users\Username\my_scripts\myscript.py" osl.run_python_file(path=script_path) osl.save_copy("MyNewProject.opf") - osl.shutdown() + osl.dispose() License and acknowledgments --------------------------- diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst index c6f0e633e..a03f90cb6 100644 --- a/doc/source/getting_started/index.rst +++ b/doc/source/getting_started/index.rst @@ -71,7 +71,7 @@ Verify installation >>> from ansys.optislang.core import Optislang >>> osl = Optislang() >>> print(osl) - >>> osl.shutdown() + >>> osl.dispose() If you see a response from the server, congratulations, you're ready to get started using OptiSLang as a service. For details regarding the diff --git a/doc/source/user_guide/functions.rst b/doc/source/user_guide/functions.rst index 3d4c8d567..6ef1cf463 100644 --- a/doc/source/user_guide/functions.rst +++ b/doc/source/user_guide/functions.rst @@ -1,7 +1,8 @@ .. _ref_functions: +=========== Basic usage ------------ +=========== In order to start project, use :func:`start ` (example :ref:`ref_simple_calculator` can be used): @@ -37,3 +38,10 @@ Or via running specific requests: print(f'Location: {osl.get_project_location()}') print(f'Name: {osl.get_project_name()}') print(f'Status: {osl.get_project_status()}') + +When the :class:`Optislang() ` instance is no longer +needed, terminate connection with optiSLang server by running: + +.. code:: python + + osl.dispose() \ No newline at end of file diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index 69a11e2fe..1a5f9983f 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -35,13 +35,27 @@ by the ``project_path`` parameter of the path = os.getcwd() file_name = 'test_optislang.opf' osl = Optislang(project_path = os.path.join(path, file_name)) - osl.shutdown() + osl.dispose() If the project file exists, it is opened; otherwise, a new project file is created on the specified -path. Please note that the :func:`shutdown() ` -function should be executed when the :class:`Optislang ` -instance is no more needed. +path. Please note that :class:`Optislang ` +instance should be always gracefully terminated when it's no longer in use by +:func:`dispose() ` method. OptiSLang server may be +optionally terminated by :func:`shutdown() ` +(this must be done before :func:`dispose() ` +method and it's not needed when started with default parameter ``shutdown_on_finished=True``). + + +Difference in these termination methods is that method +:func:`dispose() ` only terminates connection +with optiSLang server, method +:func:`shutdown() ` sends command +to terminate server, which is necessary when (server is started locally by instance of +:class:`Optislang ` with parameter +``shutdown_on_finished=False`` or connected to a remote optiSLang server) AND termination of optiSLang +server is requested. + The :class:`Optislang ` class provides several functions which enable to control or query the project. The following example shows how to open an existing project @@ -55,7 +69,7 @@ and run it using the :func:`start() ` can either launch optiSLang server +locally or it may connect to already running optiSLang server. This instance should be terminated +gracefully when it's no longer in use by calling +:func:`dispose() ` method. Therefore, it is +recommended to use instance of :class:`Optislang ` +as a context manager, that will execute +:func:`dispose() ` method automatically even +when error is raised. + +Launching optiSLang locally +--------------------------- In order to run, ``ansys.optislang.core`` needs to know the location of the optiSLang. Most of the time this can be automatically determined, but non-standard installs needs to provide the location of optiSLang. You can start optiSLang by running: @@ -11,7 +24,7 @@ to provide the location of optiSLang. You can start optiSLang by running: from ansys.optislang.core import Optislang osl = Optislang() print(osl) - osl.shutdown() + osl.dispose() List of all automatically detected, supported executables of optiSLang can be obtained by running: @@ -30,7 +43,7 @@ from list preceding, launch :class:`Optislang ` with parameter @@ -46,7 +59,36 @@ In order to open specific project or create new one, launch osl = Optislang(project_path = os.path.join(path, project_name)) print(osl) + osl.dispose() + +Keep optiSLang server running +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Default setting of optiSLang server is ``shutdown_on_finished=True``. This means that optiSLang +server is terminated automatically after +:func:`dispose() ` method was called. +If user wishes to keep optiSLang server running even after disposing +:class:`Optislang ` instance, parameter +``shutdown_on_finished=False`` must be used when creating new instance. + +.. code:: python + + from ansys.optislang.core import Optislang + + osl = Optislang(shutdown_on_finished=False) + print(osl) + osl.dispose() + +In order to terminate optiSLang server launched this way, use +:func:`shutdown() ` method: + +.. code:: python + + from ansys.optislang.core import Optislang + + osl = Optislang(shutdown_on_finished=False) + print(osl) osl.shutdown() + osl.dispose() Connect to a remote instance of optiSLang ----------------------------------------- @@ -56,7 +98,51 @@ related to the execution of the new optiSLang server are ignored. .. code:: python - from ansys.optislang.core import Optislang - osl = Optislang(host = "127.0.0.1", port = 49690) + from ansys.optislang.core import Optislang, OslServerProcess + import time + + server_process = OslServerProcess(shutdown_on_finished=False, logger=logger) + server_process.start() + time.sleep(5) # wait for launching of server process + + # connect to optiSLang server and terminate connection afterward + osl = Optislang(host = "127.0.0.1", port = 5310) + print(osl) + osl.dispose() + + # connect to optiSLang server and terminate server afterward + osl = Optislang(host = "127.0.0.1", port = 5310) print(osl) osl.shutdown() + osl.dispose() + +Context manager +--------------- +It is recommended to use +:class:`Optislang() ` as a context manager. Main advantage +of this approach is that instance of :class:`Optislang() ` +and connection to optiSLang server will be terminated gracefully even if an error occurs by calling +:func:`dispose() ` method automatically. + +.. code:: python + + from ansys.optislang.core import Optislang + with Optislang() as osl: + print(osl) + osl.start() + +.. note:: + + When instance of :class:`Optislang ` is started + with argument ``shutdown_on_finished=False`` or connected to optiSLang server started with + such setting, default behaviour is to terminate connection and keep optiSLang server running. + In order to terminate optiSLang server, method + :func:`shutdown() ` has to be used. + + .. code:: python + + from ansys.optislang.core import Optislang + with Optislang(shutdown_on_finished=False) as osl: + print(osl) + osl.start() + osl.shutdown() diff --git a/doc/source/user_guide/run_python.rst b/doc/source/user_guide/run_python.rst index 7106f442d..8f198a89c 100644 --- a/doc/source/user_guide/run_python.rst +++ b/doc/source/user_guide/run_python.rst @@ -1,7 +1,8 @@ .. _ref_run_python: +============================================ Executing commands from optiSLang Python API ------------------------------------------------ +============================================ When optiSLang is active, you can send individual python commands to it as a genuine a Python class. For example, if you want to get general information about sensitivity actor, you would call: @@ -12,7 +13,7 @@ you would call: osl = Optislang() print(osl.run_python_script("""help(actors.SensitivityActor)""")) - osl.shutdown() + osl.dispose() .. note:: Be aware that each time @@ -32,4 +33,4 @@ calculator (see example :ref:`ref_simple_calculator`): osl = Optislang() path_to_file = examples.get_files('simple_calculator')[0] osl.run_python_file(file_path=path_to_file) - osl.shutdown() + osl.dispose() diff --git a/doc/source/user_guide/troubleshooting.rst b/doc/source/user_guide/troubleshooting.rst index f5887065e..178bff5a1 100644 --- a/doc/source/user_guide/troubleshooting.rst +++ b/doc/source/user_guide/troubleshooting.rst @@ -11,7 +11,7 @@ parameter ``loglevel`` when launching optiSLang: from ansys.optislang.core import Optislang osl = Optislang(loglevel='DEBUG') - osl.shutdown() + osl.dispose() Common issues ------------- @@ -25,6 +25,6 @@ when launching optiSLang: from ansys.optislang.core import Optislang osl = Optislang(ini_timeout=30) - osl.shutdown() + osl.dispose() diff --git a/examples/01_ten_bar_truss.py b/examples/01_ten_bar_truss.py index 27014eaac..b7760dd29 100644 --- a/examples/01_ten_bar_truss.py +++ b/examples/01_ten_bar_truss.py @@ -50,7 +50,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/02_1_oscillator_robustness.py b/examples/02_1_oscillator_robustness.py index 6148a799f..3b93a2914 100644 --- a/examples/02_1_oscillator_robustness.py +++ b/examples/02_1_oscillator_robustness.py @@ -44,7 +44,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/02_2_oscillator_python_system.py b/examples/02_2_oscillator_python_system.py index d069b877b..28f72765c 100644 --- a/examples/02_2_oscillator_python_system.py +++ b/examples/02_2_oscillator_python_system.py @@ -44,7 +44,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/02_3_oscillator_optimization_on_EA.py b/examples/02_3_oscillator_optimization_on_EA.py index b19f035de..3c330990a 100644 --- a/examples/02_3_oscillator_optimization_on_EA.py +++ b/examples/02_3_oscillator_optimization_on_EA.py @@ -45,7 +45,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/02_4_oscillator_MOP_sensitivity_and_optimization.py b/examples/02_4_oscillator_MOP_sensitivity_and_optimization.py index 8a8767e9a..8b25cfdc9 100644 --- a/examples/02_4_oscillator_MOP_sensitivity_and_optimization.py +++ b/examples/02_4_oscillator_MOP_sensitivity_and_optimization.py @@ -49,7 +49,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/02_5_oscillator_calibration_systems.py b/examples/02_5_oscillator_calibration_systems.py index b89b525b8..43c334a8c 100644 --- a/examples/02_5_oscillator_calibration_systems.py +++ b/examples/02_5_oscillator_calibration_systems.py @@ -48,7 +48,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/03_etk_abaqus.py b/examples/03_etk_abaqus.py index c230fee5a..7371fcacb 100644 --- a/examples/03_etk_abaqus.py +++ b/examples/03_etk_abaqus.py @@ -39,7 +39,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/04_python_node_and_help.py b/examples/04_python_node_and_help.py index 8782b35e4..ab9a15c07 100644 --- a/examples/04_python_node_and_help.py +++ b/examples/04_python_node_and_help.py @@ -42,7 +42,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/05_optimizer_settings.py b/examples/05_optimizer_settings.py index 40ba89cfd..a53b70996 100644 --- a/examples/05_optimizer_settings.py +++ b/examples/05_optimizer_settings.py @@ -40,7 +40,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/06_sensitivity_settings.py b/examples/06_sensitivity_settings.py index 21291901c..b93199f43 100644 --- a/examples/06_sensitivity_settings.py +++ b/examples/06_sensitivity_settings.py @@ -39,7 +39,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/examples/07_simple_calculator.py b/examples/07_simple_calculator.py index 77281fe19..a88d947e8 100644 --- a/examples/07_simple_calculator.py +++ b/examples/07_simple_calculator.py @@ -44,7 +44,7 @@ ######################################################### # Terminate and cancel project. ######################################################### -osl.shutdown() +osl.dispose() ######################################################### # Generated workflow: diff --git a/src/ansys/optislang/core/optislang.py b/src/ansys/optislang/core/optislang.py index 377c3f474..0b10a2ecc 100644 --- a/src/ansys/optislang/core/optislang.py +++ b/src/ansys/optislang/core/optislang.py @@ -71,7 +71,7 @@ class Optislang: >>> osl = Optislang() >>> osl_version = osl.get_osl_version_string() >>> print(osl_version) - >>> osl.shutdown() + >>> osl.dispose() """ def __init__( @@ -143,6 +143,29 @@ def __str__(self): f"PyOptiSLang: {version('ansys.optislang.core')}" ) + def __enter__(self): + """Enter the context.""" + self.log.debug("Enter the context.") + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + """Exit the context. + + Disposes instance of Optislang. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with server. + OslCommandError + Raised when the command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + self.log.debug("Exit the context.") + if self.__osl_server: + self.dispose() + @property def name(self) -> str: """Instance unique identifier.""" @@ -158,6 +181,21 @@ def log(self): """Return instance logger.""" return self.__logger + def dispose(self) -> None: + """Terminate all local threads and unregister listeners. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with server. + OslCommandError + Raised when the command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + self.__osl_server.dispose() + self.__osl_server = None + def get_osl_version_string(self) -> str: """Get version of used optiSLang. @@ -441,7 +479,7 @@ def set_timeout(self, timeout: Union[float, None] = None) -> None: self.__osl_server.set_timeout(timeout) def shutdown(self, force: bool = False) -> None: - """Shutdown the server. + """Shutdown the optiSLang server. Stop listening for incoming connections, discard pending requests, and shut down the server. Batch mode exclusive: Continue project run until execution finished. @@ -453,8 +491,8 @@ def shutdown(self, force: bool = False) -> None: Determines whether to force shutdown the local optiSLang server. Has no effect when the connection is established to the remote optiSLang server. In all cases, it is tried to shutdown the optiSLang server process in a proper way. However, if the force - parameter is ``True``, after a while, the process is forced to terminate and - no exception is raised. Defaults to ``False``. + parameter is ``True``, after a while, the process is forced to terminate and no + exception is raised. Defaults to ``False``. Raises ------ @@ -467,6 +505,7 @@ def shutdown(self, force: bool = False) -> None: Raised when the parameter force is ``False`` and the timeout float value expires. """ self.__osl_server.shutdown(force) + self.__osl_server = None def start(self, wait_for_started: bool = True, wait_for_finished: bool = True) -> None: """Start project execution. diff --git a/src/ansys/optislang/core/osl_process.py b/src/ansys/optislang/core/osl_process.py index 4740e0bae..8785f9307 100644 --- a/src/ansys/optislang/core/osl_process.py +++ b/src/ansys/optislang/core/osl_process.py @@ -654,9 +654,12 @@ def terminate(self): self.__terminate_osl_child_processes() self.__process.terminate() - if self.__handle_process_output_thread is not None: + if ( + self.__handle_process_output_thread is not None + and self.__handle_process_output_thread.is_alive() + ): self.__handle_process_output_thread.join() - self.__handle_process_output_thread = None + self.__handle_process_output_thread = None if self.__tempdir is not None: self.__tempdir.cleanup() @@ -692,13 +695,10 @@ def finalize_process(process, **kwargs): True, self._logger, ), + daemon=True, ) self.__handle_process_output_thread.start() - def __del__(self): - """Terminates optiSLang server process.""" - self.terminate() - @staticmethod def __handle_process_output( process: subprocess.Popen, diff --git a/src/ansys/optislang/core/osl_server.py b/src/ansys/optislang/core/osl_server.py index 45dae5213..7d93d3ae1 100644 --- a/src/ansys/optislang/core/osl_server.py +++ b/src/ansys/optislang/core/osl_server.py @@ -23,6 +23,21 @@ def close(self) -> None: """ pass + @abstractmethod + def dispose(self) -> None: + """Terminate all local threads and unregister listeners. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with server. + OslCommandError + Raised when the command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + pass + @abstractmethod def get_osl_version_string(self) -> str: """Get version of used optiSLang. @@ -414,7 +429,7 @@ def set_timeout(self, timeout: Union[float, None] = None) -> None: @abstractmethod def shutdown(self, force: bool = False) -> None: - """Shutdown the server. + """Shutdown the optiSLang server. Stop listening for incoming connections, discard pending requests, and shut down the server. Batch mode exclusive: Continue project run until execution finished. @@ -426,8 +441,8 @@ def shutdown(self, force: bool = False) -> None: Determines whether to force shutdown the local optiSLang server. Has no effect when the connection is established to the remote optiSLang server. In all cases, it is tried to shutdown the optiSLang server process in a proper way. However, if the force - parameter is ``True``, after a while, the process is forced to terminate and - no exception is raised. Defaults to ``False``. + parameter is ``True``, after a while, the process is forced to terminate and no + exception is raised. Defaults to ``False``. Raises ------ diff --git a/src/ansys/optislang/core/server_commands.py b/src/ansys/optislang/core/server_commands.py index c5b6c0fef..a5777a1b9 100644 --- a/src/ansys/optislang/core/server_commands.py +++ b/src/ansys/optislang/core/server_commands.py @@ -1330,7 +1330,7 @@ def stop_gently(password: str = None) -> str: Returns ------- str - JSON string of ``shutdown`` command. + JSON string of ``stop_gently`` command. """ return _to_json(_gen_server_command(command=_STOP_GENTLY, password=password)) diff --git a/src/ansys/optislang/core/tcp_osl_server.py b/src/ansys/optislang/core/tcp_osl_server.py index 64fab361d..e7e597068 100644 --- a/src/ansys/optislang/core/tcp_osl_server.py +++ b/src/ansys/optislang/core/tcp_osl_server.py @@ -1,4 +1,5 @@ """Contains classes for plain TCP/IP communication with server.""" +import atexit from datetime import datetime import json import logging @@ -7,6 +8,7 @@ from queue import Queue import re import select +import signal import socket import struct import threading @@ -902,6 +904,8 @@ def __init__( self.__listeners_registration_thread = None self.__refresh_listeners = threading.Event() self.__listeners_refresh_interval = 20 + signal.signal(signal.SIGINT, self.__signal_handler) + atexit.register(self.dispose) if self.__host is None or self.__port is None: self.__host = self.__class__._LOCALHOST @@ -980,6 +984,21 @@ def close(self) -> None: """ raise NotImplementedError("Currently, command is not supported in batch mode.") + def dispose(self) -> None: + """Terminate all local threads and unregister listeners. + + Raises + ------ + OslCommunicationError + Raised when an error occurs while communicating with server. + OslCommandError + Raised when the command or query fails. + TimeoutError + Raised when the timeout float value expires. + """ + self.__stop_listeners_registration_thread() + self.__unregister_all_listeners() + def get_osl_version_string(self) -> str: """Get version of used optiSLang. @@ -1424,7 +1443,7 @@ def set_timeout(self, timeout: Union[float, None] = None) -> None: listener.timeout = timeout def shutdown(self, force: bool = False) -> None: - """Shutdown the server. + """Shutdown the optiSLang server. Stop listening for incoming connections, discard pending requests, and shut down the server. Batch mode exclusive: Continue project run until execution finished. @@ -1436,8 +1455,8 @@ def shutdown(self, force: bool = False) -> None: Determines whether to force shutdown the local optiSLang server. Has no effect when the connection is established to the remote optiSLang server. In all cases, it is tried to shutdown the optiSLang server process in a proper way. However, if the force - parameter is ``True``, after a while, the process is forced to terminate and - no exception is raised. Defaults to ``False``. + parameter is ``True``, after a while, the process is forced to terminate and no + exception is raised. Defaults to ``False``. Raises ------ @@ -1449,11 +1468,14 @@ def shutdown(self, force: bool = False) -> None: TimeoutError Raised when the parameter force is ``False`` and the timeout float value expires. """ - self.__finish_all_threads() + self.__stop_listeners_registration_thread() + self.__unregister_all_listeners() # Only in case shutdown_on_finished option is not set, actively send shutdown command if self.__osl_process is None or ( - self.__osl_process is not None and not self.__osl_process.shutdown_on_finished + self.__osl_process is not None + and self.__osl_process is None + or (self.__osl_process is not None and not self.__osl_process.shutdown_on_finished) ): try: self._send_command(commands.shutdown(self.__password)) @@ -1786,6 +1808,11 @@ def _start_local(self, ini_timeout: float, shutdown_on_finished: bool) -> None: self.__listeners["main_listener"] = listener self.__start_listeners_registration_thread() + def __signal_handler(self, signum, frame): + self._logger.error("Interrupt from keyboard (CTRL + C), terminating execution.") + self.dispose() + raise KeyboardInterrupt + def __create_listener(self, timeout: float, name: str, uid: str = None) -> TcpOslListener: """Create new listener. @@ -2011,9 +2038,8 @@ def __refresh_listeners_registration(self) -> None: time.sleep(check_for_refresh) self._logger.debug("Stop refreshing listener registration, self.__refresh = False") - def __finish_all_threads(self) -> None: - """Stop listeners registration and unregister them.""" - self.__stop_listeners_registration_thread() + def __unregister_all_listeners(self) -> None: + """Unregister all instance listeners.""" for listener in self.__listeners.values(): if listener.uid is not None: try: diff --git a/tests/test_optislang.py b/tests/test_optislang.py index f2b32638b..51da02584 100644 --- a/tests/test_optislang.py +++ b/tests/test_optislang.py @@ -28,7 +28,7 @@ def test_get_osl_version_string(optislang): version = optislang.get_osl_version_string() assert isinstance(version, str) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -49,7 +49,7 @@ def test_get_project_description(optislang): description = optislang.get_project_description() assert isinstance(description, str) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -58,7 +58,7 @@ def test_get_project_location(optislang): location = optislang.get_project_location() assert isinstance(location, Path) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -67,7 +67,7 @@ def test_get_project_name(optislang): name = optislang.get_project_name() assert isinstance(name, str) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -76,7 +76,7 @@ def test_get_project_status(optislang): status = optislang.get_project_status() assert isinstance(status, str) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -85,7 +85,7 @@ def test_get_working_dir(optislang): working_dir = optislang.get_working_dir() assert isinstance(working_dir, Path) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -95,7 +95,7 @@ def test_reset(optislang): optislang.reset() assert dnr is None with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -112,7 +112,7 @@ def test_run_python_script(optislang): assert isinstance(run_script, tuple) assert run_script[0][0:2] == "15" with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -130,7 +130,7 @@ def test_run_python_file(optislang, tmp_path): run_file = optislang.run_python_file(file_path=cmd_path) assert isinstance(run_file, tuple) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -142,7 +142,7 @@ def test_save_copy(optislang, tmp_path): assert dnr is None assert os.path.isfile(copy_path) with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -152,7 +152,7 @@ def test_start(optislang): optislang.start() assert dnr is None with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -177,7 +177,7 @@ def test_stop(optislang): optislang.stop() assert dnr is None with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None @@ -202,12 +202,12 @@ def test_stop_gently(optislang): optislang.stop_gently() assert dnr is None with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None -def test_shutdown(optislang): - "Test ``shutdown``." +def test_dispose(optislang): + "Test ``dispose``." with does_not_raise() as dnr: - optislang.shutdown() + optislang.dispose() assert dnr is None diff --git a/tests/test_optislang_termination.py b/tests/test_optislang_termination.py new file mode 100644 index 000000000..a7b830c9b --- /dev/null +++ b/tests/test_optislang_termination.py @@ -0,0 +1,235 @@ +from contextlib import nullcontext as does_not_raise +import socket +import time + +import pytest + +from ansys.optislang.core import Optislang, OslServerProcess +from ansys.optislang.core.errors import ConnectionNotEstablishedError, OslCommunicationError + +_host = socket.gethostbyname(socket.gethostname()) +_port = 5310 +pytestmark = pytest.mark.local_osl + +#%% CONTEXT MANAGER +@pytest.mark.parametrize( + "send_dispose, send_shutdown, osl_none", + [ + (True, False, False), # send dispose + (False, True, False), # send shutdown + (False, False, True), # set osl=None + (False, False, False), # do nothing + ], +) +def test_local_default_cm(send_dispose, send_shutdown, osl_none): + with Optislang(shutdown_on_finished=True) as osl: + version = osl.get_osl_version() + osl.start() + if send_dispose: + osl.dispose() + if send_shutdown: + osl.shutdown() + if osl_none: + osl = None + + # server not running + time.sleep(5) + with pytest.raises( + ( + ConnectionNotEstablishedError, + ConnectionRefusedError, + OslCommunicationError, + ) + ): + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=5) + osl.shutdown() + + +@pytest.mark.parametrize( + "send_dispose, send_shutdown, osl_none", + [ + (True, False, False), # send dispose + (False, True, False), # send shutdown + (False, False, True), # set osl=None + (False, False, False), # do nothing + ], +) +def test_local_shutdown_on_finished_false_cm(send_dispose, send_shutdown, osl_none): + with Optislang(shutdown_on_finished=False) as osl: + version = osl.get_osl_version() + osl.start() + if send_dispose: + osl.dispose() + if send_shutdown: + osl.shutdown() + if osl_none: + osl = None + + if not send_shutdown: + with does_not_raise() as dnr: + time.sleep(5) + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=5) + osl.shutdown() + assert dnr is None + else: + time.sleep(5) + with pytest.raises( + ( + ConnectionNotEstablishedError, + ConnectionRefusedError, + OslCommunicationError, + ) + ): + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=5) + osl.shutdown() + + +@pytest.mark.parametrize( + "send_dispose, send_shutdown, osl_none", + [ + (True, False, False), # send dispose + (False, True, False), # send shutdown + (False, False, True), # set osl=None + (False, False, False), # do nothing + ], +) +def test_remote_cm(send_dispose, send_shutdown, osl_none): + # start optiSLang server + time.sleep(2) + osl_server_process = OslServerProcess(shutdown_on_finished=False) + osl_server_process.start() + time.sleep(5) + # connect to running optiSLang server + with Optislang(host=_host, port=_port) as osl: + version = osl.get_osl_version() + osl.start() + if send_dispose: + osl.dispose() + if send_shutdown: + osl.shutdown() + if osl_none: + osl = None + + if not send_shutdown: + with does_not_raise() as dnr: + time.sleep(5) + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=10) + osl.shutdown() + assert dnr is None + else: + time.sleep(5) + with pytest.raises( + ( + ConnectionNotEstablishedError, + ConnectionRefusedError, + OslCommunicationError, + ) + ): + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=10) + osl.shutdown() + + +#%% WITHOUT CM +@pytest.mark.parametrize( + "send_dispose, send_shutdown", + [ + (True, False), # send dispose + (False, True), # send shutdown + ], +) +def test_local_default_wocm(send_dispose, send_shutdown): + osl = Optislang(shutdown_on_finished=True) + version = osl.get_osl_version() + osl.start() + if send_dispose: + osl.dispose() + if send_shutdown: + osl.shutdown() + + # server not running + time.sleep(5) + with pytest.raises( + ( + ConnectionNotEstablishedError, + ConnectionRefusedError, + OslCommunicationError, + ) + ): + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=5) + osl.shutdown() + + +@pytest.mark.parametrize( + "send_dispose, send_shutdown", + [ + (True, False), # send dispose + (False, True), # send shutdown + ], +) +def test_local_shutdown_on_finished_false_wocm(send_dispose, send_shutdown): + osl = Optislang(shutdown_on_finished=False) + version = osl.get_osl_version() + osl.start() + if send_dispose: + osl.dispose() + if send_shutdown: + osl.shutdown() + + if not send_shutdown: + with does_not_raise() as dnr: + time.sleep(5) + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=5) + osl.shutdown() + assert dnr is None + else: + time.sleep(5) + with pytest.raises( + ( + ConnectionNotEstablishedError, + ConnectionRefusedError, + OslCommunicationError, + ) + ): + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=5) + osl.shutdown() + + +@pytest.mark.parametrize( + "send_dispose, send_shutdown", + [ + (True, False), # send dispose + (False, True), # send shutdown + ], +) +def test_remote_wocm(send_dispose, send_shutdown): + # start optiSLang server + time.sleep(2) + osl_server_process = OslServerProcess(shutdown_on_finished=False) + osl_server_process.start() + time.sleep(5) + # connect to running optiSLang server + osl = Optislang(host=_host, port=_port) + version = osl.get_osl_version() + osl.start() + if send_dispose: + osl.dispose() + if send_shutdown: + osl.shutdown() + + if not send_shutdown: + with does_not_raise() as dnr: + time.sleep(5) + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=10) + osl.shutdown() + assert dnr is None + else: + time.sleep(5) + with pytest.raises( + ( + ConnectionNotEstablishedError, + ConnectionRefusedError, + OslCommunicationError, + ) + ): + osl = Optislang(host="127.0.0.1", port=5310, ini_timeout=10) + osl.shutdown() diff --git a/tests/test_start_stop_combinations.py b/tests/test_start_stop_combinations.py index aa4fd8127..4272cfc79 100644 --- a/tests/test_start_stop_combinations.py +++ b/tests/test_start_stop_combinations.py @@ -34,7 +34,7 @@ def optislang() -> Optislang: ((("start", True, True), ("start", True, True), ("start", True, True)), None), ], ) -def test_combinations(optislang, input, expected): +def test_combinations(optislang: Optislang, input, expected): "Test combinations." with does_not_raise() as dnr: for method in input: @@ -42,5 +42,5 @@ def test_combinations(optislang, input, expected): optislang.start(method[1], method[2]) if method[0] == "stop": optislang.stop(method[1]) - optislang.shutdown() + optislang.dispose() assert dnr is expected diff --git a/tests/test_tcp_osl_server.py b/tests/test_tcp_osl_server.py index 2ef6235e0..89158ee9c 100644 --- a/tests/test_tcp_osl_server.py +++ b/tests/test_tcp_osl_server.py @@ -62,7 +62,7 @@ def tcp_osl_server() -> tos.TcpOslServer: # TcpClient -def test_connect_and_disconnect(osl_server_process, tcp_client): +def test_connect_and_disconnect(osl_server_process: OslServerProcess, tcp_client: tos.TcpClient): "Test ``connect``." with does_not_raise() as dnr: tcp_client.connect(host=_host, port=_port) @@ -71,7 +71,7 @@ def test_connect_and_disconnect(osl_server_process, tcp_client): assert dnr is None -def test_send_msg(osl_server_process, tcp_client): +def test_send_msg(osl_server_process: OslServerProcess, tcp_client: tos.TcpClient): "Test ``send_msg`" with does_not_raise() as dnr: tcp_client.connect(host=_host, port=_port) @@ -82,7 +82,12 @@ def test_send_msg(osl_server_process, tcp_client): @pytest.mark.parametrize("path_type", [str, Path]) -def test_send_file(osl_server_process, tcp_client, tmp_path, path_type): +def test_send_file( + osl_server_process: OslServerProcess, + tcp_client: tos.TcpClient, + tmp_path: Path, + path_type, +): "Test ``send_file``" file_path = tmp_path / "testfile.txt" if path_type == str: @@ -100,7 +105,7 @@ def test_send_file(osl_server_process, tcp_client, tmp_path, path_type): assert dnr is None -def test_receive_msg(osl_server_process, tcp_client): +def test_receive_msg(osl_server_process: OslServerProcess, tcp_client: tos.TcpClient): "Test ``receive_msg``." tcp_client.connect(host=_host, port=_port) tcp_client.send_msg(_msg) @@ -111,7 +116,12 @@ def test_receive_msg(osl_server_process, tcp_client): @pytest.mark.parametrize("path_type", [str, Path]) -def test_receive_file(osl_server_process, tcp_client, tmp_path, path_type): +def test_receive_file( + osl_server_process: OslServerProcess, + tcp_client: tos.TcpClient, + tmp_path: Path, + path_type, +): "Test ``receive_file`" file_path = tmp_path / "testfile.txt" received_path = tmp_path / "received.txt" @@ -134,7 +144,7 @@ def test_receive_file(osl_server_process, tcp_client, tmp_path, path_type): # TcpOslServer -def test_get_server_info(tcp_osl_server): +def test_get_server_info(tcp_osl_server: tos.TcpOslServer): """Test ``_get_server_info``.""" server_info = tcp_osl_server._get_server_info() tcp_osl_server.shutdown() @@ -142,7 +152,7 @@ def test_get_server_info(tcp_osl_server): assert bool(server_info) -def test_get_basic_project_info(osl_server_process, tcp_osl_server): +def test_get_basic_project_info(tcp_osl_server: tos.TcpOslServer): """Test ``_get_basic_project_info``.""" basic_project_info = tcp_osl_server._get_basic_project_info() tcp_osl_server.shutdown() @@ -176,7 +186,7 @@ def test_get_project_description(osl_server_process, tcp_osl_server): assert not bool(project_description) -def test_get_project_location(osl_server_process, tcp_osl_server): +def test_get_project_location(tcp_osl_server: tos.TcpOslServer): """Test ``get_project_location``.""" project_location = tcp_osl_server.get_project_location() tcp_osl_server.shutdown() @@ -184,7 +194,7 @@ def test_get_project_location(osl_server_process, tcp_osl_server): assert bool(project_location) -def test_get_project_name(osl_server_process, tcp_osl_server): +def test_get_project_name(tcp_osl_server: tos.TcpOslServer): """Test ``get_project_name``.""" project_name = tcp_osl_server.get_project_name() tcp_osl_server.shutdown() @@ -192,7 +202,7 @@ def test_get_project_name(osl_server_process, tcp_osl_server): assert bool(project_name) -def test_get_project_status(osl_server_process, tcp_osl_server): +def test_get_project_status(tcp_osl_server: tos.TcpOslServer): """Test ``get_get_project_status``.""" project_status = tcp_osl_server.get_project_status() tcp_osl_server.shutdown() @@ -200,7 +210,7 @@ def test_get_project_status(osl_server_process, tcp_osl_server): assert bool(project_status) -def test_get_working_dir(osl_server_process, tcp_osl_server): +def test_get_working_dir(tcp_osl_server: tos.TcpOslServer): """Test ``get_working_dir``.""" working_dir = tcp_osl_server.get_working_dir() tcp_osl_server.shutdown() @@ -209,7 +219,7 @@ def test_get_working_dir(osl_server_process, tcp_osl_server): # not implemented -def test_new(osl_server_process, tcp_osl_server): +def test_new(tcp_osl_server: tos.TcpOslServer): """Test ``new``.""" with pytest.raises(NotImplementedError): tcp_osl_server.new() @@ -217,14 +227,14 @@ def test_new(osl_server_process, tcp_osl_server): # not implemented -def test_open(osl_server_process, tcp_osl_server): +def test_open(tcp_osl_server: tos.TcpOslServer): """Test ``open``.""" with pytest.raises(NotImplementedError): tcp_osl_server.open("string", False, False, False) tcp_osl_server.shutdown() -def test_reset(osl_server_process, tcp_osl_server): +def test_reset(tcp_osl_server: tos.TcpOslServer): """Test ``reset``.""" with does_not_raise() as dnr: tcp_osl_server.reset() @@ -233,7 +243,7 @@ def test_reset(osl_server_process, tcp_osl_server): @pytest.mark.parametrize("path_type", [str, Path]) -def test_run_python_file(tcp_osl_server, tmp_path, path_type): +def test_run_python_file(tcp_osl_server: tos.TcpOslServer, tmp_path: Path, path_type): """Test ``run_python_file``.""" cmd = """ a = 5 @@ -254,7 +264,7 @@ def test_run_python_file(tcp_osl_server, tmp_path, path_type): assert isinstance(run_file, tuple) -def test_run_python_script(osl_server_process, tcp_osl_server): +def test_run_python_script(tcp_osl_server: tos.TcpOslServer): """Test ``run_python_script``.""" cmd = """ a = 5 @@ -268,7 +278,7 @@ def test_run_python_script(osl_server_process, tcp_osl_server): # not implemented -def test_save(osl_server_process, tcp_osl_server): +def test_save(tcp_osl_server: tos.TcpOslServer): """Test ``save``.""" with pytest.raises(NotImplementedError): tcp_osl_server.save() @@ -276,22 +286,22 @@ def test_save(osl_server_process, tcp_osl_server): # not implemented -def test_save_as(osl_server_process, tcp_osl_server): +def test_save_as(tcp_osl_server: tos.TcpOslServer): """Test ``save_as``.""" with pytest.raises(NotImplementedError): tcp_osl_server.save_as("string", False, False, False) tcp_osl_server.shutdown() -def test_save_copy(osl_server_process, tmp_path, tcp_osl_server): +def test_save_copy(tmp_path: Path, tcp_osl_server: tos.TcpOslServer): """Test ``save_copy``.""" - copy_path = os.path.join(tmp_path, "test_save_copy.opf") + copy_path = tmp_path / "test_save_copy.opf" tcp_osl_server.save_copy(copy_path) tcp_osl_server.shutdown() assert os.path.isfile(copy_path) -def test_start(osl_server_process, tcp_osl_server): +def test_start(tcp_osl_server: tos.TcpOslServer): """Test ``start``.""" with does_not_raise() as dnr: tcp_osl_server.start() @@ -299,7 +309,7 @@ def test_start(osl_server_process, tcp_osl_server): assert dnr is None -def test_stop(osl_server_process, tcp_osl_server): +def test_stop(tcp_osl_server: tos.TcpOslServer): """Test ``stop``.""" with does_not_raise() as dnr: tcp_osl_server.stop() @@ -307,7 +317,7 @@ def test_stop(osl_server_process, tcp_osl_server): assert dnr is None -def test_stop_gently(osl_server_process, tcp_osl_server): +def test_stop_gently(tcp_osl_server: tos.TcpOslServer): """Test ``stop_gently``.""" with does_not_raise() as dnr: tcp_osl_server.stop_gently() @@ -315,7 +325,7 @@ def test_stop_gently(osl_server_process, tcp_osl_server): assert dnr is None -def test_shutdown(osl_server_process, tcp_osl_server): +def test_shutdown(tcp_osl_server: tos.TcpOslServer): """Test ``shutdown``.""" with does_not_raise() as dnr: tcp_osl_server.shutdown()