> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pcb.new/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Validate Zener modules with test benches and circuit graphs

# Testing

Zener tests validate module connectivity, component properties, and circuit
topology. Define tests in `.zen` files and run them with `pcb test`.

## Define a test bench

`TestBench` evaluates a module for one or more input cases and runs each check
function against the result:

```python theme={null}
MyCircuit = Module("./my_circuit.zen")

def verify_power_connections(module, inputs):
    connections = module.nets.get("VCC", [])
    check(len(connections) >= 2, "VCC must have at least two connections")

def verify_ground(module, inputs):
    check("GND" in module.nets, "GND net is missing")

TestBench(
    name = "PowerTest",
    module = MyCircuit,
    test_cases = {
        "default": {},
    },
    checks = [verify_power_connections, verify_ground],
)
```

This example assumes that `MyCircuit` has no required inputs. Supply each
module input in the case dictionary when the module requires arguments.

| Parameter    | Description                                                |
| ------------ | ---------------------------------------------------------- |
| `name`       | Test bench identifier.                                     |
| `module`     | Module loader created with `Module()`.                     |
| `test_cases` | Nonempty map from case names to module input dictionaries. |
| `checks`     | Functions to run for each evaluated case.                  |

Each check receives the evaluated module and the active case's input dictionary.
Call `check(condition, message)` or `error(message)` to fail a test. An unhandled
evaluation error also fails the test. `pcb test` reports failures with their
source locations.

## Inspect the evaluated module

The evaluated module exposes its nets and components:

| Expression           | Result                                                 |
| -------------------- | ------------------------------------------------------ |
| `module.nets`        | Map from net names to connected component-port tuples. |
| `module.components`  | Map from hierarchical paths to components.             |
| `module["U1"]`       | Direct child component or module.                      |
| `module["Power.U1"]` | Descendant selected by hierarchical path.              |

Component values expose `name`, `type`, `pins`, `properties`, sourcing fields,
and component-specific properties such as `resistance` when present.

```python theme={null}
def verify_feedback_resistor(module, inputs):
    resistor = module["Feedback.R1"]
    check(resistor.type == "resistor", "Feedback.R1 must be a resistor")
    check(resistor.resistance.matches("10k"), "Feedback.R1 must be 10 kohm")
```

## Search circuit paths

`module.graph()` returns the circuit graph. Use `graph.paths()` to find simple
paths between component pins or public module nets:

```python theme={null}
def verify_power_path(module, inputs):
    graph = module.graph()
    paths = graph.paths(
        start = ("Regulator", "VIN"),
        end = "GND_GND",
        max_depth = 5,
    )
    check(len(paths) > 0, "No path exists from VIN to ground")
```

`start` and `end` accept a `(component, pin)` tuple or the name of a public net.
`max_depth` limits the number of traversed components and defaults to 10.

Each returned path provides:

* `ports`: traversed `(component, pin)` tuples
* `components`: traversed component values
* `nets`: traversed net names

## Match components in a path

`count`, `any`, `all`, and `none` accept a function that validates one
component. The matcher succeeds when it returns without an error:

```python theme={null}
def is_resistor(component):
    check(component.type == "resistor", "component is not a resistor")

resistor_count = path.count(is_resistor)
path.any(is_resistor)
path.all(is_resistor)
path.none(is_resistor)
```

`any`, `all`, and `none` fail when their condition is not satisfied.

## Match a component sequence

`path.matches()` validates the complete ordered component sequence. Each matcher
receives the path and the current component index, then returns the number of
components it consumed.

```python theme={null}
def resistor(expected_value=None):
    def matcher(path, cursor):
        check(cursor < len(path.components), "expected a resistor at end of path")
        component = path.components[cursor]
        check(component.type == "resistor", component.name + " is not a resistor")
        if expected_value != None:
            check(component.resistance.matches(expected_value), "unexpected resistance")
        return 1

    return matcher

def capacitor(expected_value=None):
    def matcher(path, cursor):
        check(cursor < len(path.components), "expected a capacitor at end of path")
        component = path.components[cursor]
        check(component.type == "capacitor", component.name + " is not a capacitor")
        if expected_value != None:
            check(component.capacitance.matches(expected_value), "unexpected capacitance")
        return 1

    return matcher
```

Use the matchers in a topology check:

```python theme={null}
def verify_filter(module, inputs):
    paths = module.graph().paths(start=("OpAmp", "OUT"), end="GND_GND")
    check(len(paths) > 0, "filter path is missing")
    paths[0].matches(
        resistor("1k"),
        capacitor("100nF"),
        resistor("10k"),
    )
```

Matcher helpers are not prelude symbols. Define them in the test file or load
them from a project-local helper module.

Pass `suppress_errors=True` when a failed sequence is an expected search result.
The method then returns `False` instead of failing the test:

```python theme={null}
matching_paths = [path for path in paths if path.matches(
    resistor(),
    capacitor(),
    suppress_errors = True,
)]
check(len(matching_paths) > 0, "RC path is missing")
```
