-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Full Judge Solution for Testing the Judge Locally without Calling the API #9
Conversation
…ner is completed it can be now tested locally without any api call: cd core_apps/judge_engine run: python views.py and you will see the output. a demo code is added for the test
…le path in the volume. updated and refactored the overall workflow
Sweep: PR ReviewAuthors of pull request: @Mahboob-A This pull request aimed to enhance the Docker-based development environment and refactor the code execution engine for better modularity and maintainability. A The The New Dockerfiles were added for both the Django application and the C++ execution environment, with changes to the directory structure and volume mounts to ensure consistency across the development environment. Overall, these changes aimed to streamline the development workflow, improve code modularity, and enhance the local testing capabilities of the code execution engine.
|
# from core_apps.judge_engine.exceptions import TimeLimitExceedException | |
from exceptions import TimeLimitExceedException |
View Diff
TimeLimitExceedException
to exceptions
may cause runtime errors if the new module does not define the exception class correctly.online-judge/src/core_apps/judge_engine/containers.py
Lines 12 to 13 in 960a0f3
# from core_apps.judge_engine.exceptions import TimeLimitExceedException | |
from exceptions import TimeLimitExceedException |
View Diff
src/core_apps/judge_engine/exec_engine.py
- The introduction of
judge_volume_mount
and the modification ofparent_dir
could lead to incorrect file paths ifjudge_volume_mount
is not correctly set or if the split operation fails. - Changing the import paths to relative imports may cause import errors if the module structure or execution context changes.
online-judge/src/core_apps/judge_engine/exec_engine.py
Lines 113 to 123 in 960a0f3
judge_volume_mount, | |
) = result[1:] | |
# code_filepath: base-dir/user_codes/lang/uuid/main.cpp | |
# parent_dir: base-dir/user_codes/lang/uuid | |
# /app/user-files/user_codes/cpp/uuid | |
parent_dir = os.path.dirname(code_filepath) | |
# parent_dir after split: /user_codes/cpp/uuid | |
# in sibling container, data is available at: $sibling_volume_mount/splited_parent_dir | |
parent_dir = parent_dir.split(judge_volume_mount)[1] |
View Diff
online-judge/src/core_apps/judge_engine/exec_engine.py
Lines 11 to 17 in 960a0f3
# from core_apps.judge_engine.file_data_processor import file_processor | |
# from core_apps.judge_engine.containers import code_container | |
# from core_apps.judge_engine.compare_testcases import compare_output_and_testcases | |
from file_data_processor import file_processor | |
from containers import code_container | |
from compare_testcases import compare_output_and_testcases |
View Diff
src/core_apps/judge_engine/file_data_processor.py
- The additional return value
judge_volume_mount
in_process_write_data
may cause issues if the caller does not handle the new tuple structure correctly. - Sweep has identified a redundant function: The new function
__get_user_code_base_dir
is redundant because its functionality is already covered by the existing method__get_user_code_base_dir_with_lang
infile_data_processor.py
.
online-judge/src/core_apps/judge_engine/file_data_processor.py
Lines 186 to 193 in 960a0f3
True, | |
code_filepath, | |
input_filepath, | |
output_filepath, | |
testcases_filepath, | |
success_message, | |
judge_volume_mount, | |
) |
View Diff
online-judge/src/core_apps/judge_engine/file_data_processor.py
Lines 18 to 42 in 960a0f3
def __get_user_code_base_dir(self): | |
"""Return the base-dir/user_codes directory | |
Storage: | |
volume: | |
name: user_code_files | |
The below '$base_dir' is the volume mount for the Judge Container. | |
The volume mount of sibling container is specified at the module 'containers.py'. | |
Hence, everything is saved under 'base_dir' in the Judge Container, will be available at | |
the volume mount of sibling contianer. | |
Ex: Judge Container file path: /app/user-files/user_codes/lang/uuid/ | |
Sibling Container file path for the same data: $volume_mount_of_sibling_container/user_codes/lang/uuid/ | |
""" | |
# this is the volume for the main Judge Container. see the docker compose file. | |
base_dir = "/app/user-files" | |
logger.info(f"base dir: {base_dir}") | |
user_code_dir = "user_codes" | |
user_code_base_dir = os.path.join(base_dir, user_code_dir) | |
return user_code_base_dir, base_dir |
View Diff
src/core_apps/judge_engine/questions.py
- The function call
sum_of_a
in the C++ code should besum_of_a_b
, which will cause a compilation error.
"code": "#include <bits/stdc++.h>\nusing namespace std; \n\nvoid sum_of_a(int a, int b)\n{\n cout << a + b << endl; \n}\n\n\nint main()\n{\n int t, a, b; \n cin >> t; \n \n while(t--)\n {\n cin >> a >> b; \n sum_of_a_b(a, b); \n }\n\n return 0;\n}", |
View Diff
src/core_apps/judge_engine/urls.py
- Commenting out the URL patterns and imports makes the "simple-execute/" and "robust-execute/" endpoints inaccessible, which could break functionality relying on these endpoints.
online-judge/src/core_apps/judge_engine/urls.py
Lines 4 to 17 in 960a0f3
# from core_apps.judge_engine.views import CodeSubmitSimpleImplementation, CodeSubmitRobustAPI | |
# urlpatterns = [ | |
# path( | |
# "simple-execute/", | |
# CodeSubmitSimpleImplementation.as_view(), | |
# name="simple_execute_code", | |
# ), | |
# path("robust-execute/", CodeSubmitRobustAPI.as_view(), name="robust_execute_code"), | |
# ] | |
urlpatterns = [ | |
] |
View Diff
src/core_apps/judge_engine/views.py
- Commenting out the
CodeSubmitSimpleImplementation
andCodeSubmitRobustAPI
classes effectively disables the API endpoints, which could break functionality dependent on these endpoints. - The hardcoded problem definition and the immediate call to
test_inside_cont_exe
at the end of the file could lead to unintended execution and potential security risks if the script is run in a production environment. - Sweep has identified a redundant function: The new function
test_inside_cont_exe
is redundant because its functionality is already covered by thepost
method in theCodeSubmitRobustAPI
class insrc/core_apps/judge_engine/views.py
.
online-judge/src/core_apps/judge_engine/views.py
Lines 47 to 149 in 960a0f3
# class CodeSubmitSimpleImplementation(APIView): | |
# """A simple approach to test the code submission without creating the files beforehand. | |
# In this use case, the host volume creation also handled by docker. | |
# The volume created in host by docker has permission only of docker, hence the files can not be deleted by host user. | |
# The time is also same as the robust implementation of creating files beforehand. Infact, this method takes one second more | |
# one average than the robust implementation. | |
# """ | |
# def post(self, request): | |
# start = time.time() | |
# lang = request.data.get("lang") | |
# code = request.data.get("code") | |
# input_data = request.data.get("input") | |
# print("Code: ", code) | |
# client = from_env() | |
# file_path = f"user-files/{random.randint(10, 100000)}" | |
# curr_path = os.getcwd() | |
# print("curr path: ", os.getcwd()) | |
# new_file_path = os.path.join(curr_path, file_path) | |
# try: | |
# # Create and start a container | |
# container = client.containers.run( | |
# "simple_cpp", # Image name | |
# volumes={ | |
# f"{new_file_path}/": { | |
# "bind": "/user-files", | |
# "mode": "rw", | |
# } | |
# }, | |
# command=[ | |
# "sh", | |
# "-c", | |
# f'echo "{code}" > /user-files/code.cpp && echo "{input_data}" > /user-files/input.txt && g++ /user-files/code.cpp -o /user-files/code && /user-files/code < /user-files/input.txt > /user-files/output.txt', | |
# ], | |
# detach=True, | |
# ) | |
# # Wait for the container to finish | |
# result = container.wait() | |
# print("result: ", result) | |
# # Get the logs (output) | |
# output = container.logs().decode("utf-8") | |
# with open(f"{new_file_path}/output.txt", "r") as f: | |
# data = f.read() | |
# print("data: ", data) | |
# # Remove the container | |
# container.remove() | |
# end = time.time() | |
# print("total time taken: ", end - start) | |
# # Return output to the user | |
# return JsonResponse({"output": output}) | |
# except ContainerError as e: | |
# print(f"Error: {e}") | |
# return JsonResponse({"error": str(e)}, status=500) | |
# except Exception as e: | |
# print(f"Unexpected error: {e}") | |
# return JsonResponse({"error": "An unexpected error occurred"}, status=500) | |
# # return JsonResponse({"error": "Method not allowed"}, status=405) | |
# class CodeSubmitRobustAPI(APIView): | |
# """An API to test the robust implementation of the Online Judge. | |
# and compare the time with simple implementation | |
# """ | |
# def post(self, request): | |
# """Submit code to execute in secure docker container.""" | |
# lang = request.data.get("lang") | |
# code = request.data.get("code") | |
# input_file = request.data.get("input") | |
# testcases = request.data.get("testcases") | |
# # print('lang: ', lang) | |
# # print("code: ", code) | |
# # print("input file ", input_file) | |
# # print("testcases: ", testcases) | |
# # print('request.data: ', request.data) | |
# # print('\ntype of requst.data: ', type(request.data)) | |
# # print('\ntype of code: ', type(code)) | |
# # print('\ntype of input: ', type(input_file)) | |
# submission_id = uuid.uuid4() | |
# data = code_exec_engine(user_codes=request.data, submission_id=submission_id) | |
# return JsonResponse({"submission_id": submission_id, "data": data}, status=200) |
View Diff
online-judge/src/core_apps/judge_engine/views.py
Lines 28 to 164 in 960a0f3
hello_world_with_num_data_problem = { | |
"lang": "cpp", | |
"code": '#include <bits/stdc++.h>\nusing namespace std;\n\nvoid sol(int num)\n{\n cout<<"Hello World: "<<num<<endl;\n}\n\nint main()\n{\n int t, num; \n cin>>t;\n\n while(t--)\n {\n cin>>num; \n sol(num);\n }\n\n return 0;\n}', | |
"input": ["10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], | |
"testcases": [ | |
"Hello World: 1", | |
"Hello World: 2", | |
"Hello World: 3", | |
"Hello World: 4", | |
"Hello World: 5", | |
"Hello World: 6", | |
"Hello World: 7", | |
"Hello World: 8", | |
"Hello World: 9", | |
"Hello World: 10", | |
], | |
} | |
# class CodeSubmitSimpleImplementation(APIView): | |
# """A simple approach to test the code submission without creating the files beforehand. | |
# In this use case, the host volume creation also handled by docker. | |
# The volume created in host by docker has permission only of docker, hence the files can not be deleted by host user. | |
# The time is also same as the robust implementation of creating files beforehand. Infact, this method takes one second more | |
# one average than the robust implementation. | |
# """ | |
# def post(self, request): | |
# start = time.time() | |
# lang = request.data.get("lang") | |
# code = request.data.get("code") | |
# input_data = request.data.get("input") | |
# print("Code: ", code) | |
# client = from_env() | |
# file_path = f"user-files/{random.randint(10, 100000)}" | |
# curr_path = os.getcwd() | |
# print("curr path: ", os.getcwd()) | |
# new_file_path = os.path.join(curr_path, file_path) | |
# try: | |
# # Create and start a container | |
# container = client.containers.run( | |
# "simple_cpp", # Image name | |
# volumes={ | |
# f"{new_file_path}/": { | |
# "bind": "/user-files", | |
# "mode": "rw", | |
# } | |
# }, | |
# command=[ | |
# "sh", | |
# "-c", | |
# f'echo "{code}" > /user-files/code.cpp && echo "{input_data}" > /user-files/input.txt && g++ /user-files/code.cpp -o /user-files/code && /user-files/code < /user-files/input.txt > /user-files/output.txt', | |
# ], | |
# detach=True, | |
# ) | |
# # Wait for the container to finish | |
# result = container.wait() | |
# print("result: ", result) | |
# # Get the logs (output) | |
# output = container.logs().decode("utf-8") | |
# with open(f"{new_file_path}/output.txt", "r") as f: | |
# data = f.read() | |
# print("data: ", data) | |
# # Remove the container | |
# container.remove() | |
# end = time.time() | |
# print("total time taken: ", end - start) | |
# # Return output to the user | |
# return JsonResponse({"output": output}) | |
# except ContainerError as e: | |
# print(f"Error: {e}") | |
# return JsonResponse({"error": str(e)}, status=500) | |
# except Exception as e: | |
# print(f"Unexpected error: {e}") | |
# return JsonResponse({"error": "An unexpected error occurred"}, status=500) | |
# # return JsonResponse({"error": "Method not allowed"}, status=405) | |
# class CodeSubmitRobustAPI(APIView): | |
# """An API to test the robust implementation of the Online Judge. | |
# and compare the time with simple implementation | |
# """ | |
# def post(self, request): | |
# """Submit code to execute in secure docker container.""" | |
# lang = request.data.get("lang") | |
# code = request.data.get("code") | |
# input_file = request.data.get("input") | |
# testcases = request.data.get("testcases") | |
# # print('lang: ', lang) | |
# # print("code: ", code) | |
# # print("input file ", input_file) | |
# # print("testcases: ", testcases) | |
# # print('request.data: ', request.data) | |
# # print('\ntype of requst.data: ', type(request.data)) | |
# # print('\ntype of code: ', type(code)) | |
# # print('\ntype of input: ', type(input_file)) | |
# submission_id = uuid.uuid4() | |
# data = code_exec_engine(user_codes=request.data, submission_id=submission_id) | |
# return JsonResponse({"submission_id": submission_id, "data": data}, status=200) | |
############################ | |
def test_inside_cont_exe(): | |
submission_id = uuid.uuid4() | |
data = code_exec_engine( | |
user_codes=hello_world_with_num_data_problem, submission_id=submission_id | |
) | |
print("data: \n", data) | |
# This is the entrypoint function to check the Judge locally. No request is needed. | |
test_inside_cont_exe() |
View Diff
online-judge/src/core_apps/judge_engine/views.py
Lines 154 to 160 in 960a0f3
def test_inside_cont_exe(): | |
submission_id = uuid.uuid4() | |
data = code_exec_engine( | |
user_codes=hello_world_with_num_data_problem, submission_id=submission_id | |
) | |
print("data: \n", data) |
View Diff
src/dev.yml
- The external volume
user_code_files
is assumed to exist, which may cause the service to fail if the volume is not created beforehand. - Mounting the Docker socket (
/var/run/docker.sock
) inside the container can pose a security risk as it allows the container to control the Docker daemon.
Line 12 in 960a0f3
- user_code_files:/app/user-files |
View Diff
Line 10 in 960a0f3
- /var/run/docker.sock:/var/run/docker.sock |
View Diff
src/docker/dev/django/Dockerfile
- The line
RUN curl -sSL https://get.docker.com/ | sh
installs Docker inside the container, which is generally unnecessary and could introduce security vulnerabilities.
RUN curl -sSL https://get.docker.com/ | sh |
View Diff
src/docker/dev/django/Dockerfile-5
- The line to install dependencies using
pip
is commented out, which means the required packages will not be installed, potentially causing the application to fail to start.
# RUN pip install --no-cache-dir -r ./requirements/dev.txt |
View Diff
src/docker/dev/django/start
- The commented out
set -o errexit
,set -o pipefail
, andset -o nounset
options reduce the robustness of the script by not handling errors, pipeline failures, and unset variables.
online-judge/src/docker/dev/django/start
Lines 3 to 7 in 960a0f3
# set -o errexit | |
# set -o pipefail | |
# set -o nounset |
View Diff
src/docker/lang/cpp/script.sh
- The variable
user_file_parent_dir
is referenced but not defined, which will cause the script to fail.
online-judge/src/docker/lang/cpp/script.sh
Lines 10 to 12 in 960a0f3
# user_file_parent_dir= is the full path of the file inside the volume: $volume_mount/user_codes/lang/uuid | |
g++ $volume_mount$user_file_parent_dir/main.cpp -o $volume_mount$user_file_parent_dir/main |
View Diff
Potential Issues
Sweep is unsure if these are issues, but they might be worth checking out.
src/core_apps/judge_engine/questions.py
- The expected test case results in the "testcases" key do not match the actual sums of the provided input pairs, which could lead to incorrect validation.
"testcases": ["3", "12", "10", "15", "30", "55", "200", "300", "200", "300"], |
View Diff
Ignore Sweep Issues. They are already covered. |
This branch is only for running the Judge without calling the API which is available in the Clone this branch and follow the steps described above to reproduce the desired Judge result. |
Clone this branch if you just want to test how the Judge is working locally without calling the API and passing any data.
Few demo codes are attached in
dummy_questions.py
to test the Judge locally without calling the API.You can tweak the questions as per your need.
How to Run:
A.
i. Clone the
check-dnd-local
branch.ii. Create a virtual env and activate it.
iii.
cd src
iv. Create the C++ Image. (If you already have not build the same image)
a. cd
/docker/lang/cpp/
b. Run:
docker build -t algocode/cpp:v1 .
Make sure to put a "dot" at the end of the line. This will create the necessary image we need to compile the C++ code.
iv. Create the volume that will be mapped with the Judge Container and Sibling Container.
Run:
docker volume create user_code_files
iv. CD to
src
and Run:make docker-up
to run the Docker Compose file (runs the main Judge Container).B.
i. Run
docker ps
And you will see a container is running with the name
src-judge-1
ii. Copy the first 5 digits of the container ID
iii. Run
docker exec -it first_5_digit_container_id /bin/bash
You are now into the main Judge Container.
Now you need to go to the
views.py
file locationv.
cd /core_apps/judge_engine
You are now inside the Judge Engine APP.vi. Run
python views.py
You will see a output something like below depending upon the dummy_question is passed.
For AC:
For WA:
vii. CD to
src
and Runmake docker-down
to stop the container.Note: You can change the question in the
test_inside_cont_exe
function'suser_codes
parameter.