<commit_before>import {
  put,
  select,
  call,
  takeLatest,
  takeEvery,
  fork
} from 'redux-saga/effects';
import { delay, channel } from 'redux-saga';

import {
  challengeMetaSelector,
  challengeTestsSelector,
  initConsole,
  updateConsole,
  initLogs,
  updateLogs,
  logsToConsole,
  updateTests,
  challengeFilesSelector
} from './';

import {
  buildJSFromFiles,
  buildHtmlFromFiles,
  buildBackendChallenge
} from '../utils/build';

import { challengeTypes } from '../../../../utils/challengeTypes';

import WorkerExecutor from '../utils/worker-executor';
import {
  createMainFramer,
  createTestFramer,
  runTestInTestFrame
} from '../utils/frame.js';

const testWorker = new WorkerExecutor('test-evaluator');
const testTimeout = 5000;

function* ExecuteChallengeSaga() {
  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:
        testResults = yield ExecuteJSChallengeSaga();
        break;
      case backend:
        testResults = yield ExecuteBackendChallengeSaga();
        break;
      default:
        testResults = yield ExecuteDOMChallengeSaga();
    }

    yield put(updateTests(testResults));
    yield put(updateConsole('// tests completed'));
    yield put(logsToConsole('// console output'));
  } catch (e) {
    yield put(updateConsole(e));
  }
}

function* logToConsole(channel) {
  yield takeEvery(channel, function*(args) {
    yield put(updateLogs(args));
  });
}

function* ExecuteJSChallengeSaga() {
  const files = yield select(challengeFilesSelector);
  const { code, solution } = yield call(buildJSFromFiles, files);

  const consoleProxy = yield channel();
  yield fork(logToConsole, consoleProxy);
  const log = args => consoleProxy.put(args);
  testWorker.on('LOG', log);

  const testResults = yield call(executeTests, (testString, testTimeout) =>
    testWorker
      .execute({ script: solution + '\n' + testString, code }, testTimeout)
      .then(result => {
        testWorker.killWorker();
        return result;
      })
  );

  testWorker.remove('LOG', log);
  consoleProxy.close();
  return testResults;
}

function createTestFrame(state, ctx, proxyLogger) {
  return new Promise(resolve => {
    const frameTest = createTestFramer(document, state, resolve, proxyLogger);
    frameTest(ctx);
  });
}

function* ExecuteDOMChallengeSaga() {
  const state = yield select();
  const ctx = yield call(buildHtmlFromFiles, state);
  const consoleProxy = yield channel();
  yield fork(logToConsole, consoleProxy);

  yield call(createTestFrame, state, ctx, consoleProxy);
  // wait for a code execution on a "ready" event in jQuery challenges
  yield delay(100);

  const testResults = yield call(executeTests, (testString, testTimeout) =>
    Promise.race([
      runTestInTestFrame(document, testString),
      new Promise((_, reject) =>
        setTimeout(() => reject('timeout'), testTimeout)
      )
    ])
  );

  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);

  const testResults = yield call(executeTests, (testString, testTimeout) =>
    Promise.race([
      runTestInTestFrame(document, testString),
      new Promise((_, reject) =>
        setTimeout(() => reject('timeout'), testTimeout)
      )
    ])
  );

  consoleProxy.close();
  return testResults;
}

function* executeTests(testRunner) {
  const tests = yield select(challengeTestsSelector);
  const testResults = [];
  for (const { text, testString } of tests) {
    const newTest = { text, testString };
    try {
      const { pass, err } = yield call(testRunner, testString, testTimeout);<commit_msg>fix: pass proxyLogger to params<commit_after>import {
  put,
  select,
  call,
  takeLatest,
  takeEvery,
  fork
} from 'redux-saga/effects';
import { delay, channel } from 'redux-saga';

import {
  challengeMetaSelector,
  challengeTestsSelector,
  initConsole,
  updateConsole,
  initLogs,
  updateLogs,
  logsToConsole,
  updateTests,
  challengeFilesSelector
} from './';

import {
  buildJSFromFiles,
  buildHtmlFromFiles,
  buildBackendChallenge
} from '../utils/build';

import { challengeTypes } from '../../../../utils/challengeTypes';

import WorkerExecutor from '../utils/worker-executor';
import {
  createMainFramer,
  createTestFramer,
  runTestInTestFrame
} from '../utils/frame.js';

const testWorker = new WorkerExecutor('test-evaluator');
const testTimeout = 5000;

function* ExecuteChallengeSaga() {
  const consoleProxy = yield channel();
  try {
    const { js, bonfire, backend } = challengeTypes;
    const { challengeType } = yield select(challengeMetaSelector);

    yield put(initLogs());
    yield put(initConsole('// running tests'));
    yield fork(logToConsole, consoleProxy);

    let testResults;
    switch (challengeType) {
      case js:
      case bonfire:
        testResults = yield ExecuteJSChallengeSaga(consoleProxy);
        break;
      case backend:
        testResults = yield ExecuteBackendChallengeSaga(consoleProxy);
        break;
      default:
        testResults = yield ExecuteDOMChallengeSaga(consoleProxy);
    }

    yield put(updateTests(testResults));
    yield put(updateConsole('// tests completed'));
    yield put(logsToConsole('// console output'));
  } catch (e) {
    yield put(updateConsole(e));
  } finally {
    consoleProxy.close();
  }
}

function* logToConsole(channel) {
  yield takeEvery(channel, function*(args) {
    yield put(updateLogs(args));
  });
}

function* ExecuteJSChallengeSaga(proxyLogger) {
  const files = yield select(challengeFilesSelector);
  const { code, solution } = yield call(buildJSFromFiles, files);

  const log = args => proxyLogger.put(args);
  testWorker.on('LOG', log);

  try {
    return yield call(executeTests, (testString, testTimeout) =>
      testWorker
        .execute({ script: solution + '\n' + testString, code }, testTimeout)
        .then(result => {
          testWorker.killWorker();
          return result;
        })
    );
  } finally {
    testWorker.remove('LOG', log);
  }
}

function createTestFrame(state, ctx, proxyLogger) {
  return new Promise(resolve => {
    const frameTest = createTestFramer(document, state, resolve, proxyLogger);
    frameTest(ctx);
  });
}

function* ExecuteDOMChallengeSaga(proxyLogger) {
  const state = yield select();
  const ctx = yield call(buildHtmlFromFiles, state);

  yield call(createTestFrame, state, 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)
      )
    ])
  );
}

// TODO: use a web worker
function* ExecuteBackendChallengeSaga(proxyLogger) {
  const state = yield select();
  const ctx = yield call(buildBackendChallenge, state);

  yield call(createTestFrame, state, ctx, proxyLogger);

  return yield call(executeTests, (testString, testTimeout) =>
    Promise.race([
      runTestInTestFrame(document, testString),
      new Promise((_, reject) =>
        setTimeout(() => reject('timeout'), testTimeout)
      )
    ])
  );
}

function* executeTests(testRunner) {
  const tests = yield select(challengeTestsSelector);
  const testResults = [];
  for (const { text, testString } of tests) {
    const newTest = { text, testString };
    try {
      const { pass, err } = yield call(testRunner, testString, testTimeout);
{'fraction_non_alphanumeric': 0.12173802888269572, 'fraction_numerical': 0.00177349885989359, 'mean_word_length': 3.716248506571087, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4554457', 'n_tokens_mistral': 2356, 'n_tokens_neox': 2294, 'n_words': 582}