Beancount.Property (beancount_ex v0.6.0)

Copy Markdown View Source

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 positive integer Decimal amount.

Compare two engines on identical input within the parity contract enforced by Beancount.Compare.compare/3.

Generate a commodity/currency code.

Generate a Date within a bounded range for determinism.

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

account()

@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, _}

amount()

@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

balanced_transaction()

@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

compare(oracle, native, input)

@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}

currency()

@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

date()

@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

ledger()

@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

metadata()

@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