test: port analyze_differential to pytest (#432) - #1161
Conversation
20 names, `missing: 0`, and no extras. `pgcolumnar.analyze()` writes through `pg_restore_attribute_stats`, which takes `VARIADIC "any"` and validates each argument's type at run time. A mistyped argument is not an error: the function warns, sets the argument to NULL and returns cleanly having stored nothing. A value-level suite cannot see that, because the function leaves kinds it was not given in place -- so a statistic we failed to write is still in the catalog wearing core's shape, and every value assertion reads core's work and reports it as ours. Core ANALYZE is therefore the oracle and the comparison is over SHAPE. The refusal carries the shell suite's name, which commandprompt#1131 made possible: on 15, 16 and 17 `analyze_differential.sh` declines through `check_skip`, which RECORDS the refusal as "the differential analyze path". The port declines under the same name and the run exits 67, joining `test_temporal` on those majors. ONE ARM STRONGER THAN THE ORIGINAL. The shell suite names "could not read the server major, so the gate below cannot be trusted" inside a `pgc_fail` that fires only when the version is unreadable. The port asserts it on every run, so the version gate below it is known to have been decided on a real number rather than on an empty string that compared less-than 18. Independent at every seam: the shell materialises an `ad_shape` table and pairs kind/operator/collation with `unnest ... WITH ORDINALITY` joined three ways, while this reads the five slots as three arrays and zips them in Python; the shell greps psql's output for WARNING while this registers a psycopg notice handler; frequencies are checked one value at a time through a parameterised count, so a failure names the value that disagreed. Proved by removal against `pgcolumnar--1.0-alpha4.sql`, each mutation asserted to have matched its anchor exactly once and to have been restored byte-identical; control 27 checks passed: mcvfreqs::real[] -> ::float8[] and without a WARNING ... got 10 want 0 frequency / non-null count every most-common value of i ... got 2 want 0 histogram_bounds -> typed NULL and a histogram for exactly ... got 0 want 4 `cluster_tests` 441 -> 442, derived by collection. `guard_tests` re-derived in the same run and did NOT move: 382. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RSw4qMHS7ByE7PY8Ns4cs
|
Verified rather than read, on both sides of the version gate, and the claim I went looking to break is the one that held hardest. The #1131 property, confirmed at RUNTIME and not by the static grader
Against the shell suite's own record on the same server: Same string, and UNRUN rather than PASS. The name arrives without the declaration being laundered into a pass, which is the trap the Worth noting because the UNRUN display line shows the reason code and detail, not the name — so reading the terminal output alone would not have told me this worked. The record is where the name lives. "One arm stronger" — measured on both majorsThe +1 is the same arm on both majors, and the spy output above shows exactly what it is: the version-read premise recorded as a PASS even on the major where everything below it declines. The original only fires that name inside a The oracle argument, which is the part I would have got wrong
Combined with IndependenceThe three seams differ in ways that fail differently, which is the test worth applying: The last one is not just independence, it is a better failure: the port names the offending value instead of reporting a count. And your reason for the difference is the honest one — the shell materialises because every Parity, re-derived hereNot a finding, recorded so nobody chases itMy first run of Approving once the two 🤖 Generated with Claude Code |
jdatcmd
left a comment
There was a problem hiding this comment.
Approving 91eb3385. 14 of 14 on this sha, non-green 0, CLEAN — including both suites legs.
The verification is in my comment above and stands unchanged: the #1131 name is emitted at RUNTIME as name='the differential analyze path' with verdict UNRUN, matching the shell's own record on PG 17; the extra arm is measured on both majors (shell 26 / port 27 on 18, shell 1 / port 2 on 17); parity re-derived here at 18 literal + 2 template, missing 0, no extras.
The oracle argument is the part that earns this: a mistyped argument to pg_restore_attribute_stats is a WARNING and a NULL, and untouched kinds stay in the catalog wearing core's shape — so a value assertion reads core's work and reports it as ours. Comparing shape against core ANALYZE is the right answer to a trap that only appears if you ask how the arm could pass while doing nothing.
🤖 Generated with Claude Code
Port of
analyze_differential.sh(#432). 20 names,missing: 0, no extras.Why this suite cannot use values as its oracle
pgcolumnar.analyze()writes throughpg_restore_attribute_stats, which takesVARIADIC "any"and validates each argument's type at run time. A mistypedargument is not an error. The function emits a WARNING, sets that argument to
NULL, and returns cleanly having stored nothing.
pg_restore_attribute_statsalso leaves kinds it was not given in place — correct,and fatal to attribution: a statistic we failed to write is still in the catalog
wearing core's shape, so every value assertion reads core's work and reports it as
ours. Core ANALYZE is therefore the oracle and the comparison is over SHAPE.
The refusal carries the shell suite's name (#1131)
On 15, 16 and 17 the shell suite declines through
check_skip, which RECORDS therefusal as "the differential analyze path". Until
expect.cannot_runcould carry aname, no port could emit that string and the pair reported
missing: 1howeverfaithful the rest was. The precondition — the server major — exists on both sides,
which is the case resolution 1 unblocks.
One arm stronger than the original
analyze_differential.shnames "could not read the server major, so the gate belowcannot be trusted" inside a
pgc_failthat fires only when the version isunreadable. The port asserts the same property on every run, under the same
name, so the gate below it is known to have been decided on a real number rather
than on an empty string that compared less-than 18. That is why the port runs 27
checks on 18 where the shell suite runs 26.
Independent at every seam
ad_shapetable,unnest ... WITH ORDINALITYjoined three waysgrep -c WARNINGover psql's outputThe shell materialises a table because every
q()opens its own connection. Theport holds one, so it does not need to.
The element type is deliberately not compared, on both sides.
pg_statistic.stavalues1is declaredanyarray, sopg_typeofreturns theconstant string
anyarrayfor every row ever stored — a probe built on it reportsevery slot as mismatched, core's own included.
Removal proof
Three mutations of
pgcolumnar--1.0-alpha4.sql. Each asserted that its anchormatched exactly once and that the file changed, and each was restored and
re-hashed byte-identical before the next ran. Control: 27 checks passed.
mcvfreqs::real[]→::float8[]and without a WARNING, which is how pg_restore_attribute_stats drops an argument, got 10 want 0every most-common value of i exists with exactly its stored frequency, got 2 want 0'histogram_bounds', hist::text→ a typed NULLand a histogram for exactly the four core gave one, got 0 want 4Cell B is the failure mode the suite is named for, and the notices it produced say
why a value oracle cannot see it:
Ten of them — five columns, twice each, because the pair rule drops
most_common_valsalong with the mistypedmost_common_freqs. The call stillsucceeded.
The five-major gate
The 67 is not new and the classification does not move.
test_temporalalreadydeclined on 15, 16 and 17 for the same reason before this branch; this port joins it
in the same three legs. The CI cluster job runs PG18 only, where the corpus is green.
cluster_tests441 → 442 andguard_tests382, both derived by collection onthe tree as rebased onto
c050f84, never by adding a delta.🤖 Generated with Claude Code
https://claude.ai/code/session_012RSw4qMHS7ByE7PY8Ns4cs