-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest.js
More file actions
149 lines (127 loc) · 5.5 KB
/
test.js
File metadata and controls
149 lines (127 loc) · 5.5 KB
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
const fs = require('fs')
const chalk = require("chalk")
const shell = require('shelljs')
const { Utils, TestingError } = require("learnpack/plugin")
module.exports = {
validate: async function ({ exercise, configuration }) {
if (!shell.which('pytest')) {
throw Error(`🚫 You need to have pytest, pytest-testdox and mock installed to run test the exercises, run $ pip3 install pytest pytest-testdox mock`);
}
let venvPythonPath = null
if (fs.existsSync("./.venv/lib/")) {
const pythons = fs.readdirSync('./.venv/lib/')
if (pythons.length > 0) venvPythonPath = "./.venv/lib/" + pythons[0]
}
//i have to create this conftest.py configuration for pytest, to allow passing the inputs as a parameter
fs.writeFileSync("./conftest.py", `import sys, os, json
if os.path.isdir("./.venv/lib/"):
sys.path.append('${venvPythonPath}/site-packages')
def pytest_addoption(parser):
parser.addoption("--stdin", action="append", default=[],
help="json with the stdin to pass to test functions")
def pytest_generate_tests(metafunc):
if 'stdin' in metafunc.fixturenames:
if hasattr(metafunc,"config"):
metafunc.parametrize("stdin",metafunc.config.getoption('stdin'))
elif hasattr(metafunc,"configuration"):
metafunc.parametrize("stdin",metafunc.configuration.getoption('stdin'))
else:
raise Exception("Imposible to retrieve text configuration object")
if 'app' in metafunc.fixturenames:
try:
sys.path.append('${configuration.outputPath}')
import cached_app
metafunc.parametrize("app",[cached_app.execute_app])
except SyntaxError:
metafunc.parametrize("app",[lambda : None])
except ImportError:
metafunc.parametrize("app",[cached_app])
except AttributeError:
metafunc.parametrize("app",[cached_app])
if 'configuration' in metafunc.fixturenames:
metafunc.parametrize("configuration", [json.loads('${JSON.stringify(configuration)}')])
`)
return true
},
run: async ({ exercise, socket, configuration }) => {
const getContent = () => {
const appPath = exercise.files.map(f => './' + f.path).find(f => f.includes(exercise.entry || 'app.py'))
if (fs.existsSync(appPath)) {
let content = fs.readFileSync(appPath, "utf8")
return content
}
}
const getEntry = () => {
//console.log(exercise);
let entryPath = exercise.files.map(f => './' + f.path).find(f => f.includes('test.py') || f.includes('tests.py'))
if (!fs.existsSync(entryPath)) throw TestingError(`🚫 No tests.py script found on the exercise in ${entryPath}`)
const appPath = exercise.files.map(f => './' + f.path).find(f => f.includes(exercise.entry || 'app.py'))
if (fs.existsSync(appPath)) {
let content = fs.readFileSync(appPath, "utf8")
const count = Utils.getMatches(/def\s[a-zA-Z]/gm, content)
if (count.length == 0) {
// Adding main execute_app function for all the code
content = `def execute_app():\n${Utils.indent(content, 4)}`
}
const directory = `${configuration.outputPath}/cached_app.py`
fs.writeFileSync(directory, content)
}
// else if (configuration.grading === "isolated") throw TestingError(`🚫 No app.py script found on the exercise files`)
return entryPath
}
const getCommands = async function () {
const appPath = exercise.files.map(f => './' + f.path).find(f => f.includes(exercise.entry || 'app.py'))
if (appPath !== undefined) {
const content = fs.readFileSync(appPath, "utf8")
const count = Utils.getMatches(/input\((?:["'`]{1}(.*)["'`]{1})?\)/gm, content)
let answers = (count.length == 0) ? [] : await socket.ask(count)
return `pytest ${getEntry()} --testdox --capture=sys --color=yes --stdin='${JSON.stringify(answers)}'`
}
else {
return `pytest ${getEntry()} --testdox --capture=sys --color=yes`
}
}
const getStdout = (rawStdout) => {
//@pytest.mark.it('1. Your code needs to print Yellow on the console')
var regex = /@pytest\.mark\.it\(["'](.+)["']\)/gm
let errors = []
let found = null
while ((found = regex.exec(rawStdout)) !== null) {
if (found.index === regex.lastIndex) {
regex.lastIndex++
}
errors.push({ title: found[1], status: 'failed' })
}
let _stdout = [rawStdout]
if (errors.length > 0) {
msg = `\n\n
${chalk.red('Your code must to comply with the following tests:')} \n\n${[...new Set(errors)].map((e, i) => ` ${e.status !== 'failed' ? chalk.green.bold('✓ (done)') : chalk.red.bold('x (fail)')} ${i}. ${chalk.white(e.title)}`).join('\n')} \n\n`
_stdout.push(msg)
}
return _stdout
}
let commands = await getCommands()
if (!Array.isArray(commands)) commands = [commands]
let appContent = getContent()
const result = {
starting_at: Date.now(),
source_code: appContent,
}
let stdout, stderr, code = [null, null, null]
for (let cycle = 0; cycle < commands.length; cycle++) {
let resp = shell.exec(commands[cycle], { silent: true })
stdout = resp.stdout
code = resp.code
stderr = resp.stderr
if (code != 0) break
}
result.ended_at = Date.now()
result.exitCode = code
result.stdout = stdout
result.stderr = stderr
if (code != 0) {
result.stderr = getStdout(stdout || stderr).join()
}
return result
}
}