Files
freeCodeCamp/client/src/templates/Challenges/redux/execute-challenge-saga.js

201 lines
5.4 KiB
JavaScript
Raw Normal View History

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';
import { delay, channel } from 'redux-saga';
2018-11-26 02:17:38 +03:00
import {
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 './';
import {
2018-12-29 11:34:03 +03:00
buildJSChallenge,
buildDOMChallenge,
buildBackendChallenge
} from '../utils/build';
2018-11-26 02:17:38 +03:00
import { challengeTypes } from '../../../../utils/challengeTypes';
import createWorker from '../utils/worker-executor';
import {
createMainFramer,
createTestFramer,
runTestInTestFrame
} from '../utils/frame.js';
2018-11-26 02:17:38 +03:00
export function* executeChallengeSaga() {
2018-12-27 13:34:55 +03:00
const consoleProxy = yield channel();
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);
const proxyLogger = args => consoleProxy.put(args);
let testResults;
switch (challengeType) {
case js:
case bonfire:
testResults = yield executeJSChallengeSaga(proxyLogger);
break;
case backend:
testResults = yield executeBackendChallengeSaga(proxyLogger);
break;
default:
testResults = yield executeDOMChallengeSaga(proxyLogger);
}
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
}
}
function* logToConsole(channel) {
yield takeEvery(channel, function*(args) {
yield put(updateLogs(args));
});
}
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'] : '';
const testWorker = createWorker('test-evaluator');
testWorker.on('LOG', proxyLogger);
2018-12-27 13:34:55 +03:00
try {
return yield call(executeTests, async(testString, testTimeout) => {
try {
return await testWorker.execute(
{ build, testString, code, sources },
2018-12-29 11:34:03 +03:00
testTimeout
);
} finally {
testWorker.killWorker();
}
});
2018-12-27 13:34:55 +03:00
} finally {
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) {
return new Promise(resolve =>
createTestFramer(document, resolve, proxyLogger)(ctx)
);
}
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');
const ctx = yield call(buildDOMChallenge, files, meta);
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);
// wait for a code execution on a "ready" event in jQuery challenges
yield delay(100);
2018-12-27 13:34:55 +03:00
return yield call(executeTests, (testString, testTimeout) =>
runTestInTestFrame(document, testString, testTimeout)
2018-12-10 17:29:58 +03:00
);
}
// TODO: use a web worker
function* executeBackendChallengeSaga(proxyLogger) {
const formValues = yield select(backendFormValuesSelector);
2019-01-09 03:36:45 +03:00
const document = yield getContext('document');
const ctx = yield call(buildBackendChallenge, formValues);
2019-01-09 03:36:45 +03:00
yield call(createTestFrame, document, ctx, proxyLogger);
2018-12-27 13:34:55 +03:00
return yield call(executeTests, (testString, testTimeout) =>
runTestInTestFrame(document, testString, testTimeout)
2018-12-10 17:29:58 +03:00
);
}
2018-12-10 17:29:58 +03:00
function* executeTests(testRunner) {
const tests = yield select(challengeTestsSelector);
const testTimeout = 5000;
const testResults = [];
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);
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() {
yield delay(500);
2018-12-10 17:29:58 +03:00
try {
const { html, modern } = challengeTypes;
const meta = yield select(challengeMetaSelector);
const { challengeType } = meta;
2018-12-10 17:29:58 +03:00
if (challengeType !== html && challengeType !== modern) {
return;
}
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');
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) {
return [
takeLatest(types.executeChallenge, executeChallengeSaga),
takeLatest(
[
types.updateFile,
types.previewMounted,
types.challengeMounted,
types.resetChallenge
],
updateMainSaga
)
];
2018-11-26 02:17:38 +03:00
}