Skip to content

Commit 772a59f

Browse files
authored
♻️ Enhances e2e testing (#162)
1 parent 3054f30 commit 772a59f

File tree

9 files changed

+146
-120
lines changed

9 files changed

+146
-120
lines changed

clients/python/client/osparc/__init__.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,48 +70,48 @@
7070
dev_features: List[str] = []
7171
if dev_features_enabled():
7272
dev_features = [
73-
"PaginationGenerator",
74-
"StudyPort",
75-
"Study",
73+
"JobMetadata",
7674
"JobMetadataUpdate",
7775
"Links",
78-
"JobMetadata",
7976
"OnePageStudyPort",
77+
"PaginationGenerator",
78+
"Study",
79+
"StudyPort",
8080
]
8181

8282
__all__: Tuple[str, ...] = tuple(dev_features) + (
8383
"__version__",
84-
"FilesApi",
85-
"MetaApi",
86-
"SolversApi",
87-
"StudiesApi",
88-
"UsersApi",
84+
"ApiClient",
85+
"ApiException",
86+
"ApiKeyError",
87+
"ApiTypeError",
88+
"ApiValueError",
8989
"BodyUploadFileV0FilesContentPut",
90+
"Configuration",
91+
"ErrorGet",
9092
"File",
93+
"FilesApi",
9194
"Groups",
9295
"HTTPValidationError",
9396
"Job",
9497
"JobInputs",
9598
"JobOutputs",
9699
"JobStatus",
97100
"Meta",
101+
"MetaApi",
102+
"OnePageSolverPort",
103+
"openapi",
104+
"OpenApiException",
98105
"Profile",
99106
"ProfileUpdate",
107+
"RequestError",
100108
"Solver",
109+
"SolverPort",
110+
"SolversApi",
111+
"StudiesApi",
101112
"TaskStates",
102113
"UserRoleEnum",
114+
"UsersApi",
103115
"UsersGroup",
104116
"ValidationError",
105-
"ApiClient",
106-
"Configuration",
107-
"OpenApiException",
108-
"ApiTypeError",
109-
"ApiValueError",
110-
"ApiKeyError",
111-
"ApiException",
112-
"OnePageSolverPort",
113-
"SolverPort",
114-
"ErrorGet",
115-
"openapi",
116-
"RequestError",
117117
) # type: ignore

clients/python/test/conftest.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
6+
import datetime
7+
import os
8+
9+
import pytest
10+
11+
# Dictionary to store start times of tests
12+
_test_start_times = {}
13+
14+
15+
def _utc_now():
16+
return datetime.datetime.now(tz=datetime.timezone.utc)
17+
18+
19+
def _construct_graylog_url(api_host, start_time, end_time):
20+
"""
21+
Construct a Graylog URL for the given time interval.
22+
"""
23+
base_url = api_host.replace("api.", "monitoring.", 1).rstrip("/")
24+
url = f"{base_url}/graylog/search"
25+
start_time_str = start_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
26+
end_time_str = end_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
27+
query = f"from={start_time_str}&to={end_time_str}"
28+
return f"{url}?{query}"
29+
30+
31+
def pytest_runtest_setup(item):
32+
"""
33+
Hook to capture the start time of each test.
34+
"""
35+
_test_start_times[item.name] = _utc_now()
36+
37+
38+
def pytest_runtest_makereport(item, call):
39+
"""
40+
Hook to add extra information when a test fails.
41+
"""
42+
if call.when == "call":
43+
44+
# Check if the test failed
45+
if call.excinfo is not None:
46+
test_name = item.name
47+
test_location = item.location
48+
api_host = os.environ.get("OSPARC_API_HOST", "")
49+
50+
diagnostics = {
51+
"test_name": test_name,
52+
"test_location": test_location,
53+
"api_host": api_host,
54+
}
55+
56+
# Get the start and end times of the test
57+
start_time = _test_start_times.get(test_name)
58+
end_time = _utc_now()
59+
60+
if start_time:
61+
diagnostics["graylog_url"] = _construct_graylog_url(
62+
api_host, start_time, end_time
63+
)
64+
65+
# Print the diagnostics
66+
print(f"\nDiagnostics for {test_name}:")
67+
for key, value in diagnostics.items():
68+
print(" ", key, ":", value)
69+
70+
71+
@pytest.hookimpl(tryfirst=True)
72+
def pytest_configure(config):
73+
config.pluginmanager.register(pytest_runtest_setup, "osparc_test_times_plugin")
74+
config.pluginmanager.register(pytest_runtest_makereport, "osparc_makereport_plugin")

clients/python/test/e2e/ci/e2e/e2e/_models.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import osparc
66
from packaging.version import InvalidVersion, Version
7-
from pydantic import BaseModel, field_validator
7+
from pydantic import BaseModel, field_validator, SecretStr
88
from pydantic_settings import BaseSettings, SettingsConfigDict
99

1010
# Holds classes for passing data around between scripts.
@@ -14,19 +14,19 @@ class ServerSettings(BaseSettings):
1414
"""Holds data about server configuration"""
1515

1616
host: str
17-
key: str
18-
secret: str
17+
key: SecretStr
18+
secret: SecretStr
1919

2020
model_config = SettingsConfigDict(env_prefix="osparc_api_")
2121

2222
def __hash__(self):
23-
return hash(self.host + self.key + self.secret)
23+
return hash(self.host + self.key.get_secret_value() + self.secret.get_secret_value())
2424

2525
def __eq__(self, other):
2626
return (
2727
self.host == other.host
28-
and self.key == other.key
29-
and self.secret == other.secret
28+
and self.key.get_secret_value() == other.key.get_secret_value()
29+
and self.secret.get_secret_value() == other.secret.get_secret_value()
3030
)
3131

3232

@@ -43,15 +43,16 @@ class ClientSettings(BaseSettings):
4343
dev_features: bool = False
4444

4545
@field_validator("version", mode="after")
46-
def validate_client(cls, v):
46+
@classmethod
47+
def _validate_client(cls, v):
4748
if v not in {"latest_release", "latest_master"}:
4849
try:
4950
version = Version(v)
5051
assert version == Version(osparc.__version__)
51-
except InvalidVersion:
52-
raise ValueError(f"Received invalid semantic version: {v}")
53-
except AssertionError:
54-
raise ValueError(f"{v=} != {osparc.__version__=}")
52+
except InvalidVersion as e:
53+
raise ValueError(f"Received invalid semantic version: {v}") from e
54+
except AssertionError as e:
55+
raise ValueError(f"{v=} != {osparc.__version__=}") from e
5556
return v
5657

5758
@property

clients/python/test/e2e/ci/e2e/e2e/postprocess.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,8 @@ def clean_up_jobs(artifacts_dir: Path, retry_minutes: Optional[PositiveInt] = No
215215
for server_config in servers:
216216
config = osparc.Configuration(
217217
host=server_config.host,
218-
username=server_config.key,
219-
password=server_config.secret,
218+
username=server_config.key.get_secret_value(),
219+
password=server_config.secret.get_secret_value(),
220220
)
221221
msg = "Cleaning up jobs for user: "
222222
msg += f"\n{server_config.model_dump_json(indent=1)}"

clients/python/test/e2e/conftest.py

Lines changed: 12 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import datetime
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
26
import logging
37
import os
48
from pathlib import Path
@@ -15,76 +19,12 @@
1519
_GB: ByteSize = ByteSize(_MB * 1024) # in bytes
1620

1721

18-
# Dictionary to store start times of tests
19-
_test_start_times = {}
20-
21-
22-
def _utc_now():
23-
return datetime.datetime.now(tz=datetime.timezone.utc)
24-
25-
26-
def _construct_graylog_url(api_host, start_time, end_time):
27-
"""
28-
Construct a Graylog URL for the given time interval.
29-
"""
30-
base_url = api_host.replace("api.", "monitoring.", 1).rstrip("/")
31-
url = f"{base_url}/graylog/search"
32-
start_time_str = start_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
33-
end_time_str = end_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
34-
query = f"from={start_time_str}&to={end_time_str}"
35-
return f"{url}?{query}"
36-
37-
38-
def pytest_runtest_setup(item):
39-
"""
40-
Hook to capture the start time of each test.
41-
"""
42-
_test_start_times[item.name] = _utc_now()
43-
44-
45-
def pytest_runtest_makereport(item, call):
46-
"""
47-
Hook to add extra information when a test fails.
48-
"""
49-
if call.when == "call":
50-
51-
# Check if the test failed
52-
if call.excinfo is not None:
53-
test_name = item.name
54-
test_location = item.location
55-
api_host = os.environ.get("OSPARC_API_HOST", "")
56-
57-
diagnostics = {
58-
"test_name": test_name,
59-
"test_location": test_location,
60-
"api_host": api_host,
61-
}
62-
63-
# Get the start and end times of the test
64-
start_time = _test_start_times.get(test_name)
65-
end_time = _utc_now()
66-
67-
if start_time:
68-
diagnostics["graylog_url"] = _construct_graylog_url(api_host, start_time, end_time)
69-
70-
# Print the diagnostics
71-
print(f"\nDiagnostics for {test_name}:")
72-
for key, value in diagnostics.items():
73-
print(" ", key, ":", value)
74-
75-
76-
@pytest.hookimpl(tryfirst=True)
77-
def pytest_configure(config):
78-
config.pluginmanager.register(pytest_runtest_setup, "osparc_test_times_plugin")
79-
config.pluginmanager.register(pytest_runtest_makereport, "osparc_makereport_plugin")
80-
81-
8222
@pytest.fixture
83-
def configuration() -> Iterable[osparc.Configuration]:
23+
def configuration() -> osparc.Configuration:
8424
assert (host := os.environ.get("OSPARC_API_HOST"))
8525
assert (username := os.environ.get("OSPARC_API_KEY"))
8626
assert (password := os.environ.get("OSPARC_API_SECRET"))
87-
yield osparc.Configuration(
27+
return osparc.Configuration(
8828
host=host,
8929
username=username,
9030
password=password,
@@ -93,13 +33,13 @@ def configuration() -> Iterable[osparc.Configuration]:
9333

9434
@pytest.fixture
9535
def api_client(configuration: osparc.Configuration) -> Iterable[osparc.ApiClient]:
96-
with osparc.ApiClient(configuration=configuration) as api_client:
97-
yield api_client
36+
with osparc.ApiClient(configuration=configuration) as _api_client:
37+
yield _api_client
9838

9939

10040
@pytest.fixture
101-
def async_client(configuration) -> Iterable[AsyncClient]:
102-
yield AsyncClient(
41+
def async_client(configuration: osparc.Configuration) -> AsyncClient:
42+
return AsyncClient(
10343
base_url=configuration.host,
10444
auth=BasicAuth(
10545
username=configuration.username, password=configuration.password
@@ -108,7 +48,7 @@ def async_client(configuration) -> Iterable[AsyncClient]:
10848

10949

11050
@pytest.fixture
111-
def tmp_file(tmp_path: Path, caplog) -> Path:
51+
def tmp_file(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> Path:
11252
caplog.set_level(logging.INFO)
11353
byte_size: ByteSize = 1 * _GB
11454
tmp_file = tmp_path / "large_test_file.txt"

clients/python/test/e2e/test_notebooks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def test_notebook_config(tmp_path: Path):
4141

4242

4343
@pytest.mark.parametrize("notebook", all_notebooks, ids=lambda nb: nb.name)
44-
def test_run_notebooks(tmp_path: Path, notebook: Path, params: dict[str, Any] = {}):
44+
def test_run_notebooks(tmp_path: Path, notebook: Path, params: dict[str, Any]):
4545
"""Run all notebooks in the documentation"""
4646
assert (
4747
notebook.is_file()
@@ -60,6 +60,6 @@ def test_run_notebooks(tmp_path: Path, notebook: Path, params: dict[str, Any] =
6060
input_path=tmp_nb,
6161
output_path=output,
6262
kernel_name="python3",
63-
parameters=params,
63+
parameters=params or {},
6464
execution_timeout=_NOTEBOOK_EXECUTION_TIMEOUT_SECONDS,
6565
)
File renamed without changes.
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
from typing import Iterator
2-
31
import osparc
42
import pytest
5-
3+
from faker import Faker
64

75
@pytest.fixture
8-
def cfg() -> Iterator[osparc.Configuration]:
9-
yield osparc.Configuration(
10-
host="https://1a52d8d7-9f9f-48e5-b2f0-a226e6b25f0b.com",
11-
username="askjdg",
12-
password="asdjbaskjdb",
6+
def cfg(faker: Faker) -> osparc.Configuration:
7+
return osparc.Configuration(
8+
host=f"https://api.{faker.safe_domain_name()}",
9+
username=faker.user_name(),
10+
password=faker.password(),
1311
)
1412

1513

1614
@pytest.fixture
17-
def enable_dev_mode(monkeypatch):
15+
def dev_mode_enabled(monkeypatch:pytest.MonkeyPatch):
1816
monkeypatch.setenv("OSPARC_DEV_FEATURES_ENABLED", "1")

0 commit comments

Comments
 (0)