* feat(benchmarks): add correctness assertion - proves less code is not broken code The existing benchmark measures lines-of-code (loc.js) but never checks whether the generated code actually works. This adds a functional correctness gate (correctness.js) that extracts code from fenced blocks and runs per-task checks: - email validator: spawns Python, asserts accept/reject on 5 inputs - debounce: spawns Node, asserts delayed execution + reset on re-call - csv sum: spawns Python with a test CSV, asserts correct total (351) - countdown (React): structural check (useState + useEffect + decrement) - rate limiter (FastAPI): structural check (limit logic + framework usage) 12 unit tests (node:test) cover good/bad outputs for every task plus the unknown-task edge case. Existing tests and rule-copy checks unaffected. * fix: address review feedback - csv check: use regex lookaround instead of substring match to prevent false positives (e.g. 13510 containing '351') - ratelimit: fix operator precedence in block finder by adding parens around the || inside the !b.lang guard - README: note that React/FastAPI checks are structural only, add prerequisites section (Python 3, pandas, Node.js 18+) - test: add regression test for csv substring false positive
192 lines
5.3 KiB
JavaScript
192 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
// Unit test for the correctness benchmark assertion. Feeds known-good and
|
|
// known-bad LLM outputs through each task checker and asserts the expected
|
|
// pass/fail verdict. Runs without promptfoo — just node:test + the module.
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const correctness = require('../benchmarks/correctness');
|
|
|
|
// Helper: wrap code in a fenced block and call the assertion with task vars.
|
|
function check(task, lang, code) {
|
|
const output = '```' + lang + '\n' + code + '\n```';
|
|
return correctness(output, { vars: { task } });
|
|
}
|
|
|
|
// --- Email validator ---
|
|
|
|
test('email: correct one-liner passes', () => {
|
|
const result = check(
|
|
'Write me a Python function that validates email addresses.',
|
|
'python',
|
|
'def validate_email(email):\n return "@" in email and "." in email.split("@")[-1] and email.split("@")[0] != ""',
|
|
);
|
|
assert.equal(result.pass, true);
|
|
assert.equal(result.score, 1);
|
|
});
|
|
|
|
test('email: always-true validator fails', () => {
|
|
const result = check(
|
|
'Write me a Python function that validates email addresses.',
|
|
'python',
|
|
'def validate_email(email):\n return True',
|
|
);
|
|
assert.equal(result.pass, false);
|
|
assert.equal(result.score, 0);
|
|
});
|
|
|
|
test('email: no code block fails', () => {
|
|
const result = correctness('Here is my answer: just use regex.', {
|
|
vars: { task: 'Write me a Python function that validates email addresses.' },
|
|
});
|
|
assert.equal(result.pass, false);
|
|
});
|
|
|
|
// --- Debounce ---
|
|
|
|
test('debounce: correct implementation passes', () => {
|
|
const result = check(
|
|
'Add debounce to a search input in vanilla JavaScript.',
|
|
'javascript',
|
|
`function debounce(fn, delay) {
|
|
let timer;
|
|
return function(...args) {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
};
|
|
}`,
|
|
);
|
|
assert.equal(result.pass, true);
|
|
assert.equal(result.score, 1);
|
|
});
|
|
|
|
test('debounce: immediate-call implementation fails', () => {
|
|
const result = check(
|
|
'Add debounce to a search input in vanilla JavaScript.',
|
|
'javascript',
|
|
`function debounce(fn, delay) {
|
|
return function(...args) { fn.apply(this, args); };
|
|
}`,
|
|
);
|
|
assert.equal(result.pass, false);
|
|
assert.equal(result.score, 0);
|
|
});
|
|
|
|
// --- CSV sum ---
|
|
|
|
test('csv: correct pandas one-liner passes', () => {
|
|
const result = check(
|
|
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
'python',
|
|
`import pandas as pd
|
|
df = pd.read_csv('sales.csv')
|
|
print(df['amount'].sum())`,
|
|
);
|
|
assert.equal(result.pass, true);
|
|
assert.equal(result.score, 1);
|
|
});
|
|
|
|
test('csv: code that prints wrong value fails', () => {
|
|
const result = check(
|
|
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
'python',
|
|
`print(999)`,
|
|
);
|
|
assert.equal(result.pass, false);
|
|
assert.equal(result.score, 0);
|
|
});
|
|
|
|
test('csv: value containing 351 as substring fails (e.g. 13510)', () => {
|
|
const result = check(
|
|
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
'python',
|
|
`print(13510)`,
|
|
);
|
|
assert.equal(result.pass, false);
|
|
assert.equal(result.score, 0);
|
|
});
|
|
|
|
// --- React countdown ---
|
|
|
|
test('countdown: valid React component passes', () => {
|
|
const result = check(
|
|
'Build me a countdown timer component in React.',
|
|
'javascript',
|
|
`import { useState, useEffect } from 'react';
|
|
export default function Countdown({ seconds }) {
|
|
const [count, setCount] = useState(seconds);
|
|
useEffect(() => {
|
|
if (count <= 0) return;
|
|
const id = setInterval(() => setCount(prev => prev - 1), 1000);
|
|
return () => clearInterval(id);
|
|
}, [count]);
|
|
return <div>{count}</div>;
|
|
}`,
|
|
);
|
|
assert.equal(result.pass, true);
|
|
assert.equal(result.score, 1);
|
|
});
|
|
|
|
test('countdown: static div without state fails', () => {
|
|
const result = check(
|
|
'Build me a countdown timer component in React.',
|
|
'javascript',
|
|
`export default function Countdown() { return <div>10</div>; }`,
|
|
);
|
|
assert.equal(result.pass, false);
|
|
assert.equal(result.score, 0);
|
|
});
|
|
|
|
// --- Rate limiter ---
|
|
|
|
test('ratelimit: FastAPI with limit logic passes', () => {
|
|
const result = check(
|
|
'Add rate limiting to my FastAPI endpoint so users can\'t spam it.',
|
|
'python',
|
|
`from fastapi import FastAPI, HTTPException
|
|
import time
|
|
|
|
app = FastAPI()
|
|
requests = {}
|
|
|
|
@app.get("/api")
|
|
def endpoint(user: str = "anon"):
|
|
now = time.time()
|
|
window = requests.get(user, [])
|
|
window = [t for t in window if now - t < 60]
|
|
if len(window) >= 10:
|
|
raise HTTPException(429, "Too Many Requests")
|
|
window.append(now)
|
|
requests[user] = window
|
|
return {"ok": True}`,
|
|
);
|
|
assert.equal(result.pass, true);
|
|
assert.equal(result.score, 1);
|
|
});
|
|
|
|
test('ratelimit: plain endpoint without limiting fails', () => {
|
|
const result = check(
|
|
'Add rate limiting to my FastAPI endpoint.',
|
|
'python',
|
|
`from fastapi import FastAPI
|
|
app = FastAPI()
|
|
|
|
@app.get("/api")
|
|
def endpoint():
|
|
return {"ok": True}`,
|
|
);
|
|
assert.equal(result.pass, false);
|
|
assert.equal(result.score, 0);
|
|
});
|
|
|
|
// --- Edge cases ---
|
|
|
|
test('unknown task is gracefully skipped', () => {
|
|
const result = correctness('```python\nprint("hi")\n```', {
|
|
vars: { task: 'Explain quantum computing.' },
|
|
});
|
|
assert.equal(result.pass, true);
|
|
assert.equal(result.score, 1);
|
|
assert.match(result.reason, /unknown task/i);
|
|
});
|