From d10fd61595ee9d8f97df4628349ec305c4db537f Mon Sep 17 00:00:00 2001 From: MayerTim Date: Wed, 10 Jun 2026 20:48:43 +0200 Subject: [PATCH 1/2] test(formatter): polish corpus runner checks --- test/runFormatterCorpusTests.js | 80 ++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/test/runFormatterCorpusTests.js b/test/runFormatterCorpusTests.js index 1e39cb5..6b75ee7 100644 --- a/test/runFormatterCorpusTests.js +++ b/test/runFormatterCorpusTests.js @@ -69,6 +69,10 @@ function toCrLf(text) { return text.replace(/\r\n|\r|\n/gu, '\n').replace(/\n/gu, '\r\n'); } +function readFixture(fixturePath) { + return fs.readFileSync(fixturePath, 'utf8'); +} + function findCorpusFixturePairingIssues(directory) { return listSqlFixtureFiles(directory).flatMap((fixturePath) => { const relativeFixturePath = getRelativeFixturePath(fixturePath); @@ -95,9 +99,27 @@ function findCorpusFixturePairingIssues(directory) { }); } -const inputFixtures = listInputFixtures(corpusRoot); +function listFixturePairs() { + return listInputFixtures(corpusRoot).map((inputPath) => ({ + dialect: getDialectForFixture(inputPath), + expectedPath: getExpectedFixturePath(inputPath), + inputPath, + name: getFixtureName(inputPath), + })); +} + +function assertCorpusFormatting({ dialect, expected, fixtureName, input, variant }) { + const result = formatSql(input, dialect, defaultOptions); + const idempotentResult = formatSql(expected, dialect, defaultOptions); + const testName = variant ? `${fixtureName} (${variant})` : fixtureName; + + assert.equal(result.text, expected, `${testName} did not format to the expected output.`); + assert.equal(idempotentResult.text, expected, `${testName} expected output is not idempotent.`); +} -if (inputFixtures.length === 0) { +const fixturePairs = listFixturePairs(); + +if (fixturePairs.length === 0) { throw new Error(`No formatter corpus fixtures found under ${corpusRoot}.`); } @@ -105,41 +127,25 @@ runTest('formatter corpus fixtures are paired', () => { assert.deepEqual(findCorpusFixturePairingIssues(corpusRoot), []); }); -for (const inputPath of inputFixtures) { - const expectedPath = getExpectedFixturePath(inputPath); - const fixtureName = getFixtureName(inputPath); - - runTest(`formatter corpus: ${fixtureName}`, () => { - const input = fs.readFileSync(inputPath, 'utf8'); - const expected = fs.readFileSync(expectedPath, 'utf8'); - const dialect = getDialectForFixture(inputPath); - const result = formatSql(input, dialect, defaultOptions); - const idempotentResult = formatSql(expected, dialect, defaultOptions); - - assert.equal(result.text, expected, `${fixtureName} did not format to the expected output.`); - assert.equal( - idempotentResult.text, - expected, - `${fixtureName} expected output is not idempotent.`, - ); +for (const fixture of fixturePairs) { + runTest(`formatter corpus: ${fixture.name}`, () => { + assertCorpusFormatting({ + dialect: fixture.dialect, + expected: readFixture(fixture.expectedPath), + fixtureName: fixture.name, + input: readFixture(fixture.inputPath), + }); }); } -runTest('formatter corpus preserves CRLF line endings', () => { - for (const inputPath of inputFixtures) { - const expectedPath = getExpectedFixturePath(inputPath); - const fixtureName = getFixtureName(inputPath); - const input = toCrLf(fs.readFileSync(inputPath, 'utf8')); - const expected = toCrLf(fs.readFileSync(expectedPath, 'utf8')); - const dialect = getDialectForFixture(inputPath); - const result = formatSql(input, dialect, defaultOptions); - const idempotentResult = formatSql(expected, dialect, defaultOptions); - - assert.equal(result.text, expected, `${fixtureName} did not preserve CRLF output.`); - assert.equal( - idempotentResult.text, - expected, - `${fixtureName} expected CRLF output is not idempotent.`, - ); - } -}); +for (const fixture of fixturePairs) { + runTest(`formatter corpus CRLF: ${fixture.name}`, () => { + assertCorpusFormatting({ + dialect: fixture.dialect, + expected: toCrLf(readFixture(fixture.expectedPath)), + fixtureName: fixture.name, + input: toCrLf(readFixture(fixture.inputPath)), + variant: 'CRLF', + }); + }); +} From 811f55a5e9a551c96a7e6bf002f42a71863348bc Mon Sep 17 00:00:00 2001 From: MayerTim Date: Wed, 10 Jun 2026 20:48:50 +0200 Subject: [PATCH 2/2] docs(formatter): document corpus runner safeguards --- docs/DEVELOPMENT.md | 9 +++++++++ test/corpus/README.md | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 89504b7..fbfc93f 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -171,6 +171,15 @@ format(input) === expected format(expected) === expected ``` +The same checks run against synthetic CRLF variants of every fixture pair. This protects line-ending stability without storing CRLF fixture files in Git: + +```text +format(CRLF input) === CRLF expected +format(CRLF expected) === CRLF expected +``` + +The runner rejects unpaired or unsupported corpus SQL files so ZIP overlay workflows cannot leave stale fixtures silently ignored. Corpus SQL files must be named as `*.input.sql` or `*.expected.sql` pairs. + When adding a fixture: - use sanitized SQL that can safely live in the public repository diff --git a/test/corpus/README.md b/test/corpus/README.md index a47b76a..3661baa 100644 --- a/test/corpus/README.md +++ b/test/corpus/README.md @@ -3,9 +3,16 @@ Corpus fixtures are representative SQL samples that document current formatter behavior. Each fixture has an `.input.sql` file and a matching `.expected.sql` file. -The corpus runner checks two things for every fixture pair: +The corpus runner checks four things for every fixture pair: 1. formatting the input produces the expected output 2. formatting the expected output leaves it unchanged +3. formatting a synthetic CRLF version of the input produces the CRLF expected output +4. formatting a synthetic CRLF version of the expected output leaves it unchanged + +The runner also fails when corpus SQL files are not named as fixture pairs. Use only: + +- `*.input.sql` +- `*.expected.sql` Use sanitized, public-safe SQL only. These fixtures are regression tests, not product roadmap notes.