Source code for grader.execution_base
"""
This module handles the execution of the users module. It should ideally
be called in an subprocess (like code_runner does) in a secure enviroment
with all code files prepared.
This overhead is needed to avoid having extra testcases loaded by the grader.
`test_module` loads the tester code loaded in a file. In that For each test, an
async request is fired (run in another process). It is resolved within the
`resolve_testcase_run` function. If that call timeouts, it is then terminated.
See `resolve_testcase_run` for output format description.
"""
import grader
from time import time, sleep
from .program_container import ProgramContainer
from .utils import get_traceback, get_error_message, import_module, dump_json, load_json
RESULT_DEFAULTS = {
"log": [],
"error_message": "",
"traceback": ""
}
[docs]def call_all(function_list, *args, **kwargs):
for fun in function_list:
fun(*args)
[docs]def call_test_function(test_index, tester_path, solution_path):
""" Called in another process. Finds the test `test_name`, calls the
pre-test hooks and tries to execute it.
If an exception was raised by call, prints it to stdout """
import_module(tester_path)
test_name = grader.testcases.get_name(test_index)
test_function = grader.testcases[test_name]
# pre-test hooks
pre_hook_info = {
"test_name": test_name,
"tester_path": tester_path,
"solution_path": solution_path,
"extra_args": [],
"extra_kwargs": {}
}
call_all(grader.get_setting(test_name, "pre-hooks"), pre_hook_info)
results = RESULT_DEFAULTS.copy()
# start users program
try:
module = ProgramContainer(solution_path, results)
while not hasattr(module, "module"):
sleep(0.001)
module.condition.acquire()
test_function(
module,
*pre_hook_info["extra_args"],
**pre_hook_info["extra_kwargs"]
)
except Exception as e:
results["error_message"] = get_error_message(e)
results["traceback"] = get_traceback(e)
raise
finally:
module.restore_io()
print(dump_json(results))
[docs]def do_testcase_run(test_name, tester_path, solution_path, options):
""" Calls the test, checking if it doesn't raise an Exception.
Returns a dictionary in the following form::
{
"success": boolean,
"traceback": string ("" if None)
"error_message: string
"time": string (execution time, rounded to 3 decimal digits)
"description": string (test name/its description)
}
If the test timeouts, traceback is set to "timeout".
Post-hooks can manipulate with the test results before returning.
"""
from grader.code_runner import call_test
# TODO: not correct probably
options["timeout"] = grader.get_setting(test_name, "timeout")
test_index = grader.testcases.indexOf(test_name)
start = time()
success, stdout, stderr = call_test(test_index, tester_path, solution_path, options)
end = time()
result = RESULT_DEFAULTS.copy()
if (end - start) > options["timeout"]:
result["error_message"] = "Timeout"
result["traceback"] = "Timeout"
else:
try:
result = load_json(stdout)
except Exception as e:
result["traceback"] = stdout
result["stderr"] = stderr
result.update(
success=success,
description=test_name,
time=("%.3f" % (end - start))
)
# after test hooks - cleanup
call_all(grader.get_setting(test_name, "post-hooks"), result)
return result