---
title: "Testing Stateful S7 Protocols"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Testing Stateful S7 Protocols}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
knitr::read_chunk(
  system.file("examples", "store-laws.R", package = "s7contract")
)
```

```{r store-setup}
```

A mutable store must behave correctly across calls: a put changes a later get,
a delete removes a key, and a reset clears earlier entries. We can test these
relationships by generating sequential commands and comparing each operation
with a reference model.

## The protocol and its implementations

This protocol stores one integer per string key and reports keys in sorted
order. The example uses bounded strings and small integer values. Missing keys
return `NULL`; generated get and delete commands use existing keys.

```{r store-interface}
```

One implementation stores environment bindings; the other updates a list held
in an environment. Both use ordinary S7 methods.

```{r store-implementations}
```

## Commands and a reference model

Keys contain one to four code points from `a`, `b`, `c`, and `\u00e9` (é).
This recipe converts the alphabet to UTF-8, rejects missing and byte strings,
and counts code points rather than bytes or grapheme clusters. The alphabet
must be nonempty; each entry must contain exactly one code point. With `min = 0`,
the recipe also generates `""`, which environment bindings cannot use as a key.
Shrinking removes chunks before moving characters toward earlier alphabet entries.

```{r store-strings}
```

[`paste()`](https://stat.ethz.ch/R-manual/R-devel/library/base/html/paste.html)
preserves UTF-8 here and collapses zero entries to one empty string. Missing
values are rejected before concatenation because `paste()` would turn them into
the literal text `"NA"`.

The model is a named list of expected values. Each command defines its input
generator, implementation call, and postcondition. A model update derives the
next state from the input. `get` and `delete` are available only when a key
exists; their preconditions also apply during shrinking.

```{r store-commands}
```

`ensure(state, input, output)` sees the model before the command and returns a
scalar logical. It runs after `update()` has computed the next model. The
model's expected values come from the inputs, independently of the store's
answers.

## One law, two fixtures

`new_state_law()` gives every case and evaluated shrink a fresh fixture.
Teardown runs once after each successful setup,
including after a false postcondition, warning, or error.

```{r store-law}
```

Sequence length grows with the runner's size, up to `max_commands`. Generation
may stop sooner when no command is available. Empty sequences are allowed.
`expect_law()` can run the same law as one tinytest expectation.

The classifier records sequences containing multi-character and non-ASCII put
keys. It counts generated inputs, including any suffix after an execution failure.

```{r store-key-coverage}
```

## A put that truncates keys

This implementation keeps only the first character when writing a key. The law
reduces its failure to `put("aa", 0L)`: a subsequent get of `"aa"` returns `NULL`.

```{r store-truncated}
```

## A reset that leaves data behind

This subclass has every required method, but reset does nothing:

```{r store-broken}
```

The runner reduces the failure to a put followed by a reset. It first removes
chunks of commands, then shrinks their inputs. After each change, it removes
commands whose preconditions no longer hold, along with their dependents.
The search retains failures of the same command's postcondition; the result is
minimal relative to the shrink tree and evaluation budget.

The failure condition retains the resolved inputs, outputs, and model states:

```{r store-trace}
```

The original trace is in `store_failure@counterexample@original_condition$trace`.
Unexpected callback errors stop shrinking and preserve an established
counterexample. If teardown also fails, its condition is retained separately
as `cleanup_condition`. `shrink_condition` reports why the search stopped.

```{r store-replay}
```

Replay requires unchanged commands, generators, run parameters, and compatible
R/package versions. Setup must reproduce the initial state; shared mutable
state outside the fixture would break that guarantee. The runner restores the
caller's RNG state as described in [Generative Laws with tinytest](property-laws.html).

## Commands that return handles

Some protocols allocate a handle that later commands consume. During generation,
`update(state, input, output)` receives an opaque reference to the future output.
It can append that reference to a list of live handles, and later generators can
select one with `gen_element()`. During execution, the same update receives the
actual handle.

References resolve when passed directly as command inputs or nested in ordinary
lists. They retain the producer's ID when earlier commands are removed. Removing
a producer also removes consumers of its output. References inside classed
containers are not traversed; place them in a plain list before execution.
The update must work with both symbolic and concrete outputs, without inspecting
their representation. Models need value semantics; traces containing mutable
handles retain R's reference semantics.

The design follows [R Hedgehog's state-machine example](https://github.com/hedgehogqa/r-hedgehog/blob/master/vignettes/state-machines.Rmd)
and [Haskell Hedgehog's commands](https://github.com/hedgehogqa/haskell-hedgehog/blob/master/hedgehog/src/Hedgehog/Internal/State.hs).
For the model-based testing background, see
[Hughes (2016)](https://research.chalmers.se/publication/232550).
This runner executes sequential commands; it does not test concurrent histories.
