2018-12-10 08:22:32 +03:00
|
|
|
import {
|
|
|
|
put,
|
|
|
|
select,
|
|
|
|
call,
|
|
|
|
takeLatest,
|
|
|
|
takeEvery,
|
2019-01-09 03:36:45 +03:00
|
|
|
fork,
|
|
|
|
getContext
|
2018-12-10 08:22:32 +03:00
|
|
|
} from 'redux-saga/effects';
|
2018-12-11 07:33:05 +03:00
|
|
|
import { delay, channel } from 'redux-saga';
|
2018-11-26 02:17:38 +03:00
|
|
|
|
|
|
|
import {
|
2019-01-12 03:37:00 +03:00
|
|
|
backendFormValuesSelector,
|
|
|
|
challengeFilesSelector,
|
2018-11-26 02:17:38 +03:00
|
|
|
challengeMetaSelector,
|
|
|
|
challengeTestsSelector,
|
|
|
|
initConsole,
|
|
|
|
updateConsole,
|
|
|
|
initLogs,
|
|
|
|
updateLogs,
|
|
|
|
logsToConsole,
|
2018-12-29 11:34:03 +03:00
|
|
|
updateTests
|
2018-11-26 02:17:38 +03:00
|
|
|
} from './';
|
|
|
|
|
2018-12-10 10:00:26 +03:00
|
|
|
import {
|
2018-12-29 11:34:03 +03:00
|
|
|
buildJSChallenge,
|
|
|
|
buildDOMChallenge,
|
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';
|
|
|
|
|
2019-01-03 01:50:28 +03:00
|
|
|
import createWorker 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
|
|
|
|
2019-01-09 03:35:31 +03:00
|
|
|
export function* executeChallengeSaga() {
|
2018-12-27 13:34:55 +03:00
|
|
|
const consoleProxy = yield channel();
|
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'));
|
2018-12-27 13:34:55 +03:00
|
|
|
yield fork(logToConsole, consoleProxy);
|
2019-01-09 03:35:31 +03:00
|
|
|
const proxyLogger = args => consoleProxy.put(args);
|
2018-12-05 12:37:48 +03:00
|
|
|
|
|
|
|
let testResults;
|
|
|
|
switch (challengeType) {
|
|
|
|
case js:
|
|
|
|
case bonfire:
|
2019-01-12 03:37:00 +03:00
|
|
|
testResults = yield executeJSChallengeSaga(proxyLogger);
|
2018-12-05 12:37:48 +03:00
|
|
|
break;
|
|
|
|
case backend:
|
2019-01-12 03:37:00 +03:00
|
|
|
testResults = yield executeBackendChallengeSaga(proxyLogger);
|
2018-12-05 12:37:48 +03:00
|
|
|
break;
|
|
|
|
default:
|
2019-01-12 03:37:00 +03:00
|
|
|
testResults = yield executeDOMChallengeSaga(proxyLogger);
|
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-12-27 13:34:55 +03:00
|
|
|
} finally {
|
|
|
|
consoleProxy.close();
|
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));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-01-12 03:37:00 +03:00
|
|
|
function* executeJSChallengeSaga(proxyLogger) {
|
|
|
|
const files = yield select(challengeFilesSelector);
|
|
|
|
const { build, sources } = yield call(buildJSChallenge, files);
|
2018-12-29 11:34:03 +03:00
|
|
|
const code = sources && 'index' in sources ? sources['index'] : '';
|
2018-12-05 12:37:48 +03:00
|
|
|
|
2019-01-09 03:35:31 +03:00
|
|
|
const testWorker = createWorker('test-evaluator');
|
|
|
|
testWorker.on('LOG', proxyLogger);
|
2018-12-10 15:06:39 +03:00
|
|
|
|
2018-12-27 13:34:55 +03:00
|
|
|
try {
|
2019-01-09 03:35:31 +03:00
|
|
|
return yield call(executeTests, async(testString, testTimeout) => {
|
|
|
|
try {
|
|
|
|
return await testWorker.execute(
|
2019-01-15 18:27:30 +03:00
|
|
|
{ build, testString, code, sources },
|
2018-12-29 11:34:03 +03:00
|
|
|
testTimeout
|
2019-01-09 03:35:31 +03:00
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
testWorker.killWorker();
|
|
|
|
}
|
|
|
|
});
|
2018-12-27 13:34:55 +03:00
|
|
|
} finally {
|
2019-01-09 03:35:31 +03:00
|
|
|
testWorker.remove('LOG', proxyLogger);
|
2018-12-27 13:34:55 +03:00
|
|
|
}
|
2018-11-26 02:17:38 +03:00
|
|
|
}
|
|
|
|
|
2019-01-09 03:36:45 +03:00
|
|
|
function createTestFrame(document, ctx, proxyLogger) {
|
2019-01-09 03:35:31 +03:00
|
|
|
return new Promise(resolve =>
|
|
|
|
createTestFramer(document, resolve, proxyLogger)(ctx)
|
|
|
|
);
|
2018-12-10 01:46:26 +03:00
|
|
|
}
|
|
|
|
|
2019-01-12 03:37:00 +03:00
|
|
|
function* executeDOMChallengeSaga(proxyLogger) {
|
|
|
|
const files = yield select(challengeFilesSelector);
|
|
|
|
const meta = yield select(challengeMetaSelector);
|
2019-01-09 03:36:45 +03:00
|
|
|
const document = yield getContext('document');
|
2019-01-12 03:37:00 +03:00
|
|
|
const ctx = yield call(buildDOMChallenge, files, meta);
|
2019-01-12 03:41:45 +03:00
|
|
|
ctx.loadEnzyme = Object.keys(files).some(key => files[key].ext === 'jsx');
|
2019-01-09 03:36:45 +03:00
|
|
|
yield call(createTestFrame, document, ctx, proxyLogger);
|
2018-12-11 07:33:05 +03:00
|
|
|
// wait for a code execution on a "ready" event in jQuery challenges
|
|
|
|
yield delay(100);
|
2018-12-10 01:46:26 +03:00
|
|
|
|
2018-12-27 13:34:55 +03:00
|
|
|
return yield call(executeTests, (testString, testTimeout) =>
|
2019-01-09 03:35:31 +03:00
|
|
|
runTestInTestFrame(document, testString, testTimeout)
|
2018-12-10 17:29:58 +03:00
|
|
|
);
|
2018-12-10 10:00:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: use a web worker
|
2019-01-12 03:37:00 +03:00
|
|
|
function* executeBackendChallengeSaga(proxyLogger) {
|
|
|
|
const formValues = yield select(backendFormValuesSelector);
|
2019-01-09 03:36:45 +03:00
|
|
|
const document = yield getContext('document');
|
2019-01-12 03:37:00 +03:00
|
|
|
const ctx = yield call(buildBackendChallenge, formValues);
|
2019-01-09 03:36:45 +03:00
|
|
|
yield call(createTestFrame, document, ctx, proxyLogger);
|
2018-12-10 10:00:26 +03:00
|
|
|
|
2018-12-27 13:34:55 +03:00
|
|
|
return yield call(executeTests, (testString, testTimeout) =>
|
2019-01-09 03:35:31 +03:00
|
|
|
runTestInTestFrame(document, testString, testTimeout)
|
2018-12-10 17:29:58 +03:00
|
|
|
);
|
2018-12-10 09:19:47 +03:00
|
|
|
}
|
|
|
|
|
2018-12-10 17:29:58 +03:00
|
|
|
function* executeTests(testRunner) {
|
2018-12-10 09:19:47 +03:00
|
|
|
const tests = yield select(challengeTestsSelector);
|
2019-01-09 03:35:31 +03:00
|
|
|
const testTimeout = 5000;
|
2018-12-10 09:19:47 +03:00
|
|
|
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() {
|
2019-01-14 15:40:25 +03:00
|
|
|
yield delay(500);
|
2018-12-10 17:29:58 +03:00
|
|
|
try {
|
|
|
|
const { html, modern } = challengeTypes;
|
2019-01-12 03:37:00 +03:00
|
|
|
const meta = yield select(challengeMetaSelector);
|
|
|
|
const { challengeType } = meta;
|
2018-12-10 17:29:58 +03:00
|
|
|
if (challengeType !== html && challengeType !== modern) {
|
|
|
|
return;
|
|
|
|
}
|
2019-01-12 03:37:00 +03:00
|
|
|
const files = yield select(challengeFilesSelector);
|
|
|
|
const ctx = yield call(buildDOMChallenge, files, meta);
|
2019-01-09 03:36:45 +03:00
|
|
|
const document = yield getContext('document');
|
2019-01-09 03:35:31 +03:00
|
|
|
const frameMain = yield call(createMainFramer, document);
|
2018-12-10 17:29:58 +03:00
|
|
|
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 [
|
2019-01-09 03:35:31 +03:00
|
|
|
takeLatest(types.executeChallenge, executeChallengeSaga),
|
2018-12-10 01:46:26 +03:00
|
|
|
takeLatest(
|
|
|
|
[
|
|
|
|
types.updateFile,
|
|
|
|
types.previewMounted,
|
|
|
|
types.challengeMounted,
|
|
|
|
types.resetChallenge
|
|
|
|
],
|
|
|
|
updateMainSaga
|
|
|
|
)
|
|
|
|
];
|
2018-11-26 02:17:38 +03:00
|
|
|
}
|