Grove.Testing (Grove v0.1.1)

View Source

Testing utilities for Grove CRDTs.

This module provides helpers for testing CRDT behavior, especially convergence under concurrent operations.

Usage

Add to your test file:

import Grove.Testing

Example

test "replicas converge after sync" do
  # Create 3 replicas of a GCounter
  [r1, r2, r3] = create_replicas(GCounter, 3)

  # Each replica increments
  r1 = GCounter.increment(r1, 5)
  r2 = GCounter.increment(r2, 3)
  r3 = GCounter.increment(r3, 7)

  # Sync all replicas
  [r1, r2, r3] = sync_all([r1, r2, r3])

  # Assert convergence
  assert_convergent([r1, r2, r3])
  assert GCounter.value(r1) == 15
end

Summary

Functions

Applies a list of operations to a CRDT.

Asserts that all replicas have converged to the same value.

Applies a list of operations atomically using Grove.batch/2.

Creates count replicas of the given CRDT module.

Applies a function to a specific replica in a list.

Generates random operations for fuzz testing.

Returns the values of all replicas for inspection.

Syncs two replicas bidirectionally.

Syncs all replicas with each other.

Functions

apply_operations(crdt, operations)

@spec apply_operations(term(), [{atom(), [term()]}]) :: term()

Applies a list of operations to a CRDT.

Example

ops = [{:increment, [5]}, {:increment, [3]}]
counter = apply_operations(counter, ops)

assert_convergent(replicas)

@spec assert_convergent([term()]) :: :ok

Asserts that all replicas have converged to the same value.

Raises ExUnit.AssertionError if values differ.

Example

assert_convergent([r1, r2, r3])

batch_operations(crdt, operations)

@spec batch_operations(term(), [{atom(), [term()]}]) ::
  {:ok, term()} | {:error, term(), term()}

Applies a list of operations atomically using Grove.batch/2.

Like apply_operations/2 but with rollback semantics on error.

Example

{:ok, set} = batch_operations(set, [
  {:add, ["a"]},
  {:add, ["b"]}
])

create_replicas(crdt_module, count, opts \\ [])

@spec create_replicas(module(), pos_integer(), keyword()) :: [term()]

Creates count replicas of the given CRDT module.

Each replica gets a unique actor ID (:replica_1, :replica_2, etc).

Options

  • :actor_prefix - Prefix for actor IDs (default: :replica)

Example

[r1, r2, r3] = create_replicas(GCounter, 3)
[a, b] = create_replicas(ORSet, 2, actor_prefix: :node)

mutate_replica(replicas, index, fun)

@spec mutate_replica([term()], non_neg_integer(), (term() -> term())) :: [term()]

Applies a function to a specific replica in a list.

Returns the updated list with the modified replica.

Example

replicas = mutate_replica(replicas, 0, &GCounter.increment(&1, 5))

random_operations(crdt_module, count, opts \\ [])

@spec random_operations(module(), pos_integer(), keyword()) :: [{atom(), [term()]}]

Generates random operations for fuzz testing.

Returns a list of {operation, args} tuples suitable for the given CRDT.

Supported CRDTs

Example

ops = random_operations(GCounter, 10)
# => [{:increment, [5]}, {:increment, [3]}, ...]

replica_values(replicas)

@spec replica_values([term()]) :: [term()]

Returns the values of all replicas for inspection.

Useful for debugging convergence issues.

Example

values = replica_values([r1, r2, r3])
# => [10, 10, 10]

sync(replica1, replica2)

@spec sync(term(), term()) :: {term(), term()}

Syncs two replicas bidirectionally.

Merges replica1 into replica2 and vice versa, returning both updated replicas.

Example

{r1, r2} = sync(r1, r2)

sync_all(replicas)

@spec sync_all([term()]) :: [term()]

Syncs all replicas with each other.

Each replica receives updates from all other replicas. Returns the list of synced replicas in the same order.

Example

[r1, r2, r3] = sync_all([r1, r2, r3])