fix: conditionally log non-assertion errors (JS)

console.logs and errors are only reported during the first evaluation of
the user's code.  This is because the code is evaluated for each test,
but the logs will not change between the build phases of the tests.

Errors thrown during testing (except failing assertions) are always
reported. This is to inform the user that their code is faulty, rather
than that it does not meet the challenge's requirements.
This commit is contained in:
Oliver Eyton-Williams
2019-11-04 12:42:19 +01:00
committed by mrugesh
parent 9a97d639f5
commit 04d2de96df
3 changed files with 41 additions and 17 deletions

View File

@ -24,22 +24,37 @@ const __utils = (() => {
} }
const oldLog = self.console.log.bind(self.console); const oldLog = self.console.log.bind(self.console);
self.console.log = function proxyConsole(...args) { function proxyLog(...args) {
logs.push(args.map(arg => '' + JSON.stringify(arg, replacer)).join(' ')); logs.push(args.map(arg => '' + JSON.stringify(arg, replacer)).join(' '));
if (logs.join('\n').length > MAX_LOGS_SIZE) { if (logs.join('\n').length > MAX_LOGS_SIZE) {
flushLogs(); flushLogs();
} }
return oldLog(...args); return oldLog(...args);
}; }
// unless data.type is truthy, this sends data out to the testRunner
function postResult(data) { function postResult(data) {
flushLogs(); flushLogs();
self.postMessage(data); self.postMessage(data);
} }
function logToBoth(err) {
if (!(err instanceof chai.AssertionError)) {
// report to both the browser and the fcc consoles, discarding the
// stack trace via toString as it only useful to debug the site, not a
// specific challenge.
console.log(err.toString());
}
}
const toggleLogging = on => {
self.console.log = on ? proxyLog : () => {};
};
return { return {
postResult, postResult,
oldLog logToBoth,
toggleLogging
}; };
})(); })();
@ -50,6 +65,8 @@ self.onmessage = async e => {
const assert = chai.assert; const assert = chai.assert;
// Fake Deep Equal dependency // Fake Deep Equal dependency
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
__utils.toggleLogging(e.data.firstTest);
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
try { try {
let testResult; let testResult;
@ -65,14 +82,11 @@ self.onmessage = async e => {
if (__userCodeWasExecuted) { if (__userCodeWasExecuted) {
// rethrow error, since test failed. // rethrow error, since test failed.
throw err; throw err;
} else if (e.data.testString) {
// report errors to dev console if tests are running (since some
// challenges should pass with code that throws errors)
__utils.oldLog(err);
} else {
// user is editing code, so both consoles should report errors
console.log(err.toString());
} }
// log build errors
__utils.logToBoth(err);
// the tests may not require working code, so they are evaluated even if
// the user code does not get executed.
testResult = eval(e.data.testString); testResult = eval(e.data.testString);
} }
/* eslint-enable no-eval */ /* eslint-enable no-eval */
@ -83,15 +97,17 @@ self.onmessage = async e => {
pass: true pass: true
}); });
} catch (err) { } catch (err) {
// report errors that happened during testing, so the user learns of
// execution errors and not just build errors.
__utils.toggleLogging(true);
__utils.logToBoth(err);
// postResult flushes the logs and must be called after logging is finished.
__utils.postResult({ __utils.postResult({
err: { err: {
message: err.message, message: err.message,
stack: err.stack stack: err.stack
} }
}); });
if (!(err instanceof chai.AssertionError)) {
console.error(err);
}
} }
}; };

View File

@ -92,10 +92,18 @@ function* buildChallengeData(challengeData) {
function* executeTests(testRunner, tests, testTimeout = 5000) { function* executeTests(testRunner, tests, testTimeout = 5000) {
const testResults = []; const testResults = [];
for (const { text, testString } of tests) { for (let i = 0; i < tests.length; i++) {
const { text, testString } = tests[i];
const newTest = { text, testString }; const newTest = { text, testString };
// only the last test outputs console.logs to avoid log duplication.
const firstTest = i === 1;
try { try {
const { pass, err } = yield call(testRunner, testString, testTimeout); const { pass, err } = yield call(
testRunner,
testString,
testTimeout,
firstTest
);
if (pass) { if (pass) {
newTest.pass = true; newTest.pass = true;
} else { } else {

View File

@ -99,9 +99,9 @@ function getJSTestRunner({ build, sources }, proxyLogger) {
const testWorker = createWorker(testEvaluator, { terminateWorker: true }); const testWorker = createWorker(testEvaluator, { terminateWorker: true });
return (testString, testTimeout) => { return (testString, testTimeout, firstTest = true) => {
return testWorker return testWorker
.execute({ build, testString, code, sources }, testTimeout) .execute({ build, testString, code, sources, firstTest }, testTimeout)
.on('LOG', proxyLogger).done; .on('LOG', proxyLogger).done;
}; };
} }