Files
promptfoo--promptfoo/test/util/writeToFileFs.test.ts
T
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

67 lines
2.5 KiB
TypeScript

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { JsonlFileWriter } from '../../src/util/exportToFile/writeToFile';
// Unmocked counterpart to writeToFile.test.ts: pins the real-stream assumptions the
// writer relies on — createWriteStream's default emitClose means 'close' always fires
// (close() can never hang), and the file descriptor is released before close() resolves
// so the file and its directory can be removed immediately (the Windows ENOTEMPTY race).
describe('JsonlFileWriter (real filesystem)', () => {
let dir: string;
beforeEach(() => {
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptfoo-jsonl-'));
});
afterEach(() => {
fs.rmSync(dir, { recursive: true, force: true });
});
it('releases the file descriptor before close() resolves', async () => {
const file = path.join(dir, 'out.jsonl');
const writer = new JsonlFileWriter(file);
await writer.write({ a: 1 });
await writer.write({ b: 2 });
await writer.close();
const lines = fs.readFileSync(file, 'utf-8').trim().split('\n');
expect(lines.map((line) => JSON.parse(line))).toEqual([{ a: 1 }, { b: 2 }]);
// Non-recursive removal fails (EBUSY/ENOTEMPTY on Windows) if the fd is still held.
fs.rmSync(file);
fs.rmdirSync(dir);
fs.mkdirSync(dir);
});
it('resolves a second close() call on an already-closed stream', async () => {
const writer = new JsonlFileWriter(path.join(dir, 'out.jsonl'));
await writer.write({ a: 1 });
await writer.close();
await expect(writer.close()).resolves.toBeUndefined();
});
it('leaves the destination untouched when nothing was written', async () => {
const file = path.join(dir, 'never.jsonl');
await expect(new JsonlFileWriter(file).close()).resolves.toBeUndefined();
expect(fs.existsSync(file)).toBe(false);
});
it('rejects close() when the stream failed mid-run', async () => {
const file = path.join(dir, 'broken.jsonl');
const writer = new JsonlFileWriter(file);
await writer.write({ a: 1 });
// Destroy with an error to simulate an fd failure between writes; the recorded
// pre-flush error must reject close() rather than be swallowed.
(writer as unknown as { writeStream: { destroy: (error: Error) => void } }).writeStream.destroy(
new Error('EBADF: bad file descriptor'),
);
await expect(writer.close()).rejects.toThrow(
`Failed to close JSONL output ${file}: EBADF: bad file descriptor`,
);
});
});