Skip to content

Commit d8a5d3e

Browse files
committed
refactor: clean up the test client fixtures
Share one live-client matrix, skip tests when a provider is not configured, and inject ce_client as a fixture instead of getfixturevalue. Closes nothing; test-only cleanup.
1 parent df8b018 commit d8a5d3e

7 files changed

Lines changed: 136 additions & 223 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## v0.1.0-dev
44

5+
- Clean up the test suite: skip unconfigured live clients, share one client
6+
matrix, and inject `ce_client` as a fixture instead of `getfixturevalue`.
57
- Fix Sphinx autodoc imports for Pydantic-backed submission types.
68
- Add complete static typing across the SDK and tests, with typed single and batch
79
submission return values.

tests/conftest.py

Lines changed: 79 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import json
22
import os
3+
from collections.abc import Callable
4+
from typing import TypeVar
35

46
import pytest
57
from dotenv import load_dotenv
@@ -8,111 +10,114 @@
810

911
load_dotenv()
1012

13+
TClient = TypeVar("TClient", bound=clients.Client)
14+
15+
# ATD fixtures remain available but are not part of the default live matrix.
16+
DEFAULT_CLIENT_FIXTURES = (
17+
"rapid_ce_client",
18+
"rapid_extra_ce_client",
19+
"judge0_cloud_ce_client",
20+
"judge0_cloud_extra_ce_client",
21+
)
22+
CLOUD_CLIENT_FIXTURES = (
23+
"judge0_cloud_ce_client",
24+
"judge0_cloud_extra_ce_client",
25+
)
26+
RAPID_CLIENT_FIXTURES = (
27+
"rapid_ce_client",
28+
"rapid_extra_ce_client",
29+
)
30+
31+
32+
def _try_create_client(factory: Callable[[], TClient]) -> TClient | None:
33+
try:
34+
return factory()
35+
except (json.JSONDecodeError, RuntimeError):
36+
return None
37+
38+
39+
def _require_named_client(request: pytest.FixtureRequest) -> clients.Client:
40+
client = request.getfixturevalue(request.param)
41+
if client is None:
42+
pytest.skip(f"{request.param} is not configured")
43+
return client
44+
45+
46+
def _first_available_client(*candidates: clients.Client | None) -> clients.Client:
47+
for client in candidates:
48+
if client is not None:
49+
return client
50+
pytest.fail("No client available for testing. This error should not happen!")
51+
1152

1253
@pytest.fixture(scope="session")
1354
def custom_ce_client() -> clients.Client | None:
1455
endpoint = os.getenv("JUDGE0_CE_ENDPOINT")
1556
auth_headers = os.getenv("JUDGE0_CE_AUTH_HEADERS")
16-
1757
if endpoint is None or auth_headers is None:
1858
return None
19-
else:
20-
try:
21-
return clients.Client(endpoint=endpoint, headers=json.loads(auth_headers))
22-
except (json.JSONDecodeError, RuntimeError):
23-
return None
59+
return _try_create_client(
60+
lambda: clients.Client(endpoint=endpoint, headers=json.loads(auth_headers))
61+
)
2462

2563

2664
@pytest.fixture(scope="session")
2765
def custom_extra_ce_client() -> clients.Client | None:
2866
endpoint = os.getenv("JUDGE0_EXTRA_CE_ENDPOINT")
2967
auth_headers = os.getenv("JUDGE0_EXTRA_CE_AUTH_HEADERS")
30-
3168
if endpoint is None or auth_headers is None:
3269
return None
33-
else:
34-
try:
35-
return clients.Client(endpoint=endpoint, headers=json.loads(auth_headers))
36-
except (json.JSONDecodeError, RuntimeError):
37-
return None
70+
return _try_create_client(
71+
lambda: clients.Client(endpoint=endpoint, headers=json.loads(auth_headers))
72+
)
3873

3974

4075
@pytest.fixture(scope="session")
4176
def atd_ce_client() -> clients.ATDJudge0CE | None:
4277
api_key = os.getenv("JUDGE0_ATD_API_KEY")
43-
4478
if api_key is None:
4579
return None
46-
else:
47-
try:
48-
return clients.ATDJudge0CE(api_key)
49-
except RuntimeError:
50-
return None
80+
return _try_create_client(lambda: clients.ATDJudge0CE(api_key))
5181

5282

5383
@pytest.fixture(scope="session")
5484
def atd_extra_ce_client() -> clients.ATDJudge0ExtraCE | None:
5585
api_key = os.getenv("JUDGE0_ATD_API_KEY")
56-
5786
if api_key is None:
5887
return None
59-
else:
60-
try:
61-
return clients.ATDJudge0ExtraCE(api_key)
62-
except RuntimeError:
63-
return None
88+
return _try_create_client(lambda: clients.ATDJudge0ExtraCE(api_key))
6489

6590

6691
@pytest.fixture(scope="session")
6792
def rapid_ce_client() -> clients.RapidJudge0CE | None:
6893
api_key = os.getenv("JUDGE0_RAPID_API_KEY")
69-
7094
if api_key is None:
7195
return None
72-
else:
73-
try:
74-
return clients.RapidJudge0CE(api_key)
75-
except RuntimeError:
76-
return None
96+
return _try_create_client(lambda: clients.RapidJudge0CE(api_key))
7797

7898

7999
@pytest.fixture(scope="session")
80100
def rapid_extra_ce_client() -> clients.RapidJudge0ExtraCE | None:
81101
api_key = os.getenv("JUDGE0_RAPID_API_KEY")
82-
83102
if api_key is None:
84103
return None
85-
else:
86-
try:
87-
return clients.RapidJudge0ExtraCE(api_key)
88-
except RuntimeError:
89-
return None
104+
return _try_create_client(lambda: clients.RapidJudge0ExtraCE(api_key))
90105

91106

92107
@pytest.fixture(scope="session")
93108
def judge0_cloud_ce_client() -> clients.Judge0CloudCE | None:
94109
auth_headers = os.getenv("JUDGE0_CLOUD_CE_AUTH_HEADERS")
95-
96110
if auth_headers is None:
97111
return None
98-
else:
99-
try:
100-
return clients.Judge0CloudCE(auth_headers)
101-
except RuntimeError:
102-
return None
112+
return _try_create_client(lambda: clients.Judge0CloudCE(auth_headers))
103113

104114

105115
@pytest.fixture(scope="session")
106116
def judge0_cloud_extra_ce_client() -> clients.Judge0CloudExtraCE | None:
107117
auth_headers = os.getenv("JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS")
108-
109118
if auth_headers is None:
110119
return None
111-
else:
112-
try:
113-
return clients.Judge0CloudExtraCE(auth_headers)
114-
except RuntimeError:
115-
return None
120+
return _try_create_client(lambda: clients.Judge0CloudExtraCE(auth_headers))
116121

117122

118123
@pytest.fixture(scope="session")
@@ -130,42 +135,41 @@ def ce_client(
130135
custom_ce_client: clients.Client | None,
131136
judge0_cloud_ce_client: clients.Judge0CloudCE | None,
132137
rapid_ce_client: clients.RapidJudge0CE | None,
133-
# atd_ce_client,
134138
preview_ce_client: clients.Judge0CloudCE,
135139
) -> clients.Client:
136-
if custom_ce_client is not None:
137-
return custom_ce_client
138-
if judge0_cloud_ce_client is not None:
139-
return judge0_cloud_ce_client
140-
if rapid_ce_client is not None:
141-
return rapid_ce_client
142-
# if atd_ce_client is not None:
143-
# return atd_ce_client
144-
if preview_ce_client is not None:
145-
return preview_ce_client
146-
147-
pytest.fail("No CE client available for testing. This error should not happen!")
140+
return _first_available_client(
141+
custom_ce_client,
142+
judge0_cloud_ce_client,
143+
rapid_ce_client,
144+
preview_ce_client,
145+
)
148146

149147

150148
@pytest.fixture(scope="session")
151149
def extra_ce_client(
152150
custom_extra_ce_client: clients.Client | None,
153151
judge0_cloud_extra_ce_client: clients.Judge0CloudExtraCE | None,
154152
rapid_extra_ce_client: clients.RapidJudge0ExtraCE | None,
155-
# atd_extra_ce_client,
156153
preview_extra_ce_client: clients.Judge0CloudExtraCE,
157154
) -> clients.Client:
158-
if custom_extra_ce_client is not None:
159-
return custom_extra_ce_client
160-
if judge0_cloud_extra_ce_client is not None:
161-
return judge0_cloud_extra_ce_client
162-
if rapid_extra_ce_client is not None:
163-
return rapid_extra_ce_client
164-
# if atd_extra_ce_client is not None:
165-
# return atd_extra_ce_client
166-
if preview_extra_ce_client is not None:
167-
return preview_extra_ce_client
168-
169-
pytest.fail(
170-
"No Extra CE client available for testing. This error should not happen!"
155+
return _first_available_client(
156+
custom_extra_ce_client,
157+
judge0_cloud_extra_ce_client,
158+
rapid_extra_ce_client,
159+
preview_extra_ce_client,
171160
)
161+
162+
163+
@pytest.fixture(scope="session", params=DEFAULT_CLIENT_FIXTURES, ids=lambda name: name)
164+
def optional_client(request: pytest.FixtureRequest) -> clients.Client:
165+
return _require_named_client(request)
166+
167+
168+
@pytest.fixture(scope="session", params=CLOUD_CLIENT_FIXTURES, ids=lambda name: name)
169+
def cloud_client(request: pytest.FixtureRequest) -> clients.Client:
170+
return _require_named_client(request)
171+
172+
173+
@pytest.fixture(scope="session", params=RAPID_CLIENT_FIXTURES, ids=lambda name: name)
174+
def rapid_client(request: pytest.FixtureRequest) -> clients.Client:
175+
return _require_named_client(request)

tests/test_api.py

Lines changed: 18 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,27 @@
66
from judge0 import Flavor, LanguageAlias, Submission, get_client
77
from judge0.api import _resolve_client
88

9-
DEFAULT_CLIENTS = (
10-
# "atd_ce_client",
11-
# "atd_extra_ce_client",
12-
"rapid_ce_client",
13-
"rapid_extra_ce_client",
14-
"judge0_cloud_ce_client",
15-
"judge0_cloud_extra_ce_client",
16-
)
17-
189

19-
@pytest.mark.parametrize("client", DEFAULT_CLIENTS)
20-
def test_resolve_client_with_explicit_client(
21-
client: str, request: pytest.FixtureRequest
22-
) -> None:
23-
resolved_client: judge0.Client = request.getfixturevalue(client)
24-
assert _resolve_client(resolved_client) is resolved_client
10+
def test_resolve_client_with_explicit_client(optional_client: judge0.Client) -> None:
11+
assert _resolve_client(optional_client) is optional_client
2512

2613

2714
@pytest.mark.parametrize(
2815
"flavor,expected_client",
2916
[
30-
[
31-
Flavor.CE,
32-
"JUDGE0_IMPLICIT_CE_CLIENT",
33-
],
34-
[
35-
Flavor.EXTRA_CE,
36-
"JUDGE0_IMPLICIT_EXTRA_CE_CLIENT",
37-
],
17+
(Flavor.CE, "JUDGE0_IMPLICIT_CE_CLIENT"),
18+
(Flavor.EXTRA_CE, "JUDGE0_IMPLICIT_EXTRA_CE_CLIENT"),
3819
],
3920
)
4021
def test_resolve_client_with_flavor(
4122
flavor: Flavor,
4223
expected_client: str,
4324
) -> None:
44-
# We have to use getattr since both implicit clients are initially None.
25+
# Implicit clients start as None and are set on first resolution.
4526
assert _resolve_client(client=flavor) is getattr(judge0, expected_client)
4627

4728

48-
@pytest.mark.parametrize(
49-
"submissions",
50-
[
51-
[],
52-
None,
53-
],
54-
)
29+
@pytest.mark.parametrize("submissions", [[], None])
5530
def test_resolve_client_empty_submissions_argument(
5631
submissions: list[Submission] | None,
5732
) -> None:
@@ -65,50 +40,29 @@ def test_get_client_rejects_invalid_flavor_type() -> None:
6540

6641

6742
def test_resolve_client_no_common_client_for_submissions() -> None:
68-
cpp_submission = Submission(
69-
source_code="", # source code is not important in this test
70-
language=LanguageAlias.CPP_GCC,
71-
)
72-
73-
py_submission = Submission(
74-
source_code="", # source code is not important in this test
75-
language=LanguageAlias.PYTHON_FOR_ML,
76-
)
77-
78-
submissions = [cpp_submission, py_submission]
43+
submissions = [
44+
Submission(source_code="", language=LanguageAlias.CPP_GCC),
45+
Submission(source_code="", language=LanguageAlias.PYTHON_FOR_ML),
46+
]
7947

8048
with pytest.raises(RuntimeError):
8149
_resolve_client(submissions=submissions)
8250

8351

8452
def test_resolve_client_common_ce_client() -> None:
85-
cpp_submission = Submission(
86-
source_code="", # source code is not important in this test
87-
language=LanguageAlias.CPP_GCC,
88-
)
89-
90-
py_submission = Submission(
91-
source_code="", # source code is not important in this test
92-
language=LanguageAlias.PYTHON,
93-
)
94-
95-
submissions = [cpp_submission, py_submission]
53+
submissions = [
54+
Submission(source_code="", language=LanguageAlias.CPP_GCC),
55+
Submission(source_code="", language=LanguageAlias.PYTHON),
56+
]
9657

9758
assert _resolve_client(submissions=submissions) is judge0.JUDGE0_IMPLICIT_CE_CLIENT
9859

9960

10061
def test_resolve_client_common_extra_ce_client() -> None:
101-
cpp_submission = Submission(
102-
source_code="", # source code is not important in this test
103-
language=LanguageAlias.CPP_CLANG,
104-
)
105-
106-
py_submission = Submission(
107-
source_code="", # source code is not important in this test
108-
language=LanguageAlias.PYTHON_FOR_ML,
109-
)
110-
111-
submissions = [cpp_submission, py_submission]
62+
submissions = [
63+
Submission(source_code="", language=LanguageAlias.CPP_CLANG),
64+
Submission(source_code="", language=LanguageAlias.PYTHON_FOR_ML),
65+
]
11266

11367
assert (
11468
_resolve_client(submissions=submissions)

0 commit comments

Comments
 (0)