-
Notifications
You must be signed in to change notification settings - Fork 35
/
execute.js
109 lines (83 loc) · 3.45 KB
/
execute.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
const spawn = require('child_process').spawn
const path = require('path')
const fs = require('fs')
const after = require('after')
const xtend = require('xtend')
function execute (exercise, opts) {
if (!opts) opts = {}
exercise.addSetup(setup)
exercise.addProcessor(processor)
// override if you want to mess with stdout
exercise.getStdout = function (type, child) {
// type == 'submission' || 'solution'
return child.stdout
}
exercise.getSolutionFiles = function (callback) {
const translated = path.join(this.dir, './solution_' + this.lang)
const fallback = path.join(this.dir, './solution')
checkPath(translated, function (_err, list) {
if (list && list.length > 0) {
return callback(null, list)
}
checkPath(fallback, callback)
})
function checkPath (dir, callback) {
fs.stat(dir, function (_err, stat) {
if (!stat) { return callback(null, []) }
fs.readdir(dir, function (err, list) {
if (err) { return callback(err) }
list = list
.filter(function (f) { return (/\.js$/).test(f) })
.map(function (f) { return path.join(dir, f) })
callback(null, list)
})
})
}
}
return exercise
function setup (mode, callback) {
this.submission = this.args[0] // first arg obviously
// default args, override if you want to pass special args to the
// solution and/or submission, override this.setup to do this
this.submissionArgs = Array.prototype.slice.call(1, this.args)
this.solutionArgs = Array.prototype.slice.call(1, this.args)
// edit/override if you want to alter the child process environment
this.env = xtend(process.env)
// set this.solution if your solution is elsewhere
if (!this.solution) {
const localisedSolutionPath = path.join(this.dir, './solution_' + this.lang + '/solution.js')
this.solution = fs.existsSync(localisedSolutionPath) ? localisedSolutionPath : path.join(this.dir, './solution/solution.js')
}
process.nextTick(callback)
}
function kill () {
;[this.submissionChild, this.solutionChild].forEach(function (child) {
if (child && typeof child.kill === 'function') { child.kill() }
})
setTimeout(function () {
this.emit('executeEnd')
}.bind(this), 10)
}
function processor (mode, callback) {
const ended = after(mode === 'verify' ? 2 : 1, kill.bind(this))
// backwards compat for workshops that use older custom setup functions
if (!this.solutionCommand) this.solutionCommand = [this.solution].concat(this.solutionArgs)
if (!this.submissionCommand) this.submissionCommand = [this.submission].concat(this.submissionArgs)
this.submissionChild = spawn(opts.exec || process.execPath, this.submissionCommand, { env: this.env })
this.submissionStdout = this.getStdout('submission', this.submissionChild)
setImmediate(function () { // give other processors a chance to overwrite stdout
this.submissionStdout.on('end', ended)
}.bind(this))
if (mode === 'verify') {
this.solutionChild = spawn(opts.exec || process.execPath, this.solutionCommand, { env: this.env })
this.solutionStdout = this.getStdout('solution', this.solutionChild)
setImmediate(function () { // give other processors a chance to overwrite stdout
this.solutionStdout.on('end', ended)
}.bind(this))
}
process.nextTick(function () {
callback(null, true)
})
}
}
module.exports = execute