Skip to content

Commit 00a742e

Browse files
authored
add CSV export tests for all chart types (#48)
1 parent 4d47a11 commit 00a742e

1 file changed

Lines changed: 178 additions & 4 deletions

File tree

packages/app/src/lib/csv-export-helpers.test.ts

Lines changed: 178 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,59 @@ describe('inferenceChartToCsv', () => {
121121
expect(rows).toHaveLength(0);
122122
});
123123

124+
it('only includes data matching selected precisions when pre-filtered (mirrors ChartDisplay export)', () => {
125+
const fp4Point = makePoint({ hwKey: 'h100-sxm-sglang', precision: 'fp4' });
126+
const fp8Point = makePoint({ hwKey: 'h100-sxm-sglang', precision: 'fp8' });
127+
const allData = [fp4Point, fp8Point];
128+
129+
// Simulate ChartDisplay.tsx onExportCsv filter
130+
const activeHwTypes = new Set(['h100-sxm-sglang']);
131+
const selectedPrecisions = ['fp4'];
132+
const visibleData = allData.filter(
133+
(d) => activeHwTypes.has(d.hwKey as string) && selectedPrecisions.includes(d.precision),
134+
);
135+
136+
const { headers, rows } = inferenceChartToCsv(visibleData, 'llama-3.1-405b', '1k/1k');
137+
expect(rows).toHaveLength(1);
138+
expect(rows[0][headers.indexOf('Precision')]).toBe('fp4');
139+
});
140+
141+
it('filters by both GPU and precision (mirrors ChartDisplay export)', () => {
142+
const data = [
143+
makePoint({ hwKey: 'h100-sxm-sglang', precision: 'fp4' }),
144+
makePoint({ hwKey: 'h100-sxm-sglang', precision: 'fp8' }),
145+
makePoint({ hwKey: 'b200-sxm-sglang', precision: 'fp4' }),
146+
makePoint({ hwKey: 'b200-sxm-sglang', precision: 'fp8' }),
147+
];
148+
149+
const activeHwTypes = new Set(['h100-sxm-sglang']);
150+
const selectedPrecisions = ['fp4'];
151+
const visibleData = data.filter(
152+
(d) => activeHwTypes.has(d.hwKey as string) && selectedPrecisions.includes(d.precision),
153+
);
154+
155+
const { headers, rows } = inferenceChartToCsv(visibleData, 'llama-3.1-405b', '1k/1k');
156+
expect(rows).toHaveLength(1);
157+
expect(rows[0][headers.indexOf('Hardware Key')]).toBe('h100-sxm-sglang');
158+
expect(rows[0][headers.indexOf('Precision')]).toBe('fp4');
159+
});
160+
161+
it('includes multiple precisions when all are selected', () => {
162+
const data = [
163+
makePoint({ hwKey: 'h100-sxm-sglang', precision: 'fp4' }),
164+
makePoint({ hwKey: 'h100-sxm-sglang', precision: 'fp8' }),
165+
];
166+
167+
const activeHwTypes = new Set(['h100-sxm-sglang']);
168+
const selectedPrecisions = ['fp4', 'fp8'];
169+
const visibleData = data.filter(
170+
(d) => activeHwTypes.has(d.hwKey as string) && selectedPrecisions.includes(d.precision),
171+
);
172+
173+
const { rows } = inferenceChartToCsv(visibleData, 'llama-3.1-405b', '1k/1k');
174+
expect(rows).toHaveLength(2);
175+
});
176+
124177
it('uses empty string for missing optional fields', () => {
125178
// Minimal point — most AggDataEntry fields are optional via Partial
126179
const data = [
@@ -153,7 +206,7 @@ describe('inferenceChartToCsv', () => {
153206
});
154207
});
155208

156-
describe('reliabilityChartToCsv', () => {
209+
describe('reliabilityChartToCsv (mirrors ReliabilityChartDisplay export)', () => {
157210
it('exports reliability data with correct headers and values', () => {
158211
const data = [
159212
{ model: 'h100-sxm', modelLabel: 'H100 SXM', successRate: 99.5, n_success: 199, total: 200 },
@@ -179,9 +232,22 @@ describe('reliabilityChartToCsv', () => {
179232
expect(headers).toHaveLength(5);
180233
expect(rows).toHaveLength(0);
181234
});
235+
236+
it('exports all visible GPUs from chartData (no extra filtering needed)', () => {
237+
// ReliabilityChartDisplay passes chartData directly — no precision/GPU filter
238+
const chartData = [
239+
{ model: 'h100-sxm', modelLabel: 'H100 SXM', successRate: 99.5, n_success: 199, total: 200 },
240+
{ model: 'a100-sxm', modelLabel: 'A100 SXM', successRate: 95.0, n_success: 95, total: 100 },
241+
];
242+
243+
const { rows } = reliabilityChartToCsv(chartData);
244+
expect(rows).toHaveLength(2);
245+
expect(rows[0][0]).toBe('H100 SXM');
246+
expect(rows[1][0]).toBe('A100 SXM');
247+
});
182248
});
183249

184-
describe('evaluationChartToCsv', () => {
250+
describe('evaluationChartToCsv (mirrors EvaluationChartDisplay export)', () => {
185251
const makeEvalPoint = (
186252
overrides: Partial<{
187253
configLabel: string;
@@ -245,9 +311,49 @@ describe('evaluationChartToCsv', () => {
245311
expect(rows[0][headers.indexOf('Min Score')]).toBe('');
246312
expect(rows[0][headers.indexOf('Max Score')]).toBe('');
247313
});
314+
315+
it('exports all chartData entries directly (no extra filtering needed)', () => {
316+
// EvaluationChartDisplay passes chartData directly from context
317+
const data = [
318+
makeEvalPoint({ hwKey: 'h100-sxm-vllm', precision: 'fp8' }),
319+
makeEvalPoint({ hwKey: 'b200-sxm-sglang', precision: 'fp4' }),
320+
];
321+
322+
const { headers, rows } = evaluationChartToCsv(data);
323+
expect(rows).toHaveLength(2);
324+
expect(rows[0][headers.indexOf('Hardware Key')]).toBe('h100-sxm-vllm');
325+
expect(rows[1][headers.indexOf('Hardware Key')]).toBe('b200-sxm-sglang');
326+
expect(rows[0][headers.indexOf('Precision')]).toBe('fp8');
327+
expect(rows[1][headers.indexOf('Precision')]).toBe('fp4');
328+
});
329+
330+
it('includes Benchmark column reflecting the selected eval type (pre-filtered by context)', () => {
331+
// EvaluationContext filters rawData by selectedBenchmark before building chartData,
332+
// so all rows in the export share the same benchmark value
333+
const data = [
334+
makeEvalPoint({ benchmark: 'mmlu', hwKey: 'h100-sxm-vllm' }),
335+
makeEvalPoint({ benchmark: 'mmlu', hwKey: 'b200-sxm-sglang' }),
336+
];
337+
338+
const { headers, rows } = evaluationChartToCsv(data);
339+
expect(headers).toContain('Benchmark');
340+
expect(rows[0][headers.indexOf('Benchmark')]).toBe('mmlu');
341+
expect(rows[1][headers.indexOf('Benchmark')]).toBe('mmlu');
342+
});
343+
344+
it('only contains data for one benchmark at a time (context filters by selectedBenchmark)', () => {
345+
// Simulates that context already filtered to only 'humaneval' — no 'mmlu' rows leak through
346+
const data = [
347+
makeEvalPoint({ benchmark: 'humaneval', hwKey: 'h100-sxm-vllm' }),
348+
makeEvalPoint({ benchmark: 'humaneval', hwKey: 'b200-sxm-sglang' }),
349+
];
350+
351+
const { headers, rows } = evaluationChartToCsv(data);
352+
expect(rows.every((r) => r[headers.indexOf('Benchmark')] === 'humaneval')).toBe(true);
353+
});
248354
});
249355

250-
describe('calculatorChartToCsv', () => {
356+
describe('calculatorChartToCsv (mirrors ThroughputCalculatorDisplay export)', () => {
251357
it('exports calculator results with target interactivity', () => {
252358
const results = [
253359
{
@@ -318,9 +424,48 @@ describe('calculatorChartToCsv', () => {
318424
expect(headers).toHaveLength(14);
319425
expect(rows).toHaveLength(0);
320426
});
427+
428+
it('exports results with label resolver (mirrors ThroughputCalculatorDisplay export)', () => {
429+
// ThroughputCalculatorDisplay uses a getLabel that resolves hwKey → display name
430+
const results = [
431+
{
432+
resultKey: 'h100-sxm-sglang',
433+
hwKey: 'h100-sxm-sglang',
434+
precision: 'FP8',
435+
value: 1200,
436+
cost: 0.52,
437+
concurrency: 16,
438+
},
439+
{
440+
resultKey: 'b200-sxm-sglang',
441+
hwKey: 'b200-sxm-sglang',
442+
precision: 'FP4',
443+
value: 2000,
444+
cost: 0.35,
445+
concurrency: 32,
446+
},
447+
];
448+
449+
const labelMap: Record<string, string> = {
450+
'h100-sxm-sglang': 'H100 SXM (SGLang)',
451+
'b200-sxm-sglang': 'B200 SXM (SGLang)',
452+
};
453+
454+
const { headers, rows } = calculatorChartToCsv(
455+
results,
456+
125,
457+
(hwKey) => labelMap[hwKey] ?? hwKey,
458+
);
459+
expect(rows).toHaveLength(2);
460+
expect(rows[0][headers.indexOf('GPU')]).toBe('H100 SXM (SGLang)');
461+
expect(rows[0][headers.indexOf('Precision')]).toBe('FP8');
462+
expect(rows[0][headers.indexOf('Cost per Million Total Tokens ($)')]).toBe(0.52);
463+
expect(rows[1][headers.indexOf('GPU')]).toBe('B200 SXM (SGLang)');
464+
expect(rows[1][headers.indexOf('Precision')]).toBe('FP4');
465+
});
321466
});
322467

323-
describe('historicalTrendToCsv', () => {
468+
describe('historicalTrendToCsv (mirrors HistoricalTrendsDisplay export)', () => {
324469
it('flattens trend lines into rows with GPU labels', () => {
325470
const trendLines = new Map([
326471
[
@@ -378,4 +523,33 @@ describe('historicalTrendToCsv', () => {
378523
expect(headers).toHaveLength(8);
379524
expect(rows).toHaveLength(0);
380525
});
526+
527+
it('exports with dynamic metric label and target interactivity (mirrors HistoricalTrendsDisplay export)', () => {
528+
// HistoricalTrendsDisplay passes currentYLabel and targetInteractivity
529+
const trendLines = new Map([
530+
[
531+
'h100-sxm-sglang',
532+
[
533+
{ date: '2025-01-10', value: 800, x: 50 },
534+
{ date: '2025-01-15', value: 950, x: 50 },
535+
],
536+
],
537+
]);
538+
539+
const lineConfigs = [{ id: 'h100-sxm-sglang', label: 'H100 SXM (SGLang)', precision: 'fp8' }];
540+
541+
const { headers, rows } = historicalTrendToCsv(
542+
trendLines,
543+
lineConfigs,
544+
'Cost per Million Tokens ($)',
545+
50,
546+
);
547+
548+
expect(headers).toContain('Cost per Million Tokens ($)');
549+
expect(headers).toContain('Target Interactivity (tok/s/user)');
550+
expect(rows).toHaveLength(2);
551+
expect(rows[0][headers.indexOf('Cost per Million Tokens ($)')]).toBe(800);
552+
expect(rows[0][headers.indexOf('Target Interactivity (tok/s/user)')]).toBe(50);
553+
expect(rows[1][headers.indexOf('Date')]).toBe('2025-01-15');
554+
});
381555
});

0 commit comments

Comments
 (0)