Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Bdd/features/message_fields.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@udp
@freertoswip
Feature: Message ID and message body
The library includes message ID and message body in the RFC 5424 message.

Expand All @@ -13,6 +12,7 @@ Feature: Message ID and message body
When the example program sends a message with body "system started"
Then the message is "system started"

@freertoswip
Scenario: Complete RFC 5424 message with all fields
Given the syslog oracle is running
When the example program sends a complete message with message ID "CONN" and body "session opened"
Expand Down
1 change: 0 additions & 1 deletion Bdd/features/prival.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@udp
@freertoswip
Feature: PRIVAL encoding
The library calculates RFC 5424 PRIVAL from facility and severity.
The example program accepts --facility and --severity flags,
Expand Down
3 changes: 2 additions & 1 deletion Bdd/features/steps/syslog_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
otel_kill_oracle,
otel_start_oracle,
)
from target_driver import spawn_example_process, stop_example_process
from target_driver import apply_extra_args, spawn_example_process, stop_example_process

PER_TRANSPORT_LOG_SYSLOG_NG = {
"udp": RECEIVED_UDP_LOG,
Expand Down Expand Up @@ -405,6 +405,7 @@ def _run_with_prompt_protocol(context, binary, label, extra_args, expected_messa

try:
wait_for_prompt(process)
apply_extra_args(context, process, extra_args)
send_command(process, f"send {expected_messages}")
wait_for_messages(context, expected_messages)

Expand Down
71 changes: 60 additions & 11 deletions Bdd/features/steps/target_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,33 @@
The active target is selected by the BDD_TARGET environment variable,
read in environment.before_all and stashed on context.target. Steps
call spawn_example_process(context, extra_args=...) instead of
subprocess.Popen directly.
subprocess.Popen directly. After wait_for_prompt, callers also invoke
apply_extra_args so any `--flag value` pairs are delivered to the
target — appended to argv on Linux/Windows (a no-op there because
spawn already did it), or translated into `set NAME VALUE` lines over
the UART on FreeRTOS.
"""

import os
import subprocess


# Mapping from cmdline flag to FreeRTOS interactive `set` name. Only the
# flags features currently pass to run_example are mapped — adding a new
# scenario that needs a new flag should fail loudly via the
# `_FREERTOS_SET_TRANSLATION` lookup so the gap is visible, rather than
# silently no-op'ing or printing a confusing "set: invalid" on the UART.
# `--message` -> `msg` is intentional: the FreeRTOS example global is
# g_msg (Example/FreeRtos/SingleTask/main.c) and renaming the set name
# would churn the example for cosmetic gain.
_FREERTOS_SET_TRANSLATION = {
"--facility": "facility",
"--severity": "severity",
"--msgid": "msgid",
"--message": "msg",
}


# QEMU machine + UART config matches build-freertos-target's smoke run
# in .github/workflows/ci.yml: mps2-an385 / Cortex-M3 / 16 MiB / LAN9118
# NIC / -serial stdio for the polled CMSDK UART. -icount keeps virtual
Expand Down Expand Up @@ -47,10 +67,8 @@ def spawn_example_process(context, extra_args=None, binary=None):
extra_args are appended to the binary's command line on Linux and
Windows (where the example-runner parses argv via getopt). FreeRTOS
has no getopt port and consumes its config via interactive `set`
commands instead; passing extra_args on FreeRTOS therefore raises —
such scenarios should be tagged @freertoswip until a follow-up
slice teaches the FreeRTOS driver to translate cmdline flags into
the equivalent set commands.
commands instead — they are delivered via apply_extra_args after
the prompt is up.

binary defaults to context.example_binary (the standard SingleTask
binary or its FreeRTOS .elf equivalent); callers that drive a
Expand All @@ -64,12 +82,6 @@ def spawn_example_process(context, extra_args=None, binary=None):
binary = context.example_binary

if target == "freertos":
if extra_args:
raise NotImplementedError(
"FreeRTOS target does not accept argv; tag the scenario "
"@freertoswip until the driver translates flags into "
"interactive `set` commands. Got: " + repr(extra_args)
)
cmd = list(_QEMU_BASE_ARGS) + ["-kernel", os.path.abspath(binary)]
else:
cmd = [os.path.abspath(binary)]
Expand All @@ -85,6 +97,43 @@ def spawn_example_process(context, extra_args=None, binary=None):
)


def apply_extra_args(context, process, extra_args):
"""Deliver `--flag value` pairs to the example after the prompt is up.

Linux and Windows are no-ops here — spawn already placed the flags
in argv. FreeRTOS translates each pair via _FREERTOS_SET_TRANSLATION
and writes one newline-terminated `set NAME VALUE` line to the UART
per pair.

Raises ValueError if extra_args is odd-length or contains a flag
not in the translation table; the BDD scenario surfaces the gap
immediately rather than silently no-op'ing or hitting a confusing
UART-side `set: invalid` reply.
"""
if not extra_args:
return
target = getattr(context, "target", "linux")
if target != "freertos":
return
if len(extra_args) % 2 != 0:
raise ValueError(
"FreeRTOS extra_args must be flag/value pairs; got odd length: "
+ repr(extra_args)
)
for flag, value in zip(extra_args[0::2], extra_args[1::2]):
try:
name = _FREERTOS_SET_TRANSLATION[flag]
except KeyError as exc:
raise ValueError(
f"Unknown cmdline flag for FreeRTOS target: {flag!r}. "
"Add it to _FREERTOS_SET_TRANSLATION in target_driver.py "
"if a corresponding `set` name exists in the FreeRTOS "
"example's OnSet handler."
) from exc
process.stdin.write(f"set {name} {value}\n")
process.stdin.flush()


def stop_example_process(process, target, timeout=10):
"""Tear down the example process for the active target.

Expand Down
2 changes: 1 addition & 1 deletion Bdd/features/udp_mtu.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@udp
@freertoswip
Feature: UDP datagram path-MTU clipping
When a UDP message exceeds the path MTU the sender clips it to the
path-MTU's safe payload, walking back over any partial UTF-8
Expand All @@ -19,6 +18,7 @@ Feature: UDP datagram path-MTU clipping
Then the received message is byte-identical to the sent message

@windows_wip
@freertoswip
Scenario: Oversize UTF-8 message is clipped at a codepoint boundary
Given the syslog oracle is running
When the example program sends an oversize UTF-8 message
Expand Down
130 changes: 130 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,135 @@
# Dev Log

## 2026-05-09 — S08.03 slice 6 — cmdline → `set` translation in BDD target driver (#306)

Slice 5 left eight `@udp` features tagged `@freertoswip` because the
FreeRTOS BDD driver couldn't translate Linux-side cmdline flags into
the FreeRTOS example's `set NAME VALUE` UART command channel. Slice 6
closes that gap for the four flags `@udp` features actually pass to
`run_example` (`--facility`, `--severity`, `--msgid`, `--message`),
loosens the FreeRTOS example's facility/severity input validation to
mirror Linux's permissive `atoi`-and-cast, and grows the UART line
buffer + `g_msg` storage to `SOLIDSYSLOG_MAX_MESSAGE_SIZE` so a single
`set msg <body>` can carry a full path-MTU-class UTF-8 message.

After this slice the FreeRTOS BDD sweep is **4 features / 10 scenarios
passing** (up from the slice-5 walking-skeleton baseline of 1
scenario): `prival.feature` (all 6 scenarios), `message_fields.feature`
(2 of 3), `udp_mtu.feature` (`Full delivery within path MTU` —
`Oversize` stays tagged), and `header_fields.feature::App name`.

### Decisions

- **Translation table is exactly four entries; unknown flags raise.**
The FreeRTOS branch of `target_driver.spawn_example_process` no
longer raises on `extra_args`; instead a new helper
`apply_extra_args` (called from `_run_with_prompt_protocol` after
`wait_for_prompt`) walks `--flag value` pairs, looks each flag up
in `_FREERTOS_SET_TRANSLATION`, and writes `set NAME VALUE\n` lines
to the UART. An unknown flag, or an odd-length args list, raises
`ValueError` so a misconfigured scenario fails loudly with a
Python traceback rather than silently no-op'ing or hitting a
confusing `set: invalid` reply on the UART. No premature
generalisation — adding a new scenario flag is a one-line table
append.
- **`--message` ↔ `set msg` naming gap stays in the table.** The
FreeRTOS example's global is `g_msg` (`Example/FreeRtos/SingleTask/
main.c`); renaming the `set` name would churn the example for
cosmetic gain. The translation lives in one Python dict.
- **Loosen `OnSet` for facility/severity to match Linux's
atoi-and-cast.** The two `prival.feature::Out-of-range` scenarios
assert that the library encodes invalid facility/severity as the
internal-error PRIVAL 43 — which only happens if the example
forwards the bad value unchanged. The FreeRTOS `OnSet` previously
rejected `parsed > 23U` / `parsed > 7U`; slice 6 drops both
bounds so the example becomes permissive about the value and the
library is the single authority on what's valid. Mirrors the
Linux example's `(enum SolidSyslog_Facility) atoi(optarg)` flow
in `Example/Common/ExampleCommandLine.c`.
- **Expand `MAX_LINE_LENGTH` and `g_msg` to
`SOLIDSYSLOG_MAX_MESSAGE_SIZE` (2048).** The walking-skeleton
values (256 each) couldn't carry `udp_mtu.feature::Full
delivery`'s 372-byte UTF-8 body — fgets would split the `set msg
<body>` line across reads and the `HandleSet name[]` buffer was
same-size. Bumping both to 2048 unblocks Full delivery and
future-proofs the storage to the library's bound. The `Oversize`
scenario (~1600 bytes) stays tagged because UDP path-MTU
EMSGSIZE semantics on FreeRTOS-Plus-TCP are unverified — that is
its own slice.
- **Bump `INTERACTIVE_TASK_STACK_DEPTH` `*32` → `*40`.** The larger
line buffer and same-size `name[]` in `HandleSet` add ~4 KB peak
to the interactive-task stack frame; `*32` (16 KB) was the empirical
ceiling at the previous buffer size. `*40` (20 KB) gives ~4 KB
headroom. Bigger picture is captured by the existing memory note
on the CMake-driven memory-scaling follow-up (the `*32` magic
number was already deferred to that work).
- **Per-scenario `@freertoswip` tagging where features are mixed.**
`message_fields.feature` keeps `@freertoswip` only on the
`Complete RFC 5424 message` scenario (it asserts system
hostname / current timestamp / process PID — orthogonal to slice
6). `udp_mtu.feature` keeps it only on the `Oversize` scenario
(UDP-stack semantics — orthogonal). The other features are
untagged at feature level.

### Two-commit shape

Strict TDD at the BDD level:
1. **Commit A** untag prival, watch behave fail with
`NotImplementedError`, write the helper + facility/severity
table entries, loosen `OnSet`, watch all 6 prival scenarios go
green.
2. **Commit B** add `--msgid`/`--message` table entries, expand
`MAX_LINE_LENGTH` + `g_msg` + stack depth, untag message_fields
(scenario-level on `Complete RFC 5424`) and `udp_mtu`
(scenario-level on `Oversize`).

A trailing format-only commit picked up clang-format's column
realignment after `g_msg`'s longer bracket expression shifted the
`g_*` static block alignment column. The PR squash collapses all
three into one commit on `main`.

### Local verification

- `cmake --build --preset freertos-cross --target
SolidSyslogFreeRtosSingleTask` clean.
- `behave --tags='not @wip and not @freertoswip and @udp' Bdd/
features/` against `syslog-ng-freertos`: 4 features pass, 10
scenarios pass, 36 skipped (still `@freertoswip`).
- `clang-format --dry-run --Werror` on every changed C file: clean.
- Linux unit tests / cppcheck / tidy / iwyu / sanitize / coverage:
not run locally — the `freertos-target` devcontainer carries the
cross toolchain only. CI's responsibility.

### Deferred

- **Removing the remaining `@freertoswip` tags.** What's still
tagged after slice 6: `syslog.feature` (single scenario,
composite system-hostname/timestamp/PID assertion);
`header_fields.feature::Hostname` and `::Process ID` (same
reason); `message_fields.feature::Complete RFC 5424` (same
reason); `timestamp.feature` (hardcoded RFC 5424 publication
date); `structured_data.feature`, `origin.feature`,
`time_quality.feature` (no SD wired in the FreeRTOS example);
`udp_mtu.feature::Oversize` (UDP path-MTU EMSGSIZE on FreeRTOS-
Plus-TCP); `buffered.feature` (no FreeRTOS Threaded example).
- **Real RTC-backed clock callback for the FreeRTOS example.**
Until then `TEST_TIMESTAMP` (RFC 5424 publication date)
prevents the timestamp scenarios passing.
- **CMake-driven memory scaling.** The bump to *40 is empirical;
the real lower bound is unverified. Same as the existing slice-3b.2
follow-up note.

### Open questions

- Is the BDD step layer the right place for the `--message` ↔ `msg`
naming gap, or should the example rename the `set` name? Slice 6
picked the BDD-side mapping; a future cleanup could push it
either way.
- The translation table currently lives as a module-private dict
in `target_driver.py`. If a fifth flag arrives, no churn — but
if a target needs *different* flag→set mappings, the dict goes
context-aware. Keep an eye on it.

## 2026-05-09 — S08.03 slice 5 — BDD harness pointed at FreeRTOS-on-QEMU target (#304)

Slice 4 made the FreeRTOS example's identity, transport endpoint, and PRIVAL
Expand Down
8 changes: 7 additions & 1 deletion Example/Common/ExampleInteractive.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ static const char* const PROMPT = "SolidSyslog> ";

enum
{
MAX_LINE_LENGTH = 256
/* Sized to SOLIDSYSLOG_MAX_MESSAGE_SIZE so a single `set msg <body>`
* can carry a full path-MTU-class message body without fgets
* splitting it across reads. The HandleSet name[] mirrors this size
* because the parser splits at the first whitespace; future work
* may decouple the name buffer (always short — RFC 5424 maxima are
* ≤ 255 chars) from the line buffer. */
MAX_LINE_LENGTH = SOLIDSYSLOG_MAX_MESSAGE_SIZE
};

static void PrintPrompt(void)
Expand Down
Loading
Loading