Skip to content

feat: S08.03 slice 5 BDD harness pointed at FreeRTOS-on-QEMU target#305

Merged
DavidCozens merged 6 commits into
mainfrom
feat/freertos-bdd
May 9, 2026
Merged

feat: S08.03 slice 5 BDD harness pointed at FreeRTOS-on-QEMU target#305
DavidCozens merged 6 commits into
mainfrom
feat/freertos-bdd

Conversation

@DavidCozens

@DavidCozens DavidCozens commented May 9, 2026

Copy link
Copy Markdown
Owner

Purpose

Slice 5 of S08.03 (#268). Establishes the BDD harness that lets the same
Gherkin features run against the FreeRTOS-on-QEMU target alongside the
existing Linux + Windows runs. The slice-4 hand-rolled QEMU smoke (Python
listener + UART heredoc) is replaced by Behave + the existing syslog-ng
oracle, and a new bdd-freertos-qemu CI job exercises it.

Closes #304.

Change Description

  • target_driver.py abstracts the example-spawn behind a BDD_TARGET
    env var. FreeRTOS spawns qemu-system-arm with the cross-built ELF;
    Linux/Windows keep the existing native subprocess flow. Same
    SolidSyslog> prompt protocol over stdin/stdout (ExampleInteractive
    prints it on every target) so wait_for_prompt / send_command are
    unchanged. extra_args raise NotImplementedError on FreeRTOS — the
    example has no getopt port; cmdline-flag → set NAME VALUE translation
    is a follow-up.
  • Per-target oracle pairs. ci/docker-compose.bdd.yml and
    .devcontainer/docker-compose.yml rename the existing services to
    syslog-ng-linux / behave-linux and add syslog-ng-freertos +
    behave-freertos. The freertos pair shares a netns
    (network_mode: service:syslog-ng-freertos) so QEMU's slirp gateway
    10.0.2.2 NATs to the pair's loopback where syslog-ng-freertos
    listens on 0.0.0.0:5514. Pairs never run together (CI scopes services
    per job; devcontainer's depends_on only starts the active pair). Both
    oracles alias as the bare hostname syslog-ng so existing example
    wiring and step helpers keep resolving without per-target overrides.
  • CI: build-freertos-target builds and uploads
    SolidSyslogFreeRtosSingleTask.elf as an artifact; new
    bdd-freertos-qemu job downloads it and runs the freertos compose
    pair via --exit-code-from behave-freertos. Branch protection list in
    CLAUDE.md and summary job's quality monitor pattern list updated.
  • Scenario tagging. Features/scenarios that don't pass on FreeRTOS
    today are tagged @freertoswip; the FreeRTOS BDD command filters
    --tags='not @freertoswip and @udp'. Linux filter unchanged. Tagged
    for clear reasons documented in the DEVLOG entry — most are because
    the FreeRTOS SingleTask example has hardcoded TEST_* values
    (hostname, PROCID, timestamp), no meta / origin / timeQuality
    SD wiring, or because the scenarios use cmdline args. Removing each
    tag is a follow-up slice.
  • Image-bump prep, not push. dockerfile.cross in
    CppUTestFreertosDocker (separate repo) gets python3 + python3-pip + behave==1.3.3. Until pushed and the new SHA propagates, the
    behave-freertos compose command does an inline apt-get install python3-pip; pip install; that goes away in a follow-up chore: bump container image PR.

Test Evidence

Local docker-compose runs against both pairs:

  • Linux (docker compose -f ci/docker-compose.bdd.yml up --exit-code-from behave-linux behave-linux syslog-ng-linux):
    21 features / 46 scenarios pass, 0 failed, 0 skipped — pre-rename
    behaviour fully preserved.
  • FreeRTOS (docker compose -f ci/docker-compose.bdd.yml up --exit-code-from behave-freertos behave-freertos syslog-ng-freertos):
    1 feature / 1 scenario pass (header_fields.feature::App name matches the example program), 0 failed, 45 @freertoswip skipped.

CI run on this PR exercises the new bdd-freertos-qemu job end-to-end.

Areas Affected

  • Bdd/features/ — environment.py + new target_driver.py + step
    refactor in syslog_steps.py + tag annotations across the @udp
    feature files.
  • ci/docker-compose.bdd.yml and .devcontainer/docker-compose.yml
    — per-target oracle pairs, network_mode, DNS aliases.
  • .github/workflows/ci.yml — ELF artifact upload from
    build-freertos-target, new bdd-freertos-qemu job, summary needs
    list, quality monitor pattern.
  • CLAUDE.md branch-protection list + docs/containers.md image
    table + new Bdd/README.md documenting the BDD_TARGET contract
    and FreeRTOS local fault-finding tips.
  • DEVLOG entry for slice 5.

Out of solid-syslog: prepared but not pushed —
CppUTestFreertosDocker/dockerfile.cross (Python + Behave). Follow-up
PR pushes it and bumps the SHA refs in this repo.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added FreeRTOS-on-QEMU Behave-based BDD testing support alongside existing Linux tests
    • Introduced target-aware test execution framework for cross-platform validation
  • Infrastructure

    • Reorganized Docker Compose services with dedicated test environments for Linux and FreeRTOS targets
    • Enhanced CI pipeline with new FreeRTOS QEMU testing job
  • Documentation

    • Added comprehensive BDD testing guide with local execution instructions
    • Updated container configuration documentation for multi-target setup

Review Change Stack

Slice 5 of S08.03 (#304). Establishes the harness that lets BDD
scenarios run against the FreeRTOS-on-QEMU target alongside the
existing Linux/Windows runs.

- target_driver.py module abstracts the example-spawn behind a
  BDD_TARGET env var; FreeRTOS dispatches to qemu-system-arm with the
  cross-built ELF, Linux/Windows keep the existing native subprocess
  flow. Same prompt-protocol over stdin/stdout (ExampleInteractive
  prints SolidSyslog> on every target) so wait_for_prompt /
  send_command are unchanged.
- Per-target oracle pairs in ci/docker-compose.bdd.yml and
  .devcontainer/docker-compose.yml: syslog-ng-linux + behave-linux,
  syslog-ng-freertos + behave-freertos. Pairs never run together;
  network_mode: service:syslog-ng-freertos gives the FreeRTOS pair a
  shared netns so QEMU's slirp gateway 10.0.2.2 NATs to the pair's
  loopback. Both oracles alias as syslog-ng for backwards-compat.
- bdd-freertos-qemu CI job depends on build-freertos-target's new ELF
  artifact upload; runs the freertos compose pair via
  --exit-code-from. Branch protection list and summary quality
  monitor updated.
- Scenarios that don't pass on FreeRTOS today (hardcoded TEST_*
  hostname/PROCID/timestamp; no meta/origin/timeQuality SD wired;
  scenarios using cmdline args) tagged @freertoswip; the bdd-freertos
  CI command filters --tags='not @freertoswip and @udp'. Linux
  filter unchanged. Single passing scenario today is
  header_fields::App name matches the example program.
- The cpputest-freertos-cross image bump (Python + behave) is
  prepared in CppUTestFreertosDocker but not yet pushed; the
  behave-freertos compose service inline-installs Python+behave
  until the image SHA advances. DEVLOG entry calls out the follow-up
  bump PR.

Local verification:
- behave-linux: 21 features / 46 scenarios pass, 0 failed.
- behave-freertos: 1 feature / 1 scenario pass, 0 failed,
  45 @freertoswip skipped.

Closes #304.
@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@DavidCozens has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 30 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fb2dc33-b2de-485f-b31d-7614cc3dbc18

📥 Commits

Reviewing files that changed from the base of the PR and between 79332a1 and ad8a693.

📒 Files selected for processing (13)
  • .devcontainer/docker-compose.yml
  • .github/workflows/ci.yml
  • Bdd/README.md
  • Bdd/features/environment.py
  • Bdd/features/steps/target_driver.py
  • DEVLOG.md
  • Example/FreeRtos/README.md
  • README.md
  • ci/docker-compose.bdd.yml
  • docs/bdd.md
  • docs/builds.md
  • docs/ci.md
  • docs/containers.md
📝 Walkthrough

Walkthrough

This PR integrates FreeRTOS-on-QEMU BDD testing into the existing syslog-ng oracle framework. Per-target Docker Compose service pairs (syslog-ng + behave) are introduced with shared network namespaces; a target driver abstraction handles QEMU-based or native process spawning; environment.py selects targets via BDD_TARGET; and scenarios are marked with @freertoswip tags pending FreeRTOS fixes. CI gains a new bdd-freertos-qemu job with artifact handoff and quality monitor integration.

Changes

FreeRTOS-on-QEMU BDD Harness

Layer / File(s) Summary
Docker Compose & CI Configuration
.devcontainer/docker-compose.yml, ci/docker-compose.bdd.yml, .github/workflows/ci.yml
Services renamed to per-target pairs: syslog-ng-linux/behave-linux and new syslog-ng-freertos/behave-freertos with shared network namespace. CI workflow extended: FreeRTOS cross-build now produces SolidSyslogFreeRtosSingleTask ELF artifact; new bdd-freertos-qemu job downloads it, runs Behave via docker compose, uploads JUnit results; summary job updated with quality monitor integration.
Target-Aware Process Driver
Bdd/features/steps/target_driver.py
New spawn_example_process function dispatches to native binary or qemu-system-arm based on context.target; new stop_example_process function handles teardown with FreeRTOS-specific quit/kill logic; both return text-mode Popen for unified step compatibility.
Environment & Target Selection
Bdd/features/environment.py
before_all derives context.target from BDD_TARGET (default "linux") and selects target-dependent default binary; EXAMPLE_BINARY override continues to work across targets.
Behave Step Integration
Bdd/features/steps/syslog_steps.py
_run_with_prompt_protocol refactored to use spawn_example_process and stop_example_process instead of direct subprocess management; removes explicit quit/wait logic, shifting lifecycle responsibility to target driver.
Feature Tagging for FreeRTOS
Bdd/features/*.feature
Nine features tagged with @freertoswip alongside existing transport tags to mark scenarios not yet passing on FreeRTOS; CI runs with --tags='not @freertoswip' to skip them.
Documentation & Devlog
Bdd/README.md, DEVLOG.md, CLAUDE.md, docs/containers.md
Bdd/README.md documents BDD target contracts, local workflow, and troubleshooting; DEVLOG entry records slice 5 decisions (per-target oracle pairs, shared netns, target driver, tagging strategy); CLAUDE.md and containers.md updated for service renames and per-target convention.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • DavidCozens/solid-syslog#268: This PR directly implements the main epic's acceptance criteria for FreeRTOS-on-QEMU BDD support, including per-target oracle pairing, CI job integration, and target driver abstraction.
  • DavidCozens/solid-syslog#10: This PR realizes the FreeRTOS demo execution path with QEMU integration and oracle connectivity described in the epic.

Possibly related PRs

  • DavidCozens/solid-syslog#301: Introduces SolidSyslogFreeRtosSingleTask ELF target and CMake wiring that this PR builds, ships, and runs in the new bdd-freertos-qemu job.
  • DavidCozens/solid-syslog#273: Evolves FreeRTOS-on-QEMU devcontainer and CI support (freertos-target service, build-freertos-target job) that this PR extends with BDD test harness.
  • DavidCozens/solid-syslog#154: Prior BDD environment and step refactoring that this PR builds upon with target-aware dispatch and multi-oracle support.

Poem

🐰 A rabbit in devcontainers hops,
Spawning QEMU in docker-compose,
Each target gets its oracle pair,
Linux and FreeRTOS share the care,
Behave scenarios now roam free—
@freertoswip marks the ones to be! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: implementing slice 5 of S08.03 that adds a BDD harness for FreeRTOS-on-QEMU target. It is concise, directly related to the changes, and conveys the primary purpose without noise.
Description check ✅ Passed The description comprehensively covers all required sections: Purpose (linked to #268 and #304), detailed Change Description (target_driver.py, per-target oracle pairs, CI setup, scenario tagging, image-bump prep), Test Evidence (local docker-compose results), and Areas Affected (files and modules touched). It follows the template structure and provides sufficient technical context.
Linked Issues check ✅ Passed The PR successfully implements all major coding objectives from issue #304: target_driver abstraction with BDD_TARGET support [#304], per-target oracle pairs in compose files [#304], scenario tagging with @freertoswip [#304], CI job setup with artifact upload [#304], and environment.py refactoring to select drivers [#304]. All primary requirements are met.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with PR #304 scope: target_driver.py and step refactoring (in-scope), compose/CI updates (in-scope), documentation/DEVLOG (in-scope). Image-bump deferral is explicitly noted as out-of-scope but preparatory. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/freertos-bdd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1105 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1042 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1042 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 100% successful (✔️ 46 passed)
   🚦   bdd-windows-otel: 96% successful (✔️ 44 passed, 🙈 2 skipped)
   🚦   bdd-freertos-qemu: 2% successful (✔️ 1 passed, 🙈 45 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 954 passed, 🙈 1 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
Bdd/features/steps/target_driver.py (2)

108-111: 💤 Low value

process.wait(timeout=timeout) on non-FreeRTOS path can raise TimeoutExpired.

If the example does not honor quit (e.g. wedged service thread), wait raises subprocess.TimeoutExpired and stop_example_process leaves the process alive. The caller's finally block in _run_with_prompt_protocol does kill it, so it's not a leak — but consider catching TimeoutExpired, sending process.kill(), and re-raising / surfacing as an AssertionError so the failure mode is unambiguous in test reports rather than appearing as a generic timeout from a helper.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/features/steps/target_driver.py` around lines 108 - 111, The
stop_example_process helper currently calls process.wait(timeout=timeout) which
can raise subprocess.TimeoutExpired and leave the process alive; update
stop_example_process to catch subprocess.TimeoutExpired, call process.kill()
(and optionally process.wait() without timeout to reap), then raise an
AssertionError (or re-raise a clearer test failure) with a message including the
timeout and that the process ignored the "quit" request so callers of
_run_with_prompt_protocol see a clear test failure instead of a generic timeout;
reference the stop_example_process function and ensure the caller path in
_run_with_prompt_protocol still handles/propagates the AssertionError as
intended.

88-106: 💤 Low value

Docstring mentions a "short grace period" but the FreeRTOS branch kills immediately.

The docstring claims FreeRTOS is killed "after a short grace period," but in the code path, process.stdin.write("quit\n") + flush() is followed immediately by process.kill() with no sleep or wait. Since the BDD scenario has already verified oracle reception, this is functionally fine, but the "quit\n" write is essentially best-effort and the comment is misleading. Either drop the quit write (it's redundant with the immediate kill) or add a brief settle (e.g. time.sleep(0.1)) so the docstring matches reality and a graceful UART task teardown has at least a chance to run before the kill.

Proposed minor wording fix
     if target == "freertos":
-        try:
-            process.stdin.write("quit\n")
-            process.stdin.flush()
-        except (BrokenPipeError, OSError):
-            pass
+        # The BDD scenario has already verified the oracle received the
+        # frame; QEMU's exit code carries no useful signal, so just kill.
         process.kill()
         process.wait(timeout=timeout)
         return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/features/steps/target_driver.py` around lines 88 - 106, The docstring for
stop_example_process says FreeRTOS is killed "after a short grace period" but
the freertos branch writes "quit\n" then immediately calls process.kill();
either remove the redundant process.stdin.write/flush of "quit\n" or insert a
short sleep (e.g., time.sleep(0.1)) between the write/flush and process.kill()
so the implementation matches the docstring; update imports if you add
time.sleep and keep the behavior (process.kill() and
process.wait(timeout=timeout)) unchanged.
.devcontainer/docker-compose.yml (1)

121-148: 💤 Low value

syslog-ng-freertos exposes no host ports — confirm oracle traffic is observable for devs.

Unlike syslog-ng-linux, this service deliberately omits ports:, which is correct because freertos-target reaches it via network_mode: service:syslog-ng-freertos on the shared loopback. Just flagging that, as a side effect, developers cannot directly send to or observe this oracle from the host (e.g. with nc localhost 5514); they must docker compose exec syslog-ng-freertos .... Worth a one-line note in docs/containers.md or Bdd/README.md if not already covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.devcontainer/docker-compose.yml around lines 121 - 148, Add a one-line note
to the project docs (e.g., docs/containers.md or Bdd/README.md) clarifying that
the syslog-ng-freertos service (service name: syslog-ng-freertos) intentionally
exposes no host ports because freertos-target reaches it via network_mode:
service:syslog-ng-freertos on the shared loopback, and instruct developers how
to observe/send traffic from the host (e.g., use docker compose exec
syslog-ng-freertos ... rather than nc localhost:5514).
ci/docker-compose.bdd.yml (3)

22-25: ⚖️ Poor tradeoff

Same syslog-ng alias on the default network — confirm CI never starts both pairs together.

Both services attach to default with alias syslog-ng. CI is currently safe because each job runs docker compose up --exit-code-from behave-<target> which only pulls in that target's pair via depends_on. If a future job ever runs both pairs in the same compose project (e.g. a combined matrix run), the syslog-ng DNS lookup from any service on the default network will resolve non-deterministically.

Two robust options if/when the matrix grows:

  1. Drop the shared syslog-ng alias and have each consumer reach its oracle by service name (syslog-ng-linux / syslog-ng-freertos), removing the need for host="syslog-ng" defaults in wait_for_tcp_port_*.
  2. Put each pair on its own bridge network so the alias is scoped per-pair.

Same concern raised on .devcontainer/docker-compose.yml — fixing both consistently is preferable.

Also applies to: 73-76

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/docker-compose.bdd.yml` around lines 22 - 25, The shared "syslog-ng"
network alias on the default network causes non-deterministic DNS if both pairs
ever run together; either remove the common alias and update any consumers
(update wait_for_tcp_port_* defaults and calls) to target the explicit service
names "syslog-ng-linux" and "syslog-ng-freertos", or create separate bridge
networks per pair and scope the alias to each network so the alias remains
unique per-pair; apply the same change consistently for both the compose
definitions that contain the alias (and update any wait_for_tcp_port_* helper
defaults/uses to reference the new service names when you remove the shared
alias).

102-110: ⚡ Quick win

Add set -e (or set -euo pipefail) so install failures surface clearly.

The bash command runs apt-get update, apt-get install, pip install, then behave without set -e. If the apt or pip step fails (e.g. transient network issue), the script will still try to invoke behave, which will fail with "command not found" or import errors that obscure the real cause. Failing fast at the install step makes CI logs much easier to triage.

Proposed fix
     command:
       - |
+        set -euo pipefail
         # Temporary install — once the cpputest-freertos-cross image
         # gains python3-pip + behave (CppUTestFreertosDocker bump), drop
         # this line and the `apt-get install` block below.
         apt-get update >/dev/null && apt-get install -y --no-install-recommends python3-pip >/dev/null
         pip install --no-cache-dir --break-system-packages -r Bdd/requirements.txt
         behave --junit --junit-directory Bdd/junit --tags='not `@wip` and not `@freertoswip` and `@udp`' Bdd/features/
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/docker-compose.bdd.yml` around lines 102 - 110, The shell command in the
service entrypoint/command (the bash -c block that runs apt-get, pip install and
behave) should fail fast; prepend a strict shell option like "set -euo pipefail"
(or at minimum "set -e") as the first statement in that bash -c multiline
command so any apt-get or pip failure aborts immediately and surfaces the real
error instead of continuing to run behave.

2-30: ⚡ Quick win

DRY: syslog-ng-linux and syslog-ng-freertos are nearly identical — consider a YAML anchor.

The two oracle services share image, entrypoint, command body, network alias, and healthcheck. The only differences are the named volumes (bdd-output-<target>, syslog-ng-ctl-<target>). A YAML anchor / merge-key on the common block would keep them in lockstep (e.g. when the bash command is updated for both pairs). Compose supports anchors via x-syslog-ng-base: &syslog-ng-base and <<: *syslog-ng-base.

This will pay off as more target pairs are added (the comment at line 47-52 hints this is the plan).

Proposed sketch (not exhaustive)
x-syslog-ng-base: &syslog-ng-base
  image: balabit/syslog-ng:latest
  entrypoint: ["bash", "-c"]
  command:
    - |
      syslog-ng -F &
      pid=$$!
      deadline=$$((SECONDS + 30))
      while [ ! -S /var/lib/syslog-ng/syslog-ng.ctl ]; do
        kill -0 "$$pid" 2>/dev/null || { echo "syslog-ng exited unexpectedly"; exit 1; }
        [ "$$SECONDS" -ge "$$deadline" ] && { echo "Timed out waiting for syslog-ng control socket"; kill "$$pid" 2>/dev/null; exit 1; }
        sleep 0.1
      done
      chmod 0777 /var/lib/syslog-ng/syslog-ng.ctl
      wait
  networks:
    default:
      aliases:
        - syslog-ng
  healthcheck:
    test: ["CMD-SHELL", "test -S /var/lib/syslog-ng/syslog-ng.ctl"]
    interval: 2s
    timeout: 5s
    retries: 10

services:
  syslog-ng-linux:
    <<: *syslog-ng-base
    volumes:
      - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf
      - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro
      - bdd-output-linux:/var/log/syslog-ng
      - syslog-ng-ctl-linux:/var/lib/syslog-ng

  syslog-ng-freertos:
    <<: *syslog-ng-base
    volumes:
      - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf
      - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro
      - bdd-output-freertos:/var/log/syslog-ng
      - syslog-ng-ctl-freertos:/var/lib/syslog-ng

Also applies to: 53-81

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/docker-compose.bdd.yml` around lines 2 - 30, The two services
syslog-ng-linux and syslog-ng-freertos duplicate the same image, entrypoint,
command block, networks and healthcheck; extract these common fields into a YAML
anchor (e.g. x-syslog-ng-base: &syslog-ng-base) and merge into each service
using <<: *syslog-ng-base, leaving only the differing volumes
(bdd-output-<target> and syslog-ng-ctl-<target>) defined per service; ensure the
long multiline command, network alias, and healthcheck (test: ["CMD-SHELL",
"test -S /var/lib/syslog-ng/syslog-ng.ctl"]) are moved into the anchor so future
edits only need to touch x-syslog-ng-base.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.devcontainer/docker-compose.yml:
- Around line 58-61: Two services share the same DNS alias `syslog-ng` causing
ambiguous resolution; fix by giving each oracle a unique alias (e.g.,
`syslog-ng-linux` and `syslog-ng-freertos`) in the docker-compose service
definitions (`syslog-ng-linux`, `syslog-ng-freertos`) or by creating per-pair
bridge networks so the `syslog-ng` alias is scoped, and update consumers to use
the new alias; also make `syslog_steps.py`'s default host (used by
wait_for_tcp_port_open()/wait_for_tcp_port_closed()) configurable from an
environment variable (e.g., SYSLOG_HOST with fallback to "syslog-ng") instead of
hardcoding "syslog-ng"; finally add the missing depends_on: - syslog-ng-linux to
the `clang` service to match `gcc`/`behave-linux`.

In `@Bdd/features/environment.py`:
- Around line 100-109: The code currently treats any non-"freertos" BDD_TARGET
as Linux and silently misroutes "windows" or typos; update the logic around
context.target, default_binary and context.example_binary to explicitly handle
"windows" (set a Windows-specific default_binary path) and validate
context.target against allowed values (e.g., "linux", "freertos", "windows"); if
an invalid value is provided, log an error or raise an exception and fall back
to a safe default (or abort) so mis-typed BDD_TARGET values are not silently
ignored.

In `@ci/docker-compose.bdd.yml`:
- Around line 83-110: The behave-freertos service currently runs an inline
apt-get/pip install in its command block even though the
cpputest-freertos-cross:sha-44efeae image already includes python3 and behave;
remove the temporary install lines and the related TODO comment and leave only
the behave invocation. Specifically, edit the behave-freertos service
(entrypoint/command) to delete the apt-get update && apt-get install -y ...
python3-pip >/dev/null and the pip install --no-cache-dir ... line plus the
surrounding comment about the temporary install, ensuring the final command
simply runs behave --junit --junit-directory Bdd/junit --tags='not `@wip` and not
`@freertoswip` and `@udp`' Bdd/features/ so no runtime network installs occur.

In `@DEVLOG.md`:
- Around line 63-74: Add a blank line immediately before the markdown table that
begins with "| Feature | Why |" and a blank line immediately after its closing
row so the table is separated from surrounding content; update DEVLOG.md by
inserting a single empty line above the table header and a single empty line
after the table's final line to satisfy markdownlint rule MD058.

In `@docs/containers.md`:
- Around line 20-33: Update the top markdown table and any mentions of service
names to match the new per-target compose layout: replace legacy generic service
names with the current target-specific names (e.g., use "behave-linux" and
"syslog-ng-linux" for Linux, and "freertos-target" / "syslog-ng-freertos" for
FreeRTOS) and ensure the explanatory text still references the dependent
services (the "gcc" service depending on "syslog-ng-linux", and
"freertos-target" depending on "syslog-ng-freertos" with network_mode:
service:syslog-ng-freertos and QEMU gateway 10.0.2.2). Keep the existing example
wiring references (Example/Common/Example*Config.c) consistent with those
updated service names.

---

Nitpick comments:
In @.devcontainer/docker-compose.yml:
- Around line 121-148: Add a one-line note to the project docs (e.g.,
docs/containers.md or Bdd/README.md) clarifying that the syslog-ng-freertos
service (service name: syslog-ng-freertos) intentionally exposes no host ports
because freertos-target reaches it via network_mode: service:syslog-ng-freertos
on the shared loopback, and instruct developers how to observe/send traffic from
the host (e.g., use docker compose exec syslog-ng-freertos ... rather than nc
localhost:5514).

In `@Bdd/features/steps/target_driver.py`:
- Around line 108-111: The stop_example_process helper currently calls
process.wait(timeout=timeout) which can raise subprocess.TimeoutExpired and
leave the process alive; update stop_example_process to catch
subprocess.TimeoutExpired, call process.kill() (and optionally process.wait()
without timeout to reap), then raise an AssertionError (or re-raise a clearer
test failure) with a message including the timeout and that the process ignored
the "quit" request so callers of _run_with_prompt_protocol see a clear test
failure instead of a generic timeout; reference the stop_example_process
function and ensure the caller path in _run_with_prompt_protocol still
handles/propagates the AssertionError as intended.
- Around line 88-106: The docstring for stop_example_process says FreeRTOS is
killed "after a short grace period" but the freertos branch writes "quit\n" then
immediately calls process.kill(); either remove the redundant
process.stdin.write/flush of "quit\n" or insert a short sleep (e.g.,
time.sleep(0.1)) between the write/flush and process.kill() so the
implementation matches the docstring; update imports if you add time.sleep and
keep the behavior (process.kill() and process.wait(timeout=timeout)) unchanged.

In `@ci/docker-compose.bdd.yml`:
- Around line 22-25: The shared "syslog-ng" network alias on the default network
causes non-deterministic DNS if both pairs ever run together; either remove the
common alias and update any consumers (update wait_for_tcp_port_* defaults and
calls) to target the explicit service names "syslog-ng-linux" and
"syslog-ng-freertos", or create separate bridge networks per pair and scope the
alias to each network so the alias remains unique per-pair; apply the same
change consistently for both the compose definitions that contain the alias (and
update any wait_for_tcp_port_* helper defaults/uses to reference the new service
names when you remove the shared alias).
- Around line 102-110: The shell command in the service entrypoint/command (the
bash -c block that runs apt-get, pip install and behave) should fail fast;
prepend a strict shell option like "set -euo pipefail" (or at minimum "set -e")
as the first statement in that bash -c multiline command so any apt-get or pip
failure aborts immediately and surfaces the real error instead of continuing to
run behave.
- Around line 2-30: The two services syslog-ng-linux and syslog-ng-freertos
duplicate the same image, entrypoint, command block, networks and healthcheck;
extract these common fields into a YAML anchor (e.g. x-syslog-ng-base:
&syslog-ng-base) and merge into each service using <<: *syslog-ng-base, leaving
only the differing volumes (bdd-output-<target> and syslog-ng-ctl-<target>)
defined per service; ensure the long multiline command, network alias, and
healthcheck (test: ["CMD-SHELL", "test -S /var/lib/syslog-ng/syslog-ng.ctl"])
are moved into the anchor so future edits only need to touch x-syslog-ng-base.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5790e7bd-2b0b-4aa9-9ad3-e9ed43a93032

📥 Commits

Reviewing files that changed from the base of the PR and between d7d9639 and 79332a1.

📒 Files selected for processing (20)
  • .devcontainer/docker-compose.yml
  • .github/workflows/ci.yml
  • Bdd/README.md
  • Bdd/features/buffered.feature
  • Bdd/features/environment.py
  • Bdd/features/header_fields.feature
  • Bdd/features/message_fields.feature
  • Bdd/features/origin.feature
  • Bdd/features/prival.feature
  • Bdd/features/steps/syslog_steps.py
  • Bdd/features/steps/target_driver.py
  • Bdd/features/structured_data.feature
  • Bdd/features/syslog.feature
  • Bdd/features/time_quality.feature
  • Bdd/features/timestamp.feature
  • Bdd/features/udp_mtu.feature
  • CLAUDE.md
  • DEVLOG.md
  • ci/docker-compose.bdd.yml
  • docs/containers.md

Comment thread .devcontainer/docker-compose.yml
Comment thread Bdd/features/environment.py Outdated
Comment thread ci/docker-compose.bdd.yml Outdated
Comment thread DEVLOG.md
Comment thread docs/containers.md
CodeRabbit flagged that both syslog-ng-linux and syslog-ng-freertos
sharing the bare `syslog-ng` alias on the default network gives
docker's embedded DNS multiple A records when both pairs run together
— behave-linux's wait_for_tcp_port_open(host="syslog-ng") and the
example wiring's hardcoded "syslog-ng" host could non-deterministically
hit either oracle. CI is currently safe (each job scopes services to
its own pair) but a developer running plain `docker compose up` from
the host, or a future matrix run, would trigger the collision.

Each pair now lives on its own bridge network (linux / freertos);
the `syslog-ng` alias is scoped to that network so the same name
resolves to the right oracle from inside each pair, and pairs running
together cannot see each other's services. Local smoke confirms
no regressions: behave-linux 46/46 pass, behave-freertos 1/1 pass
(45 @freertoswip skipped).
Establishes the FreeRTOS doc baseline so a reader landing on README
can navigate to either build, BDD, or example detail and find current
information. Designed to be extended slice-by-slice as more FreeRTOS
features land.

- README.md: Status section names FreeRTOS Cortex-M3 (mps2-an385) as
  in-progress (E08 #268); examples list grows to four with the new
  Example/FreeRtos/SingleTask line; public-headers section lists the
  FreeRTOS adapter headers (SolidSyslogFreeRtosDatagram.h,
  SolidSyslogFreeRtosStaticResolver.h).
- docs/bdd.md: Architecture section replaces the legacy two-service
  picture with a per-target table (Linux / FreeRTOS / Windows) and
  links to Bdd/README.md for the per-target compose details. Tag
  table gains @freertoswip with the rollout-marker explanation.
  Runner filter table gains the FreeRTOS row. Local-running
  commands renamed to behave-linux / syslog-ng-linux.
- docs/ci.md: Job table picks up build-freertos-host-tdd,
  build-freertos-target, bdd-freertos-qemu, plus the pre-existing
  miss of integration-windows-openssl. Forward-looking sentence
  about ARM cross-compile / FreeRTOS QEMU oracle now matches reality.
- docs/builds.md: New 'FreeRTOS cross — freertos-cross' section
  pointing at Example/FreeRtos/README.md and Bdd/README.md. BDD
  section renamed to behave-linux.
- docs/containers.md: Top image table corrected to the per-target
  syslog-ng-linux / syslog-ng-freertos service names and the new
  behave-linux runner; cpputest-freertos-cross row already covered
  this PR's earlier doc edits.
- Example/FreeRtos/README.md: Top-level table now distinguishes
  HelloWorld (minimal bring-up smoke for cross-compile / scheduler /
  UART / newlib regression detection) from SingleTask (canonical
  syslog example, BDD-driven). Notes that HelloWorld will be retired
  once SingleTask provides sufficient diagnostic confidence.
CodeRabbit flagged that the previous environment.py treated every
non-freertos value as Linux, so a typo or BDD_TARGET=windows silently
misrouted execution rather than failing loudly. Replace the implicit
fallback with an explicit `linux | windows | freertos` dict, lowercase
the input so case differences are tolerated, and raise ValueError on
any other value with the allowed set in the message. Adds the
Windows default binary path so windows is now a first-class entry.

bdd-windows-otel CI job sets BDD_TARGET=windows explicitly so the new
validation accepts it; previously it relied on the default-Linux
fallback while overriding EXAMPLE_BINARY. Linux and FreeRTOS jobs are
already explicit via the compose file's environment block.
…install

CodeRabbit flagged the inline `apt-get install python3-pip; pip install`
in the behave-freertos compose service as redundant — the image bump
that adds python3 + behave to the cross image has now landed
(CppUTestFreertosDocker `feat: add python3 + behave to cross image`,
SHA 10a14ae, published to GHCR). Bump every reference and drop the
inline install along with its `entrypoint: bash -c` wrapper, falling
back to the simpler `command: behave ...` form mirroring behave-linux.

Files updated:
- .devcontainer/docker-compose.yml — freertos-host + freertos-target
- .github/workflows/ci.yml — build-freertos-host-tdd + build-freertos-target
- ci/docker-compose.bdd.yml — behave-freertos image + simplified command
- docs/containers.md — the cpputest-freertos / cross image table rows

Local smoke confirms behave-freertos runtime drops from ~45s
(apt+pip install on every run) to ~2.4s; same 1 scenario passes,
45 @freertoswip skipped.
- Bdd/features/steps/target_driver.py — drop the redundant `quit\n`
  write in the FreeRTOS branch of stop_example_process. The docstring
  said "kills after a short grace period" but the code immediately
  followed `quit\n` with `process.kill()`, so the write was best-effort
  and misleading. The BDD scenario has already verified the oracle
  received the frame; just kill QEMU and move on. Docstring rewritten
  to match.
- DEVLOG.md — blank lines added around the @freertoswip rationale
  table to satisfy markdownlint MD058.
- Bdd/README.md — note added to the FreeRTOS fault-finding tips
  explaining that syslog-ng-freertos exposes no host ports (unlike
  syslog-ng-linux) and how to send a probe frame from inside the
  container.
@DavidCozens

Copy link
Copy Markdown
Owner Author

@coderabbitai — thanks for the careful review. All five actionable comments addressed in five stacked commits (replied in-thread above). On the review-level nitpicks:

  • target_driver.py docstring/code mismatch (drop redundant quit\n before kill in the FreeRTOS branch of stop_example_process) — fixed in ad8a693. Docstring rewritten to match: the BDD scenario has already verified oracle reception, so QEMU's exit code carries no useful signal and the only way to terminate is process.kill().
  • set -euo pipefail for the inline-install bash block — N/A as of 3294f41; the inline-install block is gone now that the SHA bump landed (cpputest-freertos-cross:sha-10a14ae carries python3 + behave baked in). The behave-freertos service is back to a simple command: behave ... form mirroring behave-linux.
  • Note that syslog-ng-freertos exposes no host ports — added to Bdd/README.md fault-finding tips with a docker compose exec freertos-target nc ... recipe so a developer trying nc localhost 5514 from the host doesn't get confused.
  • YAML anchors for the two near-identical syslog-ng-* services — deferred. The duplication cost (one ~25-line block in two compose files) doesn't yet justify the indirection cost (anchors are easy to miss when scanning); revisit when a third target lands and we actually have three near-identical blocks. Open question in the slice DEVLOG entry.
  • process.wait(timeout) TimeoutExpired wrap in stop_example_process — skipped per CR's own "low value" rating; the caller's finally block in _run_with_prompt_protocol already kills if the process is still alive after the helper returns, so the failure mode is contained even if it's not as crisp as a wrapped AssertionError.

Local re-smoke across all 5 commits: behave-linux 46/46 pass (~32s), behave-freertos 1/1 pass (~2.3s, down from ~45s now that the inline install is gone).

@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@DavidCozens — thank you for the thorough wrap-up. Everything looks well-reasoned:

  • target_driver.py fix (ad8a693): The kill-without-exit-code approach is the right call for QEMU — oracle reception is the meaningful signal, not QEMU's exit status.
  • SHA bump (3294f41): The move from inline-install to a baked image is a significant quality improvement — the ~2.3s vs ~45s runtime speaks for itself. Good to have that landed before merge.
  • README fault-finding note: The docker compose exec freertos-target nc ... recipe is exactly the kind of thing that saves the next developer 20 minutes of confusion.
  • YAML anchors deferral: Agreed — one duplicate 25-line block across two files is well below the threshold where anchors pay off. Three targets would tip the balance.
  • TimeoutExpired skip: The finally-block safety net makes this a reasonable trade-off at this stage.

Happy to approve these changes. 🎉

[approve]


✏️ Learnings added
Learnt from: DavidCozens
URL: https://github.com/DavidCozens/solid-syslog/pull/305

Timestamp: 2026-05-09T20:16:20.432Z
Learning: In `Bdd/features/steps/target_driver.py` (solid-syslog), the FreeRTOS branch of `stop_example_process` intentionally uses `process.kill()` without capturing exit code, because QEMU's exit code carries no useful signal — the BDD scenario has already verified oracle reception before stopping.

Learnt from: DavidCozens
URL: https://github.com/DavidCozens/solid-syslog/pull/305

Timestamp: 2026-05-09T20:16:20.432Z
Learning: In solid-syslog, the `cpputest-freertos-cross` Docker image (sha-10a14ae onward) has python3 and behave baked in, so `behave-freertos` compose service uses a simple `command: behave ...` form (no inline pip-install). This reduced FreeRTOS BDD run time from ~45s to ~2.3s.

Learnt from: DavidCozens
URL: https://github.com/DavidCozens/solid-syslog/pull/305

Timestamp: 2026-05-09T20:16:20.432Z
Learning: In solid-syslog `ci/docker-compose.bdd.yml` and `.devcontainer/docker-compose.yml`, YAML anchors for the near-identical `syslog-ng-linux` / `syslog-ng-freertos` service blocks were deliberately deferred: the duplication cost (~25 lines in two files) is lower than the indirection cost of anchors until a third target lands.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1105 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1042 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1042 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 100% successful (✔️ 46 passed)
   🚦   bdd-windows-otel: 96% successful (✔️ 44 passed, 🙈 2 skipped)
   🚦   bdd-freertos-qemu: 2% successful (✔️ 1 passed, 🙈 45 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 954 passed, 🙈 1 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

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.

S08.03 slice 5: BDD harness pointed at FreeRTOS-on-QEMU target

1 participant