-
Notifications
You must be signed in to change notification settings - Fork 345
/
grader.py
154 lines (126 loc) · 5.06 KB
/
grader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""
grader.py
Extracts then grades students. Outputs to a summary file with all test scores,
with individual log files and git diffs left in each student's directory.
"""
import argparse
import json
import os
import subprocess
from distutils.dir_util import copy_tree
from shutil import copyfile, rmtree
from threading import Timer
parser = argparse.ArgumentParser()
parser.add_argument(
"-s",
"--students",
dest="students",
help="Student submission directory",
required=True)
parser.add_argument(
"-n",
"--lab-num",
dest="lab_num",
help="The number corresponding to the lab",
required=True)
parser.add_argument(
"-l",
"--lab-name",
dest='lab',
help='The name of the lab, such as "lab1-clientserver"',
required=True)
args = parser.parse_args()
STUDENT_SUBMISSION_DIR = args.students
HANDOUT_DIRECTORY = 'handout'
LAB_NAME = args.lab
LAB_NUMBER = args.lab_num
HANDOUT_DIRECTORY = 'handout'
TEST_DIRECTORY = os.path.join(HANDOUT_DIRECTORY, 'labs', LAB_NAME, 'tst')
RESULTS_DIR_NAME = 'results'
FULL_SUMMARY_NAME = 'test-summary.txt'
STUDENT_TEST_LOG_NAME = 'test-results'
STUDENT_DIFF_NAME = 'diff.txt'
TAR_NAME = 'submit.tar.gz'
GIT_DIFF_OUTPUT_NAME = 'diff.txt'
TIMEOUT = 10 * 60
TIMES_TO_RUN = 2
# Contains summary.txt, then a directory for each student containing src, diff, and full test output.
if os.path.exists(RESULTS_DIR_NAME):
rmtree(RESULTS_DIR_NAME)
os.makedirs(RESULTS_DIR_NAME)
# Parse the test log and record these lines for the summary
SEARCH_TERMS = ['Tests passed', 'Points', 'Total time']
# Used to construct the final summary
SCORES = {}
# TODO Verify that this even works. I am not sure how it handles tests that will time out.
def run(cmd, out, cwd, timeout_sec):
proc = subprocess.Popen(cmd, cwd=cwd, stdout=out, stderr=out)
kill_proc = lambda p: p.kill()
timer = Timer(timeout_sec, kill_proc, [proc])
try:
timer.start()
proc.wait()
finally:
timer.cancel()
for student in os.listdir(STUDENT_SUBMISSION_DIR):
try:
print("Setting up student " + student)
student_path = os.path.join(STUDENT_SUBMISSION_DIR, student)
# Extract their solutions
tar_path = os.path.join(student_path, TAR_NAME)
subprocess.call(['tar', '-xf', tar_path, '--directory', student_path])
# Copy over all test folders
for lab in os.listdir(os.path.join(HANDOUT_DIRECTORY, 'labs')):
src_test_path = os.path.join(HANDOUT_DIRECTORY, 'labs', lab, 'tst')
dst_test_path = os.path.join(student_path, 'labs', lab, 'tst')
copy_tree(src_test_path, dst_test_path)
for f in os.listdir(HANDOUT_DIRECTORY):
full_file_path = os.path.join(HANDOUT_DIRECTORY, f)
# Copy jars/Makefile/lombok.config/etc.
if os.path.isfile(full_file_path):
output_path = os.path.join(student_path, f)
copyfile(full_file_path, output_path)
student_result_path = os.path.join(RESULTS_DIR_NAME, student)
if not os.path.exists(student_result_path):
os.makedirs(student_result_path)
# Remove all the MacOS files that have crazy characters in them
subprocess.Popen(['find', '.', '-name', '._*', '|', 'xargs', 'rm'], cwd=student_path, shell=True)
# Calculate the score for this student
SCORES[student] = {}
for run_index in range(TIMES_TO_RUN):
# Run tests and collect output
log_out_path = os.path.join(student_result_path, STUDENT_TEST_LOG_NAME + '-' + str(run_index) + '.txt')
with open(log_out_path, 'w+') as out:
cmd = ['python', 'run-tests.py', '--lab', str(LAB_NUMBER)]
run(cmd, out, student_path, TIMEOUT)
# Add to the summary
with open(log_out_path, 'r') as out:
test_results = out.read()
SCORES[student][run_index] = {}
for line in test_results.split('\n'):
for term in SEARCH_TERMS:
if term in line:
SCORES[student][run_index][term] = line.split(':')[1].strip()
# Copy source
copy_tree(
os.path.join(student_path, 'labs', LAB_NAME, 'src'),
student_result_path + '/src')
# Copy diff
git_diff_stud_dir = os.path.join(student_path, 'labs', LAB_NAME, 'src')
git_diff_handout_dir = os.path.join('handout', 'labs', LAB_NAME, 'src')
git_diff_out_path = os.path.join(student_result_path,
GIT_DIFF_OUTPUT_NAME)
with open(git_diff_out_path, 'w+') as fd:
git_diff_cmd = [
'git', 'diff', '--no-index', '--color=always',
git_diff_handout_dir, git_diff_stud_dir
]
subprocess.call(git_diff_cmd, stdout=fd)
print('Completed running student ' + student)
except ValueError as e:
print('Encountered ' + str(e))
print('Could not grade student ' + student)
raise(e)
# Write out the summary of all students
with open(os.path.join(RESULTS_DIR_NAME, FULL_SUMMARY_NAME), 'w+') as out:
json.dump(SCORES, out)