Skip to content

Fallback preprocessing: reconcile test #includes against a plain expansion - #1346

Merged
mkarlesky merged 11 commits into
next_versionfrom
fix/fallback-includes-expansion
Oct 17, 2026
Merged

mkarlesky merged 11 commits into
next_versionfrom
fix/fallback-includes-expansion

Conversation

@mkarlesky

Copy link
Copy Markdown
Member

Summary

In whole-run fallback (no -fdirectives-only, or :test_build ↳ :preprocess_force_fallback), stage 4 used to keep every #include the text scan found. That scan ignores #if and can't read an #include whose name is a macro. So:

Stage 4 now runs the full preprocessor tool (plain -E) over each test file in fallback, with stand-ins on the search path. It then reconciles against the files the expansion's line markers name, the same way directives-only output is used.

Fallback stays a best effort with no guarantees, and code, comments, logs and docs say so. A test file whose expansion fails takes the existing per-file path: its text-scan #includes, checked against GCC's bare list.

Analysis behind this (prototype results on GCC 14 and Apple Clang 15): https://claude.ai/artifact/LqCm4jSTjkN77DnYdxdHCV

Changes

  • Shared runner. Preprocessinator has one private run_preprocessor for its directives-only, fallback and Partial full-expansion runs. Two long methods now fit RuboCop's limits, and misplaced and stale comments are fixed.
  • Rename. TestDirectivesOnlyPreprocessinator is now TestIncludesPreprocessinator. A frozen MODES table holds what differs in fallback: tool, output path, generator, testable key, and extra outputs. Caching and line-marker dependency tracking are shared.
  • Output path. The fallback expansion is written to preprocess/files/<test>/fallback_expansion/ under the test file's own name, which the line-marker reader needs. Its path is recorded in testable.preprocess[:fallback_expansion]. Stage 11 and its [:directives_only] contract are untouched.
  • Reconciliation. It runs in both modes. The first pass sets mock search paths in both modes, so the expansion finds the stand-ins.
  • Messages. Startup notices and the new stage 4 warning call fallback best effort. Stage 4's sequencer drops an unused configurator collaborator.
  • Docs. PIPELINE.md and preprocess/README.md are updated. They also correct pass 1's flags (-M -MG) and a stale file reference.

Tests (7-step test-first)

  • Pinned first: the preprocessor command shapes, plus the fallback behavior that survives (stand-ins placed, a strict mis-cased mock in a branch that is on).
  • Red, then green, at each step:
    • unit specs for the builder, the generator, the stage 1 key, the fallback second pass, and fallback reconciliation;
    • two of the new unit examples ("registers no further output" and "does not mark fresh on failure") passed vacuously before the change, since nothing ran in fallback.
  • Integration (the main proof). spec/integration/test_includes_extractinator_spec.rb runs every scenario under both modes with shared expectations. That covers Case 1, the mis-cased mock, the macro-named mock, all three Case 2 variants, the computed #include, narrowing, stand-ins and the per-file failure. It adds the mixed real-and-mocked module and a check of where the fallback expansion lives. The ten fallback scenarios failed before reconciliation was enabled.
  • System, kept minimal.
    • conditional_mock_includes: the two fallback examples now build instead of stopping. They were red on the prior commit.
    • delta_builds: expects the new warning.
  • Runs:
    • units (4692) and integration (468) pass on Apple Clang 15, and integration passes on GCC 14 in Docker;
    • system specs pass in Docker (GCC 14): conditional_mock_includes, delta_builds, plugin_delta_builds, preprocessing_fallback_conditionals, preprocessing_locale, preprocessing_encoding, encoding_stress, partials_module_disambiguation, test_source_file_directive.
  • RuboCop: backlog 840 → 838, with no stage 4 file in it. lint:changed is clean.

Notes for review

  • One-time rebuild. Fallback projects rebuild once after upgrading: their search paths now carry reconciled mock directories, which changes later stages' meta.
  • Cost. One extra -E per stale test file in fallback, matching pass 2's count in normal mode.
  • Marker-stripping tools. A full preprocessor tool configured without line markers (-P) now affects fallback reconciliation as well as stage 11. That is the empty-list case tracked separately (report: https://claude.ai/artifact/TR6uCuBLbfdWz9oUNRPcVv).
  • Release docs. Changelog, KnownIssues and mkdocs edits will follow as uncommitted diffs after CI.

🤖 Generated with Claude Code

mkarlesky and others added 11 commits October 17, 2026 10:11
The directives-only and full preprocessor commands keep the file's own
search paths ahead of vendor paths. In whole-run fallback, stand-ins are
still placed and a mis-cased mock in a branch that is on still stops the
build. The integration harness can now force fallback per example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The directives-only and Partial full-expansion runs differed only in their
tool, so one private runner now builds and runs both commands. Each caller
keeps its own failure handling, and both now fit RuboCop's method length.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ator

Stage 4's second pass is about to run in fallback too, with a plain
preprocessor expansion in place of directives-only output, so its name
follows what it produces for the third pass instead of one tool flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without -fdirectives-only, a plain preprocessor expansion still records
every file it entered in its line markers. Preprocessinator can now write
one for a test file, kept apart from stage 11's full expansion and named
after the test file, as the line-marker extractor expects. Stage 1 gives
each test a place to record its path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without -fdirectives-only, the second pass now runs the full preprocessor
over each test file into its fallback expansion, cached and tracked as the
directives-only output is. One table holds what differs between the two
modes. Reconciliation still keeps the text-scan list in fallback, so no
test's #includes change yet. Stage 4's sequencer no longer needs the
configurator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In whole-run fallback, stage 4 kept every #include the text scan found, so
a mock in a branch that is off stopped the build or replaced the real
module, and a mock named by a macro was never generated. The third pass now
reconciles against the fallback expansion's line markers, as a best effort,
and the first pass sets mock search paths in both modes so the expansion
finds the stand-ins. A test file whose expansion fails takes the per-file
path: its text-scan #includes checked against GCC's bare list.

The integration specs now run every scenario in both modes, and the
fallback system examples build where they used to stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two MethodLength offenses in Preprocessinator are gone, taking the total
from 840 to 838.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mkarlesky
mkarlesky merged commit f1a7ccb into next_version Oct 17, 2026
@mkarlesky
mkarlesky deleted the fix/fallback-includes-expansion branch October 17, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant