Skip to content

feat(ecosystem): test all 19 repos, and stop reporting passes that never ran - #16

Open
srpatcha wants to merge 5 commits into
masterfrom
feat/ecosystem-runner-all-repos
Open

feat(ecosystem): test all 19 repos, and stop reporting passes that never ran#16
srpatcha wants to merge 5 commits into
masterfrom
feat/ecosystem-runner-all-repos

Conversation

@srpatcha

Copy link
Copy Markdown
Member

eosim ecosystem is the command for validating the whole organisation. It was reaching 2 of the 19 repos, and could not report a failure for the ones it did reach.

It could not fail

tests_passed = max(tests_passed, tests_run if build_ok else 0)
tests_failed = max(0, tests_run - tests_passed)
passed       = build_ok and tests_failed == 0

tests_run was a count of executables on disk. Any repo that compiled reported every test passing. Reproduced directly:

ctest reported passing : 0
harness reports passed : 22
harness reports failed : 0
harness verdict        : PASS

It found 2 of 19 repos

find_repos held a hardcoded list of lowercase names — eai, eni, eipc, eboot, ebuild-tool. None match a real directory on a case-sensitive filesystem, and run_ecosystem_tests silently continued past anything not on the list.

Discovery is now by inspection: a .git makes it a repo, the files present decide the build system. A new product becomes testable by being cloned.

Honest statuses

A repo that cannot be tested reports SKIP, never PASS. DEPS is narrower — the suite exists but the repo's declared dependencies are absent here, which is not a broken test. Repos with two build systems get both exercised, which is how ebuild's broken CMake configure surfaced from behind its green Python suite.

Two runner bugs found by running it

  • pytest was invoked with -q; a repo whose own addopts sets -q reached -q -q, which suppresses the summary line — a fully green EoStudio read as "no summary produced".
  • PYTHONPATH omitted the repo, so anything not pip-installed died at conftest import. Root and src/ are both offered now (eDB uses a src layout).
  • CLI: --simulate was accepted and never passed through, so --no-simulate did nothing. Added --only and --list.

Result

  Repos:  19 discovered | 7 passed | 1 failed | 11 skipped
  Tests:  2420 run      | 2404 passed | 16 failed

  [FAIL ] ebuild       cmake   cmake configure failed        ← real, see eBoot#60
  [PASS ] EoSim        python  tests:1702/1702
  [PASS ] EoStudio     python  tests:475/475
  [PASS ] eAI          cmake   tests:24/24
  [PASS ] eBoot        cmake   tests:17/17
  [PASS ] eFirmware    cmake   tests:3/3
  [PASS ] eNI          cmake   tests:40/40
  [PASS ] ebuild       python  tests:98/98
  [PASS ] eos          cmake   tests:22/22
  [DEPS ] eDB          python  tests:23/39  needs fastapi

Verification: 1702 pass (1666 before, +36 new tests here). Full ecosystem run against the 19-repo workspace reproduces that table.

🤖 Generated with Claude Code

claude and others added 3 commits August 25, 2026 21:49
… wheel

EoSim is the healthiest repo in the org - 1645 tests passing before this change.
But `eosim run stm32f4` printed "PASSED (10000 cycles)" while executing nothing,
and the published wheel could not run at all. Both are fixed.

THE RUN REPORTED SUCCESS FOR DOING NOTHING

_run_eosim() never loaded firmware. It built a VirtualMachine over zeroed memory,
stepped the CPU 10000 times through NOPs, and VirtualMachine.run() returned the
literal `success: True` after printing "EoS booted successfully" unconditionally.
No EoS was involved and nothing distinguished that from a real boot.

  run() now reports why it stopped - 'no-firmware', 'halted', 'cycle-limit' or
  'timeout' - and success is derived: only a clean halt counts. Exhausting the
  cycle budget or the clock means we stopped it, not that it finished.
  `eosim run` gained --firmware and exits 2 with "NO FIRMWARE" when given none.

  Measured, from a clean wheel install:
    eosim run stm32f4                     -> NO FIRMWARE, exit 2
    eosim run stm32f4 --firmware fw.bin   -> PASSED (3 cycles, halted), exit 0

THE WHEEL WAS BROKEN

No EoSim release has ever published a wheel, so this had never been exercised.
Building one showed why it matters: `platforms/` sat at the repository root and
was addressed as EOSIM_ROOT/"platforms" where EOSIM_ROOT is site-packages once
installed. Every `eosim run` from a wheel died with FileNotFoundError on
site-packages/platforms. An editable install hides this completely.

  Moved the 150 platform directories to eosim/platforms so the package is
  self-contained, declared them as package-data, and made the lookup prefer the
  packaged location with a fallback for older layouts.
  Verified: wheel contains 149 platform.yml; a fresh venv installs it and
  `eosim stats` reports 149 platforms.

THE CPU IS REAL - now proven

eosim/engine/native/cpu implements an ARM32 subset (MOV imm, B, LDR, STR, BX LR,
SVC, UDF) and genuinely decodes and executes. tests/unit/test_native_engine_
execution.py hand-assembles instructions and asserts the resulting register and
memory state, so this is measured rather than assumed. Also fixed the cycle
accounting: the halting instruction retired but was not counted, leaving
run()['cycles'] one behind cpu.state.cycles for every program that halts.

I CHANGED FIVE EXISTING TESTS - flagging this explicitly

test_core::test_vm_run, test_gui::test_vm_uart_output,
test_gui::test_vm_run_stop_lifecycle, test_cli::test_run_eosim_engine and
test_engines_and_integrations::TestEoSimEngineRun::test_run all ran with NO
firmware and asserted success. They pinned the defect. Each now loads a real
image and asserts the real outcome, and each gained a companion asserting that
the no-firmware path is a failure. Nothing was deleted or skipped.

Two integration tests started skipping with "platforms/ directory not found"
after the move - a silent disable, which is worse than a failure. Repointed;
test_validate_all_real_platforms now validates all 149 configs for real.

ALSO

  --version said 2.0.0 while pyproject and eosim.__version__ said 3.0.1. Now
  sourced from __version__.
  Log output used escaped \\n, so every log arrived as one long line with
  literal backslash-n. Four sites fixed.
  Package metadata claimed "Development Status :: 5 - Production/Stable" and
  "World's most powerful universal simulation platform - supersedes 250+ tools
  across 20 domains". Now Beta, with a description of what it is.
  CI counted platform.yml at the old path.

Verified: 1659 passed, 3 skipped (pre-existing: tkinter absent), 0 failed.
Coverage 60.87%. ruff clean on the file I added; the repo has 490 pre-existing
ruff errors, untouched and unrelated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The decoder covers eight ARM32 instructions: MOV imm, B, LDR, STR, BX LR, SVC,
UDF and the all-zero word. Anything else fell off the end of the if/elif chain
and execution continued as though it had run.

That is the same class of defect as the hardcoded success this branch already
fixed, and it is worse, because it survives the fix. Measured before the change:

  MOV r1,#5 ; MOV r2,#3 ; ADD r0,r1,r2 ; UDF
    -> R0 = 0, reason 'halted', success True

The ADD never executed, the run reported a clean successful halt, and nothing in
the output said otherwise. Since only eight opcodes are decoded, any real
firmware is mostly undecodable - PUSH {lr}, which opens almost every compiled ARM
function, is not among them. Loading a genuine EoS image would have produced a
confident "PASSED" for a program that computed nothing.

Now:
  - _execute() reports whether it decoded the instruction.
  - step() halts on an unknown opcode when strict_undefined is set (the default),
    recording the count and the offending PC/opcode.
  - run() reports reason 'undefined-instruction', success False, and prints the
    opcode, address, and the fact that this engine cannot execute a full firmware
    image - so the limitation is visible at the point it bites.
  - undefined_count is returned in the result.
  - strict_undefined can be turned off for tracing experiments. Undecoded
    instructions are still counted then, just not fatal.

This bounds what the native engine honestly claims: it runs small hand-assembled
programs, not an operating system. Extending the ISA is the work that would
change that, and it is now a visible failure rather than a silent one.

Verified: 1663 passed, 3 skipped (pre-existing: tkinter absent), 0 failed. The
1645 tests that predate this branch all still pass. ruff clean on the test file;
the 4 errors in cpu/__init__.py are pre-existing, confirmed by stashing.
Wheel rebuilt and reinstalled clean: 149 platforms, firmware run PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ver ran

`eosim ecosystem` is the command for validating the whole organisation. It
was reaching 2 of the 19 repos in the workspace, and could not report a
failure for the ones it did reach.

Discovery. find_repos held a hardcoded list of seven lowercase names —
"eai", "eni", "eipc", "eboot", "ebuild-tool". None of those match a real
directory on a case-sensitive filesystem, so only eos and eApps were found,
and run_ecosystem_tests silently `continue`d past anything not on the list.
Repos are now discovered by looking for .git, and their build system by the
files they contain, so a new product becomes testable by being cloned.

Fabricated results. test_c_repo ended with

    tests_passed = max(tests_passed, tests_run if build_ok else 0)
    tests_failed = max(0, tests_run - tests_passed)
    passed       = build_ok and tests_failed == 0

where tests_run counted executables on disk. Any repo that compiled reported
every test passing and a verdict of PASS, whether or not one test had run.
ctest and pytest output is now parsed for the counts they actually printed,
and `passed` derives from a single status field.

A repo that cannot be tested reports SKIP, never PASS — an absent toolchain,
a repo with no tests registered, and a green suite are three different facts.
DEPS is a narrower case: the suite exists but the repo's own declared
dependencies are missing here, which is not the repo's fault and not a FAIL.

Repos with more than one build system now have each one exercised. ebuild is
a Python CLI that also ships a CMakeLists integrating the sibling repos;
running only the primary kind left one of the two untested, which is how a
broken CMake configure sat behind a green Python suite.

Two runner bugs found by running it:

- pytest was invoked with -q. A repo whose own addopts already sets -q ended
  up at -q -q, which suppresses the summary line — a fully green EoStudio
  read as "no summary produced". The flag is no longer passed.
- PYTHONPATH did not include the repo, so a repo that is not pip-installed
  failed at conftest import. Both the root and src/ are now offered, which
  covers the src-layout eDB uses.

CLI: --simulate was accepted and then never passed to run_ecosystem_tests,
so --no-simulate did nothing. Added --only to test named repos and --list to
show what would run.

    before   2 repos reached, counts fabricated, verdict always ALL PASSED
    after    19 discovered, 7 passed, 1 failed, 11 skipped, 2420 tests run

The one failure is real: ebuild's CMake configure, tracked in eBoot#60.

Verification: 1702 pass (1666 before, +36 new tests here); full ecosystem run
against the 19-repo workspace reproduces the table above.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread tests/unit/test_core.py
output = str(tmp_path / "junit.xml")
path = generate_junit(results, output)
assert os.path.exists(path)
content = open(path).read()
Comment thread tests/unit/test_cli.py
assert 'Artifacts exported' in result.output
# SPDX-License-Identifier: MIT
"""Unit tests for CLI commands using Click's CliRunner."""
from unittest.mock import patch, MagicMock
not a single test had run.
"""

import os
Comment on lines +30 to +36
from eosim.integrations.ecosystem import (
DEPS, ERROR, FAIL, PASS, SKIP,
EcosystemReport, RepoTestResult,
_parse_ctest, _parse_pytest,
detect_kind, detect_kinds, find_repos,
test_repo as run_one_repo,
)
Comment thread eosim/cli/main.py
data = yaml.safe_load(f)
if data and data.get("name") == name:
return yml, data
except Exception:
Comment thread eosim/cli/main.py
data = yaml.safe_load(f)
if data:
return candidate, data
except Exception:
srpatcha and others added 2 commits August 28, 2026 11:28
…t of the failure count

Two things the first full run exposed.

The CMake tree was written to <repo>/eosim-build, so every C repo the runner
touched was left with an untracked directory in it — a dirty working tree in
eight repos, and in one without a matching .gitignore, something a developer
could commit by accident. Build trees now go under ~/.cache/eosim/ecosystem,
one per repo, overridable with EOSIM_BUILD_ROOT.

The summary also read "0 repos failed" beside "16 tests failed", because a
DEPS repo's uncollectable tests were folded into total_failed. Those tests
never ran; counting them as failures reads as broken code when the cause is
an absent dependency. They are counted and labelled separately now.

    Tests:  2463 run | 2447 passed | 0 failed | 16 blocked on missing deps

Verification: 1706 pass; a full ecosystem run leaves no repo dirty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…it code

eosllm is built and tested through a Makefile, so the runner reported
"no runner for a 'make' project" and it contributed nothing to the ecosystem
run. Its Makefile declares a `test` target; that target now runs.

`make test` is only attempted when the Makefile actually declares the rule.
Running it otherwise fails with "No rule to make target", which would read as
a broken repo rather than one that keeps its tests somewhere else.

There is no count to parse out of make, and inventing one would be exactly
the fabrication this module was rewritten to remove, so a make repo reports
its exit code instead of a tests:0/0 that looks like nothing ran. The report
shows a count where there is one and the reason where there is not.

Measured against the 19-repo workspace, with the toolchains now installed on
this machine:

    before   8 passed | 1 failed | 10 skipped | 2867 tests
    after    9 passed | 1 failed |  9 skipped | 2867 tests

eIPC also moved from SKIP to PASS with 152 Go tests once Go was available --
no change needed here, which is the point of detecting toolchains at run time
rather than hardcoding what a machine has.

1710 tests pass, 44 in this module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
srpatcha added a commit that referenced this pull request Aug 30, 2026
detect_kinds() inspected only the root, so three of the nineteen repos in
the workspace reported "unknown", found no runner, and were skipped:

    eCAD-Hardware-Products   tests/, no packaging file
    eos-aero                 AeroSwift/software/web_app/package.json
    eos-health               firmware/build-system/CMakeLists.txt
                             apps/web/package.json

16% of the organisation was invisible to the tool whose purpose is to test
the organisation. eos-health is the sharpest case: it ships firmware with a
CMake build and a web app, and neither was ever compiled or tested.

The summary counted them as skipped, next to the passes:

    Repos: 19 discovered | N passed | 0 failed | 3 skipped

"0 failed" with everything green is the shape a healthy run makes. A skip
reads as a pass at a glance; the reason only appears in the detail rows.
Same class as the fabricated pass removed in #16 — a repo that was never
tested reporting as though nothing was wrong — through a different door.

Two changes.

detect_components() returns (kind, directory) pairs. The runners build from
the directory handed to them, so a nested component has to carry its own
location; returning "cmake" for eos-health without it would send
cmake -S at a root with no CMakeLists.txt, replacing a silent skip with a
spurious failure. Root detection runs first and is returned unchanged when
it finds anything, so the scan only ever runs for a repo that would have
been skipped. Bounded to four levels and blind to node_modules, build,
dist, venv, vendor and friends — a vendored package.json belongs to a
dependency, not to the repo.

A tests/ directory holding Python now identifies a Python project.
test_python_repo() only ever required tests/; demanding pyproject.toml to
reach it was the detector asking for something the runner does not use.
Guarded on no other kind having matched, so eBoot — CMakeLists.txt plus a
tests/ directory full of C — does not also get pytest pointed at it.

Verified no regression: detect_kinds output compared against the original
for every repo in the workspace. One difference, the intended one:

    eCAD-Hardware-Products   ['unknown']  ->  ['python']

All other 18 identical, and detect_components equals the old detect_kinds
result for all 17 root-detected repos.

After: 0 unknown, 19 repos yielding 26 components.

    tests/unit/test_ecosystem_runner.py   44 -> 54
    full suite                          1710 -> 1720 passed, 0 failed

Measured against this branch's base, which is #16, not master. master alone
is at 1648; #16 carries it to 1710. This stacks on #16 and needs it first.

Closes #17

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
srpatcha added a commit that referenced this pull request Sep 1, 2026
* fix(engine): make a simulation run mean something, and ship a working wheel

EoSim is the healthiest repo in the org - 1645 tests passing before this change.
But `eosim run stm32f4` printed "PASSED (10000 cycles)" while executing nothing,
and the published wheel could not run at all. Both are fixed.

THE RUN REPORTED SUCCESS FOR DOING NOTHING

_run_eosim() never loaded firmware. It built a VirtualMachine over zeroed memory,
stepped the CPU 10000 times through NOPs, and VirtualMachine.run() returned the
literal `success: True` after printing "EoS booted successfully" unconditionally.
No EoS was involved and nothing distinguished that from a real boot.

  run() now reports why it stopped - 'no-firmware', 'halted', 'cycle-limit' or
  'timeout' - and success is derived: only a clean halt counts. Exhausting the
  cycle budget or the clock means we stopped it, not that it finished.
  `eosim run` gained --firmware and exits 2 with "NO FIRMWARE" when given none.

  Measured, from a clean wheel install:
    eosim run stm32f4                     -> NO FIRMWARE, exit 2
    eosim run stm32f4 --firmware fw.bin   -> PASSED (3 cycles, halted), exit 0

THE WHEEL WAS BROKEN

No EoSim release has ever published a wheel, so this had never been exercised.
Building one showed why it matters: `platforms/` sat at the repository root and
was addressed as EOSIM_ROOT/"platforms" where EOSIM_ROOT is site-packages once
installed. Every `eosim run` from a wheel died with FileNotFoundError on
site-packages/platforms. An editable install hides this completely.

  Moved the 150 platform directories to eosim/platforms so the package is
  self-contained, declared them as package-data, and made the lookup prefer the
  packaged location with a fallback for older layouts.
  Verified: wheel contains 149 platform.yml; a fresh venv installs it and
  `eosim stats` reports 149 platforms.

THE CPU IS REAL - now proven

eosim/engine/native/cpu implements an ARM32 subset (MOV imm, B, LDR, STR, BX LR,
SVC, UDF) and genuinely decodes and executes. tests/unit/test_native_engine_
execution.py hand-assembles instructions and asserts the resulting register and
memory state, so this is measured rather than assumed. Also fixed the cycle
accounting: the halting instruction retired but was not counted, leaving
run()['cycles'] one behind cpu.state.cycles for every program that halts.

I CHANGED FIVE EXISTING TESTS - flagging this explicitly

test_core::test_vm_run, test_gui::test_vm_uart_output,
test_gui::test_vm_run_stop_lifecycle, test_cli::test_run_eosim_engine and
test_engines_and_integrations::TestEoSimEngineRun::test_run all ran with NO
firmware and asserted success. They pinned the defect. Each now loads a real
image and asserts the real outcome, and each gained a companion asserting that
the no-firmware path is a failure. Nothing was deleted or skipped.

Two integration tests started skipping with "platforms/ directory not found"
after the move - a silent disable, which is worse than a failure. Repointed;
test_validate_all_real_platforms now validates all 149 configs for real.

ALSO

  --version said 2.0.0 while pyproject and eosim.__version__ said 3.0.1. Now
  sourced from __version__.
  Log output used escaped \\n, so every log arrived as one long line with
  literal backslash-n. Four sites fixed.
  Package metadata claimed "Development Status :: 5 - Production/Stable" and
  "World's most powerful universal simulation platform - supersedes 250+ tools
  across 20 domains". Now Beta, with a description of what it is.
  CI counted platform.yml at the old path.

Verified: 1659 passed, 3 skipped (pre-existing: tkinter absent), 0 failed.
Coverage 60.87%. ruff clean on the file I added; the repo has 490 pre-existing
ruff errors, untouched and unrelated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(cpu): stop treating undecoded opcodes as no-ops

The decoder covers eight ARM32 instructions: MOV imm, B, LDR, STR, BX LR, SVC,
UDF and the all-zero word. Anything else fell off the end of the if/elif chain
and execution continued as though it had run.

That is the same class of defect as the hardcoded success this branch already
fixed, and it is worse, because it survives the fix. Measured before the change:

  MOV r1,#5 ; MOV r2,#3 ; ADD r0,r1,r2 ; UDF
    -> R0 = 0, reason 'halted', success True

The ADD never executed, the run reported a clean successful halt, and nothing in
the output said otherwise. Since only eight opcodes are decoded, any real
firmware is mostly undecodable - PUSH {lr}, which opens almost every compiled ARM
function, is not among them. Loading a genuine EoS image would have produced a
confident "PASSED" for a program that computed nothing.

Now:
  - _execute() reports whether it decoded the instruction.
  - step() halts on an unknown opcode when strict_undefined is set (the default),
    recording the count and the offending PC/opcode.
  - run() reports reason 'undefined-instruction', success False, and prints the
    opcode, address, and the fact that this engine cannot execute a full firmware
    image - so the limitation is visible at the point it bites.
  - undefined_count is returned in the result.
  - strict_undefined can be turned off for tracing experiments. Undecoded
    instructions are still counted then, just not fatal.

This bounds what the native engine honestly claims: it runs small hand-assembled
programs, not an operating system. Extending the ISA is the work that would
change that, and it is now a visible failure rather than a silent one.

Verified: 1663 passed, 3 skipped (pre-existing: tkinter absent), 0 failed. The
1645 tests that predate this branch all still pass. ruff clean on the test file;
the 4 errors in cpu/__init__.py are pre-existing, confirmed by stashing.
Wheel rebuilt and reinstalled clean: 149 platforms, firmware run PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(ecosystem): test all 19 repos, and stop reporting passes that never ran

`eosim ecosystem` is the command for validating the whole organisation. It
was reaching 2 of the 19 repos in the workspace, and could not report a
failure for the ones it did reach.

Discovery. find_repos held a hardcoded list of seven lowercase names —
"eai", "eni", "eipc", "eboot", "ebuild-tool". None of those match a real
directory on a case-sensitive filesystem, so only eos and eApps were found,
and run_ecosystem_tests silently `continue`d past anything not on the list.
Repos are now discovered by looking for .git, and their build system by the
files they contain, so a new product becomes testable by being cloned.

Fabricated results. test_c_repo ended with

    tests_passed = max(tests_passed, tests_run if build_ok else 0)
    tests_failed = max(0, tests_run - tests_passed)
    passed       = build_ok and tests_failed == 0

where tests_run counted executables on disk. Any repo that compiled reported
every test passing and a verdict of PASS, whether or not one test had run.
ctest and pytest output is now parsed for the counts they actually printed,
and `passed` derives from a single status field.

A repo that cannot be tested reports SKIP, never PASS — an absent toolchain,
a repo with no tests registered, and a green suite are three different facts.
DEPS is a narrower case: the suite exists but the repo's own declared
dependencies are missing here, which is not the repo's fault and not a FAIL.

Repos with more than one build system now have each one exercised. ebuild is
a Python CLI that also ships a CMakeLists integrating the sibling repos;
running only the primary kind left one of the two untested, which is how a
broken CMake configure sat behind a green Python suite.

Two runner bugs found by running it:

- pytest was invoked with -q. A repo whose own addopts already sets -q ended
  up at -q -q, which suppresses the summary line — a fully green EoStudio
  read as "no summary produced". The flag is no longer passed.
- PYTHONPATH did not include the repo, so a repo that is not pip-installed
  failed at conftest import. Both the root and src/ are now offered, which
  covers the src-layout eDB uses.

CLI: --simulate was accepted and then never passed to run_ecosystem_tests,
so --no-simulate did nothing. Added --only to test named repos and --list to
show what would run.

    before   2 repos reached, counts fabricated, verdict always ALL PASSED
    after    19 discovered, 7 passed, 1 failed, 11 skipped, 2420 tests run

The one failure is real: ebuild's CMake configure, tracked in eBoot#60.

Verification: 1702 pass (1666 before, +36 new tests here); full ecosystem run
against the 19-repo workspace reproduces the table above.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ecosystem): build outside the checkout, and keep blocked tests out of the failure count

Two things the first full run exposed.

The CMake tree was written to <repo>/eosim-build, so every C repo the runner
touched was left with an untracked directory in it — a dirty working tree in
eight repos, and in one without a matching .gitignore, something a developer
could commit by accident. Build trees now go under ~/.cache/eosim/ecosystem,
one per repo, overridable with EOSIM_BUILD_ROOT.

The summary also read "0 repos failed" beside "16 tests failed", because a
DEPS repo's uncollectable tests were folded into total_failed. Those tests
never ran; counting them as failures reads as broken code when the cause is
an absent dependency. They are counted and labelled separately now.

    Tests:  2463 run | 2447 passed | 0 failed | 16 blocked on missing deps

Verification: 1706 pass; a full ecosystem run leaves no repo dirty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(ecosystem): run Makefile-driven repos, and report make by its exit code

eosllm is built and tested through a Makefile, so the runner reported
"no runner for a 'make' project" and it contributed nothing to the ecosystem
run. Its Makefile declares a `test` target; that target now runs.

`make test` is only attempted when the Makefile actually declares the rule.
Running it otherwise fails with "No rule to make target", which would read as
a broken repo rather than one that keeps its tests somewhere else.

There is no count to parse out of make, and inventing one would be exactly
the fabrication this module was rewritten to remove, so a make repo reports
its exit code instead of a tests:0/0 that looks like nothing ran. The report
shows a count where there is one and the reason where there is not.

Measured against the 19-repo workspace, with the toolchains now installed on
this machine:

    before   8 passed | 1 failed | 10 skipped | 2867 tests
    after    9 passed | 1 failed |  9 skipped | 2867 tests

eIPC also moved from SKIP to PASS with 152 Go tests once Go was available --
no change needed here, which is the point of detecting toolchains at run time
rather than hardcoding what a machine has.

1710 tests pass, 44 in this module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ecosystem): detect build systems below the repository root

detect_kinds() inspected only the root, so three of the nineteen repos in
the workspace reported "unknown", found no runner, and were skipped:

    eCAD-Hardware-Products   tests/, no packaging file
    eos-aero                 AeroSwift/software/web_app/package.json
    eos-health               firmware/build-system/CMakeLists.txt
                             apps/web/package.json

16% of the organisation was invisible to the tool whose purpose is to test
the organisation. eos-health is the sharpest case: it ships firmware with a
CMake build and a web app, and neither was ever compiled or tested.

The summary counted them as skipped, next to the passes:

    Repos: 19 discovered | N passed | 0 failed | 3 skipped

"0 failed" with everything green is the shape a healthy run makes. A skip
reads as a pass at a glance; the reason only appears in the detail rows.
Same class as the fabricated pass removed in #16 — a repo that was never
tested reporting as though nothing was wrong — through a different door.

Two changes.

detect_components() returns (kind, directory) pairs. The runners build from
the directory handed to them, so a nested component has to carry its own
location; returning "cmake" for eos-health without it would send
cmake -S at a root with no CMakeLists.txt, replacing a silent skip with a
spurious failure. Root detection runs first and is returned unchanged when
it finds anything, so the scan only ever runs for a repo that would have
been skipped. Bounded to four levels and blind to node_modules, build,
dist, venv, vendor and friends — a vendored package.json belongs to a
dependency, not to the repo.

A tests/ directory holding Python now identifies a Python project.
test_python_repo() only ever required tests/; demanding pyproject.toml to
reach it was the detector asking for something the runner does not use.
Guarded on no other kind having matched, so eBoot — CMakeLists.txt plus a
tests/ directory full of C — does not also get pytest pointed at it.

Verified no regression: detect_kinds output compared against the original
for every repo in the workspace. One difference, the intended one:

    eCAD-Hardware-Products   ['unknown']  ->  ['python']

All other 18 identical, and detect_components equals the old detect_kinds
result for all 17 root-detected repos.

After: 0 unknown, 19 repos yielding 26 components.

    tests/unit/test_ecosystem_runner.py   44 -> 54
    full suite                          1710 -> 1720 passed, 0 failed

Measured against this branch's base, which is #16, not master. master alone
is at 1648; #16 carries it to 1710. This stacks on #16 and needs it first.

Closes #17

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ecosystem): an absent vendor SDK is not a broken build

Testing eos-health for the first time — it was one of the three repos the
previous commit unskipped — produced three CMake failures that the runner
reported identically as FAIL:

    eos-health/firmware/build-system              FAIL  cmake configure failed
    eos-health/devices/health-band-neuro/firmware FAIL  cmake configure failed
    eos-health/firmware/health-band-neuro/tests   FAIL  cmake configure failed

They are not the same thing. The first stops on

    NRF5_SDK_PATH not set.  Download nRF5 SDK 17.1.0

which means install something. The other two stop on

    Cannot find source file: src/main.c
    Cannot find source file: .../src/ecg/ecg_hrv.c

which means the CMakeLists and the tree disagree, and no installation will
help. Putting both in the same column costs the reader the one distinction
that decides what to do next.

The runner already draws exactly this line for Python — DEPS, "a narrower
SKIP: the suite exists and would run, but the repo's dependencies are
absent" — and for node's npm ci. CMake was the odd one out.

_unmet_toolchain() names the absent dependency from an unset *_SDK/_ROOT/
_DIR/_PATH/_HOME variable or a failed find_package, and the configure step
reports DEPS instead of FAIL when it finds one.

Deliberately narrow. "Cannot find source file" and "No SOURCES given" are
checked first and force None, so a repository referencing code it does not
contain stays a failure. eos-health emits both kinds in one configure run,
and the repo defect has to be the one that survives — a status reading
"not our fault" over a real defect is worse than no classification at all.
That case is pinned by a test.

    tests/unit/test_ecosystem_runner.py   54 -> 61
    full suite                          1720 -> 1727 passed, 0 failed

Refs #17

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: document the ecosystem runner, and fix the repo table's casing

The README described EoSim's platforms and engines but said nothing about
the ecosystem runner, which is the part that tests the organisation rather
than a simulation.

Adds a section covering how repositories and their build systems are
discovered, that a repo with several build systems is exercised through all
of them, and what the five statuses mean — in particular why DEPS is kept
apart from FAIL. "Install the nRF5 SDK" and "this CMakeLists references a
source file that does not exist" are opposite problems and a single red
status would hide which one you have.

Also records that counts come from the runner's own output and never from
an exit status, since the inverse was a real defect here: tests_passed was
once inferred from a successful build, so every repo that compiled reported
a passing suite whether or not it ran a single test.

The ecosystem table listed eboot, eipc, eai and eni in lowercase. GitHub
redirects those, so the links worked, but the casing is wrong on disk and
this is not a cosmetic detail in this repository: find_repos held a
hardcoded lowercase list and consequently discovered 2 of 19 repos on a
case-sensitive filesystem. Corrected, with a note saying why. eFirmware and
eDB were missing from the table and are added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ecosystem): an uninstalled test runner is not a failing suite

A full ecosystem run reported five repositories as FAIL:

    EoSim     FAIL  pytest produced no summary (exit 1)
    EoStudio  FAIL  pytest produced no summary (exit 1)
    eDB       FAIL  pytest produced no summary (exit 1)
    ebuild    FAIL  pytest produced no summary (exit 1)
    eosllm    FAIL  pytest produced no summary (exit 1)

None of them is failing. EoStudio's suite is 729 passed, run directly. The
runner's interpreter simply has no pytest installed:

    /usr/bin/python3: No module named pytest

DEPS already exists for this — "the suite exists and would run, but the
repo's dependencies are absent" — but it is only reachable after the counts
are parsed. A collection error aborts pytest before it prints any summary,
so that branch is never entered and everything lands on FAIL. The previous
commit fixed the same shape for cmake; this is the Python half.

Two changes.

The no-summary branch now checks for absent modules and reports DEPS naming
them. It asks _external_missing_modules(), which excludes anything the repo
itself provides — a package directory, a src/ layout, or a single module
file. The runner puts the checkout on PYTHONPATH, so a repo failing to
import its own package is a real defect and has to stay a FAIL. A repo's own
absence does not mask a third-party one; both are pinned by tests.

_missing_modules() now matches the unquoted spelling too. ModuleNotFoundError
quotes the name, but `python -m pytest` on an interpreter without pytest
prints "No module named pytest" bare. That is precisely the case where the
runner itself is what is missing and nothing else in the output explains the
failure, and the quoted-only pattern walked past it.

    EoStudio  DEPS  needs pytest
    eDB       DEPS  needs pytest
    eosllm    DEPS  needs pytest

    tests/unit/test_ecosystem_runner.py   61 -> 71
    full suite                          1727 -> 1737 passed, 0 failed

Refs #17

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

@srpatcha srpatcha left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review — EoSim#16 "feat(ecosystem): test all 19 repos, and stop reporting passes that never ran"

head: 37fd2f1 author: srpatcha ci: fail (two red, both pre-existing on master — measured, see CI) · mergeable: CONFLICTING

Verdict: The runner rewrite is right and the reasoning behind it is the best kind — tests_run was a count of executables on disk, so any repo that compiled reported every test passing, and the replacement takes ctest's and pytest's own summary lines or reports SKIP. Separating SKIP, DEPS and FAIL is exactly the distinction the old code destroyed. One thing carries the old shape across into the new code: at the only boundary CI can read — the process exit status — a skipped repo is still indistinguishable from a passing one.

Separately, and more important than anything in this review: this PR contains the fix for the defect that is currently failing 22 CI jobs on eos#129. See below — it deserves to be said out loud, because the PR body does not mention it and the branch has been conflicting since 2026-08-29.

Findings

# Severity File:line Finding Recommended fix
1 High eosim/cli/main.py:675-676; eosim/integrations/ecosystem.py:558-566 eosim ecosystem exits 0 when repos were never tested. The command's only failure path is if report.repos_failed > 0: sys.exit(1), and run_ecosystem_tests puts SKIP and DEPS into repos_skipped, never into repos_failed — correctly, since those are different facts. But that means the run the body reports — 19 discovered, 7 passed, 1 failed, 11 skipped — becomes exit 0 the moment ebuild's CMake configure is fixed, with eleven of nineteen repos untested. summary() is honest about it in prose (VERDICT: PASSED (11 repo(s) skipped — see above)), and prose is not what a CI gate reads. This is the same shape as tests_passed = max(tests_passed, tests_run if build_ok else 0) moved up one level: the report distinguishes "not tested" from "passed", and the exit code does not. The headline word being PASSED when 58% of the ecosystem was not exercised makes it worse, because that is the line someone pastes into a status update. Two parts, and the first is free. Change the verdict word: INCOMPLETE (%d of %d repos not tested) whenever repos_skipped is non-zero — PASSED should require that everything discovered actually ran. Then add --strict (or --allow-skips, defaulting to allow) so the exit code can be made to reflect it: local runs legitimately skip on go not installed or an absent node_modules, and should stay exit 0, while a CI invocation passes --strict and goes red on any skip. A --require <repo>... naming the repos that must not skip would be even tighter and is the form that survives a new repo being added.
2 Medium eosim/integrations/ecosystem.py:227-230 and :6-9 Discovery is by inspection; test enablement is still a hardcoded list, and the docstring says otherwise. test_c_repo configures every CMake repo with a fixed set of options — -DEOS_BUILD_TESTS=ON -DEBLDR_BUILD_TESTS=ON -DEAI_BUILD_TESTS=ON -DENI_BUILD_TESTS=ON — while the module docstring states that "a new product becomes testable by being cloned into the workspace, with no change here". That is true of find_repos and false here. A CMake repo whose option is spelled anything else (eDB, eIPC, eOffice, eFirmware, eBrowser, eos-health, eos-aero — none of their option names appear in that list) configures with tests off, registers none, and reports [SKIP ] … built OK; no tests registered. It fails safe, which is right, and the reason it prints misattributes the cause: the tests exist, they were not asked for. This is the identical failure mode as the eai/eni/eboot list the PR replaces — a hardcoded list that goes stale — surviving one function further down. Move the per-repo configure options out of the runner into something each repo owns: a .eosim-ecosystem.yml at its root, read if present, with the current four as fallbacks. Failing that, keep the table but stop describing it as inspection: name it _CMAKE_TEST_OPTIONS, say in the docstring that it must be extended for a new CMake repo, and make the skip reason distinguish "no tests registered" from "tests may exist but no known option enables them".
3 Medium .coverage, eosim.egg-info/PKG-INFO, eosim.egg-info/SOURCES.txt, out/logs/stm32f4.log Four generated files are committed, and one of them grows sevenfold in this diff. .coverage is a binary SQLite database going from 77,824 to 548,864 bytes — it will change on every local test run, conflict on every branch, and is almost certainly part of why this PR is CONFLICTING. eosim.egg-info/ is setuptools build metadata; committing SOURCES.txt (+203 lines) means a stale file manifest ships in the tree and disagrees with pyproject.toml the moment either changes. out/logs/stm32f4.log is the output of a simulation run. None of the four is source. git rm --cached all four and add .coverage, *.egg-info/, out/ to .gitignore. Doing this first will also shrink the conflict in finding 4.
4 Medium PR state The branch cannot merge. mergeable: CONFLICTING, mergeStateStatus: DIRTY, unchanged since 2026-08-29 — six days. For a 216-file change that renames the entire platform registry, the conflict surface only grows, and finding 6 means something else is waiting on it. Rebase onto origin/master and resolve. Land finding 3 in the same push; a committed .coverage binary conflicts with every other branch that ran the tests.
5 Low eosim/integrations/ecosystem.py:93-97 Dead branch: elif r.reason: detail = r.reason[:44] appears twice in immediate succession with an identical condition, so the second is unreachable. Copy-paste artifact in a file this PR largely rewrites. Delete lines 96-97.
6 Low eosim/integrations/ecosystem.py:376-386, :365-373 _parse_pytest and _missing_modules scan the last 4000 characters of stdout and stderr combined, not pytest's summary line. A test that prints 3 failed in its own output, or any log line containing No module named 'x', is counted as a result or as a missing dependency — and the DEPS classification at :350-354 turns on exactly that. Narrow, but this module's whole purpose is that reported numbers are the ones the tool produced. Anchor to the summary: match on the final line matching ^=+ .* (passed|failed|error).* =+$, or run pytest with a machine-readable report. Same for _parse_ctest, which searches all of stdout for ctest's summary pattern — less exposed, because that pattern is distinctive.
7 Low eosim/cli/main.py:40, :65 _find_platform calls PLATFORMS_DIR.iterdir() with no existence check, so a layout where the directory is absent — a wheel built before this PR's package-data change, or a partial install — raises FileNotFoundError and prints a raw traceback as the user's first experience of eosim run. .ai/tooling.md: raw tracebacks and internal exception strings do not surface as the primary message. Related and worth fixing in the same place: _load_registry hands PlatformRegistry a missing directory and eosim list then prints Available platforms (0): and exits 0 — I ran that directly against the pinned tree. An inventory command reporting success over an empty inventory is what let eos#129's nine green Install & Validate legs coexist with twenty-two red simulate jobs. Guard _resolve_platforms_dir: if neither candidate exists, exit with a message naming both paths and saying the package is installed without its platform data. And make eosim list exit non-zero on an empty registry — it is the only cheap check that an install is usable.

The part that matters outside this repo

This PR fixes the defect that is currently failing 22 CI jobs on eos#129, and neither PR mentions the other.

eos#129 pins EoSim at commit 7dec3460 and installs it with pip install <path> instead of pip install -e. Every eosim run then dies with FileNotFoundError: .../site-packages/platforms, because at that commit platforms/ is a top-level directory of 199 files outside the eosim package, pyproject.toml declares only include = ["eosim*"], and eosim/cli/main.py resolves EOSIM_ROOT = Path(__file__).parent.parent.parent. Seven Nested Simulation legs, three Guest OS Install, eleven Simulate and both gates.

This branch is the fix, and it is a complete one:

  • platforms/eosim/platforms/ (199 pure renames in the diffstat).
  • pyproject.toml:69-73 adds [tool.setuptools.package-data] for "eosim.platforms" with the comment "Without this the wheel installs but eosim run cannot find any platform".
  • eosim/cli/main.py:17-32, _resolve_platforms_dir(), prefers the packaged location and falls back to the repo-root path, with the failure mode written down: "An editable install hid this, because there EOSIM_ROOT is the checkout."
  • .github/workflows/ci.yml:140 follows the move in the platform-count check.

It also closes the second open EoSim item listed in eos#129's 2026-09-03 comment: @click.version_option(version=__version__, prog_name="eosim") replaces the hardcoded version="2.0.0", so eosim --version stops printing a constant that could not distinguish a right install from a wrong one.

Practical consequence: eos#129 cannot be fixed properly until this merges. Its only options meanwhile are to keep pip install -e — which is what I recommended there — or to pin a commit from this branch, which it should not do. Worth a line in both PR bodies, and worth prioritising the rebase in finding 4 over anything else here.

What is right, and checked

  • The core fix is real. test_c_repo:275-284 takes ctest's own summary via _parse_ctest and returns SKIP built OK; no tests registered when there is no summary line, rather than inferring a pass — which is the correct handling of ctest's exit-0-on-no-tests, and reaches the same place .ai/security.md's --no-tests=error rule aims at while keeping SKIP distinct from FAIL. test_python_repo:334-343 treats pytest exit 5 as "no tests collected" → SKIP and any other summary-less exit as FAIL. Both derive PASS from returncode == 0 **and** tests_failed == 0, never from one alone.
  • DEPS is a genuine distinction, not a softening. :349-354 reaches DEPS only when failed == 0 and errors and missing — nothing asserted wrongly and the errors are import failures for named absent modules. run_ecosystem_tests:562-566 then counts those tests as total_blocked rather than folding them into total_failed, and summary() prints them under their own label. A repo in DEPS lands in repos_skipped, so it cannot be mistaken for a pass.
  • test_repo_all exercising every detected build system is the right call and the body's evidence for it is concrete — ebuild's broken CMake configure was invisible behind its green Python suite.
  • _build_dir_for puts CMake trees under ~/.cache/eosim/ecosystem, with the reasoning recorded: building into the checkout left an untracked directory in every repo the runner touched. That is the kind of thing this job is supposed to notice, and it is already fixed.
  • The pytest -q and PYTHONPATH bugs are both real classes: -q -q suppresses the summary line entirely, which under the old code was indistinguishable from a failure, and offering both the repo root and src/ covers eDB's layout.
  • pyproject.toml:8,19 are a §28 correction and deserve saying so. "World's most powerful universal simulation platform — supersedes 250+ tools across 20 domains" becomes "Multi-architecture embedded simulation platform for EoS — native engine plus Renode/QEMU backends", and Development Status :: 5 - Production/Stable becomes 4 - Beta. §28 defines the evidence each status claim requires and §27 warns against declaring maturity from vanity metrics; this is one of the few changes in this batch that walks a claim back rather than forward.

Architecture conformance

§21 Tier 1 — conforms, and this is Tier-1 work. EoSim sits in Tier 1 Foundation with eos, eBoot and ebuild, and §17 makes it "essential to developer onboarding and CI". Nothing here creates a dependency that points up a tier: the runner shells out to cmake, ctest, pytest, go, npm and make in other repos' checkouts and imports nothing from them.

§17 — the packaging half of this PR is the section's Install step. §17's chain is "Discover → Install → ebuild run --sim → EoS running" and §39 requires an independent developer to install the SDK and run EoS in simulation unaided. Moving the platform registry into the package is what makes an installed EoSim a working one. I have raised the remaining half — that EoSim still publishes no wheel or sdist at any of its thirteen releases — as an architecture proposal against §17 (.ai/autoreview/proposals/2026-09.md, "§17 makes Install an adoption step and nothing requires a tool to be installable"), triggered by eos#129. This PR satisfies the part of that proposal about runtime data being located through the packaging system rather than a source-tree path.

§28 Status, evidence and claims — the governing section for the runner itself, and the PR is a direct answer to it. §28 makes Implemented mean "code and functional tests" and Validated mean "hardware/CI/test reports"; a runner that reported every test passing whenever a repo compiled made both states unfalsifiable across the whole organisation. Finding 1 is the residue of that at the exit-code boundary.

Weakened checks — none. Everything here tightens. Two things that could look like weakening are not: SKIP replacing a false PASS is strictly more information, and DEPS is a narrowing of SKIP rather than of FAIL.

CI

Two red — Lint & Format (failing step: Ruff check) and Quick Checks (failing step: Lint) — and both fail on master too. Measured, rather than assumed: I extracted eosim/ and pyproject.toml at origin/master and at this head and ran ruff check eosim/ against each with the repo's own [tool.ruff] config (select = ["E","F","W","I","N","UP"], ignore = ["E501"]):

origin/master   Found 510 errors
this head       Found 507 errors

Per file, for the four this PR rewrites: eosim/cli/main.py 85 → 85, eosim/integrations/ecosystem.py 38 → 35, eosim/engine/native/__init__.py 8 → 8, eosim/engine/native/cpu/__init__.py 5 → 5. So this PR introduces no new violations and removes three. Caveat: my ruff is 0.16.5 and CI installs its own version, so the absolute counts will differ; the direction will not.

Five further jobs report skippingBuild Documentation, Build Package, Code Coverage, Simulator Smoke Test, Validate Platforms, and the Test (…) matrix — which is needs: on the failed lint, not a result. Green: Analyze (python), Analyze (javascript-typescript), Security Scan, Coverage Gate, Simulator Smoke, CI Summary.

CI Summary is green while two jobs in its own workflow are red and six are skipped. Same fail-open gate as noted on EoSim#15, and it is worth naming here specifically: this PR's entire subject is a runner that reported passes it had not observed, and the workflow reviewing it has a summary job doing the same thing.

Not checked

  • Nothing in this PR was executed. No eosim ecosystem run, no pytest — the EoSim checkout has 179 modified .pyc files and the brief leaves dirty repos alone, so I read the branch through git show against a fetched ref and extracted copies, and built nothing. Findings 1, 2, 5, 6 and 7 are from reading the source; finding 3 is from the diffstat; the CI section is measured.
  • The eosim list behaviour in finding 7 was measured against the tree at 7dec3460, the commit eos#129 pins, not at this head. This PR does not change _load_registry or the list command's exit path, so the conclusion carries — but I did not re-run it here.
  • The reported table was not reproduced. 19 discovered | 7 passed | 1 failed | 11 skipped, 2420 tests run, and [PASS] eos cmake tests:22/22 are the author's, from 2026-08-28. One number is worth the author confirming rather than my guessing at: eos registers 38 tests on today's master — I configured and counted it, with and without -DEOS_PRODUCT=vbox_test, and got 38 both ways. The gap may simply be six days of new tests in eos. It is also the kind of gap this report should be able to answer for itself, which argues for the summary recording the configure flags each repo was built with.
  • test_go_repo, test_node_repo and test_make_repo were read, not exercised. No Go, Node or Makefile-only repo in the workspace reaches them, so their counting logic is unverified — test_go_repo in particular derives counts from --- PASS / --- FAIL string occurrences in go test -v output, which is the pattern finding 6 is about.
  • The 199 platform renames were verified as renames (0+ 0- in the diffstat, {platforms => eosim/platforms} in --stat), not read. I did not confirm that no platform.yml content changed beyond the path.
  • eosim/cli/main.py is 1967 changed lines and I read roughly the first hundred plus the ecosystem command. The CLI rewrite is far larger than the PR title implies and I have not reviewed most of it; the same is true of the 1981 changed lines in tests/unit/test_gui.py and 1379 in test_core.py. If any of that is unrelated to the ecosystem runner, it belongs in its own PR — and if it is related, the body should say how.

Automated architecture review of 37fd2f1717f9 — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.

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.

2 participants