system/nxinit: add cmocka unit tests for parser/action/service - #3755
system/nxinit: add cmocka unit tests for parser/action/service#3755JianyuWang0623 wants to merge 4 commits into
Conversation
3352764 to
1aacc30
Compare
linguini1
left a comment
There was a problem hiding this comment.
Please add the Assisted-by field to your commits to indicate AI usage.
4845600 to
89958f9
Compare
|
The The fix is already up as #3753 ( |
init_parse_config_lines() had a dead early "continue" for a truly empty line (buf == "\0") that skipped the memmove() bookkeeping its sibling whitespace-only-line branch performs. When a real empty line appeared mid-buffer, subsequent bytes were never shifted to the front of the working buffer, corrupting the remaining-length tracking and silently dropping every line after it for that refill chunk. The whitespace-skip loop right below already handles the empty-string case correctly (the loop body never executes, so it falls straight into the "only whitespace" -> memmove -> continue path), so the buggy early exit is simply redundant and removed. Assisted-by: GitHubCopilot:claude-sonnet-5 Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
init_parse_config_file() declares 'r' inside the read loop with no blank line before the following 'if (r < 0)' statement, violating the NuttX coding standard (nxstyle: "Missing blank line after declarations"). checkpatch.sh runs a whole-file nxstyle check on any file a commit touches, not diff-only, so this pre-existing issue surfaced on this PR's CI once parser.c was touched again. Assisted-by: GitHubCopilot:claude-sonnet-5 Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
89958f9 to
2d45ed3
Compare
init_parse_config_buffer() computed the per-refill copy length as MIN(len - off, sizeof(tmp)) without subtracting the 'n' leftover bytes already held at the front of 'tmp' from a previous refill, so memcpy(&tmp[n], ..., r) could write past the end of tmp[]. The file-based twin, init_parse_config_file(), already gets this right (read(fd, &buf[n], sizeof(buf) - n)). Reproduced locally with an AddressSanitizer host harness feeding the real 95-byte builtin "preset" rc content through init_parse_config_buffer() at CONFIG_SYSTEM_NXINIT_RC_LINE_MAX=32/48: ASan reports a stack-buffer-overflow on the 'tmp' array. Fixed to MIN(len - off, sizeof(tmp) - n) and reverified clean at RC_LINE_MAX=32/48/64/128. The default config never hits this (the builtin preset is 95 bytes and the default RC_LINE_MAX is 128), but SYSTEM_NXINIT_RC_LINE_MAX had no lower bound, so lowering it towards 32/48 for a smaller build would silently corrupt the stack while parsing the preset during boot. Add a "range 64 4096" bound so the value can no longer be set below the builtin preset's needs. Assisted-by: opencode:mimo-v2.5-pro Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
2d45ed3 to
dd7113f
Compare
Add a test/ subdirectory (mirroring apps/system/uorb/test/) with
cmocka-based unit tests covering the NxInit logic most prone to
regression:
- init_parse_arguments(): plain/quoted arguments, "--" separator vs.
"--option" long options (regression coverage for a previously fixed
bug), argv-capacity truncation (asserting the exact folded contents
of the last slot, not just its presence).
- init_parse_config_file()/init_parse_config_lines()/
init_parse_config_buffer(): section routing, blank/whitespace-only
line skipping, unknown-section rejection, over-length line rejection,
and a line straddling two read-buffer refills, exercised through both
the file-based and buffer-based entry points.
- Action event matching: exact match, invert (!=), fnmatch wildcards,
and AND semantics across multiple events per action.
- Service conflict detection: duplicate service name rejection,
override replacing an earlier duplicate, and the SERVICE_ARGS_MAX
boundary built dynamically from CONFIG_SYSTEM_NXINIT_SERVICE_ARGS_MAX
rather than a hardcoded value.
Test sources compile action.c/parser.c/service.c a second time into a
separate nxinit_unit_test program, gated behind new
CONFIG_SYSTEM_NXINIT_TEST (depends on TESTING_CMOCKA); the default init
program is unaffected. The CMake path builds a dedicated
nxinit_unit_test target (with test/test_nxinit.c placed first in SRCS
so nuttx_add_application() renames its main() correctly); the Make path
appends the test sources into the shared CSRCS list.
Supporting bits required to make the suite exercise the real code:
- init_parse_config_buffer() is declared in parser.h and made
non-static so the buffer-based boundary test can call it directly,
alongside the existing init_parse_config_file() entry point.
- CONFIG_SYSTEM_NXINIT_ACTION_EVENTS_MAX default is raised from 1 to 2
so an action can carry more than one event ("on evA && evB"), which
the multi-event AND-semantics test exercises; a single event slot
made that test dead code.
- CONFIG_SYSTEM_NXINIT_TEST_STACKSIZE defaults to 8192: several parser
test cases build multi-hundred-byte stack buffers on top of cmocka's
own overhead, and the previous DEFAULT_TASK_STACKSIZE (2048)
overflowed the test task's stack silently on real hardware (no crash
dump, no watchdog reset, output just stopped) partway through the
suite.
Testing:
Built via `make CROSSDEV=riscv-none-elf-` for
esp32p4-pico-wifi-wareshare:nsh (CONFIG_SYSTEM_NXINIT_TEST=y) and ran
nxinit_unit_test on real esp32p4-pico-wifi-wareshare hardware over
UART:
nsh> nxinit_unit_test
[==========] nxinit_tests: Running 18 test(s).
...
[==========] nxinit_tests: 18 test(s) run.
[ PASSED ] 18 test(s).
nxstyle clean on all touched files.
Assisted-by: GitHubCopilot:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
dd7113f to
7707278
Compare
Summary
Fix a real bug in
init_parse_config_lines(): a dead earlycontinuefor a truly-empty line (buf == "\0") skipped thememmove()bookkeeping its sibling whitespace-only-line branch performs, corrupting the remaining-length tracking and silently dropping every subsequent line in that refill chunk (not just one bookkeeping entry). Since "blank line between sections" is one of the most common patterns in aninit.rcfile, this is a real, reachable bug, not just dead-code cleanup. The whitespace-skip loop right below already handles the empty-string case correctly, so the buggy early exit is simply redundant and removed.Fix a second real bug found during review, in
init_parse_config_buffer()(the buffer-based counterpart ofinit_parse_config_file(), used to parse the builtin "preset" rc content): it computed the per-refill copy length asMIN(len - off, sizeof(tmp))without subtracting thenleftover bytes already held at the front oftmpfrom a previous refill, somemcpy(&tmp[n], ..., r)could write pasttmp[]. Reproduced locally with an ASan host harness atRC_LINE_MAX=32/48; fixed toMIN(len - off, sizeof(tmp) - n)and reverified clean at 32/48/64/128 (the current default).SYSTEM_NXINIT_RC_LINE_MAXalso had no lower bound, so it now hasrange 64 4096to stop this from being reachable by lowering that Kconfig value.Add a
test/subdirectory (mirroringapps/system/uorb/test/) with cmocka-based unit tests covering the logic most prone to regression in NxInit:init_parse_arguments(): plain/quoted arguments,--separator vs.--optionlong options (regression coverage for a previously fixed bug), argv-capacity truncation (now also asserting the exact folded contents of the last slot, not just that it exists).init_parse_config_file()/init_parse_config_lines()/init_parse_config_buffer(): section routing, blank/whitespace-only line skipping, unknown-section rejection, over-length line rejection, and a line straddling two read-buffer refills — exercised through both the file-based and buffer-based entry points (the latter added specifically to catch the buffer-overflow bug above;init_parse_config_buffer()is exposed inparser.hbehindCONFIG_SYSTEM_NXINIT_TESTfor this purpose only).init_action_parse()/init_action_trigger_event()): exact match, invert (!=), fnmatch wildcards, AND semantics across multiple events per action.CONFIG_SYSTEM_NXINIT_ACTION_EVENTS_MAXnow defaults to 2 underCONFIG_SYSTEM_NXINIT_TESTspecifically so this case actually runs instead of being permanently skipped (production default is unchanged at 1). All action test cases now also release the events/commands they allocate.init_service_parse()/init_service_check()): duplicate service name rejection, override replacing an earlier duplicate,SERVICE_ARGS_MAXboundary (built dynamically from the configuredCONFIG_SYSTEM_NXINIT_SERVICE_ARGS_MAXvalue rather than hardcoding the default of 8, so the test still exercises the real boundary under a different config).Test sources compile
action.c/parser.c/service.ca second time into a separatenxinit_unit_testprogram, gated behind newCONFIG_SYSTEM_NXINIT_TEST(depends onTESTING_CMOCKA); the defaultinitprogram is unaffected.CONFIG_SYSTEM_NXINIT_TEST_STACKSIZEnow defaults to 8192 (wasDEFAULT_TASK_STACKSIZE= 2048): several test cases build multi-hundred-byte stack buffers on top of cmocka's own overhead, and on real hardware the smaller default caused the test task to silently overflow its stack and stop producing output (no crash dump, no watchdog reset) partway through the suite.The two build systems handle the "compile a second time" differently: CMake actually rebuilds
action.c/parser.c/service.cinto a second, separatenxinit_unit_testtarget (verified below); Make instead appends the test sources into the same sharedCSRCSlist used byinit, soparser/action/serviceare compiled once and linked into bothinitandnxinit_unit_testfrom the same objects. Both are correct, just not equivalent in what gets compiled where.Opened as draft for early review/CI feedback.
Impact
init_parse_config_buffer(), but it is unreachable at the defaultSYSTEM_NXINIT_RC_LINE_MAX=128(the builtin preset content is 95 bytes); only reachable if that Kconfig value is lowered below ~90, which is now bounded to a 64 minimum by this PR.Testing
Build Host: Ubuntu 22.04 LTS x86_64, gcc 13.4.0 (Ubuntu 13.4.0-6ubuntu1
22ppa2)Target: sim:citest / sim:nsh (nuttx
simconfig, no cross toolchain needed; NxInit is pure apps-layer logic) plus a real esp32p4-pico-wifi-wareshare hardware target for the latest commit (see below).nxstyle/checkpatch.shpass on all changed files.Note on CI coverage: no upstream
defconfigcurrently enablesCONFIG_SYSTEM_NXINIT_TEST(checked via a full search ofboards/), so this test target is not yet exercised by the existing CI test matrix. It has been verified locally (simMake/CMakebuilds below) and on real esp32p4 hardware. Happy to wire it into an existing cmocka-enabled defconfig (e.g.sim:citest) if that's preferred — it would needCONFIG_EXPERIMENTAL,CONFIG_LIBC_EXECFUNCS,CONFIG_SCHED_CHILD_STATUS,CONFIG_SYSTEM_NXINIT, andCONFIG_SYSTEM_NXINIT_TESTadded, none of whichsim:citestcurrently has.Make build (sim:citest, default Kconfig,
ACTION_EVENTS_MAX=1)With
CONFIG_SYSTEM_NXINIT_ACTION_EVENTS_MAX=4,test_nxinit_action_event_and_semanticsruns and passes (17/17 PASSED), confirming the AND-semantics test is not dead code.With
CONFIG_SYSTEM_NXINIT_SERVICE_ARGS_MAX=10(instead of the default 8),test_nxinit_service_args_max_boundarystill passes (16/17, same as default), confirming the args-max test now tracks the configured value instead of a hardcoded 8.CMake build (sim:nsh, default Kconfig)
This CMake build previously failed to link (
undefined reference to 'nxinit_unit_test_main') becausenuttx_add_application()only renames the firstSRCSentry'smain(), andtest/test_nxinit.cwas not first inTEST_SRCS; fixed by reorderingTEST_SRCSinCMakeLists.txt.Real hardware build (esp32p4-pico-wifi-wareshare:nsh, latest commit)
Built via
make CROSSDEV=riscv-none-elf-withCONFIG_SYSTEM_NXINIT_TEST=y(which now defaultsACTION_EVENTS_MAX=2andTEST_STACKSIZE=8192), flashed and rannxinit_unit_testover UART on real esp32p4-pico-wifi-wareshare hardware:All commits carry
Assisted-by:trailers indicating AI usage, and the branch has been rebased onto currentapache/master(no conflicts).