Skip to content

Commit

Permalink
ci(test): parallel pytest execution (#2133)
Browse files Browse the repository at this point in the history
Enable the use of pytest-xdist to run tests in parallel, in CI
or developer env.

Following issues with parallel execution of tests are solved :

- some checks depended on the consistency at 2 moments in time of the
result of disk_usage, which was anyway not a good idea (dependent on
what happens elsewhere on the machine). It's now replaced with mocking
- some parameterized tests had random sort order, which is not allowed
by pytest-xdist (all workers must have exactly the same tests in the
same order)
- the command_factory test checked something in its teardown, assuming
all tests were executed in the same process, which is not the case
anymore. The same check is done as a separate unit tests.

Possible remaining issue which could cause some timeouts:
some services hold FileLock which have the same identifier
accross multiple tests.

Signed-off-by: Sylvain Leclerc <[email protected]>
  • Loading branch information
sylvlecl authored Sep 4, 2024
1 parent 675759f commit fb3715f
Show file tree
Hide file tree
Showing 6 changed files with 410 additions and 405 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
pip install -r requirements-dev.txt
- name: Test with pytest
run: |
pytest --cov antarest --cov-report xml
pytest --cov antarest --cov-report xml -n auto
- name: Archive code coverage results
if: matrix.os == 'ubuntu-20.04'
uses: actions/upload-artifact@v4
Expand Down
4 changes: 3 additions & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
-r requirements.txt
checksumdir~=1.2.0
pytest~=6.2.5
pytest~=8.3.0
pytest-xdist~=3.6.0
pytest-cov~=4.0.0
pytest-mock~=3.14.0

# In this version DataFrame conversion to Excel is done using 'xlsxwriter' library.
# But Excel files reading is done using 'openpyxl' library, during testing only.
Expand Down
26 changes: 13 additions & 13 deletions tests/integration/filesystem_blueprint/test_filesystem_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
import datetime
import operator
import re
import shutil
import typing as t
from pathlib import Path

from pytest_mock import MockerFixture
from starlette.testclient import TestClient

from tests.integration.conftest import RESOURCES_DIR
Expand Down Expand Up @@ -93,6 +93,7 @@ def test_lifecycle(
client: TestClient,
user_access_token: str,
admin_access_token: str,
mocker: MockerFixture,
) -> None:
"""
Test the lifecycle of the filesystem endpoints.
Expand All @@ -102,7 +103,7 @@ def test_lifecycle(
caplog: pytest caplog fixture.
client: test client (tests.integration.conftest.client_fixture).
user_access_token: access token of a classic user (tests.integration.conftest.user_access_token_fixture).
admin_access_token: access token of an admin user (tests.integration.conftest.admin_access_token_fixture).
admin_access_token: access token of an admin user (tests.integration.conftestin_access_token_fixture).
"""
# NOTE: all the following paths are based on the configuration defined in the app_fixture.
archive_dir = tmp_path / "archive_dir"
Expand Down Expand Up @@ -165,26 +166,25 @@ def test_lifecycle(
err_count += 1

# Known filesystem
mocker.patch("shutil.disk_usage", return_value=(100, 200, 300))
res = client.get("/v1/filesystem/ws", headers=user_headers)
assert res.status_code == 200, res.json()
actual = sorted(res.json(), key=operator.itemgetter("name"))
# Both mount point are in the same filesystem, which is the `tmp_path` filesystem
total_bytes, used_bytes, free_bytes = shutil.disk_usage(tmp_path)
expected = [
{
"name": "default",
"path": str(default_workspace),
"total_bytes": total_bytes,
"used_bytes": used_bytes,
"free_bytes": free_bytes,
"total_bytes": 100,
"used_bytes": 200,
"free_bytes": 300,
"message": AnyDiskUsagePercent(),
},
{
"name": "ext",
"path": str(ext_workspace_path),
"total_bytes": total_bytes,
"used_bytes": used_bytes,
"free_bytes": free_bytes,
"total_bytes": 100,
"used_bytes": 200,
"free_bytes": 300,
"message": AnyDiskUsagePercent(),
},
]
Expand All @@ -206,9 +206,9 @@ def test_lifecycle(
expected = {
"name": "default",
"path": str(default_workspace),
"total_bytes": total_bytes,
"used_bytes": used_bytes,
"free_bytes": free_bytes,
"total_bytes": 100,
"used_bytes": 200,
"free_bytes": 300,
"message": AnyDiskUsagePercent(),
}
assert actual == expected
Expand Down
13 changes: 8 additions & 5 deletions tests/integration/filesystem_blueprint/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import shutil
from pathlib import Path

from pytest_mock import MockerFixture

from antarest.core.filesystem_blueprint import FileInfoDTO, FilesystemDTO, MountPointDTO


Expand Down Expand Up @@ -63,15 +65,16 @@ def test_from_path__missing_file(self) -> None:
assert dto.free_bytes == 0
assert dto.message.startswith("N/A:"), dto.message

def test_from_path__file(self, tmp_path: Path) -> None:
def test_from_path__file(self, tmp_path: Path, mocker: MockerFixture) -> None:
mocker.patch("shutil.disk_usage", return_value=(100, 200, 300))

name = "foo"
dto = asyncio.run(MountPointDTO.from_path(name, tmp_path))
total_bytes, used_bytes, free_bytes = shutil.disk_usage(tmp_path)
assert dto.name == name
assert dto.path == tmp_path
assert dto.total_bytes == total_bytes
assert dto.used_bytes == used_bytes
assert dto.free_bytes == free_bytes
assert dto.total_bytes == 100
assert dto.used_bytes == 200
assert dto.free_bytes == 300
assert re.fullmatch(r"\d+(?:\.\d+)?% used", dto.message), dto.message


Expand Down
4 changes: 2 additions & 2 deletions tests/storage/repository/filesystem/config/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ def test_transform_name_to_id__valid_chars(name):
assert transform_name_to_id(name, lower=False) == name


@pytest.mark.parametrize("name", set(string.punctuation) - set(VALID_CHARS))
@pytest.mark.parametrize("name", sorted(set(string.punctuation) - set(VALID_CHARS)))
def test_transform_name_to_id__punctuation(name):
assert not transform_name_to_id(name)


@pytest.mark.parametrize("name", set(string.whitespace) - set(VALID_CHARS))
@pytest.mark.parametrize("name", sorted(set(string.whitespace) - set(VALID_CHARS)))
def test_transform_name_to_id__whitespace(name):
assert not transform_name_to_id(name)

Expand Down
Loading

0 comments on commit fb3715f

Please sign in to comment.