Skip to content

Commit

Permalink
cleanup docs
Browse files Browse the repository at this point in the history
  • Loading branch information
CoderDennis committed Jan 30, 2024
1 parent 5b7da14 commit e386341
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 34 deletions.
6 changes: 5 additions & 1 deletion NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Implementation Notes

These are my thoughts and notes while investigating how this library should be implemented.
I'm including it in the repo to preserve revision history as I find answers to my own questions and evolve the design.

Expand Down Expand Up @@ -108,7 +110,9 @@ Maybe flatten the structure while keeping `random/0` and `hardcoded/1` construct

- [ ] Run the shrinking challenges (https://github.com/jlink/shrinking-challenge)

- [ ] Publish to Hex.pm
- [x] Publish to Hex.pm

- [x] Clean up docs. The `Decorum` and maybe `Prng` modules are the only ones that need to show up in the docs.

- [ ] add `mix dialyzer` to GitHub action see https://github.com/jeremyjh/dialyxir/blob/master/docs/github_actions.md

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ I'm currently in proof-of-concept mode. I've borowed some concepts from [StreamD

There are no macros yet, so property tests are just regular ExUnit tests with names starting with "property" by convention.

See [NOTES.md](NOTES.md) for my ongoing thoughts, questions, and TODOs.
See [Implementation Notes](NOTES.md) for my ongoing thoughts, questions, and TODOs.

## Background

Expand Down
8 changes: 1 addition & 7 deletions lib/decorum/history.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
defmodule Decorum.History do
@moduledoc """
History is currently a list of non-negative integers.
It might make sense to expand it to a structure where groups of random bytes could be labeled.
The original Hypothesis implementation uses labels. The Elm test implementation does not.
"""
@moduledoc false

alias Decorum.History.Chunk

Expand Down
2 changes: 2 additions & 0 deletions lib/decorum/history/chunk.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Decorum.History.Chunk do
@moduledoc false

defstruct [:start, :length]

@type t :: %__MODULE__{start: non_neg_integer(), length: non_neg_integer()}
Expand Down
34 changes: 19 additions & 15 deletions lib/decorum/prng.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,39 @@ defmodule Decorum.Prng do
2. Hardcoded
Used to replay a previously recorded (or simplified) history.
We represent random values using 32-bit non-negative integers because
they are easier to work with when shrinking the history.
"""

@type t :: prng
@type prng :: __MODULE__.Random.t() | __MODULE__.Hardcoded.t()
@opaque t() :: __MODULE__.Random.t() | __MODULE__.Hardcoded.t()

defmodule Random do
@moduledoc """
Wraps the `:rand` module and stores history of random numbers generated.
"""

@moduledoc false
@type t :: %__MODULE__{state: :rand.state(), history: Decorum.History.t()}

@enforce_keys [:state, :history]
defstruct [:state, :history]

@int32 Integer.pow(2, 32)

@doc false
@spec new() :: t
def new do
state = :rand.jump()
%__MODULE__{state: state, history: []}
end

@doc false
@spec next!(prng :: t()) :: {non_neg_integer(), t}
def next!(%__MODULE__{state: state, history: history} = prng) do
{value, new_state} = :rand.uniform_s(@int32, state)
{value, %__MODULE__{prng | state: new_state, history: [value | history]}}
end

@doc false
@spec get_history(prng :: t()) :: Decorum.History.t()
def get_history(%__MODULE__{history: history}), do: Enum.reverse(history)
end

defmodule Hardcoded do
@moduledoc """
Replays a previously recorded (or simplified) PRNG history.
"""
@moduledoc false
@type t :: %__MODULE__{
history: Decorum.History.t(),
unusedHistory: Decorum.History.t()
Expand All @@ -57,13 +50,11 @@ defmodule Decorum.Prng do
@enforce_keys [:history, :unusedHistory]
defstruct [:history, :unusedHistory]

@doc false
@spec new(history :: Decorum.History.t()) :: t
def new(history) when is_list(history) do
%__MODULE__{history: [], unusedHistory: history}
end

@doc false
@spec next!(prng :: t()) :: {non_neg_integer(), t()}
def next!(%__MODULE__{unusedHistory: []} = _prng) do
raise Decorum.EmptyHistoryError, "PRNG history is empty"
Expand All @@ -73,21 +64,34 @@ defmodule Decorum.Prng do
{value, %__MODULE__{prng | history: [value | history], unusedHistory: rest}}
end

@doc false
@spec get_history(prng :: t()) :: Decorum.History.t()
def get_history(%__MODULE__{history: history}), do: Enum.reverse(history)
end

@doc false
@spec random() :: t()
def random(), do: __MODULE__.Random.new()

@doc false
@spec hardcoded(history :: Decorum.History.t()) :: t()
def hardcoded(history), do: __MODULE__.Hardcoded.new(history)

@doc """
Takes a `Prng` struct and returns a tuple with the next random value
and an updated `Prng` struct.
When in `Random` state, `next!/1` is not expected to fail.
When in `Hardcoded` state, `next!/1` could raise a `Decorum.EmptyHistoryError`.
Generators will call `next!/1` to get a value to use when
generating test values. They should also return the updated `Prng` struct.
"""
@spec next!(prng :: t()) :: {non_neg_integer(), t()}
def next!(%__MODULE__.Random{} = prng), do: __MODULE__.Random.next!(prng)
def next!(%__MODULE__.Hardcoded{} = prng), do: __MODULE__.Hardcoded.next!(prng)

@doc false
@spec get_history(prng :: t()) :: Decorum.History.t()
def get_history(%__MODULE__.Random{} = prng), do: __MODULE__.Random.get_history(prng)
def get_history(%__MODULE__.Hardcoded{} = prng), do: __MODULE__.Hardcoded.get_history(prng)
Expand Down
11 changes: 1 addition & 10 deletions lib/decorum/shrinker.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
defmodule Decorum.Shrinker do
@moduledoc """
@moduledoc false

Binary Search (from Martin)
We always try new values - the floored average of low and high. In each new loop we change either low or high to be the average, never both. So something like:
0,100 -> middle 50; shrink attempt failed (either value didn't generate or it passed the test) so we go up (set low := mid), because we know that at all times, _high_ works and _low_ doesn't work and we want to keep it that way
50,100 -> middle 75; shrink attempt passed so we go down (set high := mid)
50,75 -> middle 62; shrink attempt succeeded so we go down
...
etc. until we get to the moment where low and high are next to each other (eg. 53,54) and we return _high_ as the found minimum.
"""
alias Decorum.History
alias Decorum.History.Chunk
alias Decorum.Prng
Expand Down
4 changes: 4 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ defmodule Decorum.MixProject do
package: [
licenses: ["MIT"],
links: %{"GitHub" => @repo_url}
],
docs: [
extras: ["README.md", "NOTES.md"],
main: "readme"
]
]
end
Expand Down

0 comments on commit e386341

Please sign in to comment.