docgen

Guides

Document a state machine

Add a state table, a transition table and an SVG diagram of a state machine. They come from the calls that your tests make.

v2/machine/machine.go

type LampState uint8
const (
    // LampOff gives no light.
    LampOff LampState = iota
    // LampOn gives light.
    LampOn
    // LampBroken never gives light again.
    LampBroken
)
func (l *Lamp) State() LampState { return l.state }
func (l *Lamp) Start(now time.Duration) error {
    if l.state == LampOn {
        return ErrBusy
    }
    if l.state == LampOff {
        l.state, l.until = LampOn, now+l.wait
    }
    return nil
}
func (l *Lamp) Stop() error {
    if l.state != LampOn {
        return ErrOff
    }
    l.off()
    return nil
}
func (l *Lamp) Tick(now time.Duration) {
    if l.state == LampOn && now >= l.until {
        l.off()
    }
}
func (l *Lamp) Break() { l.state = LampBroken }
func (l *Lamp) off() { l.state = LampOff }

v2/machine/machine_test.go

func TestLamp(t *testing.T) {
    l, err := NewLamp(Config{})
    if err != nil {
        t.Fatal(err)
    }
    _ = l.Start(0)
    l.Tick(DefaultWait)
    _ = l.Start(0)
    _ = l.Stop()
    l.Break()
}

docs/machines/guide-states.md

Before
# The lamp

<!-- docgen:states pkg=machine type=Lamp -->
<!-- /docgen:states -->
After
# The lamp

<!-- docgen:states pkg=machine type=Lamp -->
States of [`Lamp`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/machine#Lamp), from [`Lamp.State`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/machine#Lamp.State):

| State | Meaning | Seen in tests |
|---|---|---|
| [`LampOff`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/machine#LampOff) | LampOff gives no light. | yes, first state |
| [`LampOn`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/machine#LampOn) | LampOn gives light. | yes |
| [`LampBroken`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/machine#LampBroken) | LampBroken never gives light again. | yes |

Transitions seen in tests:

| From | Method call | To |
|---|---|---|
| LampOff | `Start` | LampOn |
| LampOff | `Break` | LampBroken |
| LampOn | `Stop`, `Tick` | LampOff |

![Transitions of Lamp](states/machine-lamp.svg)
<!-- /docgen:states -->
Preview

The lamp

States of Lamp, from Lamp.State:

State Meaning Seen in tests
LampOff LampOff gives no light. yes, first state
LampOn LampOn gives light. yes
LampBroken LampBroken never gives light again. yes

Transitions seen in tests:

From Method call To
LampOff Start LampOn
LampOff Break LampBroken
LampOn Stop, Tick LampOff
Transitions of Lamp

For all options, see docgen:states.

Commit the diagram with the page. It goes in states/<pkg>-<type>.svg next to the page. For another place, add diagram=<path>, relative to the page.

Requirements§

  • A type with exported methods that have pointer receivers. Generic types are not supported.
  • A way to read the state: a State method, or methods that return a bool or a (value, bool).
  • Tests that drive the machine.

When a transition is missing§

A transition that no test makes is not in the table. Add a test. The state table lists each state that no test reached, so a gap is easy to see.

Slow tests§

Add run=<regexp> to limit the tests, as go test -run does. It applies only when each docgen:states marker of the package has one.

v2/door/door.go

// Package door is a state machine with its own tests, for the run= argument.
package door

// State is the state of a Door.
type State uint8

// The State values.
const (
    // StateClosed is shut.
    StateClosed State = iota
    // StateOpen is open.
    StateOpen
)

// Door opens and closes.
type Door struct{ state State }

// State returns the state of the Door.
func (d *Door) State() State { return d.state }

// Open opens the Door.
func (d *Door) Open() { d.state = StateOpen }

// Close closes the Door.
func (d *Door) Close() { d.state = StateClosed }

v2/door/door_test.go

func TestOpen(t *testing.T) {
    var d Door
    d.Open()
}

docs/machines/guide-states-run.md

Before
<!-- docgen:states pkg=door type=Door run=TestOpen diagram=states/door-open.svg -->
<!-- /docgen:states -->
After
<!-- docgen:states pkg=door type=Door run=TestOpen diagram=states/door-open.svg -->
States of [`Door`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/door#Door), from [`Door.State`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/door#Door.State):

| State | Meaning | Seen in tests |
|---|---|---|
| [`StateClosed`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/door#StateClosed) | StateClosed is shut. | yes, first state |
| [`StateOpen`](https://pkg.go.dev/github.com/USA-RedDragon/docgen/v2/door#StateOpen) | StateOpen is open. | yes |

Transitions seen in tests:

| From | Method call | To |
|---|---|---|
| StateClosed | `Open` | StateOpen |

![Transitions of Door](states/door-open.svg)
<!-- /docgen:states -->
Preview

States of Door, from Door.State:

State Meaning Seen in tests
StateClosed StateClosed is shut. yes, first state
StateOpen StateOpen is open. yes

Transitions seen in tests:

From Method call To
StateClosed Open StateOpen
Transitions of Door

For the exact rules, see How docgen works.

Source: docs/guides/state-machines.md