nappe
A syntax-guided test case reducer implementing the Perses algorithm (Sun et al., ICSE 2018).
Reference implementation: nnunley/bonsai (Rust).
What It Does
Given a failing test case and a program file that triggers a bug, nappe reduces the file to the smallest possible program that still reproduces the failure — while guaranteeing syntactic validity at every step.
It also provides a check command for static analysis of source files,
detecting reducible patterns like dead code, unused assignments, and style
issues.
How It Works
- Parse the input file into a concrete syntax tree using tree-sitter
- Systematically try to remove or simplify subtrees via a priority queue (largest first)
- Each candidate reduction is validated by reparsing — only syntactically valid reductions proceed
- Test each valid candidate against an "interestingness test" (any shell command that exits 0 when the bug is still present)
- Repeat until no further reductions are possible
Transforms
- Delete — remove a node entirely
- Unwrap — replace a node with one of its type-compatible children
- Unify identifiers — rename bindings to canonical short forms (planned: requires scope data)
- Dead definition removal — delete unreferenced definitions (planned: requires scope data)
Key Properties
- All intermediate results are syntactically valid
- Largest subtrees tried first (maximum reduction per test)
- Test results cached (duplicate calls avoided)
- Handles inputs with pre-existing parse errors
Supported Languages
Python, JavaScript, TypeScript, Rust, Go, C, C++ (via tree-sitter grammars).
Implementation
The project has two implementations:
- Rust (primary) — native implementation in
src/, built withcargo. This is the default and recommended implementation. - Python (reference) — pure Python implementation in
src/nappe/, kept for comparison and as a reference for the algorithm. Accessible via the--legacyflag.
Why Rust?
The Rust implementation provides significantly better performance for large files and complex reduction scenarios, while maintaining full feature parity with the Python reference.
Installation
Rust (recommended)
cargo build --release
cp target/release/nappe ~/.local/bin/
Python (reference)
uv sync
Usage
nappe reduce — Syntax-guided reduction (default)
# Reduce using a pytest interestingness test (recommended)
nappe reduce --test test_interesting.py::test_still_fails input.py
# Reduce using a shell command
nappe reduce --test-cmd "grep -q 'error'" input.py
# Auto-reduce to smallest valid program (no test needed)
nappe reduce --auto input.py
# Limit reduction time
nappe reduce --test test_interesting.py --max-time 30m --max-tests 1000 input.py
# Use Python implementation (legacy)
nappe --legacy reduce --test test_interesting.py::test_still_fails input.py
nappe check — Static analysis and fixes
# Check files for reducible patterns
nappe check src/**/*.py
# Apply safe fixes automatically
nappe check --fix src/**/*.py
# Apply all fixes (including unsafe ones like dead code removal)
nappe check --unsafe-fixes src/**/*.py
# Output as JSON
nappe check --output-format json src/**/*.py
# Filter by rule
nappe check --select RED200,RED201 src/**/*.py
nappe shrink — Shrinkray-compatible interface
nappe shrink "./check.sh" input.py
Check Rules
| Code | Description | Safe? |
|---|---|---|
| RED100 | Dead function (no callers) | unsafe |
| RED101 | Dead class (no instantiations) | unsafe |
| RED102 | Unused variable assignment | unsafe |
| RED200 | Constant expression simplification | safe |
| RED201 | Redundant parentheses | safe |
| RED202 | Unnecessary semicolon | safe |
| RED203 | Trailing whitespace | safe |
| RED204 | Redundant newline | safe |
Example: Reducing a Bug Trigger
Given a Python file that triggers a bug when fibonacci is called with
print():
Before (input.py):
import sys
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
def unused_helper(x):
return x * 2
def also_unused():
result = unused_helper(5)
return result
class MyClass:
def __init__(self):
self.value = 42
def method(self):
return self.value
if __name__ == "__main__":
for i in range(10):
print(fibonacci(i))
Write a pytest interestingness test (test_interesting.py):
import sys
def test_still_fails():
"""Exit 0 = candidate is still interesting (bug still present)."""
candidate = sys.argv[1]
content = open(candidate).read()
assert "def fibonacci" in content and "print(" in content
Run the reducer:
nappe reduce --test test_interesting.py::test_still_fails input.py
After (input.py):
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10))
Diff:
-import sys
-
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
-def unused_helper(x):
- return x * 2
-
-def also_unused():
- result = unused_helper(5)
- return result
-
-class MyClass:
- def __init__(self):
- self.value = 42
-
- def method(self):
- return self.value
-
-if __name__ == "__main__":
- for i in range(10):
- print(fibonacci(i))
+print(fibonacci(10))
The reducer removed the unused import sys, the unused_helper and
also_unused functions, the MyClass class, and simplified the if __name__
block — while preserving the core fibonacci function and the print() call
that triggers the bug.
Example: Checking and Fixing Code
Input (demo.py):
import os
def greet(name):
print(f"Hello, {name}")
def unused_helper(x):
return x * 2
count = 0;;
class EmptyClass:
pass
message = "hello"
greet("world")
Run the checker:
nappe check demo.py
Output:
demo.py:14:17: RED203 Trailing whitespace
14 | message = "hello"
^^^^^^^^^^^^^^^^^ RED203
demo.py:9:10: RED202 Unnecessary semicolon
9 | count = 0;;
^^^^^^^^^^^ RED202
demo.py:14:1: RED102 Unused variable assignment
14 | message = "hello"
^^^^^^^^^^^^^^^^^ RED102
demo.py:9:1: RED102 Unused variable assignment
9 | count = 0;;
^^^^^^^^^^^ RED102
demo.py:6:1: RED100 Dead function (no callers)
6 | def unused_helper(x):
^^^^^^^^^^^^^^^^^^^^^ RED100
demo.py:11:1: RED101 Dead class (no instantiations)
11 | class EmptyClass:
^^^^^^^^^^^^^^^^^ RED101
Found 6 issues (2 safe, 4 unsafe).
Apply safe fixes:
nappe check --fix demo.py
After (demo.py):
import os
def greet(name):
print(f"Hello, {name}")
def unused_helper(x):
return x * 2
count = 0;
class EmptyClass:
pass
message = "hello"
greet("world")
The --fix flag applies only safe fixes (RED200–RED204). Use --unsafe-fixes
to also remove dead functions, dead classes, and unused assignments.
Development
Rust (primary)
cargo build --release # build
cargo test # test
cargo clippy # lint
cargo fmt # format
Python (reference)
uv run ruff check src/nappe/ # lint
uv run ruff format src/nappe/ # format
uv run ty check src/nappe/ # type check
uv run pytest tests/ # test
License
MIT
References
- Syntax-Guided Program Reduction: A Survey and the nappe Implementation — Review paper covering the Perses algorithm, related work, and the nappe system.
Metadata
Release files for nappe 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| nappe-0.1.0.tar.gz | 19.0 kB | With provenance | Details for nappe-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| nappe-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for nappe-0.1.0-py3-none-any.whl |
Total release size: 43.9 kB
Release files / nappe-0.1.0.tar.gz
| Download URL | nappe-0.1.0.tar.gz |
|---|---|
| Size | 19.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5d6dbf71ebf5f369b3ee00b86a7319f2f8f1b0c2943a2231301dc7fe69663157
|
|
BLAKE2b-256 checksum How to use checksums |
810894bb23700bbc076c13c242cf4dc71b3b5b23e498ef7d195a87b231bc5ebd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logRelease files / nappe-0.1.0-py3-none-any.whl
| Download URL | nappe-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a868bad565448381ddd9ba95d4fc5a7ef9a1f06f33575fdd9e2e0632bc5ead9b
|
|
BLAKE2b-256 checksum How to use checksums |
afd4d05eb3c8ec90fbe9efc5ff7fd3f2d3b81cb50d388a6e214d89358bf462d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logSecurity for nappe 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for nappe-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.