Fix macro redefinition and unit test failures caused by shared platform.h leak#11746
Merged
sensei-hacker merged 2 commits intoJul 23, 2026
Conversation
…t tests target/common.h is now reachable from every unit test translation unit (platform.h started including it for MAVLink test support), so its unconditional baseline feature macros collide with the same macros several tests already set via their own -D flags, and with src/test/unit/target.h's own copies. Guard the baseline block and the test stub's duplicates with #ifndef so whichever definition comes first wins instead of warning. The redefinition also silently enabled gimbal_serial.c's headtracker vtable in the unit test build while its function bodies stayed compiled out under GIMBAL_UNIT_TEST, causing a link failure. Make the vtable's guard match the guard already used for the function definitions. M_Ef additionally collides with glibc's own GNU-extension constant of the same name, pulled in transitively via <cmath>; guard it too.
Contributor
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
time_unittest, rcdevice_unittest, and telemetry_hott_unittest declared their own usTicks/micros() mocks using types that only happened to match drivers/time.h's real declarations because USE_64BIT_TIME was never reachable in the unit test build before. Now that it legitimately is, correct each mock to match drivers/time.h: usTicks is always uint32_t (a raw tick count, not derived from timeUs_t), and micros() returns timeUs_t. gps_null_port_unittest links only io/gps.c with a deliberately minimal feature set, but USE_GPS_FIX_ESTIMATION, USE_GPS_PROTO_MSP, USE_GPS_PROTO_DRONECAN, and USE_SIMULATOR now leak in unconditionally from target/common.h, enabling code paths that reference production globals this test never links. Exclude them for GPS_NULL_PORT_UNIT_TEST, the test's own existing marker macro, the same way GIMBAL_UNIT_TEST already excludes unrelated code elsewhere. osd_unittest has the same problem in displayport_msp_osd.c, whose entire body compiles in unconditionally once USE_OSD/USE_MSP_OSD leak in; exclude it for OSD_UNIT_TEST the same way. Separately, displayport_msp_dji_compat.h already has an OSD_UNIT_TEST fallback for isDJICompatibleVideoSystem, but its outer guard didn't route to it directly, so it took the real branch instead and referenced osdConfig(). Route to the existing fallback directly. The CMakeLists entry that was trying to reach the same fallback via a typo'd DISABLE_MSP_BF_COMPAT (matches nothing) is removed rather than corrected to the real macro name, since actually defining DISABLE_MSP_DJI_COMPAT would exclude a struct field that settings.yaml references unconditionally - a separate dormant bug, left untouched.
|
Test firmware build ready — commit Download firmware for PR #11746 243 targets built. Find your board's
|
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.
Fix unit test CI build failure on maintenance-10.x (macro leak from shared platform.h)
Summary
CI's
testjob (cmake -DTOOLCHAIN=none .. && ninja check) fails to build onmaintenance-10.x.gimbal_serial_unittestfails to link, and several other unit tests print "macro redefined" warnings along the way. This PR fixes the link failure and eliminates every redefinition warning, verified with zero regressions on production/hardware builds.Scope note: fixing the reported failure unmasked 5 more pre-existing, unrelated test failures that were always broken but never reached, because
ninja's default fail-fast behavior stopped scheduling new work at the first failure (gimbal_serial_unittest, at build step 44/122). Those 5 turned out to be fixable within reasonable scope too (see "Fix, part 2" below) — all 17 unit test targets now pass, 196/196 gtest cases,ninja checkexits 0.Root cause
Two commits in the same MAVLink PR series (same author), merged back-to-back onto
maintenance-10.x:053c3977d6("Split MAVLink telemetry into modules with multi-port runtime") added#include "target/common.h"tosrc/main/telemetry/telemetry.h.be9aaa3b82("Add MAVLink unit tests for the multi-port core"), its direct child, added the same include tosrc/test/unit/platform.h:+#include "target/common.h" #include "target.h"platform.his included by every unit test translation unit, not justmavlink_unittest.cc.target/common.hunconditionally#defines a large baseline feature set.be9aaa3b82's include collided with several tests' own explicit-Dfeature macros (harmless-value but noisy "redefined" warnings), and in one case silently changed which code compiles:src/main/io/gimbal_serial.c's vtable initializer was guarded by:but the actual definitions of those two functions are additionally gated by
#ifndef GIMBAL_UNIT_TEST. Beforebe9aaa3b82,USE_HEADTRACKER_SERIALwas never defined in the unit test build, so the reference and the (absent) definitions agreed — nothing to link. Afterbe9aaa3b82,target/common.hunconditionally definesUSE_HEADTRACKER_SERIALtoo, so the vtable reference compiles in while the definitions stay compiled out:target/common.h's inclusion inplatform.his load-bearing, not removable. I confirmed this empirically: removing it breaks settings-code-generation (settings.rb, run for every unit test target viaenable_settings()) for 14 of 17 targets. Tracing further, this is because053c3977d6is also what settings-gen now silently depends on —settings.rb'scheck_conditions()builds one combined probe file (platform.h+ every settings group header, insettings.yamlorder) and only evaluates#if defined(MACRO)conditions at the very end, so it sees a macro as "on" if it's defined anywhere in that file.resolve_types()instead probes each struct inline, at that struct's own position in the same header sequence, so a macro only counts if something earlier already defined it. Oncetelemetry.h(late in the include order) started pulling intarget/common.h,check_conditions()correctly saw feature macros as defined, butresolve_types()— probing structs from headers earlier in the list, e.g.sensors/gyro.h— didn't yet have them defined, and choked on fields that "should" exist (gyroConfig_t has no member named 'dynamicGyroNotchQ', etc.).platform.hincludingtarget/common.hfirst (viabe9aaa3b82) papers over this by making every macro visible before any group header is reached. This ordering bug insettings.rbis real, pre-existing, and separate from this PR's fix — it's being tracked as its own follow-up (see below), not touched here.Fix
src/main/io/gimbal_serial.c— added the missing!defined(GIMBAL_UNIT_TEST)condition to the vtable's#ifguard, matching the guard already used for the function definitions a few dozen lines down. This is the fix for the actual link failure.src/main/target/common.h— wrapped the entire unconditional "always-on baseline" block (~90 bare, presence-only#define USE_X-style feature-toggle macros) in#ifndef X / #define X / #endifguards, rather than patching only the handful of macros that happened to surface as CI warnings. These are toggle-only flags with no competing values, so the guard is a safe no-op wherever the macro isn't already defined — true for every real hardware target. I deliberately did not guard value-bearing macros (SCHEDULER_DELAY_LIMIT 10,NAV_MAX_WAYPOINTS 120,USE_BOOTLOG 2048, etc.) since blindly keeping "whichever definition came first" could silently mask a genuine value conflict rather than a harmless duplicate toggle. Verified structurally (#if/#ifndefcount matches#endifcount exactly; the set of macro names actually defined is byte-identical before/after) and via build (SITL, MATEKH743, KAKUTEF7, and MATEKF411 — a flash-constrained target that exercises the#undefblock at the bottom of this file — all produce byte-identical/flash-identical output to the unmodified base commit, zero new warnings).src/main/common/maths.h— wrappedM_Efin#ifndef M_Ef. Unrelated to the above: INAV's ownM_Efcollides with glibc's GNU-extensionM_Ef(pulled in via<cmath>inmaths_unittest.cc), and reproduces identically onrelease/9.1andmastertoday. The two values agree to fullfloatprecision, so this is a safe no-op guard.src/test/unit/target.h— the unit test harness's own fake-target stub (test-only, not shared with any real hardware target), included byplatform.hright aftertarget/common.h. It independently redefinesUSE_GPS_PROTO_UBLOXandUSE_TELEMETRY, colliding withcommon.h's definitions the same way — wrapped both in#ifndefguards for consistency.Result: zero "macro redefined" warnings anywhere in
ninja check's output (down from 23+),gimbal_serial_unittestbuilds, links, and passes, and 11 other previously-passing unit tests confirmed still passing with no regressions.(Two small follow-ups from code review:
USE_EZ_TUNE/USE_ADAPTIVE_FILTERat the end ofcommon.hwere missed in the initial sweep and are now guarded too for consistency; a one-line comment was added explaining whyM_Efis guarded whenM_PIf/M_LN2faren't.)Fix, part 2: the 5 unmasked failures
Once
gimbal_serial_unittestno longer blocked the build, 5 more targets failed — confirmed pre-existing on unmodifiedmaintenance-10.x, not caused by this PR, and structurally different from the redefinition bug above (a macro becoming newly enabled for a test that was never built to handle it, not a macro being redefined — no#ifndefguard can fix this class of bug by construction). All 5 are now fixed:time_unittest,rcdevice_unittest,telemetry_hott_unittest(compile errors) —drivers/time.hdeclaresusTicksas a fixeduint32_t(a raw hardware tick counter, unrelated toUSE_64BIT_TIME, which only widens the derivedtimeUs_tmicrosecond counter). Each test file's own mock had drifted from that:time_unittest.ccdeclaredextern timeUs_t usTicksinstead ofuint32_t, andrcdevice_unittest.cc/telemetry_hott_unittest.cceach defined their ownmicros()mock hardcoded to returnuint32_tinstead oftimeUs_t(drivers/time.h's actual declared return type). BeforeUSE_64BIT_TIMEwas reachable in unit tests,timeUs_tsilently defaulted touint32_t, so these mismatches were invisible — the types happened to coincide.USE_64BIT_TIMEis genuinely, deliberately always-on incommon.h(not part of the leak — this was already latent, just never exercised in the test harness before). Fixed each mock to matchdrivers/time.h's real signatures.gps_null_port_unittest(link errors) — this test'sdependslist inCMakeLists.txtis deliberately minimal (io/gps.conly, with onlyUSE_GPS_PROTO_UBLOXexplicitly set), reflecting its narrow original intent (test only null-port/proto-selection behavior).USE_GPS_FIX_ESTIMATION,USE_GPS_PROTO_MSP,USE_GPS_PROTO_DRONECAN, andUSE_SIMULATORall now leak in unconditionally fromcommon.h, enabling extra code branches ingps.cthat reference production globals (posControl,baro,positionEstimationConfig(),rMat, etc.) this minimal test was never built to link. This test already has its own marker macro (GPS_NULL_PORT_UNIT_TEST), following the same convention asGIMBAL_UNIT_TEST— but it wasn't referenced anywhere ingps.cyet. Added&& !defined(GPS_NULL_PORT_UNIT_TEST)to the 10 relevant guard sites (verified: a plain-Ucompiler flag does not work here, sincecommon.h's own#ifndef-guarded definitions re-establish the macro regardless — the fix has to be a source-level guard). One furtherUSE_GPS_FIX_ESTIMATIONoccurrence, aSTATE(GPS_ESTIMATED_FIX)flag read with no unlinked-symbol reference, was deliberately left untouched.osd_unittest(link errors, two independent bugs):displayport_msp_osd.c's entire body is gated by one outer#if defined(USE_OSD) && defined(USE_MSP_OSD)guard — neither macro is in this test's own-Dlist, both leak in fromcommon.h, so the entire real DJI/MSP-displayport driver compiles in (~30 undefined symbols:armingFlags,cliMode,mspSerialPushPort,bitArray*, etc.), none of which is in the test's minimaldependslist (io/osd_utils.c,io/displayport_msp_osd.c,common/typeconversion.c). Added&& !defined(OSD_UNIT_TEST)to that guard — same pattern as everywhere else, correctly makes the file an empty translation unit for this test again.Independently:
osd_utils.c'sosdFormatCentiNumber(the function actually under test) callsisDJICompatibleVideoSystem(osdConfig()), defined indisplayport_msp_dji_compat.h. That header already has a dedicated#ifdef OSD_UNIT_TESTfallback (isDJICompatibleVideoSystem(osdConfigPtr) (true), never evaluating its argument) — but the header's outer guard didn't excludeOSD_UNIT_TESTdirectly, so it took the real branch instead, referencingosdConfig()/osdConfig_System. Added&& !defined(OSD_UNIT_TEST)to that outer guard, routing to the existing test fallback.(
src/test/unit/CMakeLists.txtpreviously tried to reach this same fallback a different way — definingDISABLE_MSP_BF_COMPAT, which is a typo; the real macro checked by the header isDISABLE_MSP_DJI_COMPAT. I deliberately did not fix the typo to the "correct" name: doing so would defineDISABLE_MSP_DJI_COMPATfor real, which excludesosdConfig_t.highlight_djis_missing_characters— a fieldsettings.yamlreferences completely unconditionally, no matchingcondition:key. That's a separate, dormant, pre-existing bug (currently unreachable, since no real target ever definesDISABLE_MSP_DJI_COMPATeither) that I don't want to trigger here. The header-guard fix above solves the original problem without needing that macro defined at all, so the typo'd flag was simply removed fromCMakeLists.txtrather than "corrected.")Result:
ninja checknow exits 0. All 17 unit test targets pass, 196/196 gtest cases, zero regressions, zero new warnings anywhere.What I deliberately did not change
src/test/unit/CMakeLists.txtstill explicitly declares macros likeUSE_ADSB,USE_SERIAL_GIMBAL,USE_HEADTRACKER,USE_TELEMETRYper-test, even thoughtarget/common.h's now-guarded definitions make most of these redundant today. Left as-is because: (a) not all of them are actually redundant —USE_TELEMETRY_MAVLINKonmavlink_unittestis real, sincecommon.honly defines it whenMCU_FLASH_SIZE > 512, which isn't set for unit test builds; and (b) the commit message onbe9aaa3b82says "later stack PRs re-add their own test sections" for themavlink_multiport2suite, so editing these lines now risks conflicting with in-flight follow-on work for no functional benefit.Testing
cmake -DTOOLCHAIN=none .. && ninja check(the exact CI command): exits 0. All 17 unit test targets build, link, and pass — 196/196 gtest cases. Zero "redefined" warnings anywhere in the build.a914d2dbe2).MCU_FLASH_SIZE <= 512): clean build, and directly verified via a targeted probe that the#undef USE_SERIALRX_SPEKTRUM/#undef USE_TELEMETRY_SRXLblock at the bottom ofcommon.hstill correctly un-defines macros that were set via the new#ifndefguard pattern.gps.c/displayport_msp_osd.c/displayport_msp_dji_compat.hguard additions specifically verified inert for production: none ofGPS_NULL_PORT_UNIT_TEST/OSD_UNIT_TESTare ever defined by any real target.Follow-ups (not in this PR)
settings.rbordering bug (described above under "Root cause") —check_conditions()andresolve_types()disagree about macro visibility depending on include order. Currently masked byplatform.hincludingtarget/common.hfirst; will resurface confusingly if that ordering ever changes. Needs its own scoped fix/review in the Ruby codegen tooling. Reported to project tracking separately.settings.yaml/osd.hmismatch —osd.h'shighlight_djis_missing_charactersfield is guarded by#ifndef DISABLE_MSP_DJI_COMPAT, butsettings.yaml'sosd_highlight_djis_missing_font_symbolsentry references that field completely unconditionally, with no matchingcondition:key — and thesettings.yamlDSL only supports positive "macro is defined" conditions, not negation, so there's no direct way to express "field only exists when macro X is not defined" today. Currently unreachable in practice (no real target definesDISABLE_MSP_DJI_COMPAT), avoided in this PR by not defining that macro forosd_unittesteither — but it's a landmine for whoever eventually does need that macro defined (e.g. a future target or test). Worth its own follow-up.Note for future test additions
src/test/unit/platform.his shared across every unit test translation unit — anything added there has global blast radius across all other tests, not just the one being worked on. Same is true ofsrc/main/telemetry/telemetry.hin this case, since it's included by settings-gen's combined probe. Worth keeping in mind for the rest of themavlink_multiport2test stack (cc @xznhj8129, since this PR series is what surfaced both issues above).