Skip to content

Instantly share code, notes, and snippets.

@bulatb
Created June 3, 2012 23:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bulatb/2865493 to your computer and use it in GitHub Desktop.
Save bulatb/2865493 to your computer and use it in GitHub Desktop.
Autograder submission bot for CSE 120
#!/software/common/python-2.7/bin/python2.7
# ^ /software is where ieng6 keeps the nice things we can't have
import os
import time
import subprocess
from optparse import OptionParser
AUTOGRADER_SCRIPT = "autograder-proj%s"
SOURCE_PACKAGES = ['test', 'threads', 'userprog', 'vm']
MTIME_FILE_NAME = ".autosubmit-mtime"
def build_parser():
"""Build an optparse OptionParser (because ieng6 runs python 2.4).
Update: python 2.7 lives in /software/common, but now there's no
point in changing it.
"""
parser = OptionParser()
parser.add_option("-a", "--assignment", dest="assignment", default="3", help="Assignment number.")
parser.add_option("-p", "--partner", dest="partner", help="Your partner's login name. Include this if you want them to receive the email.")
parser.add_option("-e", "--email", dest="email", help="Extra addresses to mail results to.")
parser.add_option("-i", "--interval", dest="interval", type="int", default=10, help="Autograder submission interval, in minutes. Default is 10; use 0 to disable.")
parser.add_option("-k", "--kill-after", dest="kill_after", type="float", default=5, help="Stop submitting after this many hours. Default is 5; use 0 to run until killed. Fractional values allowed.")
parser.add_option("-f", "--force-submit", action="store_true", dest="force_submit", default=False, help="Submit every interval, even if no watched files have changed.")
parser.add_option("-o", "--once", action="store_true", dest="run_once", default=False, help="Same as -i 0. Don't use them together or the bad thing that happens will be all your fault.")
return parser
def generate_grader_input(options):
"""Generates input sent to the autograder's stdin
"""
grader_input = "%s%sy\ny\n"
partner = "n\n"
email = "n\n"
if options.partner is not None:
partner = "y\n%s\ny\n" % options.partner
if options.email is not None:
email = "y\n%s\n" % options.email
return grader_input % (partner, email)
def has_unsubmitted_changes():
"""Runs stat on all the files in every package in SOURCE_PACKAGES and
returns true if any of their mtimes are after the last submission to
the autograder.
This is totally gross, but ieng6 doesn't seem to have inotify.
"""
try:
# IO grossness is because r+ won't create the file if it doesn't
# exist and w+ will truncate away the previous mtime. If the file
# exists, open with r+ and seek to the beginning when writing the
# new time; otherwise create it with w+ and call this again.
with open(".autosubmit-mtime", "r+") as mtime_file:
try:
last_submission_time = float(mtime_file.read().rstrip())
except ValueError:
last_submission_time = 0.0
for package in SOURCE_PACKAGES:
path_to_package = os.path.join(".", package)
for project_file in os.listdir(path_to_package):
mtime = os.stat(os.path.join(path_to_package, project_file)).st_mtime
if mtime > last_submission_time:
mtime_file.seek(0)
mtime_file.write(str(time.time()))
return True
except IOError as e:
# If the file doesn't exist, create it and try again.
if e[0] == 2:
mtime_file = open(MTIME_FILE_NAME, "w+")
mtime_file.close()
return has_unsubmitted_changes()
return False
def should_run_again(options, kill_time):
run_only_once = options.interval == "0" or options.run_once
run_until_killed = options.kill_after == "0"
before_kill_time = time.time() < kill_time
return (before_kill_time or run_until_killed) and not run_only_once
def run_grader(options):
grader = subprocess.Popen([AUTOGRADER_SCRIPT % (options.assignment)], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
output = grader.communicate(generate_grader_input(options))
print output[0]
return grader
def main():
options = build_parser().parse_args()[0]
autograder_name = AUTOGRADER_SCRIPT % options.assignment
run_interval = abs(int(options.interval)) * 60
kill_time = time.time() + abs(float(options.kill_after)) * 60 * 60
run_until_killed = options.kill_after == "0"
run_once = run_interval == 0 or options.run_once
# Header message
print "Started autograder submission bot."
if run_once:
print "Running %s once." % (AUTOGRADER_SCRIPT % options.assignment)
else:
if run_until_killed:
nice_kill_time = "killed"
else:
nice_kill_time = time.strftime("%I:%M %p", time.localtime(kill_time))
print "Running %s every %s minutes until %s." % (autograder_name, options.interval, nice_kill_time)
print "Autograder output will be shown below.\n"
# srs bsnss
first_run = True
try:
while should_run_again(options, kill_time) or first_run:
if has_unsubmitted_changes() or options.force_submit:
run_grader(options)
else:
print "No files modified since last submission."
if not run_once:
print time.strftime("Next run at %I:%M %p.", time.localtime(time.time() + run_interval))
print "Going until %s.\n" % nice_kill_time
time.sleep(run_interval)
first_run = False
except KeyboardInterrupt:
# Just fall through
pass
finally:
print "Autosubmit bot stopped at %s." % time.strftime("%I:%M %p", time.localtime())
if __name__ == "__main__":
main()
@bcarl
Copy link

bcarl commented Jun 10, 2012

@bulatb, you are my hero.

@bcarl
Copy link

bcarl commented Jun 10, 2012

I wish you could do pull requests between gists... or maybe you can and I just don't know how...

I added an option to redirect output to a file: https://gist.github.com/2905877

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment