Using Python to grade Java code

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
1 Like