Skip to content

Commit db22c5b

Browse files
Add test cases support. Make Language dataclass frozen.
* Add sync_run alias * Implement support for test cases * Make Language dataclass frozen. --------- Co-authored-by: Filip Karlo Došilović <filipk.dosilovic@gmail.com>
1 parent 672df77 commit db22c5b

File tree

7 files changed

+287
-10
lines changed

7 files changed

+287
-10
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# judge0-py
2+
23
Python client library for Judge0

examples/0005_test_cases.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import judge0
2+
3+
print("Subexample 1")
4+
result = judge0.run(
5+
source_code="print(f'Hello, {input()}')",
6+
test_cases=judge0.TestCase("Herman", "Hello, Herman"),
7+
)
8+
9+
print(result.status)
10+
11+
12+
print("Subexample 2")
13+
results = judge0.run(
14+
source_code="print(f'Hello, {input()}')",
15+
test_cases=[
16+
judge0.TestCase("Herman", "Hello, Herman"),
17+
judge0.TestCase("Filip", "Hello, Filip"),
18+
],
19+
)
20+
21+
for result in results:
22+
print(result.status)
23+
24+
25+
print("Subexample 3")
26+
submission = judge0.Submission(source_code="print(f'Hello, {input()}')")
27+
result = judge0.run(
28+
submissions=submission,
29+
test_cases=judge0.TestCase("Herman", "Hello, Herman"),
30+
)
31+
32+
print(result.status)
33+
34+
35+
print("Subexample 4")
36+
submissions = [
37+
judge0.Submission(source_code="print(f'Hello, {input()}')"),
38+
judge0.Submission(source_code="print(f'Bok, {input()}')"),
39+
]
40+
results = judge0.run(
41+
submissions=submissions,
42+
test_cases=judge0.TestCase("Herman", "Hello, Herman"),
43+
)
44+
45+
for result in results:
46+
print(result.status)
47+
48+
49+
print("Subexample 5")
50+
submissions = [
51+
judge0.Submission(source_code="print(f'Hello, {input()}')"),
52+
judge0.Submission(source_code="print(f'Bok, {input()}')"),
53+
]
54+
results = judge0.run(
55+
submissions=submissions,
56+
test_cases=[
57+
judge0.TestCase("Herman", "Hello, Herman"),
58+
judge0.TestCase("Filip", "Hello, Filip"),
59+
],
60+
)
61+
62+
for result in results:
63+
print(result.status)

src/judge0/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22

33
from .api import async_execute, execute, run, sync_execute, wait
4-
from .base_types import Flavor, Language, LanguageAlias, Status
4+
from .base_types import Flavor, Language, LanguageAlias, Status, TestCase
55
from .clients import (
66
ATD,
77
ATDJudge0CE,
@@ -38,6 +38,7 @@
3838
"Sulu",
3939
"SuluJudge0CE",
4040
"SuluJudge0ExtraCE",
41+
"TestCase",
4142
"async_execute",
4243
"execute",
4344
"run",

src/judge0/api.py

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional, Union
22

3-
from .base_types import Flavor
3+
from .base_types import Flavor, TestCase
44
from .clients import Client
55
from .retry import RegularPeriodRetry, RetryMechanism
66
from .submission import Submission
@@ -85,12 +85,61 @@ def wait(
8585
return submissions
8686

8787

88+
def create_submissions_from_test_cases(
89+
submissions: Union[Submission, list[Submission]],
90+
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
91+
):
92+
"""Utility function for creating submissions from the (submission, test_case) pairs.
93+
94+
The following table contains the return type based on the types of `submissions`
95+
and `test_cases` arguments:
96+
97+
| submissions | test_cases | returns |
98+
|:-----------------|:---------------|:-----------------|
99+
| Submission | TestCase | Submission |
100+
| Submission | list[TestCase] | list[Submission] |
101+
| list[Submission] | TestCase | list[Submission] |
102+
| list[Submission] | list[TestCase] | list[Submission] |
103+
104+
"""
105+
# Let's deal with the simplest cases where no test cases are provided. We
106+
# return original submissions argument as this gives a user an option
107+
# to change the original Submission objects, without creating copies of it.
108+
if test_cases is None or isinstance(test_cases, list) and len(test_cases) == 0:
109+
return submissions
110+
111+
if isinstance(submissions, Submission):
112+
submissions_list = [submissions]
113+
else:
114+
submissions_list = submissions
115+
116+
if isinstance(test_cases, list):
117+
test_cases_list = test_cases
118+
else:
119+
test_cases_list = [test_cases]
120+
121+
all_submissions = []
122+
for submission in submissions_list:
123+
for test_case in test_cases_list:
124+
submission_copy = submission.copy()
125+
if test_case is not None:
126+
submission_copy.stdin = test_case.input
127+
submission_copy.expected_output = test_case.expected_output
128+
all_submissions.append(submission_copy)
129+
130+
if isinstance(submissions, Submission) and not isinstance(test_cases, list):
131+
return all_submissions[0]
132+
else:
133+
return all_submissions
134+
135+
88136
def _execute(
89137
*,
90138
client: Optional[Union[Client, Flavor]] = None,
91139
submissions: Optional[Union[Submission, list[Submission]]] = None,
92140
source_code: Optional[str] = None,
93141
wait_for_result: bool = False,
142+
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
94143
**kwargs,
95144
) -> Union[Submission, list[Submission]]:
96145
if submissions is not None and source_code is not None:
@@ -115,29 +164,39 @@ def _execute(
115164

116165
client = resolve_client(client, submissions=submissions)
117166

118-
if isinstance(submissions, (list, tuple)):
119-
submissions = client.create_submissions(submissions)
167+
all_submissions = create_submissions_from_test_cases(submissions, test_cases)
168+
169+
# We differentiate between creating a single submission and multiple
170+
# submissions to be consistent with the API, even though the API
171+
# allows to create single submission with the same endpoint as for
172+
# creating the multiple submissions.
173+
if isinstance(all_submissions, Submission):
174+
all_submissions = client.create_submission(all_submissions)
175+
elif len(all_submissions) == 1:
176+
all_submissions = [client.create_submission(all_submissions[0])]
120177
else:
121-
submissions = client.create_submission(submissions)
178+
all_submissions = client.create_submissions(all_submissions)
122179

123180
if wait_for_result:
124-
return wait(client, submissions)
125-
else:
126-
return submissions
181+
all_submissions = wait(client, all_submissions)
182+
183+
return all_submissions
127184

128185

129186
def async_execute(
130187
*,
131188
client: Optional[Union[Client, Flavor]] = None,
132189
submissions: Optional[Union[Submission, list[Submission]]] = None,
133190
source_code: Optional[str] = None,
191+
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
134192
**kwargs,
135193
) -> Union[Submission, list[Submission]]:
136194
return _execute(
137195
client=client,
138196
submissions=submissions,
139197
source_code=source_code,
140198
wait_for_result=False,
199+
test_cases=test_cases,
141200
**kwargs,
142201
)
143202

@@ -147,17 +206,21 @@ def sync_execute(
147206
client: Optional[Union[Client, Flavor]] = None,
148207
submissions: Optional[Union[Submission, list[Submission]]] = None,
149208
source_code: Optional[str] = None,
209+
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
150210
**kwargs,
151211
) -> Union[Submission, list[Submission]]:
152212
return _execute(
153213
client=client,
154214
submissions=submissions,
155215
source_code=source_code,
156216
wait_for_result=True,
217+
test_cases=test_cases,
157218
**kwargs,
158219
)
159220

160221

161222
execute = sync_execute
162-
run = execute
223+
224+
run = sync_execute
225+
sync_run = sync_execute
163226
async_run = async_execute

src/judge0/base_types.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
from abc import ABC, abstractmethod
22
from dataclasses import dataclass
33
from enum import IntEnum
4+
from typing import Optional
5+
6+
7+
@dataclass(frozen=True)
8+
class TestCase:
9+
# Needed to disable pytest from recognizing it as a class containing different test cases.
10+
__test__ = False
11+
12+
input: Optional[str] = None
13+
expected_output: Optional[str] = None
414

515

616
class Encodeable(ABC):
@@ -9,7 +19,7 @@ def encode(self) -> bytes:
919
pass
1020

1121

12-
@dataclass
22+
@dataclass(frozen=True)
1323
class Language:
1424
id: int
1525
name: str

src/judge0/submission.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
from datetime import datetime
23
from typing import Union
34

@@ -176,3 +177,6 @@ def is_done(self) -> bool:
176177
return False
177178
else:
178179
return self.status not in (Status.IN_QUEUE, Status.PROCESSING)
180+
181+
def copy(self) -> "Submission":
182+
return copy.deepcopy(self)

tests/test_api_test_cases.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Separate file containg tests related to test case functionality."""
2+
3+
import judge0
4+
import pytest
5+
from judge0 import Status, Submission, TestCase
6+
from judge0.api import create_submissions_from_test_cases
7+
8+
9+
@pytest.mark.parametrize(
10+
"submissions,test_cases,expected_type",
11+
[
12+
[Submission(""), TestCase(), Submission],
13+
[[Submission("")], TestCase(), list],
14+
[Submission(""), [TestCase()], list],
15+
[[Submission("")], [TestCase()], list],
16+
],
17+
)
18+
def test_create_submissions_from_test_cases_return_type(
19+
submissions, test_cases, expected_type
20+
):
21+
output = create_submissions_from_test_cases(submissions, test_cases)
22+
assert type(output) == expected_type
23+
24+
25+
@pytest.mark.parametrize(
26+
"source_code_or_submissions,test_cases,expected_status",
27+
[
28+
[
29+
"print(f'Hello, {input()}')",
30+
[TestCase("Judge0", "Hello, Judge0")],
31+
[Status.ACCEPTED],
32+
],
33+
[
34+
"print(f'Hello, {input()}')",
35+
[
36+
TestCase("Judge0", "Hello, Judge0"),
37+
TestCase("pytest", "Hello, pytest"),
38+
],
39+
[Status.ACCEPTED, Status.ACCEPTED],
40+
],
41+
[
42+
Submission("print(f'Hello, {input()}')"),
43+
[
44+
TestCase("Judge0", "Hello, Judge0"),
45+
],
46+
[Status.ACCEPTED],
47+
],
48+
[
49+
Submission("print(f'Hello, {input()}')"),
50+
[
51+
TestCase("Judge0", "Hello, Judge0"),
52+
TestCase("pytest", "Hi, pytest"),
53+
],
54+
[Status.ACCEPTED, Status.WRONG_ANSWER],
55+
],
56+
[
57+
[
58+
Submission("print(f'Hello, {input()}')"),
59+
Submission("print(f'Hello, {input()}')"),
60+
],
61+
[
62+
TestCase("Judge0", "Hello, Judge0"),
63+
TestCase("pytest", "Hello, pytest"),
64+
],
65+
[
66+
Status.ACCEPTED,
67+
Status.ACCEPTED,
68+
Status.WRONG_ANSWER,
69+
Status.WRONG_ANSWER,
70+
],
71+
],
72+
],
73+
)
74+
def test_test_cases_from_run(
75+
source_code_or_submissions, test_cases, expected_status, request
76+
):
77+
client = request.getfixturevalue("judge0_ce_client")
78+
79+
if isinstance(source_code_or_submissions, str):
80+
submissions = judge0.run(
81+
client=client,
82+
source_code=source_code_or_submissions,
83+
test_cases=test_cases,
84+
)
85+
else:
86+
submissions = judge0.run(
87+
client=client,
88+
submissions=source_code_or_submissions,
89+
test_cases=test_cases,
90+
)
91+
92+
assert [submission.status for submission in submissions] == expected_status
93+
94+
95+
@pytest.mark.parametrize(
96+
"submissions,expected_status",
97+
[
98+
[
99+
Submission(
100+
"print(f'Hello, {input()}')",
101+
stdin="Judge0",
102+
expected_output="Hello, Judge0",
103+
),
104+
Status.ACCEPTED,
105+
],
106+
[
107+
[
108+
Submission(
109+
"print(f'Hello, {input()}')",
110+
stdin="Judge0",
111+
expected_output="Hello, Judge0",
112+
),
113+
Submission(
114+
"print(f'Hello, {input()}')",
115+
stdin="pytest",
116+
expected_output="Hello, pytest",
117+
),
118+
],
119+
[Status.ACCEPTED, Status.ACCEPTED],
120+
],
121+
],
122+
)
123+
def test_no_test_cases(submissions, expected_status, request):
124+
"""This test tests that if no test cases are provided, the submissions are changed in place."""
125+
client = request.getfixturevalue("judge0_ce_client")
126+
127+
judge0.run(
128+
client=client,
129+
submissions=submissions,
130+
)
131+
132+
if isinstance(submissions, list):
133+
assert [submission.status for submission in submissions] == expected_status
134+
else:
135+
assert submissions.status == expected_status

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy