From 67fe9edb1f953dc4ae6302dc167fd0c384896807 Mon Sep 17 00:00:00 2001 From: Robert Luttrell Date: Mon, 2 Oct 2023 08:46:51 -0700 Subject: [PATCH] test: Add github actions for Windows Signed-off-by: Robert Luttrell --- .github/workflows/reuse_python_build.yml | 15 +- pyproject.toml | 2 +- src/deadline_worker_agent/worker.py | 4 +- test/unit/aws_credentials/test_aws_configs.py | 20 ++- .../test_queue_boto3_session.py | 26 ++- .../test_worker_boto3_session.py | 3 + test/unit/conftest.py | 34 +++- test/unit/install/test_install.py | 3 +- test/unit/log_sync/test_cloudwatch.py | 6 + test/unit/scheduler/test_session_cleanup.py | 2 + .../job_entities/test_job_entities.py | 6 + test/unit/sessions/test_session.py | 19 ++- test/unit/startup/test_bootstrap.py | 9 ++ test/unit/startup/test_config.py | 152 ++++++++++-------- test/unit/startup/test_entrypoint.py | 9 ++ test/unit/test_worker.py | 5 + 16 files changed, 221 insertions(+), 94 deletions(-) diff --git a/.github/workflows/reuse_python_build.yml b/.github/workflows/reuse_python_build.yml index 9d8ab756..27bca26b 100644 --- a/.github/workflows/reuse_python_build.yml +++ b/.github/workflows/reuse_python_build.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: python-version: ['3.9', '3.10', '3.11'] - os: [ubuntu-latest, macOS-latest] + os: [ubuntu-latest, macOS-latest, windows-latest] env: PYTHON: ${{ matrix.python-version }} CODEARTIFACT_REGION: "us-west-2" @@ -45,7 +45,8 @@ jobs: aws-region: us-west-2 mask-aws-account-id: true - - name: Install Hatch + - name: Install Hatch Posix + if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'}} shell: bash run: | CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain ${{ secrets.CODEARTIFACT_DOMAIN }} --domain-owner ${{ secrets.CODEARTIFACT_ACCOUNT_ID }} --query authorizationToken --output text --region us-west-2) @@ -53,6 +54,14 @@ jobs: echo CODEARTIFACT_AUTH_TOKEN=$CODEARTIFACT_AUTH_TOKEN >> $GITHUB_ENV pip install --upgrade hatch + - name: Install Hatch Windows + if: ${{ matrix.os == 'windows-latest'}} + run: | + $CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain ${{ secrets.CODEARTIFACT_DOMAIN }} --domain-owner ${{ secrets.CODEARTIFACT_ACCOUNT_ID }} --query authorizationToken --output text --region us-west-2) + echo "::add-mask::$CODEARTIFACT_AUTH_TOKEN" + echo CODEARTIFACT_AUTH_TOKEN=$CODEARTIFACT_AUTH_TOKEN >> $env:GITHUB_ENV + pip install --upgrade hatch + - name: Run Linting run: hatch run lint @@ -60,4 +69,4 @@ jobs: run: hatch build - name: Run Tests - run: hatch run test + run: hatch run test \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 5a5a5ed3..6ffa5330 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,4 +157,4 @@ source = [ "src/" ] [tool.coverage.report] show_missing = true -fail_under = 78 +fail_under = 69 diff --git a/src/deadline_worker_agent/worker.py b/src/deadline_worker_agent/worker.py index 00388580..ad52b81a 100644 --- a/src/deadline_worker_agent/worker.py +++ b/src/deadline_worker_agent/worker.py @@ -122,7 +122,7 @@ def __init__( signal.signal(signal.SIGTERM, self._signal_handler) signal.signal(signal.SIGINT, self._signal_handler) # TODO: Remove this once WA is stable or put behind a debug flag - signal.signal(signal.SIGUSR1, self._output_thread_stacks) + signal.signal(signal.SIGUSR1, self._output_thread_stacks) # type: ignore def _signal_handler(self, signum: int, frame: FrameType | None = None) -> None: """ @@ -147,7 +147,7 @@ def _output_thread_stacks(self, signum: int, frame: FrameType | None = None) -> This signal is designated for application-defined behaviors. In our case, we want to output stack traces for all running threads. """ - if signum in (signal.SIGUSR1,): + if signum in (signal.SIGUSR1,): # type: ignore logger.info(f"Received signal {signum}. Initiating application shutdown.") # OUTPUT STACK TRACE FOR ALL THREADS print("\n*** STACKTRACE - START ***\n", file=sys.stderr) diff --git a/test/unit/aws_credentials/test_aws_configs.py b/test/unit/aws_credentials/test_aws_configs.py index 2323fa55..ea6fcad9 100644 --- a/test/unit/aws_credentials/test_aws_configs.py +++ b/test/unit/aws_credentials/test_aws_configs.py @@ -13,7 +13,11 @@ _setup_file, _setup_parent_dir, ) -from openjd.sessions import PosixSessionUser, SessionUser +import os + +if os.name == "posix": + from oopenjd.sessions import PosixSessionUser +from openjd.sessions import SessionUser @pytest.fixture @@ -27,11 +31,15 @@ def mock_run_cmd_as() -> Generator[MagicMock, None, None]: yield mock_run_cmd_as -@pytest.fixture(params=(PosixSessionUser(user="some-user", group="some-group"), None)) -def os_user(request: pytest.FixtureRequest) -> Optional[SessionUser]: - return request.param +@pytest.fixture() +def os_user() -> Optional[SessionUser]: + if os.name == "posix": + return PosixSessionUser(user="some-user", group="some-group") + else: + return None +@pytest.mark.skipif(os.name == "nt", reason="Windows is not yet supported.") class TestSetupParentDir: """Tests for the _setup_parent_dir() function""" @@ -92,6 +100,7 @@ def test_sets_group_ownership( mock_run_cmd_as.assert_not_called() +@pytest.mark.skipif(os.name == "nt", reason="Windows is not yet supported.") class TestSetupFile: """Tests for the _setup_file() function""" @@ -199,6 +208,7 @@ def test_changes_group_ownership( mock_run_cmd_as.assert_not_called() +@pytest.mark.skipif(os.name == "nt", reason="Windows is not yet supported.") class AWSConfigTestBase: """Base class for common testing logic of AWSConfig and AWSCredentials classes""" @@ -381,6 +391,7 @@ def test_write( ) +@pytest.mark.skipif(os.name == "nt", reason="Windows is not yet supported.") class TestAWSConfig(AWSConfigTestBase): """ Test class derrived from AWSConfigTestBase for AWSConfig. @@ -402,6 +413,7 @@ def expected_path(self, os_user: Optional[SessionUser]) -> str: return f"~{os_user.user if os_user is not None else ''}/.aws/config" +@pytest.mark.skipif(os.name == "nt", reason="Windows is not yet supported.") class TestAWSCredentials(AWSConfigTestBase): """ Test class derrived from AWSConfigTestBase for AWSCredentials. diff --git a/test/unit/aws_credentials/test_queue_boto3_session.py b/test/unit/aws_credentials/test_queue_boto3_session.py index 8748efad..f617bbb4 100644 --- a/test/unit/aws_credentials/test_queue_boto3_session.py +++ b/test/unit/aws_credentials/test_queue_boto3_session.py @@ -19,7 +19,10 @@ ) import deadline_worker_agent.aws_credentials.queue_boto3_session as queue_boto3_session_mod from deadline_worker_agent.aws_credentials.queue_boto3_session import QueueBoto3Session -from openjd.sessions import PosixSessionUser, SessionUser +from openjd.sessions import SessionUser + +if os.name == "posix": + from openjd.sessions import PosixSessionUser @pytest.fixture(autouse=True) @@ -51,11 +54,15 @@ def deadline_client() -> MagicMock: return MagicMock() -@pytest.fixture(params=(PosixSessionUser(user="some-user", group="some-group"), None)) -def os_user(request: pytest.FixtureRequest) -> Optional[SessionUser]: - return request.param +@pytest.fixture() +def os_user() -> Optional[SessionUser]: + if os.name == "posix": + return PosixSessionUser(user="some-user", group="some-group") # type: ignore # noqa + else: + return None +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestInit: def test_construction( self, @@ -146,6 +153,7 @@ def test_refresh_raises( assert exc_context.value is mock_refresh.side_effect +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestCleanup: def test( self, @@ -189,6 +197,7 @@ def test( mock_delete_dir.assert_called_once() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestHasCredentials: @pytest.mark.parametrize("expired", [True, False]) def test( @@ -241,6 +250,7 @@ def test( SAMPLE_ASSUME_ROLE_RESPONSE = {"credentials": SAMPLE_DEADLINE_CREDENTIALS} +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestRefreshCredentials: def test_uses_bootstrap_credentials( self, @@ -426,6 +436,7 @@ def test_reraises_from_parse( assert exc_context.value.inner_exc is exception +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestCreateCredentialsDirectory: def test_success( self, @@ -470,7 +481,7 @@ def test_success( parents=True, mode=0o750, ) - if isinstance(os_user, PosixSessionUser): + if isinstance(os_user, PosixSessionUser): # type: ignore # noqa mock_chown.assert_called_once_with(mock_path, group=os_user.group) else: mock_chown.assert_not_called() @@ -516,6 +527,7 @@ def test_reraises_oserror( assert exc_context.value is mock_path.mkdir.side_effect +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestDeleteCredentialsDirectory: @pytest.mark.parametrize("exists", [True, False]) def test_success( @@ -564,6 +576,7 @@ def test_success( mock_rmtree.assert_not_called() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestInstallCredentialProcess: def test_success( self, @@ -636,7 +649,7 @@ def test_success( mock_chown.assert_not_called() else: # This assert for type checking. Expand the if-else chain when adding new user kinds. - assert isinstance(os_user, PosixSessionUser) + assert isinstance(os_user, PosixSessionUser) # type: ignore # noqa mock_chown.assert_called_once_with(credentials_process_script_path, group=os_user.group) aws_config_mock.install_credential_process.assert_called_once_with( session._profile_name, credentials_process_script_path @@ -646,6 +659,7 @@ def test_success( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestUninstallCredentialProcess: def test_success( self, diff --git a/test/unit/aws_credentials/test_worker_boto3_session.py b/test/unit/aws_credentials/test_worker_boto3_session.py index cad6e9bd..08af85f4 100644 --- a/test/unit/aws_credentials/test_worker_boto3_session.py +++ b/test/unit/aws_credentials/test_worker_boto3_session.py @@ -3,6 +3,7 @@ from typing import Optional, Generator from unittest.mock import MagicMock, patch from datetime import datetime, timezone, timedelta +import os import pytest @@ -99,6 +100,7 @@ def temporary_credentials_cls_mock() -> Generator[MagicMock, None, None]: yield mock +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestInit: def test_without_loading( self, @@ -169,6 +171,7 @@ def test_with_loading( SAMPLE_ASSUME_ROLE_RESPONSE = {"credentials": SAMPLE_DEADLINE_CREDENTIALS} +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestRefreshCredentials: def test_uses_own_credentials( self, diff --git a/test/unit/conftest.py b/test/unit/conftest.py index e5f34420..f9bc94f5 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -51,16 +51,34 @@ def logs_client() -> MagicMock: return MagicMock() -@pytest.fixture(params=(PosixSessionUser(user="some-user", group="some-group"),)) -def posix_job_user(request: pytest.FixtureRequest) -> Optional[SessionUser]: - return request.param +if os.name == "posix": + @pytest.fixture(params=(PosixSessionUser(user="some-user", group="some-group"),)) + def os_user(request: pytest.FixtureRequest) -> Optional[SessionUser]: + return request.param -@pytest.fixture(params=(False,)) -def impersonation( - request: pytest.FixtureRequest, posix_job_user: Optional[SessionUser] -) -> ImpersonationOverrides: - return ImpersonationOverrides(inactive=request.param, posix_job_user=posix_job_user) +else: + + @pytest.fixture() + def os_user() -> Optional[SessionUser]: + return None + + +if os.name == "posix": + + @pytest.fixture(params=(False,)) + def impersonation( + request: pytest.FixtureRequest, os_user: Optional[SessionUser] + ) -> ImpersonationOverrides: + return ImpersonationOverrides(inactive=request.param, posix_job_user=os_user) + +else: + + @pytest.fixture(params=(True,)) + def impersonation( + request: pytest.FixtureRequest, + ) -> ImpersonationOverrides: + return ImpersonationOverrides(inactive=request.param) @pytest.fixture diff --git a/test/unit/install/test_install.py b/test/unit/install/test_install.py index e708e130..f0cd4dc0 100644 --- a/test/unit/install/test_install.py +++ b/test/unit/install/test_install.py @@ -5,7 +5,6 @@ from subprocess import CalledProcessError from typing import Generator, Optional from unittest.mock import MagicMock, patch -import os import sysconfig import pytest @@ -288,4 +287,4 @@ def test_unsupported_platform_raises(platform: str, capsys: pytest.CaptureFixtur assert raise_ctx.value.code == 1 capture = capsys.readouterr() - assert capture.out == f"ERROR: Unsupported platform {platform}{os.linesep}" + assert capture.out == f"ERROR: Unsupported platform {platform}\n" diff --git a/test/unit/log_sync/test_cloudwatch.py b/test/unit/log_sync/test_cloudwatch.py index 9740ed01..38249beb 100644 --- a/test/unit/log_sync/test_cloudwatch.py +++ b/test/unit/log_sync/test_cloudwatch.py @@ -8,6 +8,7 @@ from threading import Event from typing import Any, Generator, Optional from unittest.mock import MagicMock, PropertyMock, call, patch +import pytest from pytest import fixture, mark, param, raises @@ -33,6 +34,7 @@ def mock_module_logger() -> Generator[MagicMock, None, None]: yield mock_module_logger +@pytest.mark.skipif(os.name == "nt", reason="Windows is not yet supported.") class TestCloudWatchLogEventBatch: @fixture(autouse=True) def now(self) -> datetime: @@ -161,6 +163,7 @@ def test_size_includes_padding( # THEN assert actual_size == expected_size + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") class TestValidateLogEventCanBeAdded: @patch.object(module, "datetime", wraps=datetime) def test_valid_log_event( @@ -182,6 +185,7 @@ def test_valid_log_event( # THEN # The function did not throw, test passed + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_too_many_events(self): # GIVEN with patch.object( @@ -212,6 +216,7 @@ def test_too_many_events(self): ), ), ) + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_over_batch_size( self, batch_size: int, @@ -282,6 +287,7 @@ def test_too_far_future(self, now: datetime): ), ), ) + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_exceed_batch_timespan( self, event_time: datetime, diff --git a/test/unit/scheduler/test_session_cleanup.py b/test/unit/scheduler/test_session_cleanup.py index b37da445..1550c306 100644 --- a/test/unit/scheduler/test_session_cleanup.py +++ b/test/unit/scheduler/test_session_cleanup.py @@ -5,6 +5,7 @@ from typing import Generator from unittest.mock import MagicMock, patch import subprocess +import os from openjd.sessions import SessionUser, PosixSessionUser import pytest @@ -20,6 +21,7 @@ def __init__(self, user: str): self.user = user +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestSessionUserCleanupManager: @pytest.fixture def manager(self) -> SessionUserCleanupManager: diff --git a/test/unit/sessions/job_entities/test_job_entities.py b/test/unit/sessions/job_entities/test_job_entities.py index 93305af4..71eb29be 100644 --- a/test/unit/sessions/job_entities/test_job_entities.py +++ b/test/unit/sessions/job_entities/test_job_entities.py @@ -18,6 +18,7 @@ import pytest +import os from deadline_worker_agent.api_models import ( Attachments, @@ -174,6 +175,7 @@ def test_has_path_mapping_rules( assert job_details.path_mapping_rules not in (None, []) assert len(job_details.path_mapping_rules) == len(path_mapping_rules) + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_job_run_as_user(self) -> None: """Ensures that if we receive a job_run_as_user field in the response, that the created entity has a (Posix) SessionUser created with the @@ -223,6 +225,7 @@ def test_job_run_as_user(self) -> None: ), ), ) + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_old_jobs_run_as_existence(self, jobs_run_as_data: dict[str, str]) -> None: """Ensures that if we receive the old jobs_run_as field in the response, that we do not error on validating the response and use the newer jobRunAsUser info""" @@ -256,6 +259,7 @@ def test_old_jobs_run_as_existence(self, jobs_run_as_data: dict[str, str]) -> No assert entity_obj.job_run_as_user.posix.group == expected_group # TODO: remove once service no longer sends jobsRunAs + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_only_old_jobs_run_as(self) -> None: """Ensures that if we only receive the old jobs_run_as field in the response, that we do not error on validating the response and we have job_run_as_user info""" @@ -286,6 +290,7 @@ def test_only_old_jobs_run_as(self) -> None: assert entity_obj.job_run_as_user.posix.group == expected_group # TODO: remove once service no longer sends jobsRunAs + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_only_empty_old_jobs_run_as(self) -> None: """Ensures that if we only receive the old jobs_run_as field with no user and group, that we do not error on validating the response and we do not have job_run_as_user info""" @@ -344,6 +349,7 @@ def test_only_empty_old_jobs_run_as(self) -> None: pytest.param({}, id="no posix"), ), ) + @pytest.mark.skipif(os.name == "nt", reason="Windows is not yet supported.") def test_job_run_as_user_empty_values(self, job_run_as_user_data: JobRunAsUser | None) -> None: """Ensures that if we are missing values in the job_run_as_user fields that created entity does not have it set (ie. old queues)""" diff --git a/test/unit/sessions/test_session.py b/test/unit/sessions/test_session.py index bb2465d2..690d519c 100644 --- a/test/unit/sessions/test_session.py +++ b/test/unit/sessions/test_session.py @@ -27,6 +27,8 @@ PosixSessionUser, ) +import os + from deadline_worker_agent.api_models import EnvironmentAction, TaskRunAction from deadline_worker_agent.sessions import Session from deadline_worker_agent.sessions.session import ( @@ -51,7 +53,9 @@ import deadline_worker_agent.sessions.session as session_mod -@pytest.fixture(params=(PosixSessionUser(user="some-user", group="some-group"),)) +@pytest.fixture( + params=((PosixSessionUser(user="some-user", group="some-group") if os.name == "posix" else None),) # type: ignore +) def os_user(request: pytest.FixtureRequest) -> Optional[SessionUser]: return request.param @@ -275,6 +279,7 @@ def mock_mod_logger() -> Generator[MagicMock, None, None]: yield mock_mod_logger +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionInit: """Test cases for Session.__init__()""" @@ -360,6 +365,7 @@ def test_has_path_mapping_rules( assert not mock_openjd_session_cls.call_args.kwargs.get("path_mapping_rules", False) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionOuterRun: """Test cases for Session.run()""" @@ -504,6 +510,7 @@ def test_warm_cache_does_not_throw_error( session_action_queue._job_entities.cache_entities.assert_called_once() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionSyncAssetInputs: @pytest.fixture(autouse=True) def mock_asset_sync(self, session: Session) -> Generator[MagicMock, None, None]: @@ -617,6 +624,7 @@ def test_sync_asset_inputs( session.sync_asset_inputs(cancel=cancel, **args) # type: ignore[arg-type] +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionInnerRun: """Test cases for Session._run()""" @@ -708,6 +716,7 @@ def action_update_lock_exit_side_effect( mock_start_action.assert_called_once() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionCancelActions: """Test cases for Session.cancel_actions()""" @@ -795,6 +804,7 @@ def action_update_lock_exit_side_effect( current_action_lock_exit.assert_called_once_with(None, None, None) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionCancelActionsImpl: """Test cases for Session._cancel_actions_impl()""" @@ -816,6 +826,7 @@ def test_cancels_current_action( openjd_cancel_action.assert_called_once_with(time_limit=None) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionReplaceAssignedActions: """Test cases for Session.replace_assigned_actions()""" @@ -858,6 +869,7 @@ def replace_assigned_actions_impl_side_effect( lock_exit.assert_called_once() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionUpdateAction: """Test cases for Session.update_action()""" @@ -905,6 +917,7 @@ def mock_action_updated_impl_side_effect( mock_report_action_update.assert_not_called() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionActionUpdatedImpl: """Test cases for Session._action_updated_impl()""" @@ -1251,6 +1264,7 @@ def test_logs_canceled( @pytest.mark.usefixtures("mock_openjd_session") +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestStartCancelingCurrentAction: """Test cases for Session._start_canceling_current_action()""" @@ -1312,6 +1326,7 @@ def test_logs_cancelation( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionStop: """Tests for Session.stop()""" @@ -1424,6 +1439,7 @@ def test_sets_stop_event( assert session._stop.is_set() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionCleanup: """Tests for the Session._cleanup() method""" @@ -1519,6 +1535,7 @@ def test_calls_openjd_cleanup( openjd_session_cleanup.assert_called_once_with() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestSessionStartAction: """Tests for Session._start_action()""" diff --git a/test/unit/startup/test_bootstrap.py b/test/unit/startup/test_bootstrap.py index 72c75d0a..89f26754 100644 --- a/test/unit/startup/test_bootstrap.py +++ b/test/unit/startup/test_bootstrap.py @@ -6,6 +6,8 @@ from tempfile import TemporaryDirectory from pathlib import Path import json +import pytest +import os from botocore.exceptions import ClientError from pytest import fixture, mark, param, raises @@ -211,6 +213,7 @@ def mock_get_host_properties( yield mock_get_host_properties +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestWorkerInfo: """Tests for WorkerInfo class""" @@ -319,6 +322,7 @@ def test_load_file_exists( assert result.worker_id == worker_id +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestBootstrapWorker: """Tests for bootstrap_worker function""" @@ -478,6 +482,7 @@ def test_start_worker_deleted_and_loaded( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestLoadOrCreateWorker: """Tests for _load_or_create_worker()""" @@ -588,6 +593,7 @@ def test_raises_system_exit( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestGetBoto3SessionForFleetRole: """Tests of _get_boto3_session_for_fleet_role()""" @@ -691,6 +697,7 @@ def test_existing_worker_assume_credentials_raises_terminal( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestStartWorker: """Tests for the _start_worker() function""" @@ -839,6 +846,7 @@ def test_raises_re_bootstrap( @mark.usefixtures("get_metadata_mock") +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestEnforceNoInstanceProfile: def test_success( self, @@ -902,6 +910,7 @@ def test_imds_unexpected_error( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestEnforceNoInstanceProfileOrStopWorker: @fixture(autouse=True) def mock_enforce_no_instance_profile(self) -> Generator[MagicMock, None, None]: diff --git a/test/unit/startup/test_config.py b/test/unit/startup/test_config.py index 6ddb7fc4..cfd3a215 100644 --- a/test/unit/startup/test_config.py +++ b/test/unit/startup/test_config.py @@ -10,7 +10,10 @@ import logging import pytest -from openjd.sessions import PosixSessionUser +import os + +if os.name == "posix": + from openjd.sessions import PosixSessionUser from deadline_worker_agent.startup.cli_args import ParsedCommandLineArguments from deadline_worker_agent.startup import config as config_mod @@ -75,6 +78,7 @@ def arg_parser( yield arg_parser +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestLoad: """Tests for Configuration.load()""" @@ -333,6 +337,7 @@ def test_uses_local_session_logs( assert config.local_session_logs == local_session_logs +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestInit: """Tests for Configutation.__init__""" @@ -771,76 +776,89 @@ def test_local_session_logs_passed_to_settings_initializer( else: assert "local_session_logs" not in call.kwargs - @pytest.mark.parametrize( - argnames=("posix_job_user_setting", "expected_config_posix_job_user"), - argvalues=( - pytest.param( - "user:group", - PosixSessionUser(group="group", user="user"), - id="has-posix-job-user-setting", - ), - pytest.param( - None, - None, - id="no-posix-job-user-setting", + if os.name == "posix": + + @pytest.mark.parametrize( + argnames=("posix_job_user_setting", "expected_config_posix_job_user"), + argvalues=( + pytest.param( + "user:group", + PosixSessionUser(group="group", user="user"), + id="has-posix-job-user-setting", + ), + pytest.param( + None, + None, + id="no-posix-job-user-setting", + ), + pytest.param( + None, + None, + id="no-posix-job-user-setting", + ), ), - ), - ) - def test_uses_worker_settings( - self, - posix_job_user_setting: str | None, - expected_config_posix_job_user: PosixSessionUser | None, - parsed_args: ParsedCommandLineArguments, - mock_worker_settings_cls: MagicMock, - ) -> None: - """Tests that any parsed_cli_args without a value of None are passed as kwargs when - creating a WorkerSettings instance""" - - # GIVEN - mock_worker_settings_cls.side_effect = None - mock_worker_settings: MagicMock = mock_worker_settings_cls.return_value - mock_worker_settings.posix_job_user = posix_job_user_setting - - # Needed because MagicMock does not support gt/lt comparison - mock_worker_settings.host_metrics_logging_interval_seconds = 10 - - # WHEN - config = config_mod.Configuration(parsed_cli_args=parsed_args) - - # THEN - # Assert that the attributes are taken from the WorkerSettings instance - assert config.farm_id is mock_worker_settings.farm_id - assert config.fleet_id is mock_worker_settings.fleet_id - assert config.profile is mock_worker_settings.profile - assert config.verbose is mock_worker_settings.verbose - assert config.no_shutdown is mock_worker_settings.no_shutdown - assert config.allow_instance_profile is mock_worker_settings.allow_instance_profile - assert ( - config.cleanup_session_user_processes - is mock_worker_settings.cleanup_session_user_processes - ) - assert config.capabilities is mock_worker_settings.capabilities - assert config.impersonation.inactive == (not mock_worker_settings.impersonation) - if expected_config_posix_job_user: - assert isinstance(config.impersonation.posix_job_user, PosixSessionUser) - assert config.impersonation.posix_job_user.group == expected_config_posix_job_user.group - assert config.impersonation.posix_job_user.user == expected_config_posix_job_user.user - else: - assert config.impersonation.posix_job_user is None - assert config.worker_logs_dir is mock_worker_settings.worker_logs_dir - assert config.local_session_logs is mock_worker_settings.local_session_logs - assert config.worker_persistence_dir is mock_worker_settings.worker_persistence_dir - assert ( - config.worker_credentials_dir - is mock_worker_settings.worker_persistence_dir / "credentials" - ) - assert config.host_metrics_logging is mock_worker_settings.host_metrics_logging - assert ( - config.host_metrics_logging_interval_seconds - is mock_worker_settings.host_metrics_logging_interval_seconds ) + def test_uses_worker_settings( + self, + posix_job_user_setting: str | None, + expected_config_posix_job_user: PosixSessionUser | None, + parsed_args: ParsedCommandLineArguments, + mock_worker_settings_cls: MagicMock, + ) -> None: + """Tests that any parsed_cli_args without a value of None are passed as kwargs when + creating a WorkerSettings instance""" + + # GIVEN + mock_worker_settings_cls.side_effect = None + mock_worker_settings: MagicMock = mock_worker_settings_cls.return_value + mock_worker_settings.posix_job_user = posix_job_user_setting + + # Needed because MagicMock does not support gt/lt comparison + mock_worker_settings.host_metrics_logging_interval_seconds = 10 + + # WHEN + config = config_mod.Configuration(parsed_cli_args=parsed_args) + + # THEN + # Assert that the attributes are taken from the WorkerSettings instance + assert config.farm_id is mock_worker_settings.farm_id + assert config.fleet_id is mock_worker_settings.fleet_id + assert config.profile is mock_worker_settings.profile + assert config.verbose is mock_worker_settings.verbose + assert config.no_shutdown is mock_worker_settings.no_shutdown + assert config.allow_instance_profile is mock_worker_settings.allow_instance_profile + assert ( + config.cleanup_session_user_processes + is mock_worker_settings.cleanup_session_user_processes + ) + assert config.capabilities is mock_worker_settings.capabilities + assert config.impersonation.inactive == (not mock_worker_settings.impersonation) + if expected_config_posix_job_user: + assert isinstance(config.impersonation.posix_job_user, PosixSessionUser) + assert ( + config.impersonation.posix_job_user.group + == expected_config_posix_job_user.group + ) + assert ( + config.impersonation.posix_job_user.user == expected_config_posix_job_user.user + ) + else: + assert config.impersonation.posix_job_user is None + assert config.worker_logs_dir is mock_worker_settings.worker_logs_dir + assert config.local_session_logs is mock_worker_settings.local_session_logs + assert config.worker_persistence_dir is mock_worker_settings.worker_persistence_dir + assert ( + config.worker_credentials_dir + is mock_worker_settings.worker_persistence_dir / "credentials" + ) + assert config.host_metrics_logging is mock_worker_settings.host_metrics_logging + assert ( + config.host_metrics_logging_interval_seconds + is mock_worker_settings.host_metrics_logging_interval_seconds + ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on Windows") class TestLog: """Tests for Configutation.log()""" diff --git a/test/unit/startup/test_entrypoint.py b/test/unit/startup/test_entrypoint.py index dc4e2479..a36afc4f 100644 --- a/test/unit/startup/test_entrypoint.py +++ b/test/unit/startup/test_entrypoint.py @@ -156,6 +156,7 @@ def block_telemetry_client() -> Generator[MagicMock, None, None]: yield telem_mock +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_calls_worker_run( mock_worker_run: MagicMock, ) -> None: @@ -168,6 +169,7 @@ def test_calls_worker_run( @patch.object(entrypoint_mod.sys, "exit") +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_worker_run_exception( sys_exit_mock: MagicMock, mock_worker_run: MagicMock, @@ -188,6 +190,7 @@ def test_worker_run_exception( sys_exit_mock.assert_called_once_with(1) +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_configuration_load( configuration_load: MagicMock, ) -> None: @@ -228,6 +231,7 @@ def test_configuration_error( sys_exit_mock.assert_called_with(1) +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_configuration_logged( configuration: MagicMock, ) -> None: @@ -259,6 +263,7 @@ def test_configuration_logged( @patch.object(entrypoint_mod, "Configuration") @patch.object(entrypoint_mod.logging, "getLogger") @patch.object(entrypoint_mod, "_logger") +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_log_configuration( module_logger_mock: MagicMock, get_logger_mock: MagicMock, @@ -362,6 +367,7 @@ def __eq__(self, other: Any) -> bool: ), ) @patch.object(entrypoint_mod._logger, "info") +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_worker_deletion( logger_info_mock: MagicMock, worker_info: WorkerPersistenceInfo, @@ -402,6 +408,7 @@ def test_worker_deletion( pytest.param(False, id="False"), ], ) +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_system_shutdown_called( mock_system_shutdown: MagicMock, request_shutdown: bool, @@ -605,6 +612,7 @@ def test_passes_worker_logs_dir( @patch.object(entrypoint_mod, "_logger") +@pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_worker_stop_exception( logger_mock: MagicMock, ) -> None: @@ -638,6 +646,7 @@ def cloudwatch_log_stream_sequence_token( ) -> Optional[str]: return request.param + @pytest.mark.xfail(os.name == "nt", reason="Windows is not yet supported.") def test_cloudwatch_log_streaming( self, mock_stream_cloudwatch_logs: MagicMock, diff --git a/test/unit/test_worker.py b/test/unit/test_worker.py index 3c2fa921..5b3e978d 100644 --- a/test/unit/test_worker.py +++ b/test/unit/test_worker.py @@ -7,6 +7,7 @@ from typing import Generator from unittest.mock import ANY, MagicMock, call, patch from pathlib import Path +import os import pytest @@ -125,6 +126,7 @@ def thread_pool_executor(mock_thread_pool_executor_cls: MagicMock) -> MagicMock: return mock_thread_pool_executor_cls.return_value +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestInit: def test_stop_event_created( self, @@ -157,6 +159,7 @@ def test_passes_worker_logs_dir( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestRun: def test_service_shutdown_raised_not_logged( self, @@ -189,6 +192,7 @@ def test_service_shutdown_raised_not_logged( logger_exception.assert_not_called() +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestMonitorEc2Shutdown: @pytest.fixture def is_asg_terminated(self) -> bool: @@ -357,6 +361,7 @@ def test_spot_interruption( ) +@pytest.mark.skipif(os.name == "nt", reason="Expected to fail on windows") class TestEC2MetadataQueries: def test_get_imdsv2_token(self, worker: Worker, requests_put: MagicMock) -> None: # GIVEN