Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

288 lines
9.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { getDb } from '../../src/database/index';
import { runDbMigrations } from '../../src/migrate';
import Eval from '../../src/models/eval';
import {
clearCountCache,
getCachedResultsCount,
getTotalResultRowCount,
} from '../../src/models/evalPerformance';
import { ResultFailureReason } from '../../src/types/index';
describe('evalPerformance', () => {
beforeAll(async () => {
await runDbMigrations();
});
beforeEach(async () => {
const db = await getDb();
await db.run('DELETE FROM eval_results');
await db.run('DELETE FROM evals_to_datasets');
await db.run('DELETE FROM evals_to_prompts');
await db.run('DELETE FROM evals_to_tags');
await db.run('DELETE FROM evals');
clearCountCache();
});
/**
* Helper to create an eval and add results for provider x test combinations.
* Returns the eval and the expected counts.
*/
async function createEvalWithResults(numProviders: number, numTests: number) {
const providers = Array.from({ length: numProviders }, (_, i) => ({ id: `provider-${i + 1}` }));
const tests = Array.from({ length: numTests }, (_, i) => ({ vars: { input: `test${i + 1}` } }));
const eval_ = await Eval.create(
{
providers,
prompts: ['Test prompt'],
tests,
},
[{ raw: 'Test prompt', label: 'Test prompt' }],
);
// Add results for each provider × test combination
for (let providerIdx = 0; providerIdx < numProviders; providerIdx++) {
for (let testIdx = 0; testIdx < numTests; testIdx++) {
await eval_.addResult({
description: `test-${providerIdx}-${testIdx}`,
promptIdx: 0,
testIdx,
testCase: { vars: { input: `test${testIdx + 1}` } },
promptId: 'test-prompt',
provider: { id: `provider-${providerIdx + 1}`, label: `Provider ${providerIdx + 1}` },
prompt: { raw: 'Test prompt', label: 'Test prompt' },
vars: { input: `test${testIdx + 1}` },
response: {
output: `response-${providerIdx}-${testIdx}`,
tokenUsage: { total: 10, prompt: 5, completion: 5, cached: 0 },
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: {
pass: true,
score: 1,
reason: 'Pass',
namedScores: {},
tokensUsed: { total: 10, prompt: 5, completion: 5, cached: 0 },
componentResults: [],
},
namedScores: {},
cost: 0.001,
metadata: {},
});
}
}
return {
eval_,
expectedDistinctCount: numTests,
expectedTotalRowCount: numProviders * numTests,
};
}
describe('getCachedResultsCount', () => {
it('should count distinct test indices (unique test cases)', async () => {
// Create an eval with 2 providers and 3 test cases
// This should produce 6 total results (2 providers × 3 tests)
// But only 3 distinct test indices
const { eval_, expectedDistinctCount } = await createEvalWithResults(2, 3);
// Should return 3 (distinct test indices), not 6 (total rows)
const count = await getCachedResultsCount(eval_.id);
expect(count).toBe(expectedDistinctCount);
});
it('should return 0 for an eval with no results', async () => {
const eval_ = await Eval.create(
{
providers: [{ id: 'provider-1' }],
prompts: ['Test prompt'],
tests: [{ vars: { input: 'test1' } }],
},
[{ raw: 'Test prompt', label: 'Test prompt' }],
);
const count = await getCachedResultsCount(eval_.id);
expect(count).toBe(0);
});
it('should cache the count result', async () => {
const { eval_ } = await createEvalWithResults(1, 1);
// First call should hit the database
const count1 = await getCachedResultsCount(eval_.id);
expect(count1).toBe(1);
// Second call should return cached result
const count2 = await getCachedResultsCount(eval_.id);
expect(count2).toBe(1);
// Clear cache and verify we can get fresh count
clearCountCache(eval_.id);
const count3 = await getCachedResultsCount(eval_.id);
expect(count3).toBe(1);
});
});
describe('getTotalResultRowCount', () => {
it('should count all result rows (including multiple per test)', async () => {
// Create an eval with 2 providers and 3 test cases
// This should produce 6 total result rows (2 providers × 3 tests)
const { eval_, expectedTotalRowCount } = await createEvalWithResults(2, 3);
// Should return 6 (total rows), not 3 (distinct test indices)
const count = await getTotalResultRowCount(eval_.id);
expect(count).toBe(expectedTotalRowCount);
});
it('should return 0 for an eval with no results', async () => {
const eval_ = await Eval.create(
{
providers: [{ id: 'provider-1' }],
prompts: ['Test prompt'],
tests: [{ vars: { input: 'test1' } }],
},
[{ raw: 'Test prompt', label: 'Test prompt' }],
);
const count = await getTotalResultRowCount(eval_.id);
expect(count).toBe(0);
});
it('should cache the count result', async () => {
const { eval_ } = await createEvalWithResults(1, 1);
// First call should hit the database
const count1 = await getTotalResultRowCount(eval_.id);
expect(count1).toBe(1);
// Second call should return cached result
const count2 = await getTotalResultRowCount(eval_.id);
expect(count2).toBe(1);
// Clear cache and verify we can get fresh count
clearCountCache(eval_.id);
const count3 = await getTotalResultRowCount(eval_.id);
expect(count3).toBe(1);
});
});
describe('count functions comparison', () => {
it('should return same count when 1 provider per test', async () => {
const { eval_ } = await createEvalWithResults(1, 5);
const distinctCount = await getCachedResultsCount(eval_.id);
const totalCount = await getTotalResultRowCount(eval_.id);
// With 1 provider, distinct count equals total count
expect(distinctCount).toBe(5);
expect(totalCount).toBe(5);
});
it('should return different counts when multiple providers per test', async () => {
const { eval_ } = await createEvalWithResults(3, 4);
const distinctCount = await getCachedResultsCount(eval_.id);
const totalCount = await getTotalResultRowCount(eval_.id);
// With 3 providers and 4 tests:
// - distinct count = 4 (unique test indices)
// - total count = 12 (3 providers × 4 tests)
expect(distinctCount).toBe(4);
expect(totalCount).toBe(12);
});
});
describe('cache invalidation on result write (issue #9348)', () => {
const makeResult = (_evalId: string, testIdx: number) =>
({
description: `test-${testIdx}`,
promptIdx: 0,
testIdx,
testCase: { vars: { input: `test${testIdx}` } },
promptId: 'test-prompt',
provider: { id: 'provider-1', label: 'Provider 1' },
prompt: { raw: 'Test prompt', label: 'Test prompt' },
vars: { input: `test${testIdx}` },
response: {
output: `response-${testIdx}`,
tokenUsage: { total: 0, prompt: 0, completion: 0, cached: 0 },
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 10,
gradingResult: {
pass: true,
score: 1,
reason: 'Pass',
namedScores: {},
tokensUsed: { total: 0, prompt: 0, completion: 0, cached: 0 },
componentResults: [],
},
namedScores: {},
cost: 0,
metadata: {},
}) as Parameters<
typeof import('../../src/models/evalResult')['default']['createFromEvaluateResult']
>[1];
it('getCachedResultsCount refreshes after createFromEvaluateResult (issue #9348)', async () => {
const eval_ = await Eval.create(
{ providers: [{ id: 'provider-1' }], prompts: ['Test prompt'], tests: [] },
[{ raw: 'Test prompt', label: 'Test prompt' }],
);
// Seed zero into cache before any inserts
const before = await getCachedResultsCount(eval_.id);
expect(before).toBe(0);
// Insert a result — this should bust the cache automatically
await eval_.addResult(makeResult(eval_.id, 0));
// Must see fresh count without any manual clearCountCache() call
const after = await getCachedResultsCount(eval_.id);
expect(after).toBe(1);
});
it('getTotalResultRowCount refreshes after createFromEvaluateResult (issue #9348)', async () => {
const eval_ = await Eval.create(
{ providers: [{ id: 'provider-1' }], prompts: ['Test prompt'], tests: [] },
[{ raw: 'Test prompt', label: 'Test prompt' }],
);
const before = await getTotalResultRowCount(eval_.id);
expect(before).toBe(0);
await eval_.addResult(makeResult(eval_.id, 0));
const after = await getTotalResultRowCount(eval_.id);
expect(after).toBe(1);
});
it('getCachedResultsCount refreshes after createManyFromEvaluateResult (issue #9348)', async () => {
const eval_ = await Eval.create(
{ providers: [{ id: 'provider-1' }], prompts: ['Test prompt'], tests: [] },
[{ raw: 'Test prompt', label: 'Test prompt' }],
);
const before = await getCachedResultsCount(eval_.id);
expect(before).toBe(0);
const EvalResult = (await import('../../src/models/evalResult')).default;
await EvalResult.createManyFromEvaluateResult(
[makeResult(eval_.id, 0), makeResult(eval_.id, 1)] as any,
eval_.id,
);
const after = await getCachedResultsCount(eval_.id);
expect(after).toBe(2);
});
});
});