-
Notifications
You must be signed in to change notification settings - Fork 15
Autograder jobs
Once you have created your course’s ob2 config directory, you can start configuring your assignments and your autograder jobs.
Add the following to your config.yaml
inside your course’s config directory:
assignments:
# My first assignment (hw1)
- name: hw1
full_score: 1.0
min_score: 0.0
max_score: 1.0
weight: 1.00000
category: Homework
is_group: false
manual_grading: false
not_visible_before: "2016-01-01 00:00:00 -0800"
start_auto_building: "2016-01-01 00:00:00 -0800"
end_auto_building: "2020-12-31 00:00:00 -0800"
due_date: "2020-12-31 23:59:59 -0800"
cannot_build_after: "2020-12-31 23:59:59 -0800"
Then, add the following to your functions.py
file:
from ob2.util.hooks import register_job
@register_job("hw1")
def hw1_job_handler(repo, commit_hash):
print repo, commit_hash
return "I think this deserves 100%", 1.0
Now, start ob2 and try running your new assignment's autograder.
Here’s an example of a manually graded assignment. It doesn’t require as many fields as the autograded ones do.
- name: midterm
full_score: 100.0
min_score: 0.0
max_score: 100.0
weight: 1.00000
category: Exams
is_group: false
manual_grading: true
not_visible_before: "2015-01-01 00:00:00 -0800"
due_date: "2015-01-01 00:00:00 -0800"
If you put all of your autograder scripts in functions.py
, it will get very cluttered very quickly. You can improve the organization of your config directory by creating a Python module to store your autograder scripts.
Create a new folder inside your config directory called cs162
, and then create an empty file named __init__.py
inside of it.
cs162/init.py
# This file is empty
# (except for this comment)
Now, your config directory should look like:
config_dir/
config_dir/config.yaml
config_dir/functions.py
config_dir/cs162/
config_dir/cs162/__init__.py
Add the following code to your functions.py
file:
functions.py (append to the end of the file)
from os.path import abspath, dirname
import ob2.config as config
# Insert the current directory into sys.path
#
# In order to avoid naming conflicts, we created a new "cs162" module that contains all of our
# custom code. This module has many self-referencing imports, so we need all of Python's module
# infrastructure in order to support it.
current_directory = dirname(abspath(__file__))
sys.path.insert(0, current_directory)
# .. and then make sure all the submodules get initialized.
for assignment in config.assignments:
if not assignment.manual_grading:
import_module("cs162.%s" % assignment.name)
# Then, get rid of it, so we don't pollute sys.path any more
sys.path.remove(current_directory)
This code will add the cs162
directory as a Python module. It attempts to import all of your autograding scripts, under the assumption that they will be inside cs162/hw1.py
(for an assignment named hw1
).
Finally, let's create a new job definition for our hw1 job.
cs162/hw1.py
from ob2.util.hooks import register_job
@register_job("hw1")
def hw1_job_handler(repo, commit_hash):
return "I live inside a module!", 1.0
You can also create a file containing code that is shared between all the autograder jobs:
cs162/common.py
from os.path import dirname, join, realpath
RESOURCES_DIR = join(dirname(realpath(__file__)), "resources")
And then use this code inside your autograder script:
cs162/hw1.py
import cs162.common as common
from ob2.util.hooks import register_job
@register_job("hw1")
def hw1_job_handler(repo, commit_hash):
print common.RESOURCES_DIR
return "I live inside a module!", 1.0
Octobear 2 comes with a rich library of helper functions to interface with Docker. Let’s start with an example, and then I’ll show you what each of the helper functions does.
from os.path import join
from ob2.dockergrader.helpers import (
copy,
copytree,
download_repository,
ensure_files_exist,
ensure_no_binaries,
extract_repository,
get_working_directory,
safe_get_results,
)
from ob2.dockergrader.job import JobFailedError
from ob2.dockergrader.rpc import DockerClient, TimeoutError
from ob2.util.hooks import register_job
from cs162.common import RESOURCES_DIR
# This is the docker image we'll use to run our autograder
docker_image = "cs162:latest"
@register_job("hw1")
def build(source, commit):
# We're using 2 Python context managers here:
# - get_working_directory() of them creates a temporary directory on the host
# - DockerClient().start() creates a new Docker container for this autograder job
# We mount our temporary directory to "/host" inside the Docker container, so we can
# communicate with it.
with get_working_directory() as wd, \
DockerClient().start(docker_image, volumes={wd: "/host"}) as container:
# These helper functions download and extract code from GitHub.
try:
download_repository(source, commit, join(wd, "hw1.tar.gz"))
extract_repository(container, join("/host", "hw1.tar.gz"), "/home/vagrant/ag",
user="vagrant")
except TimeoutError:
raise JobFailedError("I was downloading and extracting your code from GitHub, but I "
"took too long to finish and timed out. Try again?")
# For testing purposes, we can use solution code instead of GitHub code to test our
# autograder. You're free to leave in commented code in order to support this.
# container.bash("mkdir -p /home/vagrant/ag/hw1", user="vagrant")
# copytree(join(RESOURCES_DIR, "hw-exec", "solutions"), join(wd, "solutions"))
# container.bash("cp -R /host/solutions/. /home/vagrant/ag/hw1/", user="vagrant")
# These functions will raise JobFailedError if they find problems with the student code
ensure_no_binaries(container, "/home/vagrant/ag")
ensure_files_exist(container, "/home/vagrant/ag", ["./hw1/Makefile", "./hw1/main.c",
"./hw1/map.c", "./hw1/wc.c"])
# Our autograder consists of 2 Python scripts. You are free to use whatever language you
# want in your autograders, as long as you make sure your Docker container can run them.
copy(join(RESOURCES_DIR, "hw-exec", "check.py"), join(wd, "check.py"))
copy(join(RESOURCES_DIR, "common.py"), join(wd, "common.py"))
# We run our autograder, which produces 2 outputs: a build log and a score.
try:
container.bash("""cd /host
python2.7 check.py /home/vagrant/ag/hw0 &>build_log 162>score
""", user="vagrant", timeout=60)
except TimeoutError:
raise JobFailedError("I was grading your code, but the autograder took too long to " +
"finish and timed out. Try again?")
# This function will safely retrieve the build log and the score value, without throwing
# unexpected exceptions.
return safe_get_results(join(wd, "build_log"), join(wd, "score"))
All of the CS162 autograder scripts look something like this.
- Assignments and autograder jobs are separate entities. Assignments are defined by the 'assignments' section inside
config.yaml
. Autograder jobs are registered with the@register_job
decorator. - However, assignments and autograder jobs usually have the same name. An autograder job named
hw0
will assign a score to the assignment namedhw0
. - Not all assignments need an autograder job. If an assignment is marked as
manual_grading: True
, then ob2 does not expect to find an autograder job for it.