From a9d177136d9928f880d4045af3777493fe75b8a6 Mon Sep 17 00:00:00 2001 From: Faiza Jibril Date: Sun, 21 Sep 2025 18:48:56 +0100 Subject: [PATCH 1/5] feat: migrate from unittest to pytest framework (issue #107) - Add pytest dependencies to pyproject.toml - Create CI/CD workflow for pytest (all OS, Python 3.11+) - Convert test_01_message.py to pytest format - Setup conftest.py with common fixtures - Configure test ordering for test_01 - Enable coverage reporting (no upload) --- .github/workflows/pytest-dev.yml | 122 ++++++++++++++++++ pyproject.toml | 5 +- pytest.ini | 15 +++ tests/conftest.py | 66 ++++++++++ tests/test_01_message_pytest.py | 210 +++++++++++++++++++++++++++++++ 5 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pytest-dev.yml create mode 100644 pytest.ini create mode 100644 tests/conftest.py create mode 100644 tests/test_01_message_pytest.py diff --git a/.github/workflows/pytest-dev.yml b/.github/workflows/pytest-dev.yml new file mode 100644 index 0000000..7b57aa7 --- /dev/null +++ b/.github/workflows/pytest-dev.yml @@ -0,0 +1,122 @@ +name: Pytest Tests For Development + +on: + workflow_dispatch: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + test: + strategy: + matrix: + include: + - os: windows-latest + python-version: 3.11 + - os: windows-latest + python-version: 3.12 + - os: windows-latest + python-version: 3.13 + # - os: macos-latest + # python-version: 3.11 + - os: ubuntu-latest + python-version: 3.11 + - os: ubuntu-latest + python-version: 3.12 + - os: ubuntu-latest + python-version: 3.13 + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv (Linux/macOS) + if: runner.os != 'Windows' + run: curl -LsSf https://astral.sh/uv/install.sh | sh + + - name: Install uv (Windows) + if: runner.os == 'Windows' + run: | + python -m pip install uv + + - name: Install dependencies (Linux/macOS) + if: runner.os != 'Windows' + run: | + uv venv .venv + source .venv/bin/activate + uv sync --no-install-project --group test --group dev + + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + run: | + uv venv .venv + .venv\Scripts\activate + uv sync --no-install-project --group test --group dev + + - name: Run pytest tests (Linux/macOS) + if: runner.os != 'Windows' && matrix.python-version != 3.13 + run: | + source .venv/bin/activate + uv run pytest tests/ -v --cov=hololinked --cov-report=term-missing + + - name: Run pytest tests and generate coverage report (Linux/macOS python 3.13) + if: runner.os != 'Windows' && matrix.python-version == 3.13 + run: | + source .venv/bin/activate + uv run pytest tests/ -v --cov=hololinked --cov-report=term-missing --cov-report=xml + + - name: Run pytest tests (Windows) + if: runner.os == 'Windows' + run: | + .venv\Scripts\activate + uv run pytest tests/ -v --cov=hololinked --cov-report=term-missing + + - name: Upload coverage report as artifact + uses: actions/upload-artifact@v4 + if: runner.os != 'Windows' && matrix.python-version == 3.13 + with: + name: pytest-coverage-report-ubuntu-latest-py3.13 + path: coverage.xml + if-no-files-found: warn + + publish: + name: Publish coverage (Ubuntu Python 3.13) + needs: test + runs-on: ubuntu-latest + if: ${{ always() }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download Ubuntu 3.13 coverage artifact + id: dl + uses: actions/download-artifact@v4 + with: + name: pytest-coverage-report-ubuntu-latest-py3.13 + path: . + continue-on-error: true + + - name: Upload coverage to Codecov + if: steps.dl.outcome == 'success' + uses: codecov/codecov-action@v4 + env: + CI: true + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage.xml + fail_ci_if_error: true + slug: hololinked-dev/hololinked + + - name: Skip note (no Ubuntu 3.13 artifact found) + if: steps.dl.outcome != 'success' + run: echo "No Ubuntu 3.13 coverage artifact present; skipping Codecov upload." diff --git a/pyproject.toml b/pyproject.toml index 8c7a5f9..aa97e0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,10 @@ test = [ "numpy>=2.0.0", "faker==37.5.0", "bcrypt==4.3.0", - "fastjsonschema==2.20.0" + "fastjsonschema==2.20.0", + "pytest>=8.0.0", + "pytest-cov>=4.0.0", + "pytest-order>=1.0.0" ] linux = [ "uvloop==0.20.0" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..4c0905a --- /dev/null +++ b/pytest.ini @@ -0,0 +1,15 @@ +[tool:pytest] +minversion = 8.0 +addopts = -ra --strict-markers --strict-config --order-dependencies +testpaths = tests +python_files = test_*.py *_test.py +python_classes = Test* +python_functions = test_* +markers = + order: mark test to run in a specific order + slow: marks tests as slow (deselect with '-m "not slow"') + integration: marks tests as integration tests +filterwarnings = + error + ignore::UserWarning + ignore::DeprecationWarning diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9da1aed --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,66 @@ +""" +Pytest configuration and shared fixtures for hololinked tests. +""" +import asyncio +import pytest +import zmq.asyncio +from uuid import uuid4 +from faker import Faker + +from hololinked.config import global_config + + +@pytest.fixture(scope="session") +def event_loop(): + """Create an instance of the default event loop for the test session.""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +@pytest.fixture(scope="class") +def zmq_context(): + """Setup ZMQ context for test classes.""" + global_config.ZMQ_CONTEXT = zmq.asyncio.Context() + yield global_config.ZMQ_CONTEXT + # Cleanup is handled by the context manager + + +@pytest.fixture(scope="class") +def test_ids(): + """Generate unique test IDs for each test class.""" + return { + "server_id": f"test-server-{uuid4().hex[:8]}", + "client_id": f"test-client-{uuid4().hex[:8]}", + "thing_id": f"test-thing-{uuid4().hex[:8]}" + } + + +@pytest.fixture(scope="session") +def fake(): + """Provide a Faker instance for generating test data.""" + return Faker() + + +@pytest.fixture(autouse=True) +def setup_test_environment(zmq_context): + """Automatically setup test environment for each test.""" + # This fixture runs automatically for every test + pass + + +def pytest_configure(config): + """Configure pytest with custom settings.""" + config.addinivalue_line( + "markers", "order: mark test to run in a specific order" + ) + + +def pytest_collection_modifyitems(config, items): + """Modify test collection to add ordering markers.""" + # Add order markers based on test file names + for item in items: + if "test_01_" in item.nodeid: + item.add_marker(pytest.mark.order(1)) + elif "test_00_" in item.nodeid: + item.add_marker(pytest.mark.order(0)) diff --git a/tests/test_01_message_pytest.py b/tests/test_01_message_pytest.py new file mode 100644 index 0000000..9d50145 --- /dev/null +++ b/tests/test_01_message_pytest.py @@ -0,0 +1,210 @@ +""" +Pytest tests for message validation and messaging contract. +Converted from unittest to pytest format. +""" +import pytest +from uuid import UUID, uuid4 + +from hololinked.core.zmq.message import ( + EXIT, + OPERATION, + HANDSHAKE, + PreserializedData, + SerializableData, + RequestHeader, + EventHeader, + RequestMessage, +) # client to server +from hololinked.core.zmq.message import ( + TIMEOUT, + INVALID_MESSAGE, + ERROR, + REPLY, + ERROR, + ResponseMessage, + ResponseHeader, + EventMessage, +) # server to client +from hololinked.serializers.serializers import Serializers + + +class MessageValidatorMixin: + """A mixin class to validate request and response messages""" + + @pytest.fixture(autouse=True) + def setup_message_validator(self, test_ids): + """Setup message validator with test IDs.""" + self.server_id = test_ids["server_id"] + self.client_id = test_ids["client_id"] + self.thing_id = test_ids["thing_id"] + + def validate_request_message(self, request_message: RequestMessage) -> None: + """call this method to validate request message""" + + # req. 1. check message ID is a UUID + assert isinstance(request_message.id, UUID) or isinstance(UUID(request_message.id, version=4), UUID) + # req. 2. generated byte array must confine to predefined length (which is readonly & fixed) + assert len(request_message.byte_array) == request_message.length + # req. 3. receiver which must be the server ID + assert request_message.receiver_id == self.server_id + # req. 4. sender_id is the client ID + assert request_message.sender_id == self.client_id + # req. 5. all indices of byte array are bytes + for obj in request_message.byte_array: + assert isinstance(obj, bytes) + # req. 6. check that header is correct type (RequestHeader dataclass/struct) + assert isinstance(request_message.header, RequestHeader) + # req. 7 check that body is correct type (list of SerializableData and PreserializedData) + assert isinstance(request_message.body, list) + assert len(request_message.body) == 2 + assert isinstance(request_message.body[0], SerializableData) + assert isinstance(request_message.body[1], PreserializedData) + + def validate_response_message(self, response_message: ResponseMessage) -> None: + """call this method to validate response message""" + + # check message ID is a UUID + assert isinstance(response_message.id, UUID) or isinstance(UUID(response_message.id, version=4), UUID) + # check message length + assert len(response_message.byte_array) == response_message.length + # check receiver which must be the client + assert response_message.receiver_id == self.client_id + # sender_id is not set before sending message on the socket + assert response_message.sender_id == self.server_id + # check that all indices are bytes + for obj in response_message.byte_array: + assert isinstance(obj, bytes) + # check that header is correct type + assert isinstance(response_message.header, ResponseHeader) + # check that body is correct type + assert isinstance(response_message.body, list) + assert len(response_message.body) == 2 + assert isinstance(response_message.body[0], SerializableData) + assert isinstance(response_message.body[1], PreserializedData) + + def validate_event_message(self, event_message: EventMessage) -> None: + """call this method to validate event message""" + + # check message ID is a UUID + assert isinstance(event_message.id, UUID) or isinstance(UUID(event_message.id, version=4), UUID) + # check message length + assert len(event_message.byte_array) == event_message.length + # no receiver id for event message, only event id + assert isinstance(event_message.event_id, str) + # sender_id is not set before sending message on the socket + assert event_message.sender_id == self.server_id + # check that all indices are bytes + for obj in event_message.byte_array: + assert isinstance(obj, bytes) + # check that header is correct type + assert isinstance(event_message.header, EventHeader) + # check that body is correct type + assert isinstance(event_message.body, list) + assert len(event_message.body) == 2 + assert isinstance(event_message.body[0], SerializableData) + assert isinstance(event_message.body[1], PreserializedData) + + +@pytest.mark.order(1) +class TestMessagingContract(MessageValidatorMixin): + """Tests request and response messages""" + + def test_1_request_message(self): + """test the request message""" + + # request messages types are OPERATION, HANDSHAKE & EXIT + request_message = RequestMessage.craft_from_arguments( + receiver_id=self.server_id, + sender_id=self.client_id, + thing_id=self.thing_id, + objekt="some_prop", + operation="readproperty", + ) + self.validate_request_message(request_message) + # check message type for the above craft_from_arguments method + assert request_message.type == OPERATION + + request_message = RequestMessage.craft_with_message_type( + receiver_id=self.server_id, sender_id=self.client_id, message_type=HANDSHAKE + ) + self.validate_request_message(request_message) + # check message type for the above craft_with_message_type method + assert request_message.type == HANDSHAKE + + request_message = RequestMessage.craft_with_message_type( + receiver_id=self.server_id, sender_id=self.client_id, message_type=EXIT + ) + self.validate_request_message(request_message) + # check message type for the above craft_with_message_type method + assert request_message.type == EXIT + + def test_2_response_message(self): + """test the response message""" + + # response messages types are HANDSHAKE, TIMEOUT, INVALID_MESSAGE, ERROR and REPLY + response_message = ResponseMessage.craft_from_arguments( + receiver_id=self.client_id, + sender_id=self.server_id, + message_type=HANDSHAKE, + message_id=uuid4(), + ) + self.validate_response_message(response_message) + # check message type for the above craft_with_message_type method + assert response_message.type == HANDSHAKE + + response_message = ResponseMessage.craft_from_arguments( + receiver_id=self.client_id, + sender_id=self.server_id, + message_type=TIMEOUT, + message_id=uuid4(), + ) + self.validate_response_message(response_message) + # check message type for the above craft_with_message_type method + assert response_message.type == TIMEOUT + + response_message = ResponseMessage.craft_from_arguments( + receiver_id=self.client_id, + sender_id=self.server_id, + message_type=INVALID_MESSAGE, + message_id=uuid4(), + ) + self.validate_response_message(response_message) + # check message type for the above craft_with_message_type method + assert response_message.type == INVALID_MESSAGE + + response_message = ResponseMessage.craft_from_arguments( + receiver_id=self.client_id, + sender_id=self.server_id, + message_type=ERROR, + message_id=uuid4(), + payload=SerializableData(Exception("test")), + ) + self.validate_response_message(response_message) + assert response_message.type == ERROR + assert isinstance(Serializers.json.loads(response_message._bytes[2]), dict) + + request_message = RequestMessage.craft_from_arguments( + sender_id=self.client_id, + receiver_id=self.server_id, + thing_id=self.thing_id, + objekt="some_prop", + operation="readProperty", + ) + request_message._sender_id = self.client_id # will be done by craft_from_self + response_message = ResponseMessage.craft_reply_from_request( + request_message=request_message, + ) + self.validate_response_message(response_message) + assert response_message.type == REPLY + assert Serializers.json.loads(response_message._bytes[3]) is None # INDEX_BODY = 3 + assert request_message.id == response_message.id + + def test_3_event_message(self): + """test the event message""" + event_message = EventMessage.craft_from_arguments( + event_id="test-event", + sender_id=self.server_id, + payload=SerializableData("test"), + preserialized_payload=PreserializedData(b"test"), + ) + self.validate_event_message(event_message) From 8db59200bf67de0ef413c2fa0747773c96624c6d Mon Sep 17 00:00:00 2001 From: Faiza Jibril Date: Sun, 21 Sep 2025 19:21:07 +0100 Subject: [PATCH 2/5] fix(pytest): correct pytest.ini syntax and limit CI to pytest file - Fix collect_ignore INI syntax\n- Exclude unittest files from pytest collection\n- CI runs tests/test_01_message_pytest.py only to avoid import errors --- .github/workflows/pytest-dev.yml | 6 +++--- pytest.ini | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest-dev.yml b/.github/workflows/pytest-dev.yml index 7b57aa7..3894ecb 100644 --- a/.github/workflows/pytest-dev.yml +++ b/.github/workflows/pytest-dev.yml @@ -67,19 +67,19 @@ jobs: if: runner.os != 'Windows' && matrix.python-version != 3.13 run: | source .venv/bin/activate - uv run pytest tests/ -v --cov=hololinked --cov-report=term-missing + uv run pytest tests/test_01_message_pytest.py -v --cov=hololinked --cov-report=term-missing - name: Run pytest tests and generate coverage report (Linux/macOS python 3.13) if: runner.os != 'Windows' && matrix.python-version == 3.13 run: | source .venv/bin/activate - uv run pytest tests/ -v --cov=hololinked --cov-report=term-missing --cov-report=xml + uv run pytest tests/test_01_message_pytest.py -v --cov=hololinked --cov-report=term-missing --cov-report=xml - name: Run pytest tests (Windows) if: runner.os == 'Windows' run: | .venv\Scripts\activate - uv run pytest tests/ -v --cov=hololinked --cov-report=term-missing + uv run pytest tests/test_01_message_pytest.py -v --cov=hololinked --cov-report=term-missing - name: Upload coverage report as artifact uses: actions/upload-artifact@v4 diff --git a/pytest.ini b/pytest.ini index 4c0905a..403835e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,7 +2,7 @@ minversion = 8.0 addopts = -ra --strict-markers --strict-config --order-dependencies testpaths = tests -python_files = test_*.py *_test.py +python_files = test_*_pytest.py python_classes = Test* python_functions = test_* markers = @@ -13,3 +13,20 @@ filterwarnings = error ignore::UserWarning ignore::DeprecationWarning + ignore::pytest.PytestCollectionWarning +norecursedirs = "not working - yet to be integrated" +collect_ignore = + test_00_utils.py + test_01_message.py + test_02_socket.py + test_03_serializers.py + test_04_thing_init.py + test_05_brokers.py + test_06_actions.py + test_07_properties.py + test_08_events.py + test_09_rpc_broker.py + test_10_thing_description.py + test_11_rpc_e2e.py + test_12_protocols_zmq.py + test_13_protocols_http.py From 8bf90e17b74591a0abff020bc72416c9d81059e9 Mon Sep 17 00:00:00 2001 From: Faiza Jibril Date: Sun, 21 Sep 2025 19:45:06 +0100 Subject: [PATCH 3/5] ci(pytest): disable Codecov upload for pytest workflow per issue #107 --- .github/workflows/pytest-dev.yml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pytest-dev.yml b/.github/workflows/pytest-dev.yml index 3894ecb..9192032 100644 --- a/.github/workflows/pytest-dev.yml +++ b/.github/workflows/pytest-dev.yml @@ -90,10 +90,10 @@ jobs: if-no-files-found: warn publish: - name: Publish coverage (Ubuntu Python 3.13) + name: Publish coverage (disabled for pytest per issue #107) needs: test runs-on: ubuntu-latest - if: ${{ always() }} + if: ${{ false }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -106,17 +106,11 @@ jobs: path: . continue-on-error: true - - name: Upload coverage to Codecov - if: steps.dl.outcome == 'success' + - name: Upload coverage to Codecov (disabled) + if: false uses: codecov/codecov-action@v4 - env: - CI: true with: - token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml - fail_ci_if_error: true - slug: hololinked-dev/hololinked - - name: Skip note (no Ubuntu 3.13 artifact found) - if: steps.dl.outcome != 'success' - run: echo "No Ubuntu 3.13 coverage artifact present; skipping Codecov upload." + - name: Skip note (coverage upload disabled for pytest) + run: echo "Skipping Codecov upload in pytest workflow per issue #107." From 6cdd85957777fcdc9a82a6f5c7b93421a1e47ff2 Mon Sep 17 00:00:00 2001 From: Faiza Jibril Date: Mon, 22 Sep 2025 20:25:30 +0100 Subject: [PATCH 4/5] test(pytest): move pytest config into pyproject; tests under tests/pytest; ci: run py311 on ubuntu/windows --- .github/workflows/tests.yml | 35 ++++++++++++++++++++ pyproject.toml | 19 +++++++++++ pytest.ini | 32 ------------------ tests/{ => pytest}/test_01_message_pytest.py | 2 ++ 4 files changed, 56 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 pytest.ini rename tests/{ => pytest}/test_01_message_pytest.py (99%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..2d64481 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,35 @@ +name: tests + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + pytest: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . + pip install pytest>=8.0.0 pytest-cov>=4.0.0 pytest-order>=1.0.0 coverage==7.8.0 faker==37.5.0 fastjsonschema==2.20.0 numpy>=2.0.0 bcrypt==4.3.0 requests==2.32.3 + + - name: Run tests + run: | + pytest --maxfail=1 --disable-warnings -q + + diff --git a/pyproject.toml b/pyproject.toml index aa97e0b..5eeb8a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,3 +89,22 @@ test = [ linux = [ "uvloop==0.20.0" ] + +[tool.pytest.ini_options] +minversion = "8.0" +addopts = "-ra --strict-markers --strict-config" +testpaths = ["tests/pytest"] +python_files = ["test_*_pytest.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +markers = [ + "order: mark test to run in a specific order", + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "integration: marks tests as integration tests" +] +filterwarnings = [ + "error", + "ignore::UserWarning", + "ignore::DeprecationWarning", + "ignore::pytest.PytestCollectionWarning" +] \ No newline at end of file diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 403835e..0000000 --- a/pytest.ini +++ /dev/null @@ -1,32 +0,0 @@ -[tool:pytest] -minversion = 8.0 -addopts = -ra --strict-markers --strict-config --order-dependencies -testpaths = tests -python_files = test_*_pytest.py -python_classes = Test* -python_functions = test_* -markers = - order: mark test to run in a specific order - slow: marks tests as slow (deselect with '-m "not slow"') - integration: marks tests as integration tests -filterwarnings = - error - ignore::UserWarning - ignore::DeprecationWarning - ignore::pytest.PytestCollectionWarning -norecursedirs = "not working - yet to be integrated" -collect_ignore = - test_00_utils.py - test_01_message.py - test_02_socket.py - test_03_serializers.py - test_04_thing_init.py - test_05_brokers.py - test_06_actions.py - test_07_properties.py - test_08_events.py - test_09_rpc_broker.py - test_10_thing_description.py - test_11_rpc_e2e.py - test_12_protocols_zmq.py - test_13_protocols_http.py diff --git a/tests/test_01_message_pytest.py b/tests/pytest/test_01_message_pytest.py similarity index 99% rename from tests/test_01_message_pytest.py rename to tests/pytest/test_01_message_pytest.py index 9d50145..4eeaede 100644 --- a/tests/test_01_message_pytest.py +++ b/tests/pytest/test_01_message_pytest.py @@ -208,3 +208,5 @@ def test_3_event_message(self): preserialized_payload=PreserializedData(b"test"), ) self.validate_event_message(event_message) + + From 24e6fb9d56e42c25a8128bbb5e56645e6d0befab Mon Sep 17 00:00:00 2001 From: Faiza Jibril Date: Wed, 24 Sep 2025 00:05:44 +0100 Subject: [PATCH 5/5] chore(ci): simplify pytest-dev matrix (win/ubuntu 3.11) and remove tests.yml per review --- .github/workflows/pytest-dev.yml | 26 +++++------------------- .github/workflows/tests.yml | 35 -------------------------------- 2 files changed, 5 insertions(+), 56 deletions(-) delete mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/pytest-dev.yml b/.github/workflows/pytest-dev.yml index 9192032..a4ffd3b 100644 --- a/.github/workflows/pytest-dev.yml +++ b/.github/workflows/pytest-dev.yml @@ -16,18 +16,8 @@ jobs: include: - os: windows-latest python-version: 3.11 - - os: windows-latest - python-version: 3.12 - - os: windows-latest - python-version: 3.13 - # - os: macos-latest - # python-version: 3.11 - os: ubuntu-latest python-version: 3.11 - - os: ubuntu-latest - python-version: 3.12 - - os: ubuntu-latest - python-version: 3.13 runs-on: ${{ matrix.os }} @@ -64,17 +54,11 @@ jobs: uv sync --no-install-project --group test --group dev - name: Run pytest tests (Linux/macOS) - if: runner.os != 'Windows' && matrix.python-version != 3.13 + if: runner.os != 'Windows' run: | source .venv/bin/activate uv run pytest tests/test_01_message_pytest.py -v --cov=hololinked --cov-report=term-missing - - name: Run pytest tests and generate coverage report (Linux/macOS python 3.13) - if: runner.os != 'Windows' && matrix.python-version == 3.13 - run: | - source .venv/bin/activate - uv run pytest tests/test_01_message_pytest.py -v --cov=hololinked --cov-report=term-missing --cov-report=xml - - name: Run pytest tests (Windows) if: runner.os == 'Windows' run: | @@ -83,9 +67,9 @@ jobs: - name: Upload coverage report as artifact uses: actions/upload-artifact@v4 - if: runner.os != 'Windows' && matrix.python-version == 3.13 + if: runner.os != 'Windows' with: - name: pytest-coverage-report-ubuntu-latest-py3.13 + name: pytest-coverage-report-ubuntu-latest-py3.11 path: coverage.xml if-no-files-found: warn @@ -98,11 +82,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Download Ubuntu 3.13 coverage artifact + - name: Download Ubuntu 3.11 coverage artifact id: dl uses: actions/download-artifact@v4 with: - name: pytest-coverage-report-ubuntu-latest-py3.13 + name: pytest-coverage-report-ubuntu-latest-py3.11 path: . continue-on-error: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 2d64481..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: tests - -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] - -jobs: - pytest: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install . - pip install pytest>=8.0.0 pytest-cov>=4.0.0 pytest-order>=1.0.0 coverage==7.8.0 faker==37.5.0 fastjsonschema==2.20.0 numpy>=2.0.0 bcrypt==4.3.0 requests==2.32.3 - - - name: Run tests - run: | - pytest --maxfail=1 --disable-warnings -q - -