Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added initial C grader demo
  • Loading branch information
ShawnHymel committed Aug 4, 2022
commit a73efbb9588c03edf33eb5d71031063929b588a2
15 changes: 15 additions & 0 deletions custom-graders/DemoCGrader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Demo C Custom Grader for Coursera

## Instructions

You have two options of testing the grader: with and without the [coursera_autograder](https://github.com/coursera/coursera_autograder) tool.

### Without the coursera_autograder tool

1. Build the Docker image:

```
docker build -t cgrader autograder/
```

2.
28 changes: 28 additions & 0 deletions custom-graders/DemoCGrader/autograder/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Fetch ubuntu image
FROM ubuntu:20.04

# Install Python on your ubuntu image.
RUN \
apt-get update && \
apt-get install -y python3 && \
apt-get install -y build-essential

# Make directories for the submissions and grader/test programs
RUN mkdir -p /grader/tests
RUN mkdir -p /shared/submission

# Copy the grader script into the Docker image
COPY grader.py /grader/grader.py

# Copy the C source code for the tests into the Docker image
COPY tests/* /grader/tests/

# Set grader directory to have read/write/execute permissions so we can copy in
# submissions, compile them, and run them
RUN chmod a+rwx -R /grader/

# Setup the command that will be invoked when your docker image is run.
#ENTRYPOINT ["grader/grader.py"]

# For debugging (interactive terminal): comment out above and use empty entrypoint
ENTRYPOINT []
149 changes: 149 additions & 0 deletions custom-graders/DemoCGrader/autograder/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/python3

# Demo autograder for C files submitted to Coursera. ENTRYPOINT for Dockerfile.

# Dependencies
import sys
import os
import stat
import shutil
import subprocess
import json

# Settings
SUBMISSION_SRC = "/shared/submission/" # Standard submission directory across all courses
COMPILED_APP = "testapp" # Name of the compiled program (must match in Makefile)
COMPILER_TIMEOUT = 2.0 # How long to wait for the compiler to finish (seconds)
RUN_TIMEOUT = 2.0 # How long to wait for the test program to run (seconds)

# Define the test cases and each associated project directory and file
TEST_CASES = {
"power": {
"partId": "4b371f50",
"project_dir": "/grader/tests/power",
"submission_file": "power.c"
},
"get-bit": {
"partId": "321da787",
"project_dir": "/grader/tests/get-bit",
"submission_file": "get-bit.c"
},
}

# Helper function to send score and feedback to Coursera
def send_feedback(score, msg):

# Combine score and feedback
post = {'fractionalScore': score, 'feedback': msg}

# Optional: this goes to container log and is best practice for debugging purpose
print(json.dumps(post))

# This is required for actual feedback to be surfaced
with open("/shared/feedback.json", "w") as outfile:
json.dump(post, outfile)

# Main script that runs the given test
def main(partId):

# Find the test that we are performing based on the partId
test = None
for key in TEST_CASES:
if partId == TEST_CASES[key]["partId"]:
print("Running test:", key)
test = TEST_CASES[key]
break

# Return with an error if we do not have a partId that matches to a test
if test == None:
msg = "Cannot find matching partId. Please double check your partId."
print(msg)
send_feedback(0.0, msg)
return

# Check to make sure that the student submitted a .c file
for file in os.listdir(SUBMISSION_SRC):
if file.endswith(".c"):
submitted_file = file
else:
send_feedback(0.0, "Your file must end with a .c extension.")
return
submitted_file_path = os.path.join(SUBMISSION_SRC, submitted_file)

# Copy the submitted file to the project folder (has executable permissions)
# and rename to the required filename.
uut_path = os.path.join(test["project_dir"], test["submission_file"])
shutil.copyfile(submitted_file_path, uut_path)

# Compile the program
print("Building...")
try:
ret = subprocess.run( ["make", "-C", test["project_dir"]],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=COMPILER_TIMEOUT)
except Exception as e:
msg = "ERROR: Compilation failed. " + str(e)
print(msg)
send_feedback(0.0, msg)
return

# Check to see if the program compiled successfully
if ret.returncode != 0:
msg = "Compilation failed. Try running on your computer first " \
"before submitting. Compiler error:\r\n" + \
ret.stderr.decode('utf-8')
print(msg)
send_feedback(0.0, msg)
return

# Run the compiled program
print("Running...")
app_path = os.path.join(test["project_dir"], COMPILED_APP)
try:
ret = subprocess.run( [app_path],
stdout=subprocess.PIPE,
timeout=RUN_TIMEOUT)
except Exception as e:
msg = "ERROR: Runtime failed. " + str(e)
print(msg)
send_feedback(0.0, msg)
return

# Parse the output of the program (JSON)
try:
ret_json = json.loads(ret.stdout.decode('utf-8'))
except Exception as e:
msg = "ERROR: Could not parse JSON output. " + str(e)
print(msg)
send_feedback(0.0, msg)
return

# Get the individual scores from the test cases
scores = ret_json["scores"]

# Average the scores to produce a final score (between 0.0 and 1.0)
fractional_score = sum(scores) / len(scores)

# Provide some feedback based on the total score. Feel free to provide
# individual feedback based on the partId and which tests passed/failed.
if fractional_score > 0.8:
msg = "Great job!"
elif fractional_score > 0.0:
msg = "Close, but try again"
else:
msg = "Not quite. Try again."

# Provide score and feedback
send_feedback(fractional_score, msg)

# Script entry point
if __name__ == '__main__':
try:
partid = os.environ['partId']
except Exception as e:
msg = "Please provide the partId."
print(msg)
send_feedback(0.0, msg)
else:
main(partid)
27 changes: 27 additions & 0 deletions custom-graders/DemoCGrader/autograder/tests/get-bit/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Tool macro
CC = gcc

# Settings
NAME = app

# Search path for header files (current directory)
CFLAGS = -I.

# Other compiler flags
CFLAGS += -Wall

# Names for our output object files
OBJS = main.o get-bit.o

# Default rule to make our application
.PHONY: all
all: testapp

# Compile library source code into object files
testapp:${OBJS}
${CC} ${CFLAGS} -o $@ ${OBJS}

# Remove compiled objects
.PHONY: clean
clean:
rm -r $(OBJS)
6 changes: 6 additions & 0 deletions custom-graders/DemoCGrader/autograder/tests/get-bit/get-bit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef GET_BIT_H
#define GET_BIT_H

int getBit(unsigned char byte, unsigned char bit);

#endif // GET_BIT_H
51 changes: 51 additions & 0 deletions custom-graders/DemoCGrader/autograder/tests/get-bit/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <stdio.h>

// Include the user's submission
#include "get-bit.h"

// Define the number of tests to run
#define NUM_TEST_CASES 5

int main() {

int ans;

// Define the test cases
const unsigned char inputs[NUM_TEST_CASES][2] = { {0, 5},
{255, 0},
{0xAE, 4},
{0xAE, 5},
{15, 3}};
const unsigned char outputs[NUM_TEST_CASES] = {0, 1, 0, 1, 1};

// Store which answers were correct
float scores[NUM_TEST_CASES];

// Run the tests, giving a 100% score to each correct answer
for (int i = 0; i < NUM_TEST_CASES; i++)
{
ans = getBit(inputs[i][0], inputs[i][1]);
if (ans == outputs[i])
{
scores[i] = 1.0;
}
else
{
scores[i] = 0.0;
}
}

// Print out the test results in JSON format
printf("{\"scores\":[");
for (int i = 0; i < NUM_TEST_CASES; i++)
{
printf("%f", scores[i]);
if (i < NUM_TEST_CASES - 1)
{
printf(",");
}
}
printf("]}\r\n");

return 0;
}
27 changes: 27 additions & 0 deletions custom-graders/DemoCGrader/autograder/tests/power/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Tool macro
CC = gcc

# Settings
NAME = app

# Search path for header files (current directory)
CFLAGS = -I.

# Other compiler flags
CFLAGS += -Wall

# Names for our output object files
OBJS = main.o power.o

# Default rule to make our application
.PHONY: all
all: testapp

# Compile library source code into object files
testapp:${OBJS}
${CC} ${CFLAGS} -o $@ ${OBJS}

# Remove compiled objects
.PHONY: clean
clean:
rm -r $(OBJS)
51 changes: 51 additions & 0 deletions custom-graders/DemoCGrader/autograder/tests/power/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <stdio.h>

// Include the user's submission
#include "power.h"

// Define the number of tests to run
#define NUM_TEST_CASES 5

int main() {

int ans;

// Define the test cases
const int inputs[NUM_TEST_CASES][2] = { {0, 0},
{10, 0},
{1, 2},
{3, 3},
{3, 10}};
const int outputs[NUM_TEST_CASES] = {1, 1, 1, 27, 59049};

// Store which answers were correct
float scores[NUM_TEST_CASES];

// Run the tests, giving a 100% score to each correct answer
for (int i = 0; i < NUM_TEST_CASES; i++)
{
ans = power(inputs[i][0], inputs[i][1]);
if (ans == outputs[i])
{
scores[i] = 1.0;
}
else
{
scores[i] = 0.0;
}
}

// Print out the test results in JSON format
printf("{\"scores\":[");
for (int i = 0; i < NUM_TEST_CASES; i++)
{
printf("%f", scores[i]);
if (i < NUM_TEST_CASES - 1)
{
printf(",");
}
}
printf("]}\r\n");

return 0;
}
6 changes: 6 additions & 0 deletions custom-graders/DemoCGrader/autograder/tests/power/power.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef POWER_H
#define POWER_H

int power(int a, int b);

#endif // POWER_H
14 changes: 14 additions & 0 deletions custom-graders/DemoCGrader/sample-submissions/get-bit/get-bit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include "get-bit.h"

// Find the value (0 or 1) of the n-th bit in the provided byte
int getBit(unsigned char byte, unsigned char bit)
{
int bit_val;

// YOUR CODE HERE
// ---
bit_val = (byte >> bit) & 1;
// ---

return bit_val;
}
Loading