1313 TEST_TEMPLATE = file .read ()
1414
1515
16+ class BypassDetectedError (Exception ):
17+ """Detected an attempt at bypassing the unittests."""
18+
19+
1620UnittestResult = namedtuple (
1721 "UnittestResult" , "question_id question_index return_code passed result"
1822)
@@ -65,9 +69,12 @@ async def _post_eval(code: str) -> dict[str, str]:
6569 return response .json ()
6670
6771
68- async def execute_unittest (form_response : FormResponse , form : Form ) -> list [UnittestResult ]:
72+ async def execute_unittest (
73+ form_response : FormResponse , form : Form
74+ ) -> tuple [list [UnittestResult ], list [BypassDetectedError ]]:
6975 """Execute all the unittests in this form and return the results."""
7076 unittest_results = []
77+ errors = []
7178
7279 for index , question in enumerate (form .questions ):
7380 if question .type == "code" :
@@ -101,40 +108,57 @@ async def execute_unittest(form_response: FormResponse, form: Form) -> list[Unit
101108 code = code .replace ("### UNIT CODE" , unit_code )
102109
103110 try :
104- response = await _post_eval (code )
105- except HTTPStatusError :
106- return_code = 99
107- result = "Unable to contact code runner."
108- else :
109- return_code = int (response ["returncode" ])
110-
111- # Parse the stdout if the tests ran successfully
112- if return_code == 0 :
113- stdout = response ["stdout" ]
114- passed = bool (int (stdout [0 ]))
115-
116- # If the test failed, we have to populate the result string.
117- if not passed :
118- failed_tests = stdout [1 :].strip ().split (";" )
119-
120- # Redact failed hidden tests
121- for i , failed_test in enumerate (failed_tests .copy ()):
122- if failed_test in hidden_tests :
123- failed_tests [i ] = f"hidden_test_{ hidden_tests [failed_test ]} "
124-
125- result = ";" .join (failed_tests )
126- else :
127- result = ""
128- elif return_code in (5 , 6 , 99 ):
129- result = response ["stdout" ]
130- # Killed by NsJail
131- elif return_code == 137 :
132- return_code = 7
133- result = "Timed out or ran out of memory."
134- # Another code has been returned by CPython because of another failure.
135- else :
111+ try :
112+ response = await _post_eval (code )
113+ except HTTPStatusError :
136114 return_code = 99
137- result = "Internal error."
115+ result = "Unable to contact code runner."
116+ else :
117+ return_code = int (response ["returncode" ])
118+
119+ # Parse the stdout if the tests ran successfully
120+ if return_code == 0 :
121+ stdout = response ["stdout" ]
122+ try :
123+ passed = bool (int (stdout [0 ]))
124+ except ValueError :
125+ raise BypassDetectedError ("Detected a bypass when reading result code." )
126+
127+ if passed and stdout .strip () != "1" :
128+ # Most likely a bypass attempt
129+ # A 1 was written to stdout to indicate success,
130+ # followed by the actual output
131+ raise BypassDetectedError (
132+ "Detected improper value for stdout in unittest."
133+ )
134+
135+ # If the test failed, we have to populate the result string.
136+ elif not passed :
137+ failed_tests = stdout [1 :].strip ().split (";" )
138+
139+ # Redact failed hidden tests
140+ for i , failed_test in enumerate (failed_tests .copy ()):
141+ if failed_test in hidden_tests :
142+ failed_tests [i ] = f"hidden_test_{ hidden_tests [failed_test ]} "
143+
144+ result = ";" .join (failed_tests )
145+ else :
146+ result = ""
147+ elif return_code in (5 , 6 , 99 ):
148+ result = response ["stdout" ]
149+ # Killed by NsJail
150+ elif return_code == 137 :
151+ return_code = 7
152+ result = "Timed out or ran out of memory."
153+ # Another code has been returned by CPython because of another failure.
154+ else :
155+ return_code = 99
156+ result = "Internal error."
157+ except BypassDetectedError as error :
158+ return_code = 10
159+ result = "Bypass attempt detected, aborting."
160+ errors .append (error )
161+ passed = False
138162
139163 unittest_results .append (UnittestResult (
140164 question_id = question .id ,
@@ -144,4 +168,4 @@ async def execute_unittest(form_response: FormResponse, form: Form) -> list[Unit
144168 result = result
145169 ))
146170
147- return unittest_results
171+ return unittest_results , errors
0 commit comments