diff --git a/scripts/windows_service_test.py b/scripts/windows_service_test.py index a2ee772c..0dc8ff2c 100644 --- a/scripts/windows_service_test.py +++ b/scripts/windows_service_test.py @@ -8,13 +8,13 @@ import win32serviceutil import win32service import servicemanager -import subprocess import sys import os import argparse import shlex import win32con import win32api +import pytest from getpass import getpass @@ -54,32 +54,20 @@ def SvcDoRun(self): ) code_location = os.environ["CODE_LOCATION"] pytest_args = os.environ.get("PYTEST_ARGS", None) + log_file_name = os.path.join(code_location, "test.log") - args = ["pytest", os.path.join(code_location, "test")] + # We need to disable xdist as it runs each test in a Python + # subprocess, which results in the tests not running as a + # service as we want. + args = [os.path.join(code_location, "test"), "--numprocesses=0"] if pytest_args: args.extend(shlex.split(pytest_args, posix=False)) + with open(log_file_name, mode="w") as f: + sys.stdout = f + sys.stderr = f - logging.basicConfig( - filename=os.path.join(code_location, "test.log"), - encoding="utf-8", - level=logging.INFO, - filemode="w", - ) - process = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - cwd=code_location, - ) - - while True: - output = process.stdout.readline() - if not output and process.poll() is not None: - break - - logger.info(output.strip()) + pytest.main(args) servicemanager.LogMsg( servicemanager.EVENTLOG_INFORMATION_TYPE, diff --git a/src/openjd/sessions/_scripts/_windows/_signal_win_subprocess.py b/src/openjd/sessions/_scripts/_windows/_signal_win_subprocess.py index c26a9770..ee40be6f 100644 --- a/src/openjd/sessions/_scripts/_windows/_signal_win_subprocess.py +++ b/src/openjd/sessions/_scripts/_windows/_signal_win_subprocess.py @@ -55,11 +55,6 @@ def signal_process(pgid: int): if not kernel32.GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pgid): raise ctypes.WinError() - if not kernel32.FreeConsole(): - raise ctypes.WinError() - if not kernel32.AttachConsole(ATTACH_PARENT_PROCESS): - raise ctypes.WinError() - if __name__ == "__main__": signal_process(int(sys.argv[1])) diff --git a/src/openjd/sessions/_subprocess.py b/src/openjd/sessions/_subprocess.py index 8def05ff..9b21a4ef 100644 --- a/src/openjd/sessions/_subprocess.py +++ b/src/openjd/sessions/_subprocess.py @@ -11,8 +11,7 @@ from queue import Queue, Empty from subprocess import DEVNULL, PIPE, STDOUT, Popen, list2cmdline, run from threading import Event, Thread -from typing import Any -from typing import Callable, Literal, Optional, Sequence, cast +from typing import Callable, Literal, Optional, Sequence, cast, Any from ._linux._capabilities import try_use_cap_kill from ._linux._sudo import find_sudo_child_process_group_id @@ -623,17 +622,22 @@ def _windows_notify_subprocess(self) -> None: str(WINDOWS_SIGNAL_SUBPROC_SCRIPT_PATH), str(self._process.pid), ] - result = run( - cmd, - stdout=PIPE, - stderr=STDOUT, - stdin=DEVNULL, - creationflags=CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW, + process = LoggingSubprocess( + logger=self._logger, + args=cmd, + encoding=self._encoding, + user=self._user, + os_env_vars=self._os_env_vars, + working_dir=self._working_dir, + creation_flags=CREATE_NO_WINDOW, ) - if result.returncode != 0: + + # Blocking call + process.run() + + if process.exit_code != 0: self._logger.warning( - f"Failed to send signal 'CTRL_BREAK_EVENT' to subprocess {self._process.pid}: %s", - result.stdout.decode("utf-8"), + f"Failed to send signal 'CTRL_BREAK_EVENT' to subprocess {self._process.pid}", extra=LogExtraInfo( openjd_log_content=LogContent.PROCESS_CONTROL | LogContent.EXCEPTION_INFO ), diff --git a/src/openjd/sessions/_win32/_helpers.py b/src/openjd/sessions/_win32/_helpers.py index 1eda7042..a16b1881 100644 --- a/src/openjd/sessions/_win32/_helpers.py +++ b/src/openjd/sessions/_win32/_helpers.py @@ -25,6 +25,8 @@ # Constants LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, + PI_NOUI, + PROFILEINFO, # Functions CloseHandle, CreateEnvironmentBlock, @@ -32,6 +34,8 @@ GetCurrentProcessId, LogonUserW, ProcessIdToSessionId, + LoadUserProfileW, + UnloadUserProfile, ) @@ -166,3 +170,33 @@ def environment_block_from_dict(env: dict[str, str]) -> c_wchar_p: env_block_str = null_delimited + "\0" return c_wchar_p(env_block_str) + + +def load_user_profile(token: HANDLE, username: str) -> PROFILEINFO: + """ + Load the user profile for the given logon token and user name + + NOTE: The caller *MUST* call unload_user_profile when finished with the user profile + + See: https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-loaduserprofilew + """ + profile_info = PROFILEINFO() + profile_info.dwSize = sizeof(PROFILEINFO) + profile_info.lpUserName = username + profile_info.dwFlags = PI_NOUI + profile_info.lpProfilePath = None + + if not LoadUserProfileW(token, byref(profile_info)): + raise WinError() + + return profile_info + + +def unload_user_profile(token: HANDLE, profile_info: PROFILEINFO) -> None: + """ + Unload the user profile for the given token and profile. + + See: https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-unloaduserprofile + """ + if not UnloadUserProfile(token, profile_info.hProfile): + raise WinError() diff --git a/test/openjd/sessions/conftest.py b/test/openjd/sessions/conftest.py index caa854e7..eca66db8 100644 --- a/test/openjd/sessions/conftest.py +++ b/test/openjd/sessions/conftest.py @@ -10,6 +10,7 @@ from hashlib import sha256 from unittest.mock import MagicMock import pytest +import sys from openjd.sessions import PosixSessionUser, WindowsSessionUser, BadCredentialsException from openjd.sessions._os_checker import is_posix, is_windows @@ -21,6 +22,8 @@ from openjd.sessions._win32._helpers import ( # type: ignore get_current_process_session_id, logon_user_context, + load_user_profile, + unload_user_profile, ) TEST_RUNNING_IN_WINDOWS_SESSION_0 = 0 == get_current_process_session_id() @@ -251,10 +254,10 @@ def windows_user() -> Generator[WindowsSessionUser, None, None]: if TEST_RUNNING_IN_WINDOWS_SESSION_0: try: - # Note: We don't load the user profile; it's currently not needed by our tests, - # and we're getting a mysterious crash when unloading it. with logon_user_context(user, password) as logon_token: + profile_info = load_user_profile(logon_token, user) yield WindowsSessionUser(user, logon_token=logon_token) + unload_user_profile(logon_token, profile_info) except OSError as e: raise Exception( f"Could not logon as {user}. Check the password that was provided in {WIN_PASS_ENV_VAR}." @@ -282,3 +285,10 @@ def queue_handler(message_queue: SimpleQueue) -> QueueHandler: @pytest.fixture(scope="function") def session_id() -> str: return "some Id" + + +@pytest.fixture(scope="function") +def python_exe() -> str: + if is_windows() and TEST_RUNNING_IN_WINDOWS_SESSION_0: + return sys.executable.lower().replace("pythonservice.exe", "python.exe") + return sys.executable diff --git a/test/openjd/sessions/test_runner_base.py b/test/openjd/sessions/test_runner_base.py index 00a5cd9a..c0ff42c7 100644 --- a/test/openjd/sessions/test_runner_base.py +++ b/test/openjd/sessions/test_runner_base.py @@ -2,7 +2,6 @@ import json import os -import sys import time from datetime import datetime, timedelta, timezone from logging.handlers import QueueHandler @@ -118,7 +117,7 @@ def test_initialized(self, tmp_path: Path) -> None: assert runner.state == ScriptRunnerState.READY assert runner.exit_code is None - def test_basic_run(self, tmp_path: Path) -> None: + def test_basic_run(self, tmp_path: Path, python_exe: str) -> None: # Run a simple command with no timeout and check the state during and # after the run. @@ -128,7 +127,7 @@ def test_basic_run(self, tmp_path: Path) -> None: logger=MagicMock(), session_working_directory=tmp_path, callback=callback ) as runner: # WHEN - runner._run([sys.executable, "-c", "import time; time.sleep(0.25)"]) + runner._run([python_exe, "-c", "import time; time.sleep(0.25)"]) # THEN assert runner.state == ScriptRunnerState.RUNNING @@ -162,7 +161,11 @@ def test_fast_run_no_deadlock(self, attempt: int, tmp_path: Path) -> None: time.sleep(0.0001) def test_working_dir_is_cwd( - self, tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler + self, + tmp_path: Path, + message_queue: SimpleQueue, + queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test to make sure that the current working dir of the command that's run is # the startup directory. @@ -173,7 +176,7 @@ def test_working_dir_is_cwd( logger=logger, session_working_directory=tmp_path, startup_directory=tmp_path ) as runner: # WHEN - runner._run([sys.executable, "-c", "import os; print(os.getcwd())"]) + runner._run([python_exe, "-c", "import os; print(os.getcwd())"]) # Wait until the process exits. while runner.state == ScriptRunnerState.RUNNING: time.sleep(0.1) @@ -182,14 +185,14 @@ def test_working_dir_is_cwd( messages = collect_queue_messages(message_queue) assert str(tmp_path) in messages - def test_failing_run(self, tmp_path: Path) -> None: + def test_failing_run(self, tmp_path: Path, python_exe: str) -> None: # Test to make sure that we properly communicate a process with # non-zero return as # GIVEN with TerminatingRunner(logger=MagicMock(), session_working_directory=tmp_path) as runner: # WHEN - runner._run([sys.executable, "-c", "import sys; sys.exit(1)"]) + runner._run([python_exe, "-c", "import sys; sys.exit(1)"]) # THEN while runner.state == ScriptRunnerState.RUNNING: @@ -243,6 +246,7 @@ def test_run_with_env_vars( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Run a simple command with no timeout and check the state during and # after the run. @@ -256,7 +260,7 @@ def test_run_with_env_vars( # WHEN runner._run( [ - sys.executable, + python_exe, "-c", r"import os;print(*(f'{k} = {v}' for k,v in os.environ.items()), sep='\n')", ] @@ -298,7 +302,7 @@ def test_run_as_posix_user( runner._run( [ # Note: Intentionally not `sys.executable`. Reasons: - # 1) This is a cross-account command, and sys.executable may be in a user-specific venv + # 1) This is a cross-account command, and python_exe may be in a user-specific venv # 2) This test is, generally, intended to be run in a docker container where the system # python is the correct version that we want to run under. "python", @@ -348,8 +352,8 @@ def test_run_as_posix_user_with_env_vars( # WHEN runner._run( [ - # Note: Intentionally not `sys.executable`. Reasons: - # 1) This is a cross-account command, and sys.executable may be in a user-specific venv + # Note: Intentionally not `python_exe`. Reasons: + # 1) This is a cross-account command, and python_exe may be in a user-specific venv # 2) This test is, generally, intended to be run in a docker container where the system # python is the correct version that we want to run under. "python", @@ -467,8 +471,8 @@ def test_run_as_windows_user_with_env_vars( # WHEN runner._run( [ - # Note: Intentionally not `sys.executable`. Reasons: - # 1) This is a cross-account command, and sys.executable may be in a user-specific venv + # Note: Intentionally not `python_exe`. Reasons: + # 1) This is a cross-account command, and python_exe may be in a user-specific venv # 2) This test is, generally, intended to be run in a docker container where the system # python is the correct version that we want to run under. "python", @@ -518,8 +522,8 @@ def test_does_not_inherit_env_vars_posix( # WHEN runner._run( [ - # Note: Intentionally not `sys.executable`. Reasons: - # 1) This is a cross-account command, and sys.executable may be in a user-specific venv + # Note: Intentionally not `python_exe`. Reasons: + # 1) This is a cross-account command, and python_exe may be in a user-specific venv # 2) This test is, generally, intended to be run in a docker container where the system # python is the correct version that we want to run under. "python", @@ -571,7 +575,7 @@ def test_does_not_inherit_env_vars_windows( # WHEN py_script = f"import os; v=os.environ.get('{var_name}'); print('NOT_PRESENT' if v is None else v)" # Use the default 'python' rather than 'sys.executable' since we typically do not have access to - # sys.executable when running with impersonation since it's in a hatch environment for the local user. + # python_exe when running with impersonation since it's in a hatch environment for the local user. runner._run(["python", "-c", py_script]) # THEN @@ -588,7 +592,7 @@ def test_does_not_inherit_env_vars_windows( assert os.environ[var_name] not in messages assert "NOT_PRESENT" in messages - def test_cannot_run_twice(self, tmp_path: Path) -> None: + def test_cannot_run_twice(self, tmp_path: Path, python_exe: str) -> None: # Run a simple command with no timeout and check the state during and # after the run. @@ -598,11 +602,11 @@ def test_cannot_run_twice(self, tmp_path: Path) -> None: logger=MagicMock(), session_working_directory=tmp_path, callback=callback ) as runner: # WHEN - runner._run([sys.executable, "-c", "print('hello')"]) + runner._run([python_exe, "-c", "print('hello')"]) # THEN with pytest.raises(RuntimeError): - runner._run([sys.executable, "-c", "print('hello')"]) + runner._run([python_exe, "-c", "print('hello')"]) @pytest.mark.usefixtures("message_queue", "queue_handler") def test_run_action( @@ -610,6 +614,7 @@ def test_run_action( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Run a test of the _run_action method that makes sure that the action runs # and the format strings are evaluated. @@ -623,7 +628,7 @@ def test_run_action( python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() symtab = SymbolTable( source={ - "Task.PythonInterpreter": sys.executable, + "Task.PythonInterpreter": python_exe, "Task.ScriptFile": str(python_app_loc), } ) @@ -660,6 +665,7 @@ def test_run_action_default_timeout( queue_handler: QueueHandler, default_timeout_seconds: Optional[int], action_timeout_seconds: Optional[int], + python_exe: str, ) -> None: # Tests that the effective timeout is applied correctly given a supplied default timeout # and an optional timeout defined on the action @@ -683,7 +689,7 @@ def test_run_action_default_timeout( python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() symtab = SymbolTable( source={ - "Task.PythonInterpreter": sys.executable, + "Task.PythonInterpreter": python_exe, "Task.ScriptFile": str(python_app_loc), } ) @@ -744,6 +750,7 @@ def test_cancel_terminate( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that the subprocess is terminated when doing a TERMINATE style # cancelation @@ -755,7 +762,7 @@ def test_cancel_terminate( logger=logger, session_working_directory=tmp_path, callback=callback ) as runner: python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() - runner._run([sys.executable, str(python_app_loc)]) + runner._run([python_exe, str(python_app_loc)]) # WHEN runner.cancel() @@ -779,6 +786,7 @@ def test_run_with_time_limit( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that the subprocess is terminated when doing a TERMINATE style # cancelation @@ -789,7 +797,7 @@ def test_run_with_time_limit( python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() # WHEN - runner._run([sys.executable, str(python_app_loc)], time_limit=timedelta(seconds=1)) + runner._run([python_exe, str(python_app_loc)], time_limit=timedelta(seconds=1)) # THEN # Wait until the process exits. We'll be in CANCELING state between when the timeout is reached @@ -809,6 +817,7 @@ def test_cancel_notify( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that NOTIFY_THEN_CANCEL first signals a SIGTERM and then a SIGKILL @@ -818,7 +827,7 @@ def test_cancel_notify( python_app_loc = ( Path(__file__).parent / "support_files" / "app_20s_run_ignore_signal.py" ).resolve() - runner._run([sys.executable, str(python_app_loc)]) + runner._run([python_exe, str(python_app_loc)]) # WHEN secs = 2 if not is_windows() else 5 @@ -961,6 +970,7 @@ def test_cancel_double_cancel_notify( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that NOTIFY_THEN_CANCEL can be called twice, and the second time will # shrink the grace period @@ -971,7 +981,7 @@ def test_cancel_double_cancel_notify( python_app_loc = ( Path(__file__).parent / "support_files" / "app_20s_run_ignore_signal.py" ).resolve() - runner._run([sys.executable, str(python_app_loc)]) + runner._run([python_exe, str(python_app_loc)]) # WHEN secs = 2 if not is_windows() else 5 diff --git a/test/openjd/sessions/test_runner_env_script.py b/test/openjd/sessions/test_runner_env_script.py index 0da76fff..586f08e6 100644 --- a/test/openjd/sessions/test_runner_env_script.py +++ b/test/openjd/sessions/test_runner_env_script.py @@ -1,6 +1,5 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -import sys import time from datetime import timedelta from logging.handlers import QueueHandler @@ -79,12 +78,13 @@ def test_run_basic( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that run of an onEnter action with no embedded files works as expected. # GIVEN script = EnvironmentScript_2023_09(actions=env_actions) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) logger = build_logger(queue_handler) runner = EnvironmentScriptRunner( logger=logger, @@ -135,6 +135,7 @@ def test_run_handles_none( env_actions: EnvironmentActions_2023_09, tmp_path: Path, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that when given an environment that doesn't have the corresponding # action defined we: @@ -144,7 +145,7 @@ def test_run_handles_none( # GIVEN script = EnvironmentScript_2023_09(actions=env_actions) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) logger = build_logger(queue_handler) callback = MagicMock() runner = EnvironmentScriptRunner( @@ -170,12 +171,13 @@ def test_run_handles_none_script( self, tmp_path: Path, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that when given an environment that doesn't have a script we: # a) Don't explode; # b) Don't run anything; and # c) Invoke the callback - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) logger = build_logger(queue_handler) callbackOnEnter = MagicMock() callbackOnExit = MagicMock() @@ -235,6 +237,7 @@ def test_run_with_files( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that run of an action with embedded files works as expected. @@ -249,7 +252,7 @@ def test_run_with_files( ) ], ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) logger = build_logger(queue_handler) runner = EnvironmentScriptRunner( logger=logger, @@ -300,6 +303,7 @@ def test_run_with_bad_files( self, env_actions: EnvironmentActions_2023_09, tmp_path: Path, + python_exe: str, ) -> None: # Test that run of an action with embedded files that cannot be materialized to # disk: @@ -319,7 +323,7 @@ def test_run_with_bad_files( ) ], ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) callback = MagicMock() runner = EnvironmentScriptRunner( logger=MagicMock(), @@ -375,6 +379,7 @@ def test_cancel( ] ], expected: CancelMethod, + python_exe: str, ) -> None: # Test that cancel invokes the base class' cancel with the appropriate arguments. @@ -395,7 +400,7 @@ def test_cancel( ) ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) runner = EnvironmentScriptRunner( logger=MagicMock(), session_working_directory=tmp_path, @@ -427,6 +432,7 @@ def test_run_env_action_passes_default_timeout( self, tmp_path: Path, default_timeout: Optional[timedelta], + python_exe: str, ) -> None: # GIVEN action = Action_2023_09( @@ -439,7 +445,7 @@ def test_run_env_action_passes_default_timeout( ) ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) runner = EnvironmentScriptRunner( logger=MagicMock(), session_working_directory=tmp_path, @@ -467,6 +473,7 @@ def test_run_env_action_passes_default_timeout( def test_exit_uses_default_timeout( self, tmp_path: Path, + python_exe: str, ): # GIVEN # An "onExit" action with no defined timeout @@ -481,7 +488,7 @@ def test_exit_uses_default_timeout( ) ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) runner = EnvironmentScriptRunner( logger=MagicMock(), session_working_directory=tmp_path, diff --git a/test/openjd/sessions/test_runner_step_script.py b/test/openjd/sessions/test_runner_step_script.py index f2458f1c..2b258adf 100644 --- a/test/openjd/sessions/test_runner_step_script.py +++ b/test/openjd/sessions/test_runner_step_script.py @@ -1,6 +1,5 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -import sys import time from datetime import timedelta from logging.handlers import QueueHandler @@ -62,6 +61,7 @@ def test_run_basic( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that run of an action with no embedded files works as expected. @@ -74,7 +74,7 @@ def test_run_basic( ) ) ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) logger = build_logger(queue_handler) runner = StepScriptRunner( logger=logger, @@ -99,6 +99,7 @@ def test_run_with_files( tmp_path: Path, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Test that that en embedded file is properly materialized and can be used in the action @@ -118,7 +119,7 @@ def test_run_with_files( ) ], ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) logger = build_logger(queue_handler) runner = StepScriptRunner( logger=logger, @@ -243,6 +244,7 @@ def test_cancel( ] ], expected: CancelMethod, + python_exe: str, ) -> None: # Test that cancel invokes the base class' cancel with the appropriate arguments. @@ -262,7 +264,7 @@ def test_cancel( ) ) ) - symtab = SymbolTable(source={"Task.Command": sys.executable}) + symtab = SymbolTable(source={"Task.Command": python_exe}) runner = StepScriptRunner( logger=MagicMock(), session_working_directory=tmp_path, diff --git a/test/openjd/sessions/test_session.py b/test/openjd/sessions/test_session.py index eddafb8e..e4490d72 100644 --- a/test/openjd/sessions/test_session.py +++ b/test/openjd/sessions/test_session.py @@ -666,11 +666,11 @@ class TestSessionRunTask_2023_09: # noqa: N801 @staticmethod @pytest.fixture - def fix_basic_task_script() -> StepScript_2023_09: + def fix_basic_task_script(python_exe: str) -> StepScript_2023_09: return StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Foo }}")], ) ), @@ -740,12 +740,14 @@ def test_run_task_no_log_banners( # THEN assert "--------- Running Task" not in caplog.messages - def test_run_task_with_env_vars(self, caplog: pytest.LogCaptureFixture) -> None: + def test_run_task_with_env_vars( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: # GIVEN step_script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Foo }}")], ) ), @@ -793,7 +795,7 @@ def test_run_task_with_env_vars(self, caplog: pytest.LogCaptureFixture) -> None: if state != SessionState.READY ], ) - def test_cannot_run_not_ready(self, state: SessionState) -> None: + def test_cannot_run_not_ready(self, state: SessionState, python_exe: str) -> None: # This is checking that we cannot run a task unless the Session is READY # GIVEN @@ -802,7 +804,7 @@ def test_cannot_run_not_ready(self, state: SessionState) -> None: script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -818,7 +820,7 @@ def test_cannot_run_not_ready(self, state: SessionState) -> None: with pytest.raises(RuntimeError): session.run_task(step_script=script, task_parameter_values=task_params) - def test_run_task_fail_early(self) -> None: + def test_run_task_fail_early(self, python_exe: str) -> None: # Testing a task that fails before running. # This'll fail because we're referencing a Task parameter that doesn't exist. @@ -829,7 +831,7 @@ def test_run_task_fail_early(self) -> None: step_script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Foo }}")], ) ), @@ -854,14 +856,14 @@ def test_run_task_fail_early(self) -> None: fail_message="Error resolving format string: Failed to parse interpolation expression at [37, 55]. Expression: Task.Param.P . Reason: Expression failed validation: Task.Param.P has no value.", ) - def test_run_task_fail_run(self) -> None: + def test_run_task_fail_run(self, python_exe: str) -> None: # Testing a task that fails while running # GIVEN script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Foo }}")], ) ), @@ -931,14 +933,14 @@ def test_run_task_with_variables( class TestSessionCancel: """Test that cancelation will cancel the currently running Script.""" - def test_cancel(self) -> None: + def test_cancel(self, python_exe: str) -> None: # Testing a task that fails while running # GIVEN script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Foo }}")], ) ), @@ -994,7 +996,7 @@ def test_cancel(self) -> None: ) ), ) - def test_cancel_time_limit(self, time_limit: Optional[timedelta]) -> None: + def test_cancel_time_limit(self, time_limit: Optional[timedelta], python_exe: str) -> None: # Testing that the time_limit argument is forwarded to the runner # GIVEN @@ -1002,7 +1004,7 @@ def test_cancel_time_limit(self, time_limit: Optional[timedelta]) -> None: script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Foo }}")], ) ), @@ -1049,13 +1051,14 @@ def _make_environment( exit_script: bool = False, variables: Optional[dict[str, EnvironmentVariableValueString_2023_09]] = None, name: Optional[str] = None, + python_exe: str = sys.executable, ) -> Environment_2023_09: script = ( EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=( Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) if enter_script @@ -1063,7 +1066,7 @@ def _make_environment( ), onExit=( Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) if exit_script @@ -1091,7 +1094,9 @@ class TestSessionEnterEnvironment_2023_09: # noqa: N801 """Testing running tasks with the 2023-09 schema.""" @pytest.mark.usefixtures("caplog") # builtin fixture - def test_enter_environment_basic(self, caplog: pytest.LogCaptureFixture) -> None: + def test_enter_environment_basic( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: # GIVEN # Crafting a EnvironmentScript that ensures that references to Job parameters. # This ensures that we are correctly constructing the symbol table for the run. @@ -1099,7 +1104,7 @@ def test_enter_environment_basic(self, caplog: pytest.LogCaptureFixture) -> None EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1133,7 +1138,9 @@ def test_enter_environment_basic(self, caplog: pytest.LogCaptureFixture) -> None assert "Jvalue Jvalue" in caplog.messages @pytest.mark.usefixtures("caplog") # builtin fixture - def test_enter_environment_with_env_vars(self, caplog: pytest.LogCaptureFixture) -> None: + def test_enter_environment_with_env_vars( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: # GIVEN # Crafting a EnvironmentScript that ensures that references to Job parameters. # This ensures that we are correctly constructing the symbol table for the run. @@ -1141,7 +1148,7 @@ def test_enter_environment_with_env_vars(self, caplog: pytest.LogCaptureFixture) EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1188,7 +1195,7 @@ def test_enter_environment_with_env_vars(self, caplog: pytest.LogCaptureFixture) if state != SessionState.READY ], ) - def test_cannot_enter_when_not_ready(self, state: SessionState) -> None: + def test_cannot_enter_when_not_ready(self, state: SessionState, python_exe: str) -> None: # This is checking that we cannot enter an environment unless the Session # is in READY state @@ -1199,7 +1206,7 @@ def test_cannot_enter_when_not_ready(self, state: SessionState) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -1215,7 +1222,7 @@ def test_cannot_enter_when_not_ready(self, state: SessionState) -> None: with pytest.raises(RuntimeError): session.enter_environment(environment=environment) - def test_enter_two_environments(self) -> None: + def test_enter_two_environments(self, python_exe: str) -> None: # This is checking that we construct the list of entered environments # correctly (i.e. the order of identifiers in the list is the order in which # they were entered) @@ -1226,7 +1233,7 @@ def test_enter_two_environments(self) -> None: script = EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -1249,7 +1256,7 @@ def test_enter_two_environments(self) -> None: # THEN assert session.environments_entered == (identifier1, identifier2) - def test_cannot_enter_same_environment_twice(self) -> None: + def test_cannot_enter_same_environment_twice(self, python_exe: str) -> None: # This is checking that we cannot enter the same environment twice, as defined by its # identifier @@ -1260,7 +1267,7 @@ def test_cannot_enter_same_environment_twice(self) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -1279,7 +1286,7 @@ def test_cannot_enter_same_environment_twice(self) -> None: with pytest.raises(RuntimeError): session.enter_environment(environment=environment, identifier=identifier1) - def test_enter_environment_fail_early(self) -> None: + def test_enter_environment_fail_early(self, python_exe: str) -> None: # Testing an environment that fails before running. # This'll fail because we're referencing a Task parameter that doesn't exist. @@ -1288,7 +1295,7 @@ def test_enter_environment_fail_early(self) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1316,7 +1323,7 @@ def test_enter_environment_fail_early(self) -> None: fail_message="Error resolving format string: Failed to parse interpolation expression at [37, 55]. Expression: Task.Param.P . Reason: Expression failed validation: Task.Param.P has no value.", ) - def test_enter_environment_fail_run(self) -> None: + def test_enter_environment_fail_run(self, python_exe: str) -> None: # Testing an Environment enter that fails while running # GIVEN @@ -1324,7 +1331,7 @@ def test_enter_environment_fail_run(self) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1350,7 +1357,7 @@ def test_enter_environment_fail_run(self) -> None: assert session.state == SessionState.READY_ENDING assert session.action_status == ActionStatus(state=ActionState.FAILED, exit_code=1) - def test_enter_no_action(self, caplog: pytest.LogCaptureFixture) -> None: + def test_enter_no_action(self, caplog: pytest.LogCaptureFixture, python_exe: str) -> None: # Testing an environment enter where the given environment has no # onEnter action defined. @@ -1359,7 +1366,7 @@ def test_enter_no_action(self, caplog: pytest.LogCaptureFixture) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -1375,7 +1382,7 @@ def test_enter_no_action(self, caplog: pytest.LogCaptureFixture) -> None: assert session.state == SessionState.READY assert session.action_status == ActionStatus(state=ActionState.SUCCESS) - def test_enter_environment_with_variables(self) -> None: + def test_enter_environment_with_variables(self, python_exe: str) -> None: # GIVEN session_id = uuid.uuid4().hex job_params = {"J": ParameterValue(type=ParameterValueType.STRING, value="Jvalue")} @@ -1385,7 +1392,9 @@ def test_enter_environment_with_variables(self) -> None: with Session(session_id=session_id, job_parameter_values=job_params) as session: # WHEN session.enter_environment( - environment=_make_environment(enter_script=True, variables=variables) + environment=_make_environment( + enter_script=True, variables=variables, python_exe=python_exe + ) ) # Wait for the process to exit while session.state == SessionState.RUNNING: @@ -1396,7 +1405,7 @@ def test_enter_environment_with_variables(self) -> None: @pytest.mark.usefixtures("caplog") # builtin fixture def test_enter_environment_with_resolved_variables( - self, caplog: pytest.LogCaptureFixture + self, caplog: pytest.LogCaptureFixture, python_exe: str ) -> None: # GIVEN session_id = uuid.uuid4().hex @@ -1409,7 +1418,7 @@ def test_enter_environment_with_resolved_variables( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1435,7 +1444,7 @@ def test_enter_environment_with_resolved_variables( assert session._runner is not None assert "Jvalue" in caplog.messages - def test_enter_two_environments_with_variables(self) -> None: + def test_enter_two_environments_with_variables(self, python_exe: str) -> None: # GIVEN session_id = uuid.uuid4().hex job_params = {"J": ParameterValue(type=ParameterValueType.STRING, value="Jvalue")} @@ -1447,11 +1456,15 @@ def test_enter_two_environments_with_variables(self) -> None: } with Session(session_id=session_id, job_parameter_values=job_params) as session: # WHEN - session.enter_environment(environment=_make_environment(variables=variables1)) + session.enter_environment( + environment=_make_environment(variables=variables1, python_exe=python_exe) + ) assert session.state == SessionState.READY session.enter_environment( - environment=_make_environment(enter_script=True, variables=variables2) + environment=_make_environment( + enter_script=True, variables=variables2, python_exe=python_exe + ) ) # Wait for the process to exit while session.state == SessionState.RUNNING: @@ -1465,7 +1478,9 @@ class TestSessionExitEnvironment_2023_09: # noqa: N801 """Testing running tasks with the 2023-09 schema.""" @pytest.mark.usefixtures("caplog") # builtin fixture - def test_exit_environment_basic(self, caplog: pytest.LogCaptureFixture) -> None: + def test_exit_environment_basic( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: # GIVEN # Crafting a EnvironmentScript that ensures that references to Job parameters. # This ensures that we are correctly constructing the symbol table for the run. @@ -1473,7 +1488,7 @@ def test_exit_environment_basic(self, caplog: pytest.LogCaptureFixture) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1509,7 +1524,9 @@ def test_exit_environment_basic(self, caplog: pytest.LogCaptureFixture) -> None: assert "Jvalue Jvalue" in caplog.messages @pytest.mark.usefixtures("caplog") # builtin fixture - def test_exit_environment_with_env_vars(self, caplog: pytest.LogCaptureFixture) -> None: + def test_exit_environment_with_env_vars( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: # GIVEN # Crafting a EnvironmentScript that ensures that references to Job parameters. # This ensures that we are correctly constructing the symbol table for the run. @@ -1517,7 +1534,7 @@ def test_exit_environment_with_env_vars(self, caplog: pytest.LogCaptureFixture) EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1564,7 +1581,7 @@ def test_exit_environment_with_env_vars(self, caplog: pytest.LogCaptureFixture) if state not in (SessionState.READY, SessionState.READY_ENDING) ], ) - def test_cannot_exit_when_not_ready(self, state: SessionState) -> None: + def test_cannot_exit_when_not_ready(self, state: SessionState, python_exe: str) -> None: # This is checking that we cannot exit an environment unless the Session # is in READY or READY_ENDING state @@ -1573,7 +1590,7 @@ def test_cannot_exit_when_not_ready(self, state: SessionState) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -1591,7 +1608,7 @@ def test_cannot_exit_when_not_ready(self, state: SessionState) -> None: with pytest.raises(RuntimeError): session.exit_environment(identifier=identifier) - def test_exit_with_two_environments(self) -> None: + def test_exit_with_two_environments(self, python_exe: str) -> None: # This is checking that we can only exit the most recently entered environment # GIVEN @@ -1601,7 +1618,7 @@ def test_exit_with_two_environments(self) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -1618,7 +1635,7 @@ def test_exit_with_two_environments(self) -> None: with pytest.raises(RuntimeError): session.exit_environment(identifier=identifier1) - def test_exit_environment_fail_early(self) -> None: + def test_exit_environment_fail_early(self, python_exe: str) -> None: # Testing an environment that fails before running. # This'll fail because we're referencing a Task parameter that doesn't exist. @@ -1627,7 +1644,7 @@ def test_exit_environment_fail_early(self) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1657,7 +1674,7 @@ def test_exit_environment_fail_early(self) -> None: fail_message="Error resolving format string: Failed to parse interpolation expression at [37, 55]. Expression: Task.Param.P . Reason: Expression failed validation: Task.Param.P has no value.", ) - def test_exit_environment_fail_run(self) -> None: + def test_exit_environment_fail_run(self, python_exe: str) -> None: # Testing an Environment enter that fails while running # GIVEN @@ -1665,7 +1682,7 @@ def test_exit_environment_fail_run(self) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Foo }}")], ) ), @@ -1693,7 +1710,7 @@ def test_exit_environment_fail_run(self) -> None: assert session.state == SessionState.READY_ENDING assert session.action_status == ActionStatus(state=ActionState.FAILED, exit_code=1) - def test_exit_no_action(self, caplog: pytest.LogCaptureFixture) -> None: + def test_exit_no_action(self, caplog: pytest.LogCaptureFixture, python_exe: str) -> None: # Testing an environment exit where the given environment has no # onExit action defined. @@ -1702,7 +1719,7 @@ def test_exit_no_action(self, caplog: pytest.LogCaptureFixture) -> None: EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("-c"), ArgString_2023_09("print('hi')")], ) ), @@ -1723,14 +1740,16 @@ def test_exit_no_action(self, caplog: pytest.LogCaptureFixture) -> None: assert session.state == SessionState.READY_ENDING assert session.action_status == ActionStatus(state=ActionState.SUCCESS) - def test_exit_environment_with_variables(self) -> None: + def test_exit_environment_with_variables(self, python_exe: str) -> None: # GIVEN session_id = uuid.uuid4().hex job_params = {"J": ParameterValue(type=ParameterValueType.STRING, value="Jvalue")} variables = { "FOO": EnvironmentVariableValueString_2023_09("bar"), } - environment = _make_environment(enter_script=False, exit_script=True, variables=variables) + environment = _make_environment( + enter_script=False, exit_script=True, variables=variables, python_exe=python_exe + ) with Session(session_id=session_id, job_parameter_values=job_params) as session: # WHEN identifier = session.enter_environment(environment=environment) @@ -1746,7 +1765,7 @@ def test_exit_environment_with_variables(self) -> None: assert session._runner is not None assert session._runner._os_env_vars == dict(variables) - def test_exit_two_environments_with_variables(self) -> None: + def test_exit_two_environments_with_variables(self, python_exe: str) -> None: # GIVEN session_id = uuid.uuid4().hex job_params = {"J": ParameterValue(type=ParameterValueType.STRING, value="Jvalue")} @@ -1757,8 +1776,12 @@ def test_exit_two_environments_with_variables(self) -> None: "FOO": EnvironmentVariableValueString_2023_09("corge"), "BAZ": EnvironmentVariableValueString_2023_09("QUX"), } - environment1 = _make_environment(enter_script=False, exit_script=True, variables=variables1) - environment2 = _make_environment(enter_script=False, exit_script=True, variables=variables2) + environment1 = _make_environment( + enter_script=False, exit_script=True, variables=variables1, python_exe=python_exe + ) + environment2 = _make_environment( + enter_script=False, exit_script=True, variables=variables2, python_exe=python_exe + ) with Session(session_id=session_id, job_parameter_values=job_params) as session: # WHEN identifier1 = session.enter_environment(environment=environment1) @@ -1787,7 +1810,7 @@ def test_exit_two_environments_with_variables(self) -> None: assert session._runner is not None assert session._runner._os_env_vars == dict(variables1) - def test_run_task_after_env_exit(self) -> None: + def test_run_task_after_env_exit(self, python_exe: str) -> None: # By default, tasks can't run after an environment exit. The exit_environment call # can override that, however, and allow it. @@ -1797,11 +1820,13 @@ def test_run_task_after_env_exit(self) -> None: "FOO": EnvironmentVariableValueString_2023_09("corge"), "BAZ": EnvironmentVariableValueString_2023_09("QUX"), } - environment = _make_environment(enter_script=False, exit_script=False, variables=variables) + environment = _make_environment( + enter_script=False, exit_script=False, variables=variables, python_exe=python_exe + ) step_script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Foo }}")], ) ), @@ -2019,7 +2044,9 @@ def test_materialize( assert contents == expected_json @pytest.mark.usefixtures("caplog") # builtin fixture - def test_run_task(self, caplog: pytest.LogCaptureFixture, session_id: str) -> None: + def test_run_task( + self, caplog: pytest.LogCaptureFixture, session_id: str, python_exe: str + ) -> None: # Test that path mapping rules are passed through to a running Task. # i.e. that run_task hooks _materialize_path_mapping() up correctly. @@ -2031,7 +2058,7 @@ def test_run_task(self, caplog: pytest.LogCaptureFixture, session_id: str) -> No script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Script }}")], ) ), @@ -2068,7 +2095,9 @@ def test_run_task(self, caplog: pytest.LogCaptureFixture, session_id: str) -> No assert "Has: true" in caplog.messages @pytest.mark.usefixtures("caplog") # builtin fixture - def test_enter_environment(self, caplog: pytest.LogCaptureFixture, session_id: str) -> None: + def test_enter_environment( + self, caplog: pytest.LogCaptureFixture, session_id: str, python_exe: str + ) -> None: # Test that path mapping rules are passed through to a running environment-enter. # i.e. that enter_environment hooks _materialize_path_mapping() up correctly. @@ -2081,7 +2110,7 @@ def test_enter_environment(self, caplog: pytest.LogCaptureFixture, session_id: s EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Script }}")], ) ), @@ -2118,7 +2147,9 @@ def test_enter_environment(self, caplog: pytest.LogCaptureFixture, session_id: s assert "Has: true" in caplog.messages @pytest.mark.usefixtures("caplog") # builtin fixture - def test_exit_environment(self, caplog: pytest.LogCaptureFixture, session_id: str) -> None: + def test_exit_environment( + self, caplog: pytest.LogCaptureFixture, session_id: str, python_exe: str + ) -> None: # Test that path mapping rules are passed through to a running environment-exit. # i.e. that exit_environment hooks _materialize_path_mapping() up correctly. @@ -2131,7 +2162,7 @@ def test_exit_environment(self, caplog: pytest.LogCaptureFixture, session_id: st EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Script }}")], ) ), @@ -2497,7 +2528,7 @@ class TestEnvironmentVariablesInTasks_2023_09: @staticmethod @pytest.fixture - def step_script_definition() -> StepScript_2023_09: + def step_script_definition(python_exe: str) -> StepScript_2023_09: return StepScript_2023_09( embeddedFiles=[ EmbeddedFileText_2023_09( @@ -2511,7 +2542,7 @@ def step_script_definition() -> StepScript_2023_09: ], actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Task.File.Run }}")], ) ), @@ -2593,7 +2624,10 @@ def test_redefinition_nested( @pytest.mark.usefixtures("caplog") # builtin fixture def test_redefinition_exit( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when one environement redefines an env var that a previous one defined, # and then we exit that environment then the running task gets the values from the @@ -2619,7 +2653,7 @@ def test_redefinition_exit( ], actions=EnvironmentActions_2023_09( onExit=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ArgString_2023_09("{{ Env.File.Run }}")], ) ), @@ -2652,7 +2686,10 @@ def test_redefinition_exit( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_stdout( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables via a stdout handler then the variable # is available in the Task. @@ -2663,7 +2700,7 @@ def test_def_via_stdout( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_env: FOO=FOO-value')"), @@ -2694,7 +2731,10 @@ def test_def_via_stdout( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_multi_line_nonvalid_json_stdout( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables via a stdout handler # as a multiline json it is processed properly @@ -2705,7 +2745,7 @@ def test_def_via_multi_line_nonvalid_json_stdout( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("import json; print('openjd_env: \"FOO=12\\\\n34')"), @@ -2728,8 +2768,7 @@ def test_def_via_multi_line_nonvalid_json_stdout( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_multi_line_stdout( - self, - caplog: pytest.LogCaptureFixture, + self, caplog: pytest.LogCaptureFixture, python_exe: str ) -> None: # Test that when an environment defines variables via a stdout handler # as a multiline json it is processed properly @@ -2740,7 +2779,7 @@ def test_def_via_multi_line_stdout( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_env: \"FOO=12\\\\n34\"')"), @@ -2753,7 +2792,7 @@ def test_def_via_multi_line_stdout( script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09( @@ -2785,7 +2824,10 @@ def test_def_via_multi_line_stdout( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_stdout_overrides_direct( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables directly, and then redefines them via a stdout # handler then the variable from the stdout handler takes precidence. @@ -2796,7 +2838,7 @@ def test_def_via_stdout_overrides_direct( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_env: FOO=FOO-value')"), @@ -2830,7 +2872,10 @@ def test_def_via_stdout_overrides_direct( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_stdout_set_empty_json( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables directly and as empty json it is processed properly @@ -2840,7 +2885,7 @@ def test_def_via_stdout_set_empty_json( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_env: \"FOO=\"')"), @@ -2869,7 +2914,10 @@ def test_def_via_stdout_set_empty_json( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_stdout_set_empty( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables directly and as empty string it is processed properly @@ -2879,7 +2927,7 @@ def test_def_via_stdout_set_empty( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_env: FOO=')"), @@ -2908,7 +2956,10 @@ def test_def_via_stdout_set_empty( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_stdout_fails_session_action_on_error( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables that are malformed then session fails @@ -2918,7 +2969,7 @@ def test_def_via_stdout_fails_session_action_on_error( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_env: FOO')"), @@ -2974,7 +3025,10 @@ def test_def_via_stdout_fails_session_action_on_error( @pytest.mark.usefixtures("caplog") # builtin fixture def test_undef_via_stdout( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment unsets an environment variable that it is actually not defined in # the task that's run. @@ -2992,7 +3046,7 @@ def test_undef_via_stdout( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_unset_env: FOO')"), @@ -3023,7 +3077,10 @@ def test_undef_via_stdout( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_redacted_env_stdout( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables via a stdout handler with openjd_redacted_env # the variable is set correctly but the value is redacted in logs @@ -3034,7 +3091,7 @@ def test_def_via_redacted_env_stdout( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_redacted_env: PASSWORD=secret123')"), @@ -3048,7 +3105,7 @@ def test_def_via_redacted_env_stdout( script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09( @@ -3087,7 +3144,10 @@ def test_def_via_redacted_env_stdout( @pytest.mark.usefixtures("caplog") # builtin fixture def test_def_via_redacted_env_json_stdout( - self, caplog: pytest.LogCaptureFixture, step_script_definition: StepScript_2023_09 + self, + caplog: pytest.LogCaptureFixture, + step_script_definition: StepScript_2023_09, + python_exe: str, ) -> None: # Test that when an environment defines variables via a stdout handler with openjd_redacted_env # using JSON format, the variable is set correctly but the value is redacted in logs @@ -3098,7 +3158,7 @@ def test_def_via_redacted_env_json_stdout( script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_redacted_env: API_KEY=abc123def456')"), @@ -3112,7 +3172,7 @@ def test_def_via_redacted_env_json_stdout( script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("import os; print(f'API_KEY={os.environ[\"API_KEY\"]}')"), @@ -3185,7 +3245,9 @@ def test_session_with_no_extensions(self, caplog: pytest.LogCaptureFixture) -> N assert session.get_enabled_extensions() == [] @pytest.mark.usefixtures("caplog") # builtin fixture - def test_def_via_redacted_env_with_variables(self, caplog: pytest.LogCaptureFixture) -> None: + def test_def_via_redacted_env_with_variables( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: """Test that redacted env vars override directly defined variables.""" # GIVEN environment = Environment_2023_09( @@ -3193,7 +3255,7 @@ def test_def_via_redacted_env_with_variables(self, caplog: pytest.LogCaptureFixt script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_redacted_env: TOKEN=secret-token')"), @@ -3208,7 +3270,7 @@ def test_def_via_redacted_env_with_variables(self, caplog: pytest.LogCaptureFixt script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("import os; print(f'TOKEN={os.environ[\"TOKEN\"]}')"), @@ -3246,7 +3308,9 @@ def test_def_via_redacted_env_with_variables(self, caplog: pytest.LogCaptureFixt assert "Setting: TOKEN=public-token" in caplog.messages @pytest.mark.usefixtures("caplog") # builtin fixture - def test_def_via_redacted_env_with_extension(self, caplog: pytest.LogCaptureFixture) -> None: + def test_def_via_redacted_env_with_extension( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: """Test that redacted env vars are set when the extension is enabled.""" # GIVEN environment = Environment_2023_09( @@ -3254,7 +3318,7 @@ def test_def_via_redacted_env_with_extension(self, caplog: pytest.LogCaptureFixt script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09("print('openjd_redacted_env: PASSWORD=secret123')"), @@ -3268,7 +3332,7 @@ def test_def_via_redacted_env_with_extension(self, caplog: pytest.LogCaptureFixt script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09( @@ -3312,7 +3376,9 @@ def test_def_via_redacted_env_with_extension(self, caplog: pytest.LogCaptureFixt assert "secret123" not in "\n".join(caplog.messages) @pytest.mark.usefixtures("caplog") # builtin fixture - def test_multiple_different_redacted_env_vars(self, caplog: pytest.LogCaptureFixture) -> None: + def test_multiple_different_redacted_env_vars( + self, caplog: pytest.LogCaptureFixture, python_exe: str + ) -> None: """Test that multiple redacted env vars with similar but different values are handled correctly.""" # GIVEN # Create an environment that sets two similar but different redacted env vars @@ -3321,7 +3387,7 @@ def test_multiple_different_redacted_env_vars(self, caplog: pytest.LogCaptureFix script=EnvironmentScript_2023_09( actions=EnvironmentActions_2023_09( onEnter=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09( @@ -3337,7 +3403,7 @@ def test_multiple_different_redacted_env_vars(self, caplog: pytest.LogCaptureFix script = StepScript_2023_09( actions=StepActions_2023_09( onRun=Action_2023_09( - command=CommandString_2023_09(sys.executable), + command=CommandString_2023_09(python_exe), args=[ ArgString_2023_09("-c"), ArgString_2023_09( diff --git a/test/openjd/sessions/test_subprocess.py b/test/openjd/sessions/test_subprocess.py index c6856eff..5f38d7ac 100644 --- a/test/openjd/sessions/test_subprocess.py +++ b/test/openjd/sessions/test_subprocess.py @@ -41,14 +41,14 @@ def test_must_have_args(self, queue_handler: QueueHandler) -> None: with pytest.raises(ValueError): LoggingSubprocess(logger=logger, args=[]) - def test_getters_return_none(self, queue_handler: QueueHandler) -> None: + def test_getters_return_none(self, queue_handler: QueueHandler, python_exe: str) -> None: # Check that the getters all return None if the subprocess hasn't run yet. # GIVEN logger = build_logger(queue_handler) subproc = LoggingSubprocess( logger=logger, - args=[sys.executable, "-c", 'print("Test")'], + args=[python_exe, "-c", 'print("Test")'], ) # THEN @@ -58,7 +58,11 @@ def test_getters_return_none(self, queue_handler: QueueHandler) -> None: @pytest.mark.parametrize("exitcode", [0, 1]) def test_basic_operation( - self, exitcode: int, message_queue: SimpleQueue, queue_handler: QueueHandler + self, + exitcode: int, + message_queue: SimpleQueue, + queue_handler: QueueHandler, + python_exe: str, ) -> None: # Can we run a process, capture its output, and discover its return code? @@ -67,7 +71,7 @@ def test_basic_operation( message = "this is 'output'" subproc = LoggingSubprocess( logger=logger, - args=[sys.executable, "-c", f'import sys; print("{message}"); sys.exit({exitcode})'], + args=[python_exe, "-c", f'import sys; print("{message}"); sys.exit({exitcode})'], ) # WHEN @@ -85,7 +89,11 @@ def test_basic_operation( @pytest.mark.skipif(not is_posix(), reason="posix-specific test") @pytest.mark.parametrize("exitcode", [0, 1]) def test_basic_operation_with_sameuser( - self, exitcode: int, message_queue: SimpleQueue, queue_handler: QueueHandler + self, + exitcode: int, + message_queue: SimpleQueue, + queue_handler: QueueHandler, + python_exe: str, ) -> None: # If the SessionUser is the process owner, then do we still run correctly. # Note: PosixSessionUser autopopulates the group if it's not given. @@ -98,7 +106,7 @@ def test_basic_operation_with_sameuser( message = "this is output" subproc = LoggingSubprocess( logger=logger, - args=[sys.executable, "-c", f'import sys; print("{message}"); sys.exit({exitcode})'], + args=[python_exe, "-c", f'import sys; print("{message}"); sys.exit({exitcode})'], user=user, ) @@ -161,7 +169,9 @@ def test_cannot_run_with_callback( assert not subproc.is_running callback_mock.assert_called_once() - def test_captures_stderr(self, message_queue: SimpleQueue, queue_handler: QueueHandler) -> None: + def test_captures_stderr( + self, message_queue: SimpleQueue, queue_handler: QueueHandler, python_exe: str + ) -> None: # Ensure that messages sent to stderr are logged # GIVEN @@ -169,7 +179,7 @@ def test_captures_stderr(self, message_queue: SimpleQueue, queue_handler: QueueH message = "this is output" subproc = LoggingSubprocess( logger=logger, - args=[sys.executable, "-c", f'import sys; print("{message}", file=sys.stderr)'], + args=[python_exe, "-c", f'import sys; print("{message}", file=sys.stderr)'], ) # WHEN @@ -179,14 +189,14 @@ def test_captures_stderr(self, message_queue: SimpleQueue, queue_handler: QueueH messages = collect_queue_messages(message_queue) assert message in messages - def test_cannot_run_twice(self, queue_handler: QueueHandler) -> None: + def test_cannot_run_twice(self, queue_handler: QueueHandler, python_exe: str) -> None: # We should fail if we try to run a LoggingSubprocess twice # GIVEN logger = build_logger(queue_handler) subproc = LoggingSubprocess( logger=logger, - args=[sys.executable, "-c", "print('Test')"], + args=[python_exe, "-c", "print('Test')"], ) # WHEN @@ -196,7 +206,7 @@ def test_cannot_run_twice(self, queue_handler: QueueHandler) -> None: with pytest.raises(RuntimeError): subproc.run() - def test_invokes_callback(self, queue_handler: QueueHandler) -> None: + def test_invokes_callback(self, queue_handler: QueueHandler, python_exe: str) -> None: # Make sure that the given callback is invoked when the process exits. # GIVEN @@ -205,7 +215,7 @@ def test_invokes_callback(self, queue_handler: QueueHandler) -> None: subproc = LoggingSubprocess( logger=logger, args=[ - sys.executable, + python_exe, "-c", "print('This is just a test')", ], @@ -219,7 +229,7 @@ def test_invokes_callback(self, queue_handler: QueueHandler) -> None: callback_mock.assert_called_once() def test_notify_ends_process( - self, message_queue: SimpleQueue, queue_handler: QueueHandler + self, message_queue: SimpleQueue, queue_handler: QueueHandler, python_exe: str ) -> None: # Make sure that process is sent a notification signal @@ -228,7 +238,7 @@ def test_notify_ends_process( python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() subproc = LoggingSubprocess( logger=logger, - args=[sys.executable, str(python_app_loc)], + args=[python_exe, str(python_app_loc)], ) all_messages = [] @@ -260,7 +270,7 @@ def end_proc(): assert subproc.exit_code != 0 def test_terminate_ends_process( - self, message_queue: SimpleQueue, queue_handler: QueueHandler + self, message_queue: SimpleQueue, queue_handler: QueueHandler, python_exe: str ) -> None: # Make sure that the subprocess is forcefully killed when terminated @@ -269,7 +279,7 @@ def test_terminate_ends_process( python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() subproc = LoggingSubprocess( logger=logger, - args=[sys.executable, str(python_app_loc)], + args=[python_exe, str(python_app_loc)], ) all_messages = [] @@ -309,6 +319,7 @@ def test_terminate_ends_process_tree( self, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Make sure that the subprocess and all of its children are forcefully killed when terminated from psutil import Process, NoSuchProcess, STATUS_ZOMBIE @@ -316,14 +327,14 @@ def test_terminate_ends_process_tree( # GIVEN logger = build_logger(queue_handler) script_loc = (Path(__file__).parent / "support_files" / "run_app_20s_run.py").resolve() - args = [sys.executable, str(script_loc)] + args = [python_exe, str(script_loc)] subproc = LoggingSubprocess(logger=logger, args=args) children = [] all_messages = [] # Note: This is the number of *CHILD* processes of the main process that we start. # The total number of processes in flight will be this plus one. - # On Posix and on Windows not in a virutal environment: + # On Posix and on Windows not in a virtual environment: # Process tree: python -> python # Children: python expected_num_child_procs = 1 @@ -334,6 +345,11 @@ def test_terminate_ends_process_tree( # Process tree: conhost -> python -> python -> python # Children: python, python, python expected_num_child_procs = 3 + elif is_windows() and are_tests_in_windows_session_0(): + # When running as a service there's an additional process that gets added + # Process tree: conhost -> python -> python + # Children: python, python + expected_num_child_procs = 2 def end_proc(): subproc.wait_until_started() @@ -387,6 +403,7 @@ def test_run_reads_max_line_length( self, message_queue: SimpleQueue, queue_handler: QueueHandler, + python_exe: str, ) -> None: # Make sure the run method reads up to a max line length @@ -396,7 +413,7 @@ def test_run_reads_max_line_length( subproc = LoggingSubprocess( logger=logger, args=[ - sys.executable, + python_exe, "-c", f"""import sys print("a" * {expected_max_line_length}, end="") @@ -453,6 +470,7 @@ def test_run_gracetime_when_process_ends_but_grandchild_uses_stdout( queue_handler: QueueHandler, command: str, expected_message_indices: list[int], + python_exe: str, ) -> None: # Make sure that the run command ends when the main subprocess ends # GIVEN @@ -460,7 +478,7 @@ def test_run_gracetime_when_process_ends_but_grandchild_uses_stdout( subproc = LoggingSubprocess( logger=logger, args=[ - sys.executable, + python_exe, "-c", f'import subprocess;process = subprocess.Popen({command}, encoding="utf-8")', ], @@ -896,7 +914,6 @@ def test_notify_ends_process( windows_user: WindowsSessionUser, ) -> None: # Make sure that process is sent a notification signal - # GIVEN logger = build_logger(queue_handler) python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() @@ -1005,10 +1022,6 @@ def test_terminate_ends_process_tree( all_messages = [] # conhost, python expected_num_child_procs: int = 2 - if are_tests_in_windows_session_0(): - # Session 0 doesn't get the conhost process, so just: - # python - expected_num_child_procs = 1 def end_proc(): subproc.wait_until_started() diff --git a/test/openjd/sessions/test_windows_process_killer.py b/test/openjd/sessions/test_windows_process_killer.py index 8fb397ed..74905ff9 100644 --- a/test/openjd/sessions/test_windows_process_killer.py +++ b/test/openjd/sessions/test_windows_process_killer.py @@ -1,7 +1,6 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. import subprocess -import sys import time from pathlib import Path import psutil @@ -20,11 +19,11 @@ @pytest.mark.usefixtures("message_queue", "queue_handler") class TestWindowsProcessKiller: - def test_suspend_process_tree(self, queue_handler: QueueHandler) -> None: + def test_suspend_process_tree(self, queue_handler: QueueHandler, python_exe: str) -> None: # GIVEN logger = build_logger(queue_handler) python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() - process = Popen([sys.executable, python_app_loc], stdout=subprocess.PIPE, text=True) + process = Popen([python_exe, python_app_loc], stdout=subprocess.PIPE, text=True) # When # Give a few seconds for running the python script @@ -38,11 +37,11 @@ def test_suspend_process_tree(self, queue_handler: QueueHandler) -> None: finally: proc.kill() - def test_suspend_process(self, queue_handler: QueueHandler) -> None: + def test_suspend_process(self, queue_handler: QueueHandler, python_exe: str) -> None: # GIVEN logger = build_logger(queue_handler) python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() - process = Popen([sys.executable, python_app_loc], stdout=subprocess.PIPE, text=True) + process = Popen([python_exe, python_app_loc], stdout=subprocess.PIPE, text=True) # When # Give a few seconds for running the python script @@ -57,11 +56,11 @@ def test_suspend_process(self, queue_handler: QueueHandler) -> None: finally: proc.kill() - def test_kill_processes(self, queue_handler: QueueHandler) -> None: + def test_kill_processes(self, queue_handler: QueueHandler, python_exe: str) -> None: # GIVEN logger = build_logger(queue_handler) python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() - process = Popen([sys.executable, python_app_loc], stdout=subprocess.PIPE, text=True) + process = Popen([python_exe, python_app_loc], stdout=subprocess.PIPE, text=True) # When # Give a few seconds for running the python script @@ -72,11 +71,11 @@ def test_kill_processes(self, queue_handler: QueueHandler) -> None: # Then assert not psutil.pid_exists(process.pid) - def test_kill_windows_process_tree(self, queue_handler: QueueHandler) -> None: + def test_kill_windows_process_tree(self, queue_handler: QueueHandler, python_exe: str) -> None: # GIVEN logger = build_logger(queue_handler) python_app_loc = (Path(__file__).parent / "support_files" / "app_20s_run.py").resolve() - process = Popen([sys.executable, python_app_loc], stdout=subprocess.PIPE, text=True) + process = Popen([python_exe, python_app_loc], stdout=subprocess.PIPE, text=True) # When # Give a few seconds for running the python script