Skip to content

Commit

Permalink
Merge pull request #4 from asyncjs/verifier-fixes
Browse files Browse the repository at this point in the history
Verifier fixes
  • Loading branch information
almost authored Jul 3, 2024
2 parents f930f98 + d3af4b3 commit c5ef41c
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 58 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@ results

npm-debug.log
node_modules/
/.vscode/*
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": []
}
9 changes: 0 additions & 9 deletions src/challenges/c2-sort-the-numbers/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import type { Challenge } from '../types.js';

class ArrayNoSort extends Array {
public override sort() {
return ['allowed', 'no', 'sort'] as unknown as this;
}
}

const challenge: Challenge<[arr: readonly number[]], readonly number[]> = {
title: 'Sort The Numbers',
description:
Expand All @@ -20,9 +14,6 @@ const challenge: Challenge<[arr: readonly number[]], readonly number[]> = {
output: [1, 1, 2, 10, 12, 16, 22, 23, 33, 34, 65, 150, 250, 300],
},
],
context: {
Array: ArrayNoSort,
},
};

export default challenge;
2 changes: 1 addition & 1 deletion src/challenges/c5-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const challenge: Challenge<[input: [number, number]], number> = {
},
],
context: {
eval: () => 42,
eval: () => "eval is not the answer you're looking for",
},
assertRules: (playString) => {
if (playString.includes('+')) {
Expand Down
1 change: 1 addition & 0 deletions src/challenges/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export interface Challenge<
assertRules?: (playString: string) => void;
context?: Record<string, unknown>;
timeLimitMinutes?: number;
runBefore?: () => void;
}
11 changes: 2 additions & 9 deletions src/game/game-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import * as url from 'url';

import type { VerifyJob } from './verifier.js';
import * as game from './game.js';
import { challenges } from '../challenges/index.js';
import { Primitive } from '../challenges/types.js';

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

Expand All @@ -17,15 +15,10 @@ export const verify = (
) => {
const currentGame = game.getOrError();
const verifier = child_process.fork(path.resolve(__dirname, 'verifier.ts'));
const challenge = challenges.find((ch) => ch.key === currentGame.key);

if (!challenge) {
throw new Error('Challenge not found');
}

const job: VerifyJob<readonly Primitive[], Primitive> = {
const job: VerifyJob = {
file,
challenge,
key: currentGame.key,
};

verifier.send(job);
Expand Down
26 changes: 7 additions & 19 deletions src/game/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,21 @@ export const formatValue = (value: unknown): string => {
return `Unknown value: ${value}`;
};

export const formatTypeAndValue = (value: unknown, actual: Primitive) => {
export const formatTypeAndValue = (
value: Primitive | readonly unknown[] | Record<string, unknown>,
isResult: Primitive
) => {
if (value === null) {
return 'null';
}

if (typeof value === 'undefined') {
return 'undefined';
}

if (typeof value === 'function') {
return 'function';
return `${isResult ? 'different ' : ''}function`;
}

if (Array.isArray(value)) {
return (actual ? 'different ' : '') + 'array of ' + value.length + ' items';
}

if (typeof value === 'string') {
return (
(actual ? 'different ' : '') + 'string of ' + value.length + ' chars'
);
}

if (typeof value === 'object') {
return (actual ? 'different ' : '') + 'object';
return `array ${formatValue(value)}`;
}

const digits = value.toString().replace(/[^0-9]/g, '').length;
return (actual ? 'different ' : '') + `${digits} digit number`;
return `${typeof value} ${formatValue(value)}`;
};
50 changes: 31 additions & 19 deletions src/game/verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,35 @@ import lodash from 'lodash';
import { readFileSync } from 'fs';
import { runInNewContext } from 'vm';

import type { Challenge, Primitive } from '../challenges/types.js';
import { formatTypeAndValue } from './utils.js';
import { formatTypeAndValue, formatValue } from './utils.js';
import { challenges } from '../challenges/index.js';
import { Primitive } from '../challenges/types.js';

export interface VerifyJob<
A extends readonly Primitive[],
R extends Primitive,
> {
export interface VerifyJob {
file: string;
challenge: Omit<Challenge<A, R>, 'example'> & {
key: string;
solution: string;
example: string;
};
key: string;
}

const hasKey = <K extends string>(
obj: object,
key: K
): obj is { [P in K]: unknown } => key in obj;

process.on('message', (entry: VerifyJob<readonly Primitive[], Primitive>) => {
process.on('message', (entry: VerifyJob) => {
try {
const challenge = challenges.find((ch) => ch.key === entry.key);

if (!challenge) {
throw new Error('Challenge not found');
}

const script = readFileSync(entry.file, 'utf8');
const context: { play?: unknown; module: { exports: unknown } } = {
...challenge.context,
module: {
exports: {},
exports: {
runBefore: challenge.runBefore,
},
},
};

Expand All @@ -38,6 +41,15 @@ process.on('message', (entry: VerifyJob<readonly Primitive[], Primitive>) => {

runInNewContext(toRun, context);

if (
typeof context.module.exports === 'object' &&
!!context.module.exports &&
hasKey(context.module.exports, 'runBefore') &&
typeof context.module.exports.runBefore === 'function'
) {
context.module.exports.runBefore();
}

const play =
typeof context.module.exports === 'function'
? context.module.exports
Expand All @@ -56,7 +68,9 @@ process.on('message', (entry: VerifyJob<readonly Primitive[], Primitive>) => {
}

try {
entry.challenge.assertions.forEach((assertion) => {
challenge.assertRules?.(script);

challenge.assertions.forEach((assertion) => {
const result = play(...assertion.input);

const expected =
Expand All @@ -75,20 +89,18 @@ process.on('message', (entry: VerifyJob<readonly Primitive[], Primitive>) => {
)} but received ${formatTypeAndValue(
result,
true
)} when supplied with ${formatTypeAndValue(assertion.input, false)}`
)} when supplied with arguments: ${assertion.input.map((input) => formatValue(input)).join(', ')}`
);
}
});

entry.challenge.assertRules?.(entry.challenge.solution);
} catch (err) {
return process.send?.({
err:
typeof err === 'string'
? err
: err instanceof Error
? err.message
: 'Unknown error',
: `Unknown error ${err}`,
valid: false,
});
}
Expand All @@ -99,7 +111,7 @@ process.on('message', (entry: VerifyJob<readonly Primitive[], Primitive>) => {
console.error('Verifier failed with error:', err);

process.send?.({
err: 'Your script contains an error',
err: `Your script contains an error: ${err}`,
valid: false,
});
}
Expand Down

0 comments on commit c5ef41c

Please sign in to comment.