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/block_lifecycle.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@tcp @buffered
@tcp @buffered @store
Feature: Block lifecycle
The block store rotates blocks across files of fixed size. Once every record
in a non-active block is sent and acknowledged, that block is disposed
Expand Down
2 changes: 1 addition & 1 deletion Bdd/features/capacity_threshold.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@tcp @buffered
@tcp @buffered @store
Feature: Capacity threshold alert
an early-warning callback fires when the block store crosses a configured
capacity threshold, before the terminal full-store callback engages.
Expand Down
2 changes: 1 addition & 1 deletion Bdd/features/power_cycle_replay.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@tcp @buffered
@tcp @buffered @store
Feature: Power cycle replay from block store
After a power cycle, unsent messages persisted in the block store
are replayed before new messages. The collector sees old-session
Expand Down
88 changes: 48 additions & 40 deletions Bdd/features/steps/syslog_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import re
import shutil
import socket
import subprocess
import threading
import time
from datetime import datetime, timezone
Expand Down Expand Up @@ -489,65 +488,75 @@ def step_syslog_ng_is_running(context):
)


def build_buffered_command(context, transport, no_sd=False):
"""Build the command line for the buffered BDD target with all options.
def build_buffered_extra_args(context, transport, no_sd=False):
"""Build the extra-args list for the buffered BDD target.

Returned list excludes the binary itself — start_bdd_target_process
routes the spawn through target_driver.spawn_example_process, which
appends the args to argv on Linux/Windows and translates them to
interactive `set NAME VALUE` lines after the prompt on FreeRTOS.

Linux runner (syslog-ng oracle) and Windows runner (OTel oracle) both
ship SolidSyslogBddTarget — same basename, same flags, same wire-record
sizes byte-for-byte (S24.05 unified the basename so capacity-feature
block packing is identical across runners).
"""
binary = context.example_binary
assert os.path.exists(binary), (
f"BDD target binary not found at {binary} — build with cmake first"
)

cmd = [
os.path.join(".", binary),
"--transport", transport,
]
args = ["--transport", transport]
if getattr(context, "store_type", None):
cmd.extend(["--store", context.store_type])
if getattr(context, "store_max_blocks", None):
cmd.extend(["--max-blocks", str(context.store_max_blocks)])
if getattr(context, "store_max_block_size", None):
cmd.extend(["--max-block-size", str(context.store_max_block_size)])
args.extend(["--store", context.store_type])
# Numeric option guards use `is not None` so an explicit 0 survives:
# `--capacity-threshold 0` is the documented "disable" sentinel for
# SolidSyslogStoreThresholdFunction (Core/Interface/SolidSyslogBlockStore.h).
if getattr(context, "store_max_blocks", None) is not None:
args.extend(["--max-blocks", str(context.store_max_blocks)])
if getattr(context, "store_max_block_size", None) is not None:
args.extend(["--max-block-size", str(context.store_max_block_size)])
if getattr(context, "store_discard_policy", None):
cmd.extend(["--discard-policy", context.store_discard_policy])
if getattr(context, "capacity_threshold", None):
cmd.extend(["--capacity-threshold", str(context.capacity_threshold)])
args.extend(["--discard-policy", context.store_discard_policy])
if getattr(context, "capacity_threshold", None) is not None:
args.extend(["--capacity-threshold", str(context.capacity_threshold)])
if getattr(context, "message_body", None):
cmd.extend(["--message", context.message_body])
args.extend(["--message", context.message_body])
if no_sd:
cmd.append("--no-sd")
args.append("--no-sd")
if getattr(context, "halt_exit", False):
cmd.append("--halt-exit")
return cmd


def start_bdd_target_process(context, cmd):
"""Start the BDD target process and wait for the initial prompt."""
context.interactive_process = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
args.append("--halt-exit")
return args


def start_bdd_target_process(context, extra_args):
"""Start the buffered BDD target and wait for the initial prompt.

Single Popen path across native and QEMU backends — target_driver
picks the spawn (subprocess on Linux/Windows, qemu-system-arm with
-kernel on FreeRTOS) and apply_extra_args delivers the same
--flag value pairs either as argv (native) or `set NAME VALUE` over
the UART (FreeRTOS) after the prompt is up. Adding a new embedded
platform reuses both helpers without touching this function.
"""
binary = context.example_binary
assert os.path.exists(binary), (
f"BDD target binary not found at {binary} — build with cmake first"
)

context.interactive_process = spawn_example_process(
context, extra_args=extra_args
)
context.example_pid = context.interactive_process.pid
wait_for_prompt(context.interactive_process)
apply_extra_args(context, context.interactive_process, extra_args)


@given("the BDD target is running with transport {transport:w}")
def step_bdd_target_running_with_transport(context, transport):
cmd = build_buffered_command(context, transport)
start_bdd_target_process(context, cmd)
start_bdd_target_process(context, build_buffered_extra_args(context, transport))


@given("the BDD target is running with transport {transport:w} and no structured data")
def step_bdd_target_running_with_transport_no_sd(context, transport):
cmd = build_buffered_command(context, transport, no_sd=True)
start_bdd_target_process(context, cmd)
start_bdd_target_process(
context, build_buffered_extra_args(context, transport, no_sd=True)
)


@given("the block store is enabled")
Expand Down Expand Up @@ -1269,8 +1278,7 @@ def step_check_last_sequence_id(context, value):

@given("the BDD target is running with default transport {transport:w}")
def step_bdd_target_running_with_default_transport(context, transport):
cmd = build_buffered_command(context, transport)
start_bdd_target_process(context, cmd)
start_bdd_target_process(context, build_buffered_extra_args(context, transport))


@when("the client switches to transport {transport:w}")
Expand Down
2 changes: 1 addition & 1 deletion Bdd/features/store_and_forward.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@tcp @buffered
@tcp @buffered @store
Feature: Store and forward during sender outage
When the syslog oracle goes down, messages accumulate in the
file-based store. Once the oracle recovers, the service loop
Expand Down
2 changes: 1 addition & 1 deletion Bdd/features/store_capacity.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@tcp @buffered
@tcp @buffered @store
Feature: Store capacity limit and discard policy
The block store uses rotating files with a configurable capacity.
When the store is full, the discard policy determines whether
Expand Down
170 changes: 170 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,175 @@
# Dev Log

## 2026-05-11 — S08.10 tcp_reconnect BDD scenario on QEMU (#339)

Second slice of S08.06. The reconnect-on-the-wire is already a property
of `SolidSyslogStreamSender` plus the close-on-Send-failure contract
the FreeRTOS adapter inherited in S08.09 (test
`SendClosesSocketOnError` pins it). So this story is the verification
slice: re-tag the BDD features so the FreeRTOS runner can pick up
`tcp_reconnect.feature` without dragging in the file-store-dependent
ones, and unify the interactive-process spawn through `target_driver`
so the buffered scenarios run on QEMU.

### Decisions

- **Split `@buffered` into `@buffered` + `@store`.** The earlier tag
was overloaded: it meant *both* "uses the long-lived interactive
process" *and* "needs a file-backed store". The freertos compose
filter `not @buffered` then excluded scenarios that need only the
interactive process (tcp_reconnect, switching_transport) alongside
the genuinely file-store-dependent ones (store_and_forward,
capacity_threshold, store_capacity, block_lifecycle,
power_cycle_replay). New convention: `@buffered` keeps the
interactive-process meaning, `@store` marks the file-store
dependency. FreeRTOS filter now excludes `@store` only;
`tcp_reconnect.feature` (`@tcp @buffered`, no `@store`) is admitted.
`switching_transport.feature` would also be admitted but lacks
`@udp`/`@tcp` at the feature level, so the existing
`(@udp or @tcp)` clause keeps it out until S08.11 lights it up.
- **Route `start_bdd_target_process` through `target_driver`, not
`subprocess.Popen` directly.** The one-shot path
(`run_example` → `spawn_example_process` → `apply_extra_args`)
already abstracted native-vs-QEMU spawn and argv-vs-UART
translation; the interactive path bypassed it. Unifying both
through `spawn_example_process` + `apply_extra_args` removes the
duplicated Popen shape and means future embedded platforms
(semihosting, OpenOCD/picocom against real hardware, …) extend
one branch point in `target_driver` instead of two parallel
orchestration helpers. Linux/Windows behaviour is byte-for-byte
unchanged: flags still arrive as argv. FreeRTOS gets QEMU plus
`set transport tcp` over the UART after `wait_for_prompt`.
- **`SendClosesSocketOnFailure` already covered.** The story's
optional unit-test deliverable was satisfied by S08.09's
`SendClosesSocketOnError` (and the matching
`SendClosesSocketOnShortWrite` /
`ReadReturnsNegativeOneOnErrorAndClosesSocket`) — the close-on-
failure contract is the *only* thing that makes reconnect possible
and it's already pinned by three tests using
`CHECK_SOCKET_CLOSED_ONCE`. No additional test added here.
- **`SO_SNDTIMEO=200ms` Service-starvation question deferred to the
CI scenario.** Local docker isn't available in the freertos-target
devcontainer and the BDD step that swaps the syslog-ng config to
trigger the outage can break docker networking if run outside the
compose environment — `bdd-freertos-qemu` is the safe arbiter.
QEMU smoke proved the interactive task survives `set transport tcp`
plus repeated `send` cycles, so the producer side is unblocked;
the Service-task drain behaviour during the actual outage is what
CI will surface.

### Deferred

- **`switching_transport.feature` on FreeRTOS** → S08.11 (#340). The
tagging cleanup now leaves the door open (no `@store`), but the
feature is tagged `@buffered` only — adding `@udp`/`@tcp` is part
of S08.11.
- **CMake-driven memory scaling** still open from S08.09 — captured
in memory `project_freertos_stack_budget`. Not touched here.
- **End-to-end BDD verification locally.** Docker still unavailable
in the freertos-target devcontainer; CI remains the real arbiter
for outage scenarios.

### Open questions

- Whether the 200ms blocking-connect cap during the outage window
starves the Service task enough to delay the post-resume delivery
past `wait_for_messages`' 10s deadline. CI will tell us; if it
flakes, the fix is to bump the timeout (per the
`feedback_no_flaky_ci` rule) rather than chase a real bug.

## 2026-05-11 — S08.09 SolidSyslogFreeRtosTcpStream + tcp_transport BDD on QEMU (#341)

First slice of S08.06 (TCP transport on FreeRTOS-Plus-TCP). Lands the
TCP stream adapter, extends the sockets fake to cover the TCP-side
API, flips Plus-TCP into TCP mode in the BDD target, and wraps UDP+TCP
under SolidSyslogSwitchingSender so behave's `--transport tcp` flips
the active transport over the UART. `tcp_transport.feature` green
against QEMU on the first CI run; no slirp-NAT flakiness on the
bounded-connect cap.

### Decisions

- **Bounded blocking connect via SO_SNDTIMEO=200ms, not non-blocking +
select.** FreeRTOS-Plus-TCP does not expose non-blocking connect
with a `select` companion (no analogue of POSIX `EINPROGRESS` →
`select` → `SO_ERROR`). The choreography is therefore: socket →
setsockopt(SNDTIMEO=200ms) → connect → setsockopt(SNDTIMEO=0,
RCVTIMEO=0) on success, or closesocket on failure. 200ms is short
enough that the Service task keeps draining predictably during an
outage, long enough for a healthy peer to ACK over slirp/LAN.
Clearing both timeouts post-connect restores the non-blocking
single-call contract `SolidSyslogStream` requires of Send/Read.
- **SwitchingSender with two inners on FreeRTOS, not three.** The
BDD_TARGET_SWITCH_TLS enum slot stays in `BddTargetSwitchConfig` for
cross-platform parity, but the FreeRTOS wiring sets `senderCount = 2`
with only UDP+TCP inners. SwitchingSender's existing bound-check
falls a `set transport tls` through to its NilSender (returns false,
no crash) rather than a NULL deref — graceful degradation until
S08.07 lands real TLS on FreeRTOS.
- **Pin every argument to every FreeRTOS call in the unit tests, not
just the obvious ones.** First-pass tests covered socket/connect/
send/recv args by value but only checked setsockopt's *option name*
via the side-effect on `LastSndTimeoSet`/`LastRcvTimeoSet`. Added
two tests pinning the setsockopt socket handle, level=0, and
optlen=sizeof(TickType_t) too, so a regression that changed the
level or optlen would fail at the bar instead of slipping through
the option-name proxy. MISRA-style argument coverage.
- **Single level of abstraction in Open/Send/Read, mirrored across all
three.** Each vtable function now reads as a three-line sentence:
guard on stream state → call the same-level helper
(`OpenSocket` / `SendOrCloseOnFailure` / `ReceiveOrCloseOnFailure`)
→ return. Native FreeRTOS calls are pushed into the second layer
(`TryConnect`, `TrySend`) where they belong. Pulled the
`Try*`/`*OrCloseOnFailure` pair pattern across Send and Read for
symmetry even though Receive has different return semantics than
Send — the names carry the intent at each layer. `IsClosed` removes
the negative guard in Open. Every static now wears the
`FreeRtosTcpStream_` prefix for MISRA C 5.9 file-scope identifier
uniqueness, matching FreeRtosDatagram.
- **InteractiveTask stack *40 → *48.** StreamSender.Connect allocates
a SolidSyslogAddressStorage plus a SolidSyslogFormatter sized for
SOLIDSYSLOG_MAX_HOST_SIZE on stack; TransmitFramed adds a small
octet-prefix formatter. Empirically tipped a *40 budget into a
Cortex-M lockup (PC=0, fault-during-fault — classic
stack-overflow-corrupting-the-exception-return signature) when
SwitchingSender flipped UDP→TCP→UDP across repeated sends. *48
restored ~4 KB headroom and the same scenario completed cleanly.
- **Test fixture: openStream() / readIntoBuffer() helpers and a
CHECK_SOCKET_CLOSED_ONCE macro.** The arrange step in 25+ tests
collapses to `openStream();`. The macro pins both the closesocket
call count *and* the identity of the closed socket — applying it
uniformly across the close-on-failure tests caught three sites
(SendClosesSocketOnError, ReadReturnsNegativeOneOnErrorAndClosesSocket,
DestroyClosesOpenSocket) that previously only checked the count.
The check count went 56 → 59 from that tightening alone.

### Deferred

- **CMake-driven memory scaling** for both static-`_Create` storage
sizes and FreeRTOS task stack depths. The recurring stack bumps
(*16 → *32 → *40 → *48) and the per-class `SOLIDSYSLOG_*_SIZE`
macros point to this as the right next intervention: expose them as
CMake-tunable variables so integrators size memory once,
declaratively, instead of editing `main.c` each time a new feature
lands. Captured in memory `project_freertos_stack_budget` (renamed
from *40 to *48 ceiling).
Comment on lines +129 to +155

@coderabbitai coderabbitai Bot May 11, 2026

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix malformed emphasis markers in stack-size text.

The *N tokens are being parsed as emphasis and trigger markdownlint MD037 warnings (Line 135, Line 150, Line 155). Wrap these values in backticks (or escape *) to keep the intended literal text.

Suggested patch
-- **InteractiveTask stack *40 → *48.** StreamSender.Connect allocates
+- **InteractiveTask stack `*40` → `*48`.** StreamSender.Connect allocates

-  (*16 → *32 → *40 → *48) and the per-class `SOLIDSYSLOG_*_SIZE`
+  (`*16` → `*32` → `*40` → `*48`) and the per-class `SOLIDSYSLOG_*_SIZE`

-  from *40 to *48 ceiling).
+  from `*40` to `*48` ceiling).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **InteractiveTask stack *40 → *48.** StreamSender.Connect allocates
a SolidSyslogAddressStorage plus a SolidSyslogFormatter sized for
SOLIDSYSLOG_MAX_HOST_SIZE on stack; TransmitFramed adds a small
octet-prefix formatter. Empirically tipped a *40 budget into a
Cortex-M lockup (PC=0, fault-during-fault — classic
stack-overflow-corrupting-the-exception-return signature) when
SwitchingSender flipped UDP→TCP→UDP across repeated sends. *48
restored ~4 KB headroom and the same scenario completed cleanly.
- **Test fixture: openStream() / readIntoBuffer() helpers and a
CHECK_SOCKET_CLOSED_ONCE macro.** The arrange step in 25+ tests
collapses to `openStream();`. The macro pins both the closesocket
call count *and* the identity of the closed socket — applying it
uniformly across the close-on-failure tests caught three sites
(SendClosesSocketOnError, ReadReturnsNegativeOneOnErrorAndClosesSocket,
DestroyClosesOpenSocket) that previously only checked the count.
The check count went 56 → 59 from that tightening alone.
### Deferred
- **CMake-driven memory scaling** for both static-`_Create` storage
sizes and FreeRTOS task stack depths. The recurring stack bumps
(*16 → *32 → *40 → *48) and the per-class `SOLIDSYSLOG_*_SIZE`
macros point to this as the right next intervention: expose them as
CMake-tunable variables so integrators size memory once,
declaratively, instead of editing `main.c` each time a new feature
lands. Captured in memory `project_freertos_stack_budget` (renamed
from *40 to *48 ceiling).
- **InteractiveTask stack `*40``*48`.** StreamSender.Connect allocates
a SolidSyslogAddressStorage plus a SolidSyslogFormatter sized for
SOLIDSYSLOG_MAX_HOST_SIZE on stack; TransmitFramed adds a small
octet-prefix formatter. Empirically tipped a *40 budget into a
Cortex-M lockup (PC=0, fault-during-fault — classic
stack-overflow-corrupting-the-exception-return signature) when
SwitchingSender flipped UDP→TCP→UDP across repeated sends. *48
restored ~4 KB headroom and the same scenario completed cleanly.
- **Test fixture: openStream() / readIntoBuffer() helpers and a
CHECK_SOCKET_CLOSED_ONCE macro.** The arrange step in 25+ tests
collapses to `openStream();`. The macro pins both the closesocket
call count *and* the identity of the closed socket — applying it
uniformly across the close-on-failure tests caught three sites
(SendClosesSocketOnError, ReadReturnsNegativeOneOnErrorAndClosesSocket,
DestroyClosesOpenSocket) that previously only checked the count.
The check count went 56 → 59 from that tightening alone.
### Deferred
- **CMake-driven memory scaling** for both static-`_Create` storage
sizes and FreeRTOS task stack depths. The recurring stack bumps
(`*16``*32``*40``*48`) and the per-class `SOLIDSYSLOG_*_SIZE`
macros point to this as the right next intervention: expose them as
CMake-tunable variables so integrators size memory once,
declaratively, instead of editing `main.c` each time a new feature
lands. Captured in memory `project_freertos_stack_budget` (renamed
from `*40` to `*48` ceiling).
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 135-135: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


[warning] 150-150: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


[warning] 150-150: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


[warning] 155-155: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

🤖 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 `@DEVLOG.md` around lines 129 - 155, Replace the literal asterisk-number tokens
that are being parsed as emphasis (e.g. "*40", "*48", the sequence "*16 → *32 →
*40 → *48", and any occurrences in "SOLIDSYSLOG_*_SIZE") with their code-literal
form by wrapping them in backticks (or escaping the asterisks) so the markdown
renders them as literal tokens; update the DEVLOG.md instances that mention the
stack budgets and the "project_freertos_stack_budget" rename accordingly.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Declining — those *40/*48/*16 tokens are in the S08.09 DEVLOG entry (commit 712bde8, PR #341, already merged) which this PR does not touch. Per the project SKILL.md convention "Never rewrite history. Always append.", historical DEVLOG entries are not edited even to fix lint warnings. The S08.10 entry added in this PR doesn't use bare *N tokens, so no new lint warnings are introduced. Future entries will avoid the pattern (use backticks around \*N`` or call them out as e.g. "INTERACTIVE_TASK_STACK_DEPTH = configMINIMAL_STACK_SIZE * 48").

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.

@DavidCozens Acknowledged — makes sense to keep history immutable per SKILL.md. I’ve double-checked: the S08.10 DEVLOG chunk in this PR doesn’t introduce any bare “*N” tokens, so no new MD037 warnings are added.

If you’d like to silence the legacy warnings without touching prior entries, we can:

  • Add DEVLOG.md to markdownlint-cli2 ignores (repo-wide), or
  • Add a rule override for MD037 scoped to DEVLOG.md.

Happy to open a tiny follow-up PR either way; just say which option you prefer (or leave as-is).


✏️ Learnings added
Learnt from: DavidCozens
Repo: DavidCozens/solid-syslog PR: 342
File: DEVLOG.md:129-155
Timestamp: 2026-05-11T19:55:23.240Z
Learning: Do not edit historical DEVLOG entries; follow SKILL.md “Never rewrite history. Always append.” For future DEVLOG entries, avoid bare *N tokens; use backticks (e.g., `*48`) or explicit expressions instead.

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.

- **tcp_reconnect.feature** → S08.10 (#339, next session).
- **switching_transport.feature** (and the `switch` runtime command in
`OnSet`) → S08.11 (#340).
- **TLS on FreeRTOS** → S08.07 (#272). The
`BDD_TARGET_SWITCH_TLS` slot graceful-fallback decision above is
the bridge until then.
- **End-to-end BDD verification locally.** Docker isn't available
inside the freertos-target devcontainer, so the
`bdd-freertos-qemu` job was the real arbiter; QEMU smoke (UDP+TCP
switching) was the strongest local signal. Captured in memory
`project_behave_container_gaps`.

### Open questions

- None. The merged CI run was all-green first try — including the
bounded-connect over slirp NAT, which was the most-suspect path.

## 2026-05-11 — S24.03 drop AtomicOps vtable; select atomics at link time (#326)

E24 close-out story. The vtable through which AtomicCounter reached
Expand Down
8 changes: 5 additions & 3 deletions ci/docker-compose.bdd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ services:
- BDD_TARGET=freertos
- EXAMPLE_BINARY=build/freertos-cross/Bdd/Targets/FreeRtos/SolidSyslogBddTarget.elf
# S08.09 admits @tcp scenarios so tcp_transport.feature runs alongside the
# existing @udp ones; @buffered excludes store-and-forward features that
# need a file-backed store (no FreeRTOS file abstraction yet), and
# existing @udp ones. S08.10 splits the earlier @buffered exclusion: @store
# marks features that need a file-backed store (no FreeRTOS file
# abstraction yet), while plain @buffered just means "uses the long-lived
# interactive process" and is supported on FreeRTOS via QEMU's UART.
# @windows_wip excludes the Windows-only TCP singletask variant.
command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and not @rtc and not @buffered and not @windows_wip and (@udp or @tcp)' Bdd/features/
command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and not @rtc and not @store and not @windows_wip and (@udp or @tcp)' Bdd/features/

volumes:
bdd-output-linux:
Expand Down
Loading