These two Python scripts that can be used to grade Java code. You would call the template.py
from an Advanced Code Test after setting up the tests you want to make. The second script, java_grader_methods.py
contains the functions that do the actual work.
In addition to executing code to check for output, these scripts may be used to check for specific words in the code and check if the code uses recursion.
template.py
import os, sys, requests, random, re, io, subprocess
sys.path.append('/usr/share/codio/assessments')
from lib.grade import send_partial_v2, FORMAT_V2_MD, FORMAT_V2_HTML, FORMAT_V2_TXT
#change for correct language
import java_grader_methods as jgm
## uses the above method to grade programs
path = ""
file = "filename.ext"
student_code = os.path.join(path, file)
copy_code_file(student_code, file)
points = 100
feedback = ""
#check functionality
if not(check_output(file, 'INPUT', "OUTPUT", True)):
feedback += "Test case failed: INPUT \n"
points -= 20
#check compliance with problem spec
with open(file) as student_code:
code_without_comments = strip_comments(student_code.read())
limited = {"keyword":1}
if not(check_code_for_limited(code_without_comments, limited)):
feedback += "FEEDBACK \n"
points -= 20
banned = []
if not(check_code_for_banned(code_without_comments, banned)):
feedback += "FEEDBACK \n"
points -= 20
required = []
if not(check_code_for_required(code_without_comments, required)):
feedback += "FEEDBACK \n"
points -= 20
# check for recursion
with open(file) as student_code:
if not(check_code_for_recursive_call(student_code, "method name", "method header")):
feedback += "FEEDBACK \n"
points -= 20
feedback+= "<h2>On this question you earned " + str(points) + "% out of 100%</h2>"
res = send_partial_v2(points, feedback, FORMAT_V2_HTML)
exit(0 if res else 1)
java_grader_methods.py
import os, sys, requests, random, re, io, subprocess, shutil
from subprocess import Popen, PIPE, STDOUT
import time
def copy_code_file(file_path, file_name):
shutil.copyfile(file_path, '.guides/secure/'+file_name)
## check function of code using output - keyboard flag to indicate input type
def check_output(file, path, arguments, expected_output, keyboard=False):
expected_output = expected_output.rstrip('\x00')
#compile and run code with given input
compile_cmd = "javac " + path + file
file_no_ext = os.path.splitext(file)[0]
if keyboard:
subprocess.Popen(compile_cmd, shell=True)
#https://stackoverflow.com/questions/33976094/subprocess-stdin-input
p = Popen(['java', file_no_ext], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
student_output = p.communicate(input=str.encode(arguments))[0]
else:
subprocess.Popen(compile_cmd, shell=True)
# one-second pause
time.sleep(1)
student_output = subprocess.check_output(['java', '-cp', path, file_no_ext]).rstrip()
#check generated output
if student_output.decode("utf-8").strip() == expected_output:
return True
else:
return False
## returns file name of code without comments
def strip_comments(text_of_file):
return re.sub('//.*?\n|/\*.*?\*/', '', str(text_of_file))
#checks for banned keywords
def check_code_for_banned(code_without_comments, list_of_banned):
for taboo in list_of_banned:
if taboo in code_without_comments:
print("BANNED KEYWORD " + taboo + " FOUND")
return False
return True
#checks for keywords with limits - takes DICTIONARY
def check_code_for_limited(code_without_comments, dict_of_limited):
for limited in dict_of_limited:
if code_without_comments.count(limited) > dict_of_limited[limited]:
print("LIMITED KEYWORD " + limited + " OVER-USED - USED " + str(code_without_comments.count(limited)) + " TIMES, ONLY ALLOWED " + str(dict_of_limited[limited]))
return False
elif code_without_comments.count(limited) < dict_of_limited[limited]:
print("LIMITED KEYWORD " + limited + " UNDER-USED - USED " + str(code_without_comments.count(limited)) + " TIMES, EXPECTED " + str(dict_of_limited[limited]))
return False
return True
#checks for required words
def check_code_for_required(code_without_comments, list_of_required):
for required in list_of_required:
if not(required in code_without_comments):
print("REQUIRED KEYWORD " + required + " NOT USED")
return False
return True
#checks if the given method is recursive
def check_code_for_recursive_call(code, recursive_method_name, recursive_method_header):
method = False
method_opened = False
recursive_call = False
opened = 0
for i in code:
if "//" in i:
i = i[:i.index("//")]
if not(method) and recursive_method_header in i:
method = True
if method and method_opened and recursive_method_name in i:
recursive_call = True
if method:
opened += i.count('{')
opened -= i.count('}')
if method and not(method_opened) and opened > 0:
method_opened = True
if method and method_opened and opened == 0:
method = False
return recursive_call