Skip to content
Open
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
34 changes: 34 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Bytecode / Python
__pycache__/
*.py[cod]
*$py.class
*.so

# Distribution / packaging
dist/
build/
*.egg-info/
*.egg

# Virtual environments
venv/
.venv/
env/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Testing
.pytest_cache/
.coverage
htmlcov/

# OS
.DS_Store
Thumbs.db

# Fleet health checks (keep directory, ignore contents)
for-fleet/
72 changes: 70 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,73 @@
# flux-coverage

FLUX coverage analyzer instruction, branch, path, and register coverage
> Bytecode coverage analyzer measuring instruction, branch, path, and register coverage for FLUX programs.

8 tests passing.
## What This Is

`flux-coverage` is a Python module that **measures how much of a FLUX bytecode program was actually executed** — it tracks which instruction addresses were hit, which branches were taken/not-taken, how many unique execution paths occurred, and which registers were used.

## Role in the FLUX Ecosystem

Coverage ensures comprehensive testing of agent programs:

- **`flux-timeline`** shows execution order; coverage shows breadth
- **`flux-profiler`** measures frequency; coverage measures completeness
- **`flux-debugger`** helps find bugs; coverage finds untested code
- **`flux-signatures`** detects patterns; coverage verifies they're all exercised
- **`flux-decompiler`** shows all instructions; coverage shows which ran

## Key Features

| Feature | Description |
|---------|-------------|
| **Instruction Coverage** | Percentage of instructions that were executed |
| **Branch Coverage** | Both-way coverage (taken AND not-taken) for conditional branches |
| **Path Tracking** | Count of unique execution paths through the program |
| **Register Coverage** | Which registers were read/written during execution |
| **Markdown Reports** | Formatted coverage report table |
| **Multiple Run Support** | Create fresh collector per test input for differential coverage |
| **Factorial Validation** | Known-answer tests (e.g., 6! = 720) verify both correctness and coverage |

## Quick Start

```python
from flux_coverage import CoverageCollector
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 README Quick Start imports non-existent module flux_coverage

The Quick Start example imports from flux_coverage import CoverageCollector, but the actual module file is coverage.py, not flux_coverage.py. There is no flux_coverage module, package, or installed distribution in the repository. The test suite (tests/test_coverage.py:4) correctly imports from coverage import CoverageCollector. Copying the README example would raise ModuleNotFoundError.

Suggested change
from flux_coverage import CoverageCollector
from coverage import CoverageCollector
Staging: Open in Devin

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground


# Analyze coverage of a factorial program
bytecode = [0x18, 0, 6, 0x18, 1, 1, 0x22, 1, 1, 0, 0x09, 0, 0x3D, 0, -6, 0, 0x00]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 README Quick Start bytecode contains -6 which crashes bytes() constructor

The Quick Start example on line 37 uses -6 in the bytecode list: bytecode = [... 0x3D, 0, -6, 0, 0x00]. When this is passed to CoverageCollector.__init__ (coverage.py:68), it calls self.bytecode = bytes(bytecode), which raises ValueError: bytes must be in range(0, 256) because -6 is negative. The working tests use 0xFA for this value (the unsigned byte representation whose signed interpretation via sb() is -6).

Suggested change
bytecode = [0x18, 0, 6, 0x18, 1, 1, 0x22, 1, 1, 0, 0x09, 0, 0x3D, 0, -6, 0, 0x00]
bytecode = [0x18, 0, 6, 0x18, 1, 1, 0x22, 1, 1, 0, 0x09, 0, 0x3D, 0, 0xFA, 0, 0x00]
Staging: Open in Devin

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

collector = CoverageCollector(bytecode)

regs, report = collector.run()

print(f"Instruction coverage: {report.instruction_pct:.1f}%")
print(f"Branch coverage: {report.branch_pct:.1f}%")
print(f"Register coverage: {report.register_pct:.1f}%")
print(f"Unique paths: {report.unique_paths}")

# Generate report
print(report.to_markdown())

# Test with different inputs for differential coverage
collector2 = CoverageCollector(bytecode)
_, report2 = collector2.run(initial_regs={0: 1}) # n=1 instead of n=6
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 README differential coverage example is ineffective — initial_regs={0: 1} is immediately overwritten

Line 52 shows collector2.run(initial_regs={0: 1}) with the comment # n=1 instead of n=6, implying this runs the factorial with a different input. However, the bytecode starts with MOVI r0, 6 (opcode 0x18, 0, 6 at position 0), which unconditionally sets r0 to 6, overwriting the initial value of 1. The factorial still computes 6!=720, not 1!=1. This undermines the "differential coverage" demonstration — both runs produce identical behavior.

Prompt for agents
The differential coverage example at README.md:50-52 is misleading. The bytecode begins with MOVI r0, 6 which unconditionally sets r0=6, so passing initial_regs={0: 1} has no effect — the program still computes 6!=720.

To fix this, either:
1. Modify the bytecode to not hardcode the input (remove the MOVI r0,6 instruction and start from ADD/MUL), relying on initial_regs to set the input, or
2. Change the example to use a different bytecode that actually depends on initial register values, or
3. At minimum, fix the comment to not claim 'n=1 instead of n=6' since that's incorrect.
Staging: Open in Devin

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

```

## Running Tests

```bash
python -m pytest tests/ -v
# or
python coverage.py
```

## Related Fleet Repos

- [`flux-timeline`](https://github.com/SuperInstance/flux-timeline) — Execution tracing
- [`flux-profiler`](https://github.com/SuperInstance/flux-profiler) — Performance profiling
- [`flux-debugger`](https://github.com/SuperInstance/flux-debugger) — Step debugger
- [`flux-signatures`](https://github.com/SuperInstance/flux-signatures) — Pattern detection
- [`flux-decompiler`](https://github.com/SuperInstance/flux-decompiler) — Bytecode decompilation

## License

Part of the [SuperInstance](https://github.com/SuperInstance) FLUX fleet.
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Pytest configuration for flux-coverage."""
import sys
from pathlib import Path

# Ensure the repo root is on sys.path so `import coverage` works
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
Loading
Loading