Skip to content

Commit

Permalink
Add grader support for runes (#21)
Browse files Browse the repository at this point in the history
* Add gitignore

* Add support for JS files

- Needed for WebGL code brought in from cadet-frontend
- Changes build directory to avoid overwriting JS files

* Stop ignoring JS files

* Add lodash dependency

* Add dependencies

* Add graphics and matrix library

* Add rune autograder test cases

* Include rune library and fix run to take in Library

* Fix globals in test_runes

* Add build script and ensure build works

* Remove commented-out line

* Use import instead of require

* Add tests for 3D runes

* Fix broken build script

* Ignore grader.zip

* Add dummy visualisation functions

- No parameters causes a slang runtime error
- Not defining the functions will also cause a runtime error

* Use dynamic import for graphics library

The handler used in the AWS lambda is runAll. However, we have no idea
whether the gloval environment (modified by rune_library.js) is
preserved between lambda calls (or if it was there to begin with). This
change causes the rune library to be loaded everytime the handler is
called, ensuring the pre-condition of having the rune libraries. If
needed,  event.name can be used to load up particular libraries if there
are clashes.

Note that this behavior parallels how the libraries are loaded in the
frontend, having to be loaded into the global scope.

* Update README with rune functions
  • Loading branch information
thenaesh authored and remo5000 committed Aug 22, 2018
1 parent 51d38b9 commit cd1dd14
Show file tree
Hide file tree
Showing 12 changed files with 5,298 additions and 6 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
*.map
build/
grader.zip
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ The grader is a component of the `Cadet backend`_. The grader,
.. _Cadet backend: https://github.com/source-academy/cadet
.. _js-slang: https://github.com/source-academy/js-slang

========================
External Library Support
========================

Only support for the "runes" library is available. These include the functions:
- `__compile`
- `__are_pictures_equal`

Grader Program
==============

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "rm -rf node_modules && yarn install --production=true && tsc && zip -r --exclude=*terraform* ./grader.zip index.js node_modules/ && yarn install --production=false",
"build": "./scripts/build.sh",
"typecheck": "tsc --noEmit",
"test": "TIMEOUT=1000 jest",
"test-coveralls": "TIMEOUT=1000 jest --coverage --coverageReporters=text-lcov | coveralls"
"test": "TIMEOUT=1000 jest --env=node",
"test-coveralls": "TIMEOUT=1000 jest --env=node --coverage --coverageReporters=text-lcov | coveralls"
},
"dependencies": {
"@types/node": "^10.5.2",
"js-slang": "0.1.4",
"lodash": "4.17.10",
"typescript": "^2.9.2"
},
"devDependencies": {
Expand Down
22 changes: 22 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash -

if [ -d node_modules ]
then
rm -rf node_modules
fi

if [ -d build ]
then
rm -rf build
fi

if [ -f grader.zip ]
then
rm grader.zip
fi

yarn install --production=true
tsc
cd build && zip -r --exclude=*terraform* ../grader.zip index.js graphics/ && cd ..
zip -ur grader.zip node_modules/
yarn install --production=false
94 changes: 94 additions & 0 deletions src/__tests__/examples/runes2d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Grader, Student } from './types';

const validStudentCorrect =
`
const a = () => blue(quarter_turn_left(quarter_turn_left(quarter_turn_left(nova_bb))));
const b = () => stack(quarter_turn_right(nova_bb), quarter_turn_right(nova_bb));
const c = (p1, p2, p3, p4) => beside(stack(p1, p2), stack(p3, p4));
function d(rune, n) {
if (n === 1) {
return rune;
} else {
return beside(rune, stack(d(rune, n - 1), d(rune, n - 1)));
}
}
`

const validStudentWrong =
`
const a = () => quarter_turn_left(nova_bb);
const b = () => stack(quarter_turn_left(nova_bb), quarter_turn_right(nova_bb));
const c = (p1, p2, p3, p4) => beside(stack(p2, p1), stack(p4, p3));
const d = (rune, n) => rune;
`

const validStudentPartial =
`
const a = () => blue(quarter_turn_right(nova_bb));
const b = () => stack(quarter_turn_left(nova_bb), quarter_turn_left(nova_bb));
const c = (p1, p2, p3, p4) => stack(beside(p1, p3), beside(p2, p4));
const d = (rune, n) => (n === 1) ? rune : beside(rune, stack(d(rune, n - 1), d(rune, n - 1)));
`

export const student: Student = {
invalid: {
runtime: null,
syntax: null
},
valid: {
correct: validStudentCorrect,
wrong: validStudentWrong,
partial: validStudentPartial
}
}

const validGrader = [
`
function rune_testcase_a() {
const expected_result = quarter_turn_right(blue(nova_bb));
const actual_result = a();
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_a();
`,
`
function rune_testcase_b() {
const expected_result = stack(quarter_turn_right(nova_bb), quarter_turn_right(nova_bb));
const actual_result = b();
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_b();
`,
`
function rune_testcase_c() {
const expected_result = beside(stack(nova_bb, heart_bb), stack(rcross_bb, circle_bb));
const actual_result = c(nova_bb, heart_bb, rcross_bb, circle_bb);
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_c();
`,
`
function rune_testcase_d() {
function fractal(rune, n) {
if (n === 1) {
return rune;
} else {
const pic = fractal(rune, n - 1);
return beside(rune, stack(pic, pic));
}
}
const expected_result = fractal(nova_bb, 7);
const actual_result = d(nova_bb, 7);
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_d();
`,
]

export const grader: Grader = {
invalid: {
runtime: null,
syntax: null
},
valid: validGrader
}
95 changes: 95 additions & 0 deletions src/__tests__/examples/runes3d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Grader, Student } from './types';

const validStudentCorrect =
`
const a = () => overlay_frac(0.5, heart_bb, nova_bb);
const b = () => overlay_frac(0.25, quarter_turn_left(quarter_turn_left(quarter_turn_left(nova_bb))), scale(0.4, heart_bb));
const c = (p1, p2, p3, p4) => overlay(stack(beside(p1, p2), beside(blank_bb, blank_bb)), stack(beside(blank_bb, blank_bb), beside(p3, p4)));
function d(rune, n) {
if (n === 1) {
return rune;
} else {
const bottom = d(rune, n - 1);
return overlay_frac(1/n, scale(1/n, rune), bottom);
}
}
`

const validStudentWrong =
`
const a = () => overlay(nova_bb, heart_bb);
const b = () => overlay_frac(0.25, quarter_turn_left(quarter_turn_left(quarter_turn_left(nova_bb))), scale(0.3, heart_bb));
const c = (p1, p2, p3, p4) => overlay(stack(beside(p1, blank_bb), beside(blank_bb, p2)), stack(beside(blank_bb, p3), beside(p4, blank_bb)));
const d = (rune, n) => rune;
`

const validStudentPartial =
`
const a = () => overlay_frac(0.5, heart_bb, nova_bb);
const b = () => overlay_frac(0.3, quarter_turn_left(quarter_turn_left(quarter_turn_left(nova_bb))), scale(0.4, heart_bb));
const c = (p1, p2, p3, p4) => overlay(beside(stack(p1, blank_bb), stack(p2, blank_bb)), beside(stack(blank_bb, p3), stack(blank_bb, p4)));
const d = (rune, n) => (n === 1) ? rune : beside(rune, stack(d(rune, n - 1), d(rune, n - 1)));
`

export const student: Student = {
invalid: {
runtime: null,
syntax: null
},
valid: {
correct: validStudentCorrect,
wrong: validStudentWrong,
partial: validStudentPartial
}
}

const validGrader = [
`
function rune_testcase_a() {
const expected_result = overlay(heart_bb, nova_bb);
const actual_result = a();
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_a();
`,
`
function rune_testcase_b() {
const expected_result = overlay_frac(0.25, quarter_turn_right(nova_bb), scale(0.4, heart_bb));
const actual_result = b();
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_b();
`,
`
function rune_testcase_c() {
const expected_result = overlay(stack(beside(nova_bb, heart_bb), beside(blank_bb, blank_bb)), stack(beside(blank_bb, blank_bb), beside(rcross_bb, circle_bb)));
const actual_result = c(nova_bb, heart_bb, rcross_bb, circle_bb);
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_c();
`,
`
function rune_testcase_d() {
function tree(rune, n) {
if (n === 1) {
return rune;
} else {
const bottom = tree(rune, n - 1);
return overlay_frac(1/n, scale(1/n, rune), bottom);
}
}
const expected_result = tree(nova_bb, 7);
const actual_result = d(nova_bb, 7);
return __are_pictures_equal(actual_result, expected_result) ? 2 : 0;
}
rune_testcase_d();
`,
]

export const grader: Grader = {
invalid: {
runtime: null,
syntax: null
},
valid: validGrader
}
83 changes: 83 additions & 0 deletions src/__tests__/test_runes2d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { grader, student } from './examples/runes2d'
import { awsEventFactory } from './helpers'
import { runAll } from '../index'

const makeAwsEvent = awsEventFactory({
chapter: 1,
external: {
name: 'TWO_DIM_RUNES',
symbols: [
'red',
'pink',
'purple',
'indigo',
'blue',
'green',
'yellow',
'orange',
'brown',
'black',
'white',
'black_bb',
'blank_bb',
'rcross_bb',
'sail_bb',
'corner_bb',
'nova_bb',
'circle_bb',
'heart_bb',
'pentagram_bb',
'ribbon_bb',
'quarter_turn_left',
'quarter_turn_right',
'scale_independent',
'scale',
'translate',
'rotate',
'stack_frac',
'stack',
'stackn',
'beside_frac',
'beside',
'flip_vert',
'flip_horiz',
'make_cross',
'repeat_pattern',
'overlay_frac',
'overlay',
'__compile',
'__are_pictures_equal',
]
},
globals: []
})

test('rune grader OK, student OK, correct', async () => {
const results = await runAll(makeAwsEvent(grader.valid, student.valid.correct))
expect(results).toEqual([
{'grade': 2, 'resultType': 'pass'},
{'grade': 2, 'resultType': 'pass'},
{'grade': 2, 'resultType': 'pass'},
{'grade': 2, 'resultType': 'pass'},
])
})

test('rune grader OK, student OK, wrong', async () => {
const results = await runAll(makeAwsEvent(grader.valid, student.valid.wrong))
expect(results).toEqual([
{'grade': 0, 'resultType': 'pass'},
{'grade': 0, 'resultType': 'pass'},
{'grade': 0, 'resultType': 'pass'},
{'grade': 0, 'resultType': 'pass'},
])
})

test('rune grader OK, student OK, partial', async () => {
const results = await runAll(makeAwsEvent(grader.valid, student.valid.partial))
expect(results).toEqual([
{'grade': 2, 'resultType': 'pass'},
{'grade': 0, 'resultType': 'pass'},
{'grade': 2, 'resultType': 'pass'},
{'grade': 2, 'resultType': 'pass'},
])
})
Loading

0 comments on commit cd1dd14

Please sign in to comment.