StreamData generators and helpers for property-based testing.
These generators produce valid, balanced Beancount data so that
properties such as "every generated ledger passes bean-check" and
"rendering is deterministic" can be expressed.
This module is only compiled when StreamData is available (the :test
and :dev environments).
Oracle comparison
Use Beancount.Property.compare/3 (or Beancount.Compare.compare/3) to
validate a native engine against the CLI oracle on identical input.
Summary
Functions
Generate a valid account name such as Assets:Bank.
Generate a balanced Beancount.Directives.Transaction.
Compare two engines on identical input within the parity contract enforced
by Beancount.Compare.compare/3.
Generate a commodity/currency code.
Generate a complete, valid ledger: open directives for every account
used, followed by a balanced transaction.
Generate a small metadata map with string values.
Functions
@spec account() :: StreamData.t(String.t())
Generate a valid account name such as Assets:Bank.
Examples
data = Beancount.Property.account() |> Enum.take(1) |> hd()
String.match?(data, ~r/^(Assets|Liabilities|Equity|Income|Expenses):/)
# => {:ok, _}
@spec amount() :: StreamData.t(Decimal.t())
Generate a positive integer Decimal amount.
Examples
iex> amount = Beancount.Property.amount() |> Enum.take(1) |> hd()
iex> %Decimal{} = amount
amount
@spec balanced_transaction() :: StreamData.t(Beancount.Directives.Transaction.t())
Generate a balanced Beancount.Directives.Transaction.
The generated postings always sum to zero in a single currency, so the transaction is guaranteed to balance.
Examples
iex> txn = Beancount.Property.balanced_transaction() |> Enum.take(1) |> hd()
iex> %Beancount.Directives.Transaction{} = txn
txn
@spec compare(module(), module(), Beancount.directive() | binary()) :: {:ok, :equivalent} | {:error, Beancount.Property.Diff.t()}
Compare two engines on identical input within the parity contract enforced
by Beancount.Compare.compare/3.
Delegates to Beancount.Compare.compare/3.
Examples
iex> ledger = [
...> Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"]),
...> Beancount.open(~D[2026-01-01], "Income:Salary", ["USD"]),
...> Beancount.open(~D[2026-01-01], "Equity:Opening", ["USD"]),
...> Beancount.transaction(~D[2026-01-31], "*", nil, "Salary", [
...> Beancount.posting("Assets:Bank", Decimal.new("100"), "USD"),
...> Beancount.posting("Income:Salary", Decimal.new("-100"), "USD")
...> ])
...> ]
iex> Beancount.Property.compare(Beancount.Engine.Elixir, Beancount.Engine.Elixir, ledger)
{:ok, :equivalent}
@spec currency() :: StreamData.t(String.t())
Generate a commodity/currency code.
Examples
iex> data = Beancount.Property.currency() |> Enum.take(1) |> hd()
iex> data in ~w(USD EUR GBP CAD CHF JPY)
true
@spec date() :: StreamData.t(Date.t())
Generate a Date within a bounded range for determinism.
Examples
iex> date = Beancount.Property.date() |> Enum.take(1) |> hd()
iex> %Date{} = date
date
@spec ledger() :: StreamData.t([Beancount.Directive.t()])
Generate a complete, valid ledger: open directives for every account
used, followed by a balanced transaction.
Examples
iex> ledger = Beancount.Property.ledger() |> Enum.take(1) |> hd()
iex> is_list(ledger)
true
@spec metadata() :: StreamData.t(map())
Generate a small metadata map with string values.
Examples
iex> map = Beancount.Property.metadata() |> Enum.take(1) |> hd()
iex> is_map(map)
true