After years of building test benches, automating web and API test suites, setting up mobile testing pipelines, and debugging why test run #847 failed at 3am on a Friday — I realized every project had the same problem. Not the bugs themselves, but the inability to trace them back to where they came from.
The Real Problem: Traceability
People think testing is about running tests. It's not. Testing is about answering questions: Did we cover this requirement? Which spec does this failure violate? What changed between build 23 and build 24 that broke the overvoltage protection? When a regulator audits your safety case, can you hand them a traceability matrix without spending three weeks building it manually?
I've worked on BMS validation, rail system testing, web applications, mobile apps, cloud APIs, and smart grid infrastructure. Different domains, different interfaces, different hardware. But the traceability gap was identical everywhere:
- Requirements lived in one tool (Polarion, DOORS, JIRA), test cases in another (Excel, Confluence, custom scripts), and results in a third (email, shared folders, someone's laptop)
- When a test failed, finding the root requirement meant manually cross-referencing three documents and a commit history
- Test coverage reports were assembled by hand every sprint — someone copying numbers into a PowerPoint
- Auditors asked for a requirement-to-test-to-defect trace and the answer was always "give us two weeks"
- Knowledge walked out the door with the person — nothing was structured, nothing was repeatable
What I Actually Needed
I didn't need another test runner. pytest is excellent. Robot Framework is excellent. Selenium, Appium, Cypress — all excellent at what they do. What I needed was a layer above the test execution that understood the full lifecycle:
- Requirement traceability by default — every executable test can carry a Bloom Testcase ID, so results close the loop to the requirement that case verifies. Zero spreadsheet archaeology.
- Multi-domain test execution — the same runner and library pattern works for HiL/SiL benches, API integration tests, and UI automation. Real projects span all of these.
- Station visibility — runners register with a name and heartbeat so you can see which benches are online, without pretending Bud is a Kubernetes scheduler for hardware.
- JUnit-compatible everywhere — because every CI system speaks JUnit. Jenkins, GitLab CI, GitHub Actions — Bud plugs in as a shell step.
- Self-hosted TMP — runs, artifacts, and runner identity in one place you control, not locked behind a six-figure license.
How Bud Works
Bud is split into layers that can be used independently or together:
budtestlibrary
The Python test framework. You write test classes with optional BloomMetaData so uploaded results carry a Bloom Testcase ID. It handles setup/teardown and produces JUnit XML. Whether you're asserting a CAN response or a REST endpoint, the structure stays the same.
bud-runner
A CLI you install with pip. Register a machine against your Bud backend, then execute a local test-case list and upload results:
python -m bud_runner register --username my-runner --socket-port 53035
python -m bud_runner run-tests --test-case-list tests.smoke.SMOKE_TEST_LIST --upload
The list attribute is a Python list of "module.ClassName" strings in your repo — Bud does not pull suite definitions from the server. Integrates with GitHub Actions, GitLab CI, and Jenkins as a pipeline step.
Bud Web App
FastAPI + React dashboard for visualizing test runs, tracking pass/fail trends, drilling into failures with full log context, and monitoring runners under Test Stations. One place for who ran what, where, and with which artifacts.
Bud Test GUI
A PyQt6 desktop client for engineers who need to interact with test hardware from the bench — without living in the browser.
Why This Matters Across Every Domain
The traceability problem isn't embedded-specific. When I was automating web and API tests, the same gaps existed. When I was setting up mobile testing pipelines, the same gaps existed. The domain changes — CAN bus becomes REST, test benches become staging environments, dSPACE becomes Docker — but the fundamental need to trace a failing test back to a requirement never changes.
If a test fails and you can't answer "what requirement does this violate?" without opening three tools, your tooling has failed you — regardless of whether you're testing firmware, a web app, or a mobile interface.
The Hard Parts
The easy part was writing the test framework. The hard part was keeping the Bud↔Bloom bridge narrow and reliable: execution status keyed by Testcase ID after you configure PLM Integration — not a second requirements database inside Bud, and not fuzzy name matching across tools.
For hardware-dependent projects specifically, every bench is different. Multiple engineers sharing stations need heartbeat visibility and parallel uploads — not a fantasy of fully automatic capability-based scheduling that doesn't match how labs actually run today.
Bud and Bloom: Closing the Loop
Bud handles test execution and reporting. Bloom handles product lifecycle artefacts — requirements, test cases, campaigns, baselines, directional trace links. The bridge is deliberate: attach a Testcase ID in code via BloomMetaData, upload to Bud, and once Bloom is connected under Settings → PLM Integration, Bud posts aggregated execution status for that ID. Bud does not pull suites from Bloom, and it does not sync campaigns, requirements, or documents.
That's the shipped loop: from "what did we intend to verify" through "did this run pass" keyed by a stable Testcase ID. Auditable. Explicit. No spreadsheet glue.