diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js index 8868e48420..45989a8a7f 100644 --- a/client/src/templates/Challenges/redux/execute-challenge-saga.js +++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js @@ -34,10 +34,7 @@ import { runTestInTestFrame } from '../utils/frame.js'; -const testWorker = createWorker('test-evaluator'); -const testTimeout = 5000; - -function* ExecuteChallengeSaga() { +export function* executeChallengeSaga() { const consoleProxy = yield channel(); try { const { js, bonfire, backend } = challengeTypes; @@ -46,6 +43,7 @@ function* ExecuteChallengeSaga() { yield put(initLogs()); yield put(initConsole('// running tests')); yield fork(logToConsole, consoleProxy); + const proxyLogger = args => consoleProxy.put(args); const state = yield select(); @@ -53,13 +51,13 @@ function* ExecuteChallengeSaga() { switch (challengeType) { case js: case bonfire: - testResults = yield ExecuteJSChallengeSaga(state, consoleProxy); + testResults = yield executeJSChallengeSaga(state, proxyLogger); break; case backend: - testResults = yield ExecuteBackendChallengeSaga(state, consoleProxy); + testResults = yield executeBackendChallengeSaga(state, proxyLogger); break; default: - testResults = yield ExecuteDOMChallengeSaga(state, consoleProxy); + testResults = yield executeDOMChallengeSaga(state, proxyLogger); } yield put(updateTests(testResults)); @@ -78,72 +76,59 @@ function* logToConsole(channel) { }); } -function* ExecuteJSChallengeSaga(state, proxyLogger) { +function* executeJSChallengeSaga(state, proxyLogger) { const { build, sources } = yield call(buildJSChallenge, state); const code = sources && 'index' in sources ? sources['index'] : ''; - const log = args => proxyLogger.put(args); - testWorker.on('LOG', log); + const testWorker = createWorker('test-evaluator'); + testWorker.on('LOG', proxyLogger); try { - return yield call(executeTests, (testString, testTimeout) => - testWorker - .execute( + return yield call(executeTests, async(testString, testTimeout) => { + try { + return await testWorker.execute( { script: build + '\n' + testString, code, sources }, testTimeout - ) - .then(result => { - testWorker.killWorker(); - return result; - }) - ); + ); + } finally { + testWorker.killWorker(); + } + }); } finally { - testWorker.remove('LOG', log); + testWorker.remove('LOG', proxyLogger); } } -function createTestFrame(state, ctx, proxyLogger) { - return new Promise(resolve => { - const frameTest = createTestFramer(document, state, resolve, proxyLogger); - frameTest(ctx); - }); +function createTestFrame(ctx, proxyLogger) { + return new Promise(resolve => + createTestFramer(document, resolve, proxyLogger)(ctx) + ); } -function* ExecuteDOMChallengeSaga(state, proxyLogger) { +function* executeDOMChallengeSaga(state, proxyLogger) { const ctx = yield call(buildDOMChallenge, state); - - yield call(createTestFrame, state, ctx, proxyLogger); + yield call(createTestFrame, ctx, proxyLogger); // wait for a code execution on a "ready" event in jQuery challenges yield delay(100); return yield call(executeTests, (testString, testTimeout) => - Promise.race([ - runTestInTestFrame(document, testString), - new Promise((_, reject) => - setTimeout(() => reject('timeout'), testTimeout) - ) - ]) + runTestInTestFrame(document, testString, testTimeout) ); } // TODO: use a web worker -function* ExecuteBackendChallengeSaga(state, proxyLogger) { +function* executeBackendChallengeSaga(state, proxyLogger) { const ctx = yield call(buildBackendChallenge, state); - - yield call(createTestFrame, state, ctx, proxyLogger); + yield call(createTestFrame, ctx, proxyLogger); return yield call(executeTests, (testString, testTimeout) => - Promise.race([ - runTestInTestFrame(document, testString), - new Promise((_, reject) => - setTimeout(() => reject('timeout'), testTimeout) - ) - ]) + runTestInTestFrame(document, testString, testTimeout) ); } function* executeTests(testRunner) { const tests = yield select(challengeTestsSelector); + const testTimeout = 5000; const testResults = []; for (const { text, testString } of tests) { const newTest = { text, testString }; @@ -180,8 +165,8 @@ function* updateMainSaga() { return; } const state = yield select(); - const frameMain = yield call(createMainFramer, document, state); const ctx = yield call(buildDOMChallenge, state); + const frameMain = yield call(createMainFramer, document); yield call(frameMain, ctx); } catch (err) { console.error(err); @@ -190,7 +175,7 @@ function* updateMainSaga() { export function createExecuteChallengeSaga(types) { return [ - takeLatest(types.executeChallenge, ExecuteChallengeSaga), + takeLatest(types.executeChallenge, executeChallengeSaga), takeLatest( [ types.updateFile, diff --git a/client/src/templates/Challenges/utils/frame.js b/client/src/templates/Challenges/utils/frame.js index c0aa72d0d7..3402a5e393 100644 --- a/client/src/templates/Challenges/utils/frame.js +++ b/client/src/templates/Challenges/utils/frame.js @@ -3,8 +3,6 @@ import { configure, shallow, mount } from 'enzyme'; import Adapter16 from 'enzyme-adapter-react-16'; import { setConfig } from 'react-hot-loader'; -import { isJSEnabledSelector } from '../redux'; - // we use two different frames to make them all essentially pure functions // main iframe is responsible rendering the preview and is where we proxy the // console.log @@ -33,23 +31,24 @@ const createHeader = (id = mainId) => ` `; -export const runTestInTestFrame = async function(document, test) { +export const runTestInTestFrame = async function(document, test, timeout) { const { contentDocument: frame } = document.getElementById(testId); // Enable Stateless Functional Component. Otherwise, enzyme-adapter-react-16 // does not work correctly. setConfig({ pureSFC: true }); - const result = await frame.__runTest(test); - setConfig({ pureSFC: false }); - return result; + try { + return await Promise.race([ + new Promise((_, reject) => setTimeout(() => reject('timeout'), timeout)), + frame.__runTest(test) + ]); + } finally { + setConfig({ pureSFC: false }); + } }; -const createFrame = (document, state, id) => ctx => { - const isJSEnabled = isJSEnabledSelector(state); +const createFrame = (document, id) => ctx => { const frame = document.createElement('iframe'); frame.id = id; - if (!isJSEnabled) { - frame.sandbox = 'allow-same-origin'; - } return { ...ctx, element: frame @@ -77,7 +76,7 @@ const mountFrame = document => ({ element, ...rest }) => { const buildProxyConsole = proxyLogger => ctx => { const oldLog = ctx.window.console.log.bind(ctx.window.console); ctx.window.console.log = function proxyConsole(...args) { - proxyLogger.put(args); + proxyLogger(args); return oldLog(...args); }; return ctx; @@ -111,16 +110,16 @@ const writeContentToFrame = ctx => { return ctx; }; -export const createMainFramer = (document, state) => +export const createMainFramer = document => flow( - createFrame(document, state, mainId), + createFrame(document, mainId), mountFrame(document), writeContentToFrame ); -export const createTestFramer = (document, state, frameReady, proxyConsole) => +export const createTestFramer = (document, frameReady, proxyConsole) => flow( - createFrame(document, state, testId), + createFrame(document, testId), mountFrame(document), writeTestDepsToDocument(frameReady), buildProxyConsole(proxyConsole),