From a115082ca671aefd175274037237ff7ebcffb305 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 21 Sep 2026 23:22:30 +0300 Subject: [PATCH 1/5] CI: test-macos-14 is added --- .github/workflows/ci.yml | 106 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d275c5..252a555 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: run: | twine check dist/* - test: + test-linux-container: runs-on: ubuntu-latest needs: build-check strategy: @@ -250,3 +250,107 @@ jobs: docker rm -f "${{ env.RUN_CFG__MACHINE2_NAME }}" || true # Delete the network (it will be deleted successfully, since all containers in it have already been destroyed) docker network rm "${{ env.RUN_CFG__NETWORK_NAME }}" || true + + test-macos-14: + runs-on: macos-14 + needs: build-check + strategy: + fail-fast: false + matrix: + python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + + env: + BASE_SIGN: "macos-py${{ matrix.python }}" + + steps: + - name: Prepare variables + run: | + echo "RUN_CFG__NOW=$(date +'%Y%m%d_%H%M%S')" >> $GITHUB_ENV + echo "RUN_CFG__LOGS_DIR=logs-${{ env.BASE_SIGN }}" >> $GITHUB_ENV + echo "RUN_CFG__SSH_KEY_NAME=id_ed25519-${{ env.BASE_SIGN }}" >> $GITHUB_ENV + echo "---------- [$GITHUB_ENV]" + cat $GITHUB_ENV + + - name: Checkout + uses: actions/checkout@v7 + + - name: Prepare logs folder on the host + run: mkdir -p "${{ env.RUN_CFG__LOGS_DIR }}" + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v7 + with: + python-version: ${{ matrix.python }} + + - name: Generate temporary SSH Key pair for CI + run: | + # Generate a temporary key in the root of the workspace (like in the Linux version) + ssh-keygen -q -t ed25519 -N "" -f ${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }} + + - name: Create Remote User and Configure SSH + run: | + # 1. Create a new user in macOS (similar to machine2) + # In macOS, this is done via dscl (Directory Service command line) + sudo dscl . -create /Users/test_remote + sudo dscl . -create /Users/test_remote UserShell /bin/bash + sudo dscl . -create /Users/test_remote RealName "Test Remote User" + sudo dscl . -create /Users/test_remote UniqueID 555 + sudo dscl . -create /Users/test_remote PrimaryGroupID 20 # staff group + sudo dscl . -create /Users/test_remote NFSHomeDirectory /Users/test_remote + + # Create a home directory and set permissions + sudo mkdir -p /Users/test_remote/.ssh + sudo chmod 700 /Users/test_remote + sudo chmod 700 /Users/test_remote/.ssh + + # 2. Add the created public key to the authorized_keys of the test_remote user + cat ${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }}.pub | sudo tee -a /Users/test_remote/.ssh/authorized_keys > /dev/null + sudo chmod 600 /Users/test_remote/.ssh/authorized_keys + + # Change the owner of all created files to the new user + sudo chown -R test_remote:staff /Users/test_remote + + #3. Enable and configure the SSH server on macOS + sudo systemsetup -setremotelogin on + + # Setting up the client for the current runner user to avoid questions about Fingerprint + mkdir -p ~/.ssh + chmod 700 ~/.ssh + cat << 'EOF' > ~/.ssh/config + Host localhost 127.0.0.1 + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + EOF + chmod 600 ~/.ssh/config + + - name: Run native tests (Machine 1 emulation) + run: | + set -eux + + # We pass environment variables that the tests are linked to + export TEST_CFG__LOG_DIR="${{ github.workspace }}/${{ env.RUN_CFG__LOGS_DIR }}" + + export TEST_CFG__REMOTE_HOST="127.0.0.1" + export TEST_CFG__REMOTE_PORT="22" + export TEST_CFG__REMOTE_USERNAME="test_remote" + export TEST_CFG__REMOTE_SSH_KEY="${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }}" + + # Run the test script + export PYTHON_BINARY="python3" + + bash run_tests3.sh + + - name: Upload Logs + uses: actions/upload-artifact@v7 + if: always() + with: + name: testgres.os_ops--test_logs--${{ env.RUN_CFG__NOW }}-${{ env.BASE_SIGN }}-id${{ github.run_id }} + path: "${{ env.RUN_CFG__LOGS_DIR }}/" + + - name: Cleanup macOS Remote User + if: always() + run: | + echo "=== Garbage Collection ===" + # Delete the created user and his files + sudo dscl . -delete /Users/test_remote || true + sudo rm -rf /Users/test_remote || true From fb31aca45d3d980e7842e57920f296a76f52ab46 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 22 Sep 2026 00:00:56 +0300 Subject: [PATCH 2/5] macos: run local tests only --- .github/workflows/ci.yml | 64 ++++------------------------------------ run_tests3.sh | 6 +++- 2 files changed, 11 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 252a555..efde7a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -267,7 +267,6 @@ jobs: run: | echo "RUN_CFG__NOW=$(date +'%Y%m%d_%H%M%S')" >> $GITHUB_ENV echo "RUN_CFG__LOGS_DIR=logs-${{ env.BASE_SIGN }}" >> $GITHUB_ENV - echo "RUN_CFG__SSH_KEY_NAME=id_ed25519-${{ env.BASE_SIGN }}" >> $GITHUB_ENV echo "---------- [$GITHUB_ENV]" cat $GITHUB_ENV @@ -281,61 +280,18 @@ jobs: uses: actions/setup-python@v7 with: python-version: ${{ matrix.python }} + cache: 'pip' - - name: Generate temporary SSH Key pair for CI - run: | - # Generate a temporary key in the root of the workspace (like in the Linux version) - ssh-keygen -q -t ed25519 -N "" -f ${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }} - - - name: Create Remote User and Configure SSH - run: | - # 1. Create a new user in macOS (similar to machine2) - # In macOS, this is done via dscl (Directory Service command line) - sudo dscl . -create /Users/test_remote - sudo dscl . -create /Users/test_remote UserShell /bin/bash - sudo dscl . -create /Users/test_remote RealName "Test Remote User" - sudo dscl . -create /Users/test_remote UniqueID 555 - sudo dscl . -create /Users/test_remote PrimaryGroupID 20 # staff group - sudo dscl . -create /Users/test_remote NFSHomeDirectory /Users/test_remote - - # Create a home directory and set permissions - sudo mkdir -p /Users/test_remote/.ssh - sudo chmod 700 /Users/test_remote - sudo chmod 700 /Users/test_remote/.ssh - - # 2. Add the created public key to the authorized_keys of the test_remote user - cat ${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }}.pub | sudo tee -a /Users/test_remote/.ssh/authorized_keys > /dev/null - sudo chmod 600 /Users/test_remote/.ssh/authorized_keys - - # Change the owner of all created files to the new user - sudo chown -R test_remote:staff /Users/test_remote - - #3. Enable and configure the SSH server on macOS - sudo systemsetup -setremotelogin on - - # Setting up the client for the current runner user to avoid questions about Fingerprint - mkdir -p ~/.ssh - chmod 700 ~/.ssh - cat << 'EOF' > ~/.ssh/config - Host localhost 127.0.0.1 - StrictHostKeyChecking no - UserKnownHostsFile /dev/null - EOF - chmod 600 ~/.ssh/config - - - name: Run native tests (Machine 1 emulation) + - name: Run native tests (LocalOperations only) run: | set -eux + echo "HELLO FROM MACOS RUNNER" + echo "HOME DIR IS [$(realpath ~/)]" + echo "WORK DIR IS [$(pwd)]" - # We pass environment variables that the tests are linked to export TEST_CFG__LOG_DIR="${{ github.workspace }}/${{ env.RUN_CFG__LOGS_DIR }}" - export TEST_CFG__REMOTE_HOST="127.0.0.1" - export TEST_CFG__REMOTE_PORT="22" - export TEST_CFG__REMOTE_USERNAME="test_remote" - export TEST_CFG__REMOTE_SSH_KEY="${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }}" - - # Run the test script + export TEST_FILTER="not remote" export PYTHON_BINARY="python3" bash run_tests3.sh @@ -346,11 +302,3 @@ jobs: with: name: testgres.os_ops--test_logs--${{ env.RUN_CFG__NOW }}-${{ env.BASE_SIGN }}-id${{ github.run_id }} path: "${{ env.RUN_CFG__LOGS_DIR }}/" - - - name: Cleanup macOS Remote User - if: always() - run: | - echo "=== Garbage Collection ===" - # Delete the created user and his files - sudo dscl . -delete /Users/test_remote || true - sudo rm -rf /Users/test_remote || true diff --git a/run_tests3.sh b/run_tests3.sh index f97de54..49303e0 100755 --- a/run_tests3.sh +++ b/run_tests3.sh @@ -1,6 +1,10 @@ #!/bin/bash set -eux +if [ -z ${TEST_FILTER+x} ]; \ +then export TEST_FILTER=""; \ +fi + # prepare python environment VENV_PATH="/tmp/testgres_venv" rm -rf $VENV_PATH @@ -13,6 +17,6 @@ pip install -r tests/requirements.txt export -p # run builtin tests -pytest -l -vvv -n 4 +pytest -l -vvv -n 4 -k "${TEST_FILTER}" set +eux From 99e79ff80e10350566aff2152a49a21700f19987 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 22 Sep 2026 00:24:54 +0300 Subject: [PATCH 3/5] tests are updated (linux/darwin) --- tests/helpers/run_conditions.py | 10 ++++++ tests/test_os_ops_common.py | 60 +++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/tests/helpers/run_conditions.py b/tests/helpers/run_conditions.py index f847d87..ea20e5b 100644 --- a/tests/helpers/run_conditions.py +++ b/tests/helpers/run_conditions.py @@ -11,3 +11,13 @@ class RunConditions: def skip_if_windows(): if platform.system().lower() == "windows": pytest.skip("This test does not support Windows.") + + @staticmethod + def skip_if_darwin(): + if platform.system().lower() == "darwin": + pytest.skip("This test does not support Darwin.") + + @staticmethod + def skip_if_linux(): + if platform.system().lower() == "linux": + pytest.skip("This test does not support Linux.") diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index abe0819..97725b3 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -225,7 +225,7 @@ def test_get_platform__is_known(self, os_ops_descr: OsOpsDescr): p = os_ops.get_platform() assert p is not None assert type(p) is str - assert p in {"win32", "linux"} + assert p in {"win32", "linux", "darwin"} return def test_create_clone( @@ -422,11 +422,12 @@ def test_exec_command_with_exec_env__2(self, os_ops_descr: OsOpsDescr): assert not os_ops.path_exists(tmp_file) return - def test_exec_command_with_cwd(self, os_ops_descr: OsOpsDescr): + def test_exec_command_with_cwd__linux(self, os_ops_descr: OsOpsDescr): assert type(os_ops_descr) is OsOpsDescr assert isinstance(os_ops_descr.os_ops, OsOperations) RunConditions.skip_if_windows() + RunConditions.skip_if_darwin() os_ops = os_ops_descr.os_ops assert isinstance(os_ops, OsOperations) @@ -616,7 +617,7 @@ def test_makedirs_and_rmdirs_success( assert not os_ops.path_exists(path) return - def test_makedirs_failure( + def test_makedirs_failure__linux( self, os_ops_descr: OsOpsDescr, name_with_surprize: tagNameWithSurprize, @@ -633,6 +634,7 @@ def test_makedirs_failure( assert isinstance(os_ops, OsOperations) RunConditions.skip_if_windows() + RunConditions.skip_if_darwin() path = "/root/test_dir-{}-{}".format( name_with_surprize.value, @@ -651,6 +653,42 @@ def test_makedirs_failure( __class__.helper__bug_check__unknown_os_ops_type(os_ops) return + def test_makedirs_failure__darwin( + self, + os_ops_descr: OsOpsDescr, + name_with_surprize: tagNameWithSurprize, + ): + """ + Test makedirs for failure. + """ + # Try to create a directory in a read-only location + assert type(os_ops_descr) is OsOpsDescr + assert type(name_with_surprize) is __class__.tagNameWithSurprize + assert isinstance(os_ops_descr.os_ops, OsOperations) + + os_ops = os_ops_descr.os_ops + assert isinstance(os_ops, OsOperations) + + RunConditions.skip_if_windows() + RunConditions.skip_if_linux() + + path = "/root/test_dir-{}-{}".format( + name_with_surprize.value, + uuid.uuid4().bytes.hex(), + ) + + # Test makedirs + with pytest.raises(Exception) as x: + os_ops.makedirs(path) + + if type(os_ops).__name__ == "LocalOperations": + assert type(x.value) is OSError + elif type(os_ops).__name__ == "RemoteOperations": + assert type(x.value) is ExecUtilException + else: + __class__.helper__bug_check__unknown_os_ops_type(os_ops) + return + def test_listdir( self, os_ops_descr: OsOpsDescr, @@ -2559,13 +2597,16 @@ def test_is_abs_path__no( return # -------------------------------------------------------------------- - def test_get_abs_path( + def test_get_abs_path__linux( self, os_ops_descr: OsOpsDescr, ): assert type(os_ops_descr) is OsOpsDescr assert isinstance(os_ops_descr.os_ops, OsOperations) + RunConditions.skip_if_windows() + RunConditions.skip_if_darwin() + os_ops = os_ops_descr.os_ops assert isinstance(os_ops, OsOperations) @@ -3154,7 +3195,7 @@ def test_readlines__BIN( assert lines == result_bin return - def test_prove_environment_isolation( + def test_prove_environment_isolation__linux( self, os_ops_descr: OsOpsDescr, ): @@ -3166,6 +3207,9 @@ def test_prove_environment_isolation( os_ops = os_ops_descr.os_ops assert isinstance(os_ops, OsOperations) + RunConditions.skip_if_windows() + RunConditions.skip_if_darwin() + logging.info("=================== COKANUM PROOF START ===================") logging.info(f"Target environment type: [{os_ops_descr.sign}]") @@ -5130,7 +5174,7 @@ def test_popen_replace_env_of_os_ops( return - def test_popen_cwd( + def test_popen_cwd__linux( self, os_ops_descr: OsOpsDescr, ): @@ -5138,6 +5182,7 @@ def test_popen_cwd( assert isinstance(os_ops_descr.os_ops, OsOperations) RunConditions.skip_if_windows() + RunConditions.skip_if_darwin() os_ops = os_ops_descr.os_ops @@ -5908,7 +5953,7 @@ def test_run_input( return - def test_run_cwd( + def test_run_cwd__linux( self, os_ops_descr: OsOpsDescr, ): @@ -5916,6 +5961,7 @@ def test_run_cwd( assert isinstance(os_ops_descr.os_ops, OsOperations) RunConditions.skip_if_windows() + RunConditions.skip_if_darwin() os_ops = os_ops_descr.os_ops From d82d695796164c4dd89c8d9cc7ab9aa57e043271 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 22 Sep 2026 00:36:18 +0300 Subject: [PATCH 4/5] CI: --color=auto --- run_tests3.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests3.sh b/run_tests3.sh index 49303e0..215e472 100755 --- a/run_tests3.sh +++ b/run_tests3.sh @@ -17,6 +17,6 @@ pip install -r tests/requirements.txt export -p # run builtin tests -pytest -l -vvv -n 4 -k "${TEST_FILTER}" +pytest -l -vvv -n 4 --color=auto -k "${TEST_FILTER}" set +eux From c2bf1507f6be91349c6fb3d8353f009cf8c6bbc2 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 22 Sep 2026 00:38:30 +0300 Subject: [PATCH 5/5] CI: --color=yes --- run_tests3.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests3.sh b/run_tests3.sh index 215e472..0d0c809 100755 --- a/run_tests3.sh +++ b/run_tests3.sh @@ -17,6 +17,6 @@ pip install -r tests/requirements.txt export -p # run builtin tests -pytest -l -vvv -n 4 --color=auto -k "${TEST_FILTER}" +pytest -l -vvv -n 4 --color=yes -k "${TEST_FILTER}" set +eux