Skip to content

Instantly share code, notes, and snippets.

@jkeesh
Created September 18, 2016 20:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jkeesh/bc057feaa7af5b36048720f261ef653c to your computer and use it in GitHub Desktop.
Save jkeesh/bc057feaa7af5b36048720f261ef653c to your computer and use it in GitHub Desktop.
Snackpack Main Scanner grader
import java.io.*;
import java.lang.*;
import java.util.*;
import org.json.*;
public class Autograder
{
/**
* Necessary private fields for the Autograder class.
*/
private Interceptor interceptor;
private HashMap<String, ArrayList<String>> outputs;
private PrintStream origOut;
/**
* Autograders contain the main logic for autograding and are used by
* Grader.java
*/
public Autograder()
{
tests = new ArrayList<TestCase>();
outputs = new HashMap<String, ArrayList<String>>();
origOut = System.out;
interceptor = new Interceptor(origOut, this);
System.setOut(interceptor);// just add the interceptor
}
/**
* This adds a TestCase. Instead of doing the comparisons itself, it takes
* a boolean which is the result of some custom test. Also allows for any
* custom message.
* @param <T> This allows for any type of output
* @param testName Name of the test being run
* @param status Boolean result of anything
* @param studentOutput Student output that was tested
* @param solutionOutput Solution output that was tested
* @param message Message to show
*/
public <T> void addTest(String testName, boolean status,
T studentOutput, T solutionOutput, String message)
{
tests.add(new TestCase(testName, status, message, studentOutput, solutionOutput));
}
/**
* This will do a standard comparison between studentOutput and
* solutionOutput. And depending on the result will create a TestCase with
* the result and either the messagePass or messageFail
* @param <T> This allows for any type of output
* @param testName Name of the test being run
* @param studentOutput Student output to test
* @param solutionOutput Solution output to test
* @param messagePass Message to show if assertEqual passes
* @param messageFail Message to show if assertEqual fails
*/
public <T> void assertEqual(String testName, T studentOutput,
T solutionOutput, String messagePass, String messageFail)
{
String studentOutputString;
String solutionOutputString;
if (studentOutput == null)
{
studentOutputString = "";
} else
{
studentOutputString = studentOutput.toString();
}
if (solutionOutput == null)
{
solutionOutputString = "";
} else
{
solutionOutputString = solutionOutput.toString();
}
boolean success = solutionOutput.equals(studentOutput);
String message = success ? messagePass : messageFail;
addTest(testName, success, studentOutputString, solutionOutputString,
message);
}
/**
* Allows you to get the original output stream.
* @return System.out
*/
public PrintStream getOriginal()
{
return origOut;
}
/**
* Adds an output from the Interceptor to outputs
* @param className Name of the class the output came from
* @param s Output string
*/
public void addOutput(String className, String s)
{
ArrayList<String> arr = outputs.get(className);
if(arr == null)
{
arr = new ArrayList<String>();
}
arr.add(s);
outputs.put(className, arr);
}
/**
* Get a string of all the outputs for a given class name.
* @param className Name of the class to retriev output from.
* @return Returns output for the given className as a concated string
*/
public String getOutput(String className)
{
ArrayList<String> output = outputs.get(className);
if (output == null)
{
return "You forgot to print something.";
}
String outputString = "";
for (String s : output)
{
outputString += s;
}
return outputString;
}
/**
* Get an ArrayList of all outputs for a given class name.
* @param className Name of the class to retriev output from.
* @return Returns output for the given className as an ArrayList.
*/
public ArrayList<String> getOutputArrayList(String className)
{
ArrayList<String> output = outputs.get(className);
return output;
}
/**
* This resets the current outputs. This should be used after
* create a new test so that tests don't also test previous
* outputs.
*/
public void clearOutput()
{
outputs = new HashMap<String, ArrayList<String>>();
}
/**
* Similar to the method above, however, instead of clearing out all output
* it will only clear output for a given classname. However, it only does
* this if output exists for the class. Otherwise, it does nothing.
* @param className Class to clear output for
*/
public void clearOutput(String className)
{
ArrayList<String> output = outputs.get(className);
if (output != null)
{
ArrayList<String> arr = new ArrayList<String>();
outputs.put(className, arr);
}
}
/**
* Used for printing the output of all test results.
* @return string of JSON-like results
*/
public String toString() {
JSONObject jsonResults = new JSONObject();
JSONArray jsonTests = new JSONArray();
jsonResults.put("tests", jsonTests);
for (int i = 0; i < tests.size(); i++) {
TestCase test = tests.get(i);
JSONObject jsonTest = new JSONObject();
jsonTest.put("success", test.success);
jsonTest.put("test", test.test);
jsonTest.put("message", test.message);
jsonTest.put("studentOutput", test.studentOutput);
jsonTest.put("solutionOutput", test.solutionOutput);
jsonTests.put(jsonTest);
}
return "__unittests__" + jsonResults.toString();
}
/**
* Contains all created test cases.
*/
private ArrayList<TestCase> tests;
/**
* Contains the results a of single test case.
*/
class TestCase
{
public TestCase(String test, boolean success, String message,
Object studentOutput, Object solutionOutput)
{
this.success = success;
this.message = message;
this.studentOutput = studentOutput;
this.solutionOutput = solutionOutput;
this.test = test;
}
private String message;
private boolean success;
private Object studentOutput;
private Object solutionOutput;
private String test;
}
}
import java.util.*;
public class Grader
{
public static void runInputTest(Autograder grader, String[] inputs, String message)
{
// Create a student program
String[] input = {};
Scanner.setInputs(inputs);
SnackPack.main(input);
// Create a solution program
String[] input2 = {};
Scanner.setInputs(inputs);
SnackPackSolution.main(input);
// Get the string output from the student and reference solution
String studentOutput = grader.getOutput("SnackPack");
String solutionOutput = grader.getOutput("SnackPackSolution");
// Add a test to the grader
grader.assertEqual(message, studentOutput, solutionOutput, "Great!", "Not quite.");
grader.clearOutput();
}
public static void main(String [] args)
{
Autograder grader = new Autograder();
runInputTest(grader, new String[]{"0","0","0","0","0"}, "Input: 0,0,0,0,0");
runInputTest(grader, new String[]{"0","1","0","0","0"}, "Input: 0,1,0,0,0");
runInputTest(grader, new String[]{"3","2","1","1","1"}, "Input: 3,2,1,1,1");
runInputTest(grader, new String[]{"0","1","4","8","9"}, "Input: 0,1,4,8,9");
runInputTest(grader, new String[]{"0","1","1","2","1"}, "Input: 0,1,1,2,1");
System.out.println(grader);
}
}
import java.io.*;
import java.lang.*;
/**
* This class intercepts output destined for System.out so it can be tracked.
*/
public class Interceptor extends PrintStream
{
private Autograder prog;
private static final int CLASS_STACK_POS = 3;
/**
* Setups up an interceptor for System.out
* @param out The output stream being used (should be System.out)
* @param prog The autograder that is using this Incerceptor
*/
public Interceptor(OutputStream out, Autograder prog)
{
super(out, true);
this.prog = prog;
}
/**
* We need to prevent double printing so see if the stack trace already
* has a call to addOutput.
* @param arr
* @return
*/
private boolean stackTraceAddOutputAppearsTwice(StackTraceElement[] arr)
{
int count = 0;
for(int i = 0; i < arr.length; i++){
if(arr[i].getMethodName().equals("addOutput")){
count++;
}
}
return count >= 2;
}
/**
* This handles the general logic for printing to System.out. Based on the
* `newLine` boolean, we decide whether we print or add a line break. After,
* we determine which class it came from, based on the stacktrace. If the
* class is `ConsoleProgram`, we look one level up on the stack trace.
* @param output The output to print to System.out and store
* @param newLine Boolean for whether this is using print or println
*/
private void addOutput(String output, boolean newLine)
{
if (newLine)
{
super.println(output);
output += "\n";
} else {
super.print(output);
}
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
String className = stackTraceElements[CLASS_STACK_POS].getClassName();
int stackPos = CLASS_STACK_POS;
while (true)
{
if(stackPos >= stackTraceElements.length -1){
break;
}
boolean passThrough = false;
// Pass through PrintStream
if(className.indexOf("PrintStream") != -1){
stackPos++;
passThrough = true;
}
// Pass through Interceptor
if(className.indexOf("Interceptor") != -1){
stackPos++;
passThrough = true;
}
// Pass through Scanner
if(className.indexOf("Scanner") != -1){
stackPos++;
passThrough = true;
}
if(!passThrough){
break;
}
className = stackTraceElements[stackPos].getClassName();
}
// Don't double print.
if(stackTraceAddOutputAppearsTwice(stackTraceElements)){
return;
}
this.prog.addOutput(className, output);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param s String to be printed
*/
@Override
public void print(String s)
{
this.addOutput(s, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param x Boolean to be printed
*/
@Override
public void print(boolean x)
{
String output = Boolean.toString(x);
this.addOutput(output, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param x Character to be printed
*/
@Override
public void print(char x)
{
String output = Character.toString(x);
this.addOutput(output, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param x Character array to be printed
*/
@Override
public void print(char[] x)
{
String output = new String(x);
this.addOutput(output, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param x Integer to be printed
*/
@Override
public void print(int x)
{
String output = Integer.toString(x);
this.addOutput(output, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param x Long to be printed
*/
@Override
public void print(long x)
{
String output = Long.toString(x);
this.addOutput(output, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param x Float to be printed
*/
@Override
public void print(float x)
{
String output = Float.toString(x);
this.addOutput(output, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param x Double to be printed
*/
@Override
public void print(double x)
{
String output = Double.toString(x);
this.addOutput(output, false);
}
/**
* This overrides System.out.print. It prints out to System.out like normal.
* Afterwards, it stores the output in the Autograder along with the class
* name that is trying to print based on the stack trace (up one for
* ConsoleProgram).
* @param o Object to be printed
*/
@Override
public void print(Object o)
{
String output = o.toString();
this.addOutput(output, false);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param s String to be printed
*/
@Override
public void println(String s)
{
this.addOutput(s, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
*/
@Override
public void println()
{
this.addOutput("", true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param x Boolean to be printed
*/
@Override
public void println(boolean x)
{
String output = Boolean.toString(x);
this.addOutput(output, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param x Character to be printed
*/
@Override
public void println(char x)
{
String output = Character.toString(x);
this.addOutput(output, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param x Character array to be printed
*/
@Override
public void println(char[] x)
{
String output = new String(x);
this.addOutput(output, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param x Integer to be printed
*/
@Override
public void println(int x)
{
String output = Integer.toString(x);
this.addOutput(output, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param x Long to be printed
*/
@Override
public void println(long x)
{
String output = Long.toString(x);
this.addOutput(output, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param x Float to be printed
*/
@Override
public void println(float x)
{
String output = Float.toString(x);
this.addOutput(output, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param x Double to be printed
*/
@Override
public void println(double x)
{
String output = Double.toString(x);
this.addOutput(output, true);
}
/**
* This overrides System.out.println. It prints out to System.out like
* normal. Afterwards, it stores the output in the Autograder along with
* the class name that is trying to print based on the stack trace (up one
* for ConsoleProgram).
* @param o Object to be printed
*/
@Override
public void println(Object o)
{
String output = o.toString();
this.addOutput(output, true);
}
}
import java.io.*;
public class Scanner
{
private static int curIndex;
private static String[] curInputs = {};
public static void setInputs(String[] inputs)
{
curInputs = inputs;
curIndex = 0;
}
public Scanner(InputStream source)
{
curIndex = 0;
}
public int nextInt()
{
String stringVal = curInputs[curIndex];
curIndex++;
System.out.println(stringVal);
return Integer.parseInt(stringVal);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment