Skip to content

Commit 1d70758

Browse files
authored
test(compiler): cover every render input in the compiler differential (#1001)
* test(compiler): generate every render input in the differential seeds Add rendering feature blocks to the seeded generator: macros reading context, constants, enums and the environment, nested and typed-reference macro calls, macro-generated references with relaxed explicit-reference rules, table functions, variable, environment and hook-context interpolation, runtime placeholders, cursor intrinsics, macros in tests, scenarios, audits, functions and source expressions, enum-typed columns, model-private declarations, named hook arguments, and model, source and seed audits. The harness provides the fixed environment variable the generated projects read. * test(compiler): require render coverage in the compiler differential Classify CompileProjectInputs stage captures into render input kinds, crediting a kind only from rendered evidence: a template absent from the rendered SQL, a recorded usage, reference, grant, hook entry or attached audit. Encoded empty sets and mappings prove nothing. Kinds credited from a used macro's source text are reported as indirect. Add --require-render-coverage and require it in the per-PR compiler differential job. Share the capture readers with discovery coverage. * test(compiler): add a render failure corpus Add one failing project per reachable render, scope and attachment code (P005, P007, P008, P009, S006, S007, S008, S009, K012, C214 and the C216 warning) and P001 cases for macro, interpolation, reference and hook argument errors, each checked against its first error message. render_error_codes derives the code set from the compiler; the end-to-end test requires every code to be reached unless it is listed as unreachable with a reason. Share the base-project case builder across the corpus modules. * test(compiler): prove compile-input stage captures are complete Plan a generated project with every feature block and require its CompileProjectInputs capture to hold every field canonically, name every callable, and prove every required render kind. Perturb the native compile inputs to show the differential localises a render mismatch to the compile_project_inputs capture.
1 parent 908e1f0 commit 1d70758

36 files changed

Lines changed: 2848 additions & 302 deletions

‎.github/workflows/verify.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,8 @@ jobs:
293293
- name: Compare fixtures, examples, generated seeds and the failure corpus
294294
run: >-
295295
make compiler-differential
296-
COMPILER_DIFFERENTIAL_ARGS="--stage-captures --require-discovery-coverage"
296+
COMPILER_DIFFERENTIAL_ARGS="--stage-captures --require-discovery-coverage
297+
--require-render-coverage"
297298
298299
format_performance:
299300
name: Formatter performance guards

‎scripts/compiler_differential/_helpers/comparing/comparison.py‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,28 @@ def compare_engine_runs(*, project: str, left: EngineRun, right: EngineRun) -> l
7272
def diagnostic_codes(*, outcome: CommandOutcome, severity: str | None) -> tuple[str, ...]:
7373
"""Return a JSON compile report's diagnostic codes in order, optionally of one severity."""
7474

75+
return tuple(
76+
str(diagnostic.get("code"))
77+
for diagnostic in _diagnostics(outcome=outcome, severity=severity)
78+
)
79+
80+
81+
def first_diagnostic_message(*, outcome: CommandOutcome, severity: str) -> str | None:
82+
"""Return the message of a JSON compile report's first diagnostic of one severity."""
83+
84+
diagnostics: list[dict[str, object]] = _diagnostics(outcome=outcome, severity=severity)
85+
return str(diagnostics[0].get("message")) if diagnostics else None
86+
87+
88+
def _diagnostics(*, outcome: CommandOutcome, severity: str | None) -> list[dict[str, object]]:
7589
payload: dict[str, object] = as_json_object(_loads(outcome.stdout)) or {}
7690
diagnostics: object = payload.get("diagnostics")
77-
found: list[str] = []
91+
found: list[dict[str, object]] = []
7892
for raw_diagnostic in diagnostics if isinstance(diagnostics, list) else ():
7993
diagnostic: dict[str, object] | None = as_json_object(raw_diagnostic)
8094
if diagnostic is not None and (severity is None or diagnostic.get("severity") == severity):
81-
found.append(str(diagnostic.get("code")))
82-
return tuple(found)
95+
found.append(diagnostic)
96+
return found
8397

8498

8599
def _command_differences(

‎scripts/compiler_differential/_helpers/comparing/project_comparison.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
diagnostic_codes,
1212
)
1313
from scripts.compiler_differential._helpers.coverage.discovery import project_discovery_kinds
14+
from scripts.compiler_differential._helpers.coverage.render import project_render_kinds
1415
from scripts.compiler_differential._helpers.running.execution import run_engine
1516
from scripts.compiler_differential.constants import ERROR_SEVERITY, WARNING_SEVERITY
1617
from scripts.compiler_differential.models import (
@@ -50,6 +51,7 @@ def compare_project(*, project: CorpusProject, options: DifferentialOptions) ->
5051
engine=right_engine,
5152
options=options,
5253
)
54+
counted: bool = project.seed_coverage and options.stage_captures
5355
differences: list[Difference] = [
5456
*_timeout_differences(project=project, runs=(left, right)),
5557
*_corpus_differences(project=project, run=left),
@@ -61,9 +63,10 @@ def compare_project(*, project: CorpusProject, options: DifferentialOptions) ->
6163
seconds=time.monotonic() - started,
6264
discovered_kinds=(
6365
project_discovery_kinds(captures=left.captures, source_dir=source_dir)
64-
if project.discovery_coverage and options.stage_captures
66+
if counted
6567
else frozenset()
6668
),
69+
rendered_kinds=project_render_kinds(left.captures) if counted else frozenset(),
6770
)
6871

6972

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Build failure cases as small edits of the shared base project."""
2+
3+
from scripts.compiler_differential.constants import (
4+
FAILURE_BASE_CONFIG,
5+
FAILURE_BASE_FILES,
6+
FAILURE_CONFIG_PATH,
7+
FAILURE_MART_PATH,
8+
FAILURE_STAGING_PATH,
9+
)
10+
from scripts.compiler_differential.models import FailureCase
11+
12+
_MART_HEADER: str = 'MODEL (\n description "Order totals per customer",\n);\n\n'
13+
14+
15+
def failure_case(
16+
*,
17+
name: str,
18+
expected_code: str,
19+
files: dict[str, str],
20+
expected_warning_code: str | None = None,
21+
expected_message: str | None = None,
22+
) -> FailureCase:
23+
"""Return the base project with `files` added or replaced."""
24+
25+
return FailureCase(
26+
files={**FAILURE_BASE_FILES, **files},
27+
name=name,
28+
expected_code=expected_code,
29+
expected_warning_code=expected_warning_code,
30+
expected_message=expected_message,
31+
)
32+
33+
34+
def staging_files(sql: str) -> dict[str, str]:
35+
"""Replace the staging model."""
36+
37+
return {FAILURE_STAGING_PATH: sql}
38+
39+
40+
def mart_body_files(body: str) -> dict[str, str]:
41+
"""Replace the mart model's query, keeping its header."""
42+
43+
return {FAILURE_MART_PATH: _MART_HEADER + body}
44+
45+
46+
def config_files(extra: str) -> dict[str, str]:
47+
"""Append `extra` to the base project configuration."""
48+
49+
return {FAILURE_CONFIG_PATH: FAILURE_BASE_CONFIG + extra}

‎scripts/compiler_differential/_helpers/corpus/corpus.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,5 @@ def _seed_project(seed: int) -> CorpusProject:
143143
succeeding_commands=generated.succeeding_commands,
144144
),
145145
writer=generated.write,
146-
discovery_coverage=True,
146+
seed_coverage=True,
147147
)

0 commit comments

Comments
 (0)