Skip to content

Commit 59f054b

Browse files
committed
test(plpgsql-deparser): add comprehensive round-trip test for ALL generated fixtures
Add test that runs round-trip assertions on all 176 fixtures from plpgsql-generated/generated.json. This ensures the deparser produces semantically equivalent output for all canonical PL/pgSQL test cases. 16 fixtures are in a known-failing allowlist due to pre-existing issues: - Schema qualification loss (pg_catalog.pg_class%rowtype[] -> pg_class%rowtype[]) - Tagged dollar quote reconstruction ($tag$...$tag$ not supported) - Exception block handling issues The test will fail if any NEW fixtures start failing (regression detection) while allowing the known issues to be tracked and fixed incrementally. Also improved cleanPlpgsqlTree to normalize 'undefined vs missing' properties for more accurate AST comparison.
1 parent d8360b6 commit 59f054b

2 files changed

Lines changed: 77 additions & 9 deletions

File tree

packages/plpgsql-deparser/__tests__/plpgsql-deparser.test.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,74 @@ describe('PLpgSQLDeparser', () => {
3232
});
3333

3434
describe('round-trip tests using generated.json', () => {
35-
it('should round-trip plpgsql_domain fixtures', async () => {
36-
const entries = fixtureTestUtils.getTestEntries(['plpgsql_domain']);
35+
// Known failing fixtures due to pre-existing deparser issues:
36+
// - Schema qualification loss (pg_catalog.pg_class%rowtype[] -> pg_class%rowtype[])
37+
// - Tagged dollar quote reconstruction ($tag$...$tag$ not supported)
38+
// - Exception block handling issues
39+
// TODO: Fix these underlying issues and remove from allowlist
40+
const KNOWN_FAILING_FIXTURES = new Set([
41+
'plpgsql_varprops-13.sql',
42+
'plpgsql_trap-1.sql',
43+
'plpgsql_trap-2.sql',
44+
'plpgsql_trap-3.sql',
45+
'plpgsql_trap-4.sql',
46+
'plpgsql_trap-5.sql',
47+
'plpgsql_trap-6.sql',
48+
'plpgsql_trap-7.sql',
49+
'plpgsql_transaction-17.sql',
50+
'plpgsql_transaction-19.sql',
51+
'plpgsql_transaction-20.sql',
52+
'plpgsql_transaction-21.sql',
53+
'plpgsql_control-15.sql',
54+
'plpgsql_control-17.sql',
55+
'plpgsql_call-44.sql',
56+
'plpgsql_array-20.sql',
57+
]);
58+
59+
it('should round-trip ALL generated fixtures (excluding known failures)', async () => {
60+
// Get all fixtures without any filter - this ensures we test everything
61+
const entries = fixtureTestUtils.getTestEntries();
3762
expect(entries.length).toBeGreaterThan(0);
3863

64+
const failures: { key: string; error: string }[] = [];
65+
const unexpectedPasses: string[] = [];
66+
3967
for (const [key] of entries) {
40-
await fixtureTestUtils.runSingleFixture(key);
68+
const isKnownFailing = KNOWN_FAILING_FIXTURES.has(key);
69+
try {
70+
await fixtureTestUtils.runSingleFixture(key);
71+
if (isKnownFailing) {
72+
unexpectedPasses.push(key);
73+
}
74+
} catch (err) {
75+
if (!isKnownFailing) {
76+
failures.push({
77+
key,
78+
error: err instanceof Error ? err.message : String(err),
79+
});
80+
}
81+
}
4182
}
42-
});
83+
84+
// Report unexpected passes (fixtures that should be removed from allowlist)
85+
if (unexpectedPasses.length > 0) {
86+
console.log(`\nUnexpected passes (remove from KNOWN_FAILING_FIXTURES):\n${unexpectedPasses.join('\n')}`);
87+
}
88+
89+
// Fail if any non-allowlisted fixtures fail (regression detection)
90+
if (failures.length > 0) {
91+
const failureReport = failures
92+
.map(f => ` - ${f.key}: ${f.error}`)
93+
.join('\n');
94+
throw new Error(
95+
`${failures.length} NEW fixture failures (not in allowlist):\n${failureReport}`
96+
);
97+
}
98+
99+
// Report coverage stats
100+
const testedCount = entries.length - KNOWN_FAILING_FIXTURES.size;
101+
console.log(`\nRound-trip tested ${testedCount} of ${entries.length} fixtures (${KNOWN_FAILING_FIXTURES.size} known failures skipped)`);
102+
}, 120000); // 2 minute timeout for all fixtures
43103
});
44104

45105
describe('PLpgSQLDeparser class', () => {

packages/plpgsql-deparser/test-utils/index.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,19 +178,27 @@ export const transform = (obj: any, props: any): any => {
178178
copy = {};
179179
for (const attr in obj) {
180180
if (obj.hasOwnProperty(attr)) {
181+
let value: any;
181182
if (props.hasOwnProperty(attr)) {
182183
if (typeof props[attr] === 'function') {
183-
copy[attr] = props[attr](obj[attr]);
184+
value = props[attr](obj[attr]);
184185
} else if (props[attr].hasOwnProperty(obj[attr])) {
185-
copy[attr] = props[attr][obj[attr]];
186+
value = props[attr][obj[attr]];
186187
} else {
187-
copy[attr] = transform(obj[attr], props);
188+
value = transform(obj[attr], props);
188189
}
189190
} else {
190-
copy[attr] = transform(obj[attr], props);
191+
value = transform(obj[attr], props);
192+
}
193+
// Skip undefined values to normalize "missing vs present-but-undefined"
194+
if (value !== undefined) {
195+
copy[attr] = value;
191196
}
192197
} else {
193-
copy[attr] = transform(obj[attr], props);
198+
const value = transform(obj[attr], props);
199+
if (value !== undefined) {
200+
copy[attr] = value;
201+
}
194202
}
195203
}
196204
return copy;

0 commit comments

Comments
 (0)