2018-12-10 08:22:32 +03:00
|
|
|
import {
|
|
|
|
put,
|
|
|
|
select,
|
|
|
|
call,
|
|
|
|
takeLatest,
|
|
|
|
takeEvery,
|
|
|
|
fork
|
|
|
|
} from 'redux-saga/effects';
|
2018-12-10 09:19:47 +03:00
|
|
|
import { channel } from 'redux-saga';
|
2018-11-26 02:17:38 +03:00
|
|
|
|
|
|
|
import {
|
|
|
|
challengeMetaSelector,
|
|
|
|
challengeTestsSelector,
|
|
|
|
initConsole,
|
|
|
|
updateConsole,
|
|
|
|
initLogs,
|
|
|
|
updateLogs,
|
|
|
|
logsToConsole,
|
|
|
|
updateTests,
|
|
|
|
challengeFilesSelector
|
|
|
|
} from './';
|
|
|
|
|
2018-12-10 10:00:26 +03:00
|
|
|
import {
|
|
|
|
buildJSFromFiles,
|
2018-12-10 17:29:58 +03:00
|
|
|
buildHtmlFromFiles,
|
2018-12-10 10:00:26 +03:00
|
|
|
buildBackendChallenge
|
|
|
|
} from '../utils/build';
|
2018-11-26 02:17:38 +03:00
|
|
|
|
|
|
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
|
|
|
|
|
|
|
import WorkerExecutor from '../utils/worker-executor';
|
2018-12-10 01:46:26 +03:00
|
|
|
import {
|
|
|
|
createMainFramer,
|
|
|
|
createTestFramer,
|
|
|
|
runTestInTestFrame
|
|
|
|
} from '../utils/frame.js';
|
2018-11-26 02:17:38 +03:00
|
|
|
|
|
|
|
const testWorker = new WorkerExecutor('test-evaluator');
|
|
|
|
const testTimeout = 5000;
|
|
|
|
|
2018-12-04 17:23:15 +03:00
|
|
|
function* ExecuteChallengeSaga() {
|
2018-12-05 12:37:48 +03:00
|
|
|
try {
|
|
|
|
const { js, bonfire, backend } = challengeTypes;
|
|
|
|
const { challengeType } = yield select(challengeMetaSelector);
|
|
|
|
|
|
|
|
yield put(initLogs());
|
|
|
|
yield put(initConsole('// running tests'));
|
|
|
|
|
|
|
|
let testResults;
|
|
|
|
switch (challengeType) {
|
|
|
|
case js:
|
|
|
|
case bonfire:
|
2018-12-10 09:19:47 +03:00
|
|
|
testResults = yield ExecuteJSChallengeSaga();
|
2018-12-05 12:37:48 +03:00
|
|
|
break;
|
|
|
|
case backend:
|
2018-12-10 10:00:26 +03:00
|
|
|
testResults = yield ExecuteBackendChallengeSaga();
|
2018-12-05 12:37:48 +03:00
|
|
|
break;
|
|
|
|
default:
|
2018-12-10 09:19:47 +03:00
|
|
|
testResults = yield ExecuteDOMChallengeSaga();
|
2018-12-05 12:37:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
yield put(updateTests(testResults));
|
|
|
|
yield put(updateConsole('// tests completed'));
|
|
|
|
yield put(logsToConsole('// console output'));
|
|
|
|
} catch (e) {
|
|
|
|
yield put(updateConsole(e));
|
2018-11-26 02:17:38 +03:00
|
|
|
}
|
2018-12-04 17:23:15 +03:00
|
|
|
}
|
|
|
|
|
2018-12-10 15:06:39 +03:00
|
|
|
function* logToConsole(channel) {
|
|
|
|
yield takeEvery(channel, function*(args) {
|
|
|
|
yield put(updateLogs(args));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-12-10 09:19:47 +03:00
|
|
|
function* ExecuteJSChallengeSaga() {
|
2018-12-05 12:37:48 +03:00
|
|
|
const files = yield select(challengeFilesSelector);
|
|
|
|
const { code, solution } = yield call(buildJSFromFiles, files);
|
|
|
|
|
2018-12-10 15:06:39 +03:00
|
|
|
const consoleProxy = yield channel();
|
|
|
|
yield fork(logToConsole, consoleProxy);
|
|
|
|
const log = args => consoleProxy.put(args);
|
|
|
|
testWorker.on('LOG', log);
|
|
|
|
|
2018-12-10 17:29:58 +03:00
|
|
|
const testResults = yield call(executeTests, (testString, testTimeout) =>
|
|
|
|
testWorker
|
|
|
|
.execute({ script: solution + '\n' + testString, code }, testTimeout)
|
|
|
|
.then(result => {
|
|
|
|
testWorker.killWorker();
|
|
|
|
return result;
|
|
|
|
})
|
|
|
|
);
|
2018-12-10 15:06:39 +03:00
|
|
|
|
|
|
|
testWorker.remove('LOG', log);
|
|
|
|
consoleProxy.close();
|
2018-12-05 12:37:48 +03:00
|
|
|
return testResults;
|
2018-11-26 02:17:38 +03:00
|
|
|
}
|
|
|
|
|
2018-12-10 01:46:26 +03:00
|
|
|
function createTestFrame(state, ctx, proxyLogger) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const frameTest = createTestFramer(document, state, resolve, proxyLogger);
|
|
|
|
frameTest(ctx);
|
2018-12-10 17:29:58 +03:00
|
|
|
});
|
2018-12-10 01:46:26 +03:00
|
|
|
}
|
|
|
|
|
2018-12-10 09:19:47 +03:00
|
|
|
function* ExecuteDOMChallengeSaga() {
|
2018-12-10 01:46:26 +03:00
|
|
|
const state = yield select();
|
2018-12-10 17:29:58 +03:00
|
|
|
const ctx = yield call(buildHtmlFromFiles, state);
|
2018-12-10 08:22:32 +03:00
|
|
|
const consoleProxy = yield channel();
|
|
|
|
yield fork(logToConsole, consoleProxy);
|
|
|
|
|
|
|
|
yield call(createTestFrame, state, ctx, consoleProxy);
|
2018-12-10 01:46:26 +03:00
|
|
|
|
2018-12-10 17:29:58 +03:00
|
|
|
const testResults = yield call(executeTests, (testString, testTimeout) =>
|
|
|
|
Promise.race([
|
|
|
|
runTestInTestFrame(document, testString),
|
|
|
|
new Promise((_, reject) =>
|
|
|
|
setTimeout(() => reject('timeout'), testTimeout)
|
|
|
|
)
|
|
|
|
])
|
|
|
|
);
|
2018-12-10 10:00:26 +03:00
|
|
|
|
|
|
|
consoleProxy.close();
|
|
|
|
return testResults;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: use a web worker
|
|
|
|
function* ExecuteBackendChallengeSaga() {
|
|
|
|
const state = yield select();
|
|
|
|
const ctx = yield call(buildBackendChallenge, state);
|
|
|
|
const consoleProxy = yield channel();
|
|
|
|
|
|
|
|
yield call(createTestFrame, state, ctx, consoleProxy);
|
|
|
|
|
2018-12-10 17:29:58 +03:00
|
|
|
const testResults = yield call(executeTests, (testString, testTimeout) =>
|
|
|
|
Promise.race([
|
|
|
|
runTestInTestFrame(document, testString),
|
|
|
|
new Promise((_, reject) =>
|
|
|
|
setTimeout(() => reject('timeout'), testTimeout)
|
|
|
|
)
|
|
|
|
])
|
|
|
|
);
|
2018-12-10 09:19:47 +03:00
|
|
|
|
|
|
|
consoleProxy.close();
|
|
|
|
return testResults;
|
|
|
|
}
|
|
|
|
|
2018-12-10 17:29:58 +03:00
|
|
|
function* executeTests(testRunner) {
|
2018-12-10 09:19:47 +03:00
|
|
|
const tests = yield select(challengeTestsSelector);
|
|
|
|
const testResults = [];
|
2018-12-10 01:46:26 +03:00
|
|
|
for (const { text, testString } of tests) {
|
|
|
|
const newTest = { text, testString };
|
|
|
|
try {
|
2018-12-10 17:29:58 +03:00
|
|
|
const { pass, err } = yield call(testRunner, testString, testTimeout);
|
2018-12-10 01:46:26 +03:00
|
|
|
if (pass) {
|
|
|
|
newTest.pass = true;
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
newTest.message = text.replace(/<code>(.*?)<\/code>/g, '$1');
|
|
|
|
if (err === 'timeout') {
|
|
|
|
newTest.err = 'Test timed out';
|
|
|
|
newTest.message = `${newTest.message} (${newTest.err})`;
|
|
|
|
} else {
|
|
|
|
const { message, stack } = err;
|
|
|
|
newTest.err = message + '\n' + stack;
|
|
|
|
newTest.stack = stack;
|
|
|
|
}
|
|
|
|
yield put(updateConsole(newTest.message));
|
|
|
|
} finally {
|
|
|
|
testResults.push(newTest);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return testResults;
|
|
|
|
}
|
|
|
|
|
2018-12-10 17:29:58 +03:00
|
|
|
function* updateMainSaga() {
|
|
|
|
try {
|
|
|
|
const { html, modern } = challengeTypes;
|
|
|
|
const { challengeType } = yield select(challengeMetaSelector);
|
|
|
|
if (challengeType !== html && challengeType !== modern) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const state = yield select();
|
|
|
|
const frameMain = yield call(createMainFramer, document, state);
|
|
|
|
const ctx = yield call(buildHtmlFromFiles, state);
|
|
|
|
yield call(frameMain, ctx);
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-26 02:17:38 +03:00
|
|
|
export function createExecuteChallengeSaga(types) {
|
2018-12-10 01:46:26 +03:00
|
|
|
return [
|
|
|
|
takeLatest(types.executeChallenge, ExecuteChallengeSaga),
|
|
|
|
takeLatest(
|
|
|
|
[
|
|
|
|
types.updateFile,
|
|
|
|
types.previewMounted,
|
|
|
|
types.challengeMounted,
|
|
|
|
types.resetChallenge
|
|
|
|
],
|
|
|
|
updateMainSaga
|
|
|
|
)
|
|
|
|
];
|
2018-11-26 02:17:38 +03:00
|
|
|
}
|