Repository navigation
Compile a source beside its inline-mocked header from an isolated copy - #1328
Merged
Merged
Conversation
mkarlesky
force-pushed
the
bug/inline-mock-sibling-source
branch
from
October 8, 2026 23:02
2666a93 to
c5e1c2c
Compare
Ceedling copies a file alone into a fresh directory wherever C's quoted extraction and sibling-header isolation each did this through FileWrapper staging calls, with the include-by-name scan kept in TestBuildExecutor. QuoteIncludeIsolator now owns staging, scoped release, mapping a copy's path back to its original, an optional #line directive that keeps a copy reporting its original location, and the include-by-name scan. Both existing uses move onto it with no change in behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under :treat_inlines :include, CMock writes an inline-stripped copy of a mocked header first on the search path. A source sharing a directory with the real header still found the real one through C's quote-include rule, so its inline bodies ran in place of the mock and could crash the test. Such a source now compiles from a copy staged alone by QuoteIncludeIsolator. A #line directive keeps diagnostics, __FILE__, debug info and coverage naming the original. The original's directory follows every other search path, and the dependency file is restored to name the original before registration. Plugins and logs still see the original as :source while Generator receives the copy as compile_source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mkarlesky
force-pushed
the
bug/inline-mock-sibling-source
branch
from
October 8, 2026 23:37
c5e1c2c to
1448077
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes
:cmock↳:treat_inlines: :includefor sources that share a directory with the header their test mocks. That is a very common layout: a source and its headers together insrc/. In it, the source under test still ran the real inline bodies instead of the mock, and a hardware-touching inline could crash the test. The fault-injection stress rig found this in its own first fixture. It may also be the crash reported in #1240.Cause
C's
#include "..."checks the including file's own directory before any-Ipath. Under:treat_inlines: :include, CMock writes an inline-stripped copy of each mocked header into the mocks directory, first on the search path. A source beside the real header never consults that path, so#include "board.h"insrc/gpio.cfindssrc/board.hdirectly.Ceedling already handles this rule for headers.
isolate_sibling_headersstages a sibling header that a test file's compile reaches. The source under test is not a header, and it never appears in the test file's.dfile, so nothing redirected it.Fix
A qualifying source compiles from a copy staged alone in a throwaway directory. With no real header beside the copy, its
#includereaches CMock's stripped copy through the search path. The decision is made up front: mocks enabled,:treat_inlines: :include, a real-header mock rather than a Partial mock, a source other than the test file, the same directory as the mocked header, and a quoted#includeof that header by name.Four details keep the copy invisible to everything but header lookup:
#line 1 "<original path>"opens the copy. Diagnostics,__FILE__, debug info and coverage all name the original at its original line numbers. Verified with gcov: the report readsFile 'src/reader.c', and gcovr's report is unaffected..dfile is rewritten to name the original before registration. Dependency tracking never records a file that disappears once the compile ends, so a second build skips the object as usual.Generator#generate_object_file_cgainscompile_source:for the command line.arg_hash[:source], which compile_commands_json_db, Bullseye, gcov and command_hooks read, is unchanged.Each staging lives for one compile and writes nothing to the testable, so it never touches the two-pass barrier in
stage_build_objects.Consolidation
Staging a file away from its neighbors already happened in two places, and this fix needed a third. The first commit extracts
QuoteIncludeIsolator(lib/ceedling/quote_include_isolator.rb) with no change in behavior. It owns staging, scoped release, mapping a copy's path back to its original, the optional#linedirective, and the include-by-name scan. Bare-includes extraction and sibling-header isolation move onto it, replacingFileWrapper#stage_isolated_copiesand#remove_isolated_copiesand the executor's private scan. The second commit is the fix, built on the isolator. Each commit passes the full unit and integration tiers on its own.Changes
lib/ceedling/quote_include_isolator.rb: new, wired inlib/ceedling/objects.yml.lib/ceedling/test_invoker/test_build_executor.rb:inline_mocked_sibling_headerdecides isolation, andcompile_isolated_sourcecompiles the copy. Sibling-header isolation now stages through the isolator.lib/ceedling/preprocess/preprocessinator_includes_handler.rb: bare-includes extraction stages throughQuoteIncludeIsolator#within.lib/ceedling/generators/generator.rb:compile_source:.lib/ceedling/file_wrapper.rb: the two staging methods are removed.lib/ceedling/test_invoker/HEADER_COLLISIONS.md,lib/ceedling/preprocess/README.md: the isolator as the mechanism's single home, plus a new section on sources under test.Tests
spec/units/quote_include_isolator_spec.rb, new: staging, release on raise,#lineescaping for Windows paths, path restoration in plain, make-escaped and binary text, and the include scan.spec/integration/quote_include_isolator_spec.rb, renamed fromfile_wrapper_isolated_copies_spec.rb: real files plus a real gcc compile of a staged copy. It reaches a shadow header ahead of the real one beside the original, resolves the original's other includes, reports the original as__FILE__, and leaves a dependency file free of the copy.spec/units/test_build_executor_spec.rb: the isolated compile,.drestoration before registration and on failure, the notice, unchanged staleness meta, and an ordinary compile for each non-qualifying case.spec/units/generators/generator_spec.rb, new: the command line receivescompile_sourcewhile plugin hooks receive:source.spec/system/sibling_header_collision_spec.rb: a source beside a crash-on-purpose inline header passes, and a second build leaves the object fresh. Without the fix, the same test segfaults.The existing executor and includes-handler specs now use the isolator in place of the FileWrapper staging stubs. Units pass 3992/0 and integration 298/0, along with the include-handling, preprocessing, Partials and gcov system specs.
Not verified
Bullseye could not be run here. Its
covcwraps the real command line, so whether its coverage data names the copy or follows#lineis unconfirmed. A source reaching the real header only through a private header of its own is not redirected. That is the same one-level limit sibling-header isolation documents.