camisole’s documentation

camisole is a secure online judge for code compilation and execution. You give some untrusted source code and a test suite, and camisole compiles the code and runs it against the test suite.

It uses isolate as a backend to safely compile and execute source codes using linux kernel features such as namespaces, cgroups, chroot and resources limits.

camisole is aimed at:

  • Computer science teachers who want to grade their students or provide them with an online tool to check their own code.
  • Programming contests who want to grade the submissions of the contestants with an online judge.
  • Programming websites who want to have an interactive demo where people can run arbitrary code.
  • Online compiler/interpreter websites.

camisole handles all major languages, and can be easily extended to support more. The current supported languages are:

Ada, C, Brainfuck, C#, C++, F#, Haskell, Java, Javascript, Lua, OCaml, Pascal, Perl, PHP, Python, Ruby, Rust, Scheme, VisualBasic.

Communication with the camisole engine relies on a simple HTTP/JSON REST API.

You can contribute to the project development on GitHub by reporting issues, making suggestions or opening pull requests.

Quickstart

Once the camisole server is running, you can query it with an HTTP client like curl:

$ curl camisole/run -d '{"lang": "python", "source": "print(42)"}'

Result:

{
    "success": true,
    "tests": [
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2408,
                "csw-forced": 9,
                "csw-voluntary": 2,
                "exitcode": 0,
                "exitsig": null,
                "killed": false,
                "max-rss": 6628,
                "message": null,
                "status": "OK",
                "time": 0.009,
                "time-wall": 0.028
            },
            "name": "test000",
            "stderr": "",
            "stdout": "42\n"
        }
    ]
}

You can easily limit, on a global or per-test basis, the maximum execution time (user and wall), the available memory, the number of processes…

Those options are available both for the compilation and the tests.

Input:

{
    "lang": "ocaml",
    "source": "print_string \"Hello, world!\\\\n\"",
    "compile": {
        "wall-time": 10
    },
    "execute": {
        "time": 2,
        "wall-time": 5,
        "processes": 1,
        "mem": 100000
    }
}

Output:

{
    "compile": {
        "exitcode": 0,
        "meta": {
            "cg-mem": 13752,
            "csw-forced": 17,
            "csw-voluntary": 11,
            "exitcode": 0,
            "exitsig": null,
            "killed": false,
            "max-rss": 13360,
            "message": null,
            "status": "OK",
            "time": 0.033,
            "time-wall": 0.069
        },
        "stderr": "",
        "stdout": ""
    },
    "success": true,
    "tests": [
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2424,
                "csw-forced": 4,
                "csw-voluntary": 3,
                "exitcode": 0,
                "exitsig": null,
                "killed": false,
                "max-rss": 3572,
                "message": null,
                "status": "OK",
                "time": 0.0,
                "time-wall": 0.012
            },
            "name": "test000",
            "stderr": "",
            "stdout": "Hello, world!\\n"
        }
    ]
}

You can give a full testsuite with different inputs and constraints, and you will get a separate result for each test:

{
    "lang": "python",
    "source": "print(int(input()) * 2)",
    "tests": [{"name": "test_h2g2", "stdin": "42", "mem": 200000},
              {"name": "test_notfound", "stdin": "404"},
              {"name": "test_leet", "stdin": "1337"},
              {"name": "test_666", "stdin": "27972", "time": 1}]
}

Output:

{
    "success": true,
    "tests": [
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2284,
                "csw-forced": 7,
                "csw-voluntary": 2,
                "exitcode": 0,
                "exitsig": null,
                "killed": false,
                "max-rss": 6948,
                "message": null,
                "status": "OK",
                "time": 0.013,
                "time-wall": 0.036
            },
            "name": "test_h2g2",
            "stderr": "",
            "stdout": "84\n"
        },
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2280,
                "csw-forced": 6,
                "csw-voluntary": 3,
                "exitcode": 0,
                "exitsig": null,
                "killed": false,
                "max-rss": 6828,
                "message": null,
                "status": "OK",
                "time": 0.013,
                "time-wall": 0.032
            },
            "name": "test_notfound",
            "stderr": "",
            "stdout": "808\n"
        },
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2280,
                "csw-forced": 8,
                "csw-voluntary": 2,
                "exitcode": 0,
                "exitsig": null,
                "killed": false,
                "max-rss": 6828,
                "message": null,
                "status": "OK",
                "time": 0.009,
                "time-wall": 0.032
            },
            "name": "test_leet",
            "stderr": "",
            "stdout": "2674\n"
        },
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2280,
                "csw-forced": 8,
                "csw-voluntary": 2,
                "exitcode": 0,
                "exitsig": null,
                "killed": false,
                "max-rss": 6724,
                "message": null,
                "status": "OK",
                "time": 0.009,
                "time-wall": 0.028
            },
            "name": "test_666",
            "stderr": "",
            "stdout": "55944\n"
        }
    ]
}