Giter Site home page Giter Site logo

javagrading's Introduction

JavaGrading: grading java made simple

Simply grade student assignments made in Java or anything that runs on the JVM (Scala/Kotlin/Jython/...).

@Test
@Grade(value = 5, cpuTimeout=1000)
@GradeFeedback("Are you sure your code is in O(n) ?", onTimeout=true)
@GradeFeedback("Sorry, something is wrong with your algorithm", onFail=true)
void yourtest() {
    //a test for the student's code
}

Features:

  • CPU timeouts on the code
  • Jails student code
    • No I/O, including stdout/err
    • No thread creating by the student, ...
    • Most things involving syscalls are forbidden
    • specific permissions can be added on specifics tests if needed
  • Text/RST reporting
  • Custom feedback, both from outside the test (onFail, onTimeout, ...) but also from inside (see below).

We use this library at UCLouvain in the following courses:

  • Data Structures and Algorithms (LSINF1121)
  • Computer Science 2 (LEPL1402)
  • Constraint Programming (LING2365)

This library is best used with an autograder, such as INGInious.

Example

Add the @Grade annotation on your JUnit test like this:

@RunWith(GradingRunner.class)
public class MyTests {
    @Test
    @Grade(value = 5)
    void mytest1() {
        //this works
        something();
    }
    
    @Test
    @Grade(value = 3)
    @GradeFeedback("You forgot to consider this particular case [...]", onFail=true)
    void mytest2() {
        //this doesn't
        somethingelse();
    }
}

Note that we demonstrate here the usage of the @GradeFeedback annotation, that allows to give feedback to the students.

You can then run the tests using this small boilerplate:

public class RunTests {
    public static void main(String args[]) {
        JUnitCore runner = new JUnitCore();
        runner.addListener(new GradingListener(false));
        runner.run(MyTests.class);
    }
}

This will print the following on the standard output:

--- GRADE ---
- class MyTests 8/8
	mytest1(StdTests) SUCCESS 5/5
	ignored(StdTests) FAILED 0/3
	    You forgot to consider this particular case [...]
TOTAL 5/8
TOTAL WITHOUT IGNORED 5/8
--- END GRADE ---

Documentation & installation

Everything needed is located inside the files:

To add it as a dependency of your project, you can add this to your pom.xml in maven:

<dependency>
  <groupId>com.github.guillaumederval</groupId>
  <artifactId>JavaGrading</artifactId>
  <version>0.5.1</version>
</dependency>

If you are not using maven, search.maven probably has the line of code you need.

Advanced examples

Cpu timeout

It is (strongly) advised when using an autograder (did I already say that INGInious is a very nice one?) to put a maximum time to run a test:

@Test
@Grade(value = 5, cpuTimeout=1000)
void yourtest() {
    //a test for the student's code
}

If the test runs for more than 1000 milliseconds, it will receive a TIMEOUT error and receive a grade of 0/5.

Note that if you allow the student (via the addition of some permission) to create new threads, the time taken in the new threads won't be taken into account!

It is also possible to add a wall-clock-time timeout, via JUnit:

@Test(timeout=3000) //kills the test after 3000ms in real, wall-clock time
@Grade(value = 5)
void yourtest() {
    //a test for the student's code
}

By default, setting a CPU timeout also sets a wall-clock timeout at three times the cpu timeout. If you want to override that, set a different value to @Test(timeout=XXX).

Ignored tests

Ignored tests are supported:

@Test
@Grade(value = 5)
void yourtest() {
    Assume.assumeFalse(true); //JUnit function to indicate that the test should be ignored
}

Custom feedback (outside the test)

Use the @GradeFeedback annotation to give feedback about specific type of errors

@Test
@Grade(value = 5)
@GradeFeedback("Congrats!", onSuccess=True)
@GradeFeedback("Something is wrong", onFail=True)
@GradeFeedback("Too slow!", onTimeout=True)
@GradeFeedback("We chose to ignore this test", onIgnore=True)
void yourtest() {
    //
}

Custom grade and feedback (inside the test)

Throw the exception CustomGradingResult to give a custom grading from inside the text.

In order to avoid that students throw this exception, this feature is disabled by default. You must activate it by setting @Grade(custom=true) and protect yourself your code against evil students that may throw the exception themselves.

@Test
@Grade(value = 2, cpuTimeout=1000, custom=true)
void yourtest() {
    try {
        //code of the student here
    }
    catch (CustomGradingResult e) {
        throw new CustomGradingResult(TestStatus.FAILED, 0, "Well tried, but we are protected against that");
    }
    
    if(something) {
        throw new CustomGradingResult(TestStatus.FAILED, 1, "Sadly, you are not *completely* right.");
    }
    else if(somethingelse) {
        throw new CustomGradingResult(TestStatus.FAILED, 1.5, "Still not there!");
    }
    else if(somethingentirelydifferent) {
        throw new CustomGradingResult(TestStatus.TIMEOUT, 1.75, "A bit too slow, I'm afraid");
    }
    else if(otherthing) {
        throw new CustomGradingResult(TestStatus.SUCCESS, 2.5, "Good! Take these 0.5 bonus points with you");
    }
    
    //by default, if you throw nothing, it's SUCCESS with the maximum grade
}

RST output

When using an autograder (I may already have told you that INGInious is very nice) you might want to output something nice (i.e. not text) for the students. JavaGrading can output a nice RestructuredText table:

public class RunTests {
    public static void main(String args[]) {
        JUnitCore runner = new JUnitCore();
        runner.addListener(new GradingListener(true)); //notice the *true* here 
        runner.run(MyTests.class);
    }
}

Screenshot of the RST output

@GradeClass

The @GradeClass annotation allows setting a default grade for all test (avoiding to put @Grade everywhere) and also to give an overall max grade for the whole class. See next example for... an example.

Parameterized tests

JUnit's parameterized tests are also supported:

import com.github.guillaumederval.javagrading.Grade;
import com.github.guillaumederval.javagrading.GradeClass;
import com.github.guillaumederval.javagrading.GradingRunnerWithParametersFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.Collection;

@RunWith(Parameterized.class)
@Parameterized.UseParametersRunnerFactory(GradingRunnerWithParametersFactory.class)
@GradeClass(totalValue = 100)
public class ParametersTests {
    @Parameterized.Parameters
    public static Collection numbers() {
        return Arrays.asList(new Object[][] {
                { 1 },
                { 2 },
                { 3 },
                { 4 },
                { 5 }
        });
    }

    int param;
    public ParametersTests(int param) {
        this.param = param;
    }

    @Test
    @Grade(value = 1)
    public void mytest() throws Exception {
        if(param % 2 != 0)
            throw new Exception("not even");
    }
}

Output:

- class ParametersTests 40/100
	mytest[0](ParametersTests) FAILED 0/20
	mytest[1](ParametersTests) SUCCESS 20/20
	mytest[2](ParametersTests) FAILED 0/20
	mytest[3](ParametersTests) SUCCESS 20/20
	mytest[4](ParametersTests) FAILED 0/20

Multiple test classes

If you have multiple test classes, simply update the main function like this:

public class RunTests {
    public static void main(String args[]) {
        JUnitCore runner = new JUnitCore();
        runner.addListener(new GradingListener(false));
        runner.run(MyTests.class, MyTests2.class, MyOtherTests.class /*, ... */);
    }
}

Custom permissions

JavaGrading installs a custom SecurityManager that forbids the tested code to do anything that it should not do.

It effectively forbids a lot of things. JavaGrading adds an additionnal permission to this list, namely PrintPermission, that allows the test code to print things on stdout/stderr.

You can re-enable some permissions for a specific test if needed, but it does requires some boilerplate:

@RunWith(GradingRunner.class)
public class PermissionTest {
    @Test
    @Grade(value = 5.0, customPermissions = MyPerms1.class)
    public void allowPrint() {
        System.out.println("I was allowed to print!");
    }

    @Test
    @Grade(value = 5.0, customPermissions = MyPerms2.class)
    public void allowThread() {
        Thread t = new Thread() {
            @Override
            public void run() {
                // nothing
            }
        };
        t.start();
    }

    /*
       NOTE: the class MUST be public AND static (if it is an inner class) for this to work.
       => it must have an accessible constructor without args.
     */
    public static class MyPerms1 implements Grade.PermissionCollectionFactory {
        @Override
        public PermissionCollection get() {
            Permissions perms = new Permissions();
            perms.add(PrintPermission.instance);
            return perms;
        }
    }

    public static class MyPerms2 implements Grade.PermissionCollectionFactory {
        @Override
        public PermissionCollection get() {
            Permissions perms = new Permissions();
            perms.add(new RuntimePermission("modifyThreadGroup"));
            perms.add(new RuntimePermission(("modifyThread")));
            return perms;
        }
    }
}

javagrading's People

Contributors

dependabot[bot] avatar guillaumederval avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

javagrading's Issues

deprecated method called

WARNING: A terminally deprecated method in java.lang.System has been called
WARNING: System::setSecurityManager has been called by com.github.guillaumederval.javagrading.GradingRunnerUtils (file:/Users/pschaus/.m2/repository/com/github/guillaumederval/JavaGrading/0.5.1/JavaGrading-0.5.1.jar)
WARNING: Please consider reporting this to the maintainers of com.github.guillaumederval.javagrading.GradingRunnerUtils
WARNING: System::setSecurityManager will be removed in a future release

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.