defmodule Tilly.BDDTest do use ExUnit.Case, async: true alias Tilly.BDD.Node describe "init_bdd_store/1" do test "initializes bdd_store in typing_ctx with predefined false and true nodes" do typing_ctx = %{} new_ctx = Tilly.BDD.init_bdd_store(typing_ctx) assert %{bdd_store: bdd_store} = new_ctx assert is_map(bdd_store.nodes_by_structure) assert is_map(bdd_store.structures_by_id) assert bdd_store.next_node_id == 2 # 0 for false, 1 for true assert bdd_store.ops_cache == %{} # Check false node false_id = Tilly.BDD.false_node_id() false_ops_module = Tilly.BDD.universal_ops_module() assert bdd_store.nodes_by_structure[{Node.mk_false(), false_ops_module}] == false_id assert bdd_store.structures_by_id[false_id] == %{structure: Node.mk_false(), ops_module: false_ops_module} # Check true node true_id = Tilly.BDD.true_node_id() true_ops_module = Tilly.BDD.universal_ops_module() assert bdd_store.nodes_by_structure[{Node.mk_true(), true_ops_module}] == true_id assert bdd_store.structures_by_id[true_id] == %{structure: Node.mk_true(), ops_module: true_ops_module} end end describe "get_or_intern_node/3" do setup do typing_ctx = Tilly.BDD.init_bdd_store(%{}) %{initial_ctx: typing_ctx} end test "interning Node.mk_false() returns predefined false_id and doesn't change store", %{initial_ctx: ctx} do false_ops_module = Tilly.BDD.universal_ops_module() {new_ctx, node_id} = Tilly.BDD.get_or_intern_node(ctx, Node.mk_false(), false_ops_module) assert node_id == Tilly.BDD.false_node_id() assert new_ctx.bdd_store == ctx.bdd_store end test "interning Node.mk_true() returns predefined true_id and doesn't change store", %{initial_ctx: ctx} do true_ops_module = Tilly.BDD.universal_ops_module() {new_ctx, node_id} = Tilly.BDD.get_or_intern_node(ctx, Node.mk_true(), true_ops_module) assert node_id == Tilly.BDD.true_node_id() assert new_ctx.bdd_store == ctx.bdd_store end test "interning a new leaf node returns a new ID and updates the store", %{initial_ctx: ctx} do leaf_structure = Node.mk_leaf("test_leaf") ops_mod = :my_ops {ctx_after_intern, node_id} = Tilly.BDD.get_or_intern_node(ctx, leaf_structure, ops_mod) assert node_id == 2 # Initial next_node_id assert ctx_after_intern.bdd_store.next_node_id == 3 assert ctx_after_intern.bdd_store.nodes_by_structure[{leaf_structure, ops_mod}] == node_id assert ctx_after_intern.bdd_store.structures_by_id[node_id] == %{structure: leaf_structure, ops_module: ops_mod} end test "interning the same leaf node again returns the same ID and doesn't change store", %{initial_ctx: ctx} do leaf_structure = Node.mk_leaf("test_leaf") ops_mod = :my_ops {ctx_after_first_intern, first_node_id} = Tilly.BDD.get_or_intern_node(ctx, leaf_structure, ops_mod) {ctx_after_second_intern, second_node_id} = Tilly.BDD.get_or_intern_node(ctx_after_first_intern, leaf_structure, ops_mod) assert first_node_id == second_node_id assert ctx_after_first_intern.bdd_store == ctx_after_second_intern.bdd_store end test "interning a new split node returns a new ID and updates the store", %{initial_ctx: ctx} do split_structure = Node.mk_split(:el, Tilly.BDD.true_node_id(), Tilly.BDD.false_node_id(), Tilly.BDD.true_node_id()) ops_mod = :split_ops {ctx_after_intern, node_id} = Tilly.BDD.get_or_intern_node(ctx, split_structure, ops_mod) assert node_id == 2 # Initial next_node_id assert ctx_after_intern.bdd_store.next_node_id == 3 assert ctx_after_intern.bdd_store.nodes_by_structure[{split_structure, ops_mod}] == node_id assert ctx_after_intern.bdd_store.structures_by_id[node_id] == %{structure: split_structure, ops_module: ops_mod} end test "interning structurally identical nodes with different ops_modules results in different IDs", %{initial_ctx: ctx} do leaf_structure = Node.mk_leaf("shared_leaf") ops_mod1 = :ops1 ops_mod2 = :ops2 {ctx1, id1} = Tilly.BDD.get_or_intern_node(ctx, leaf_structure, ops_mod1) {_ctx2, id2} = Tilly.BDD.get_or_intern_node(ctx1, leaf_structure, ops_mod2) assert id1 != id2 assert id1 == 2 assert id2 == 3 end test "raises ArgumentError if bdd_store is not initialized" do assert_raise ArgumentError, ~r/BDD store not initialized/, fn -> Tilly.BDD.get_or_intern_node(%{}, Node.mk_leaf("foo"), :ops) end end end describe "get_node_data/2" do setup do ctx = Tilly.BDD.init_bdd_store(%{}) leaf_structure = Node.mk_leaf("data") ops_mod = :leaf_ops {new_ctx, leaf_id_val} = Tilly.BDD.get_or_intern_node(ctx, leaf_structure, ops_mod) %{ctx: new_ctx, leaf_structure: leaf_structure, ops_mod: ops_mod, leaf_id: leaf_id_val} end test "returns correct data for false node", %{ctx: ctx} do false_id = Tilly.BDD.false_node_id() false_ops_module = Tilly.BDD.universal_ops_module() assert Tilly.BDD.get_node_data(ctx, false_id) == %{structure: Node.mk_false(), ops_module: false_ops_module} end test "returns correct data for true node", %{ctx: ctx} do true_id = Tilly.BDD.true_node_id() true_ops_module = Tilly.BDD.universal_ops_module() assert Tilly.BDD.get_node_data(ctx, true_id) == %{structure: Node.mk_true(), ops_module: true_ops_module} end test "returns correct data for a custom interned leaf node", %{ctx: ctx, leaf_structure: ls, ops_mod: om, leaf_id: id} do assert Tilly.BDD.get_node_data(ctx, id) == %{structure: ls, ops_module: om} end test "returns nil for an unknown node ID", %{ctx: ctx} do assert Tilly.BDD.get_node_data(ctx, 999) == nil end test "returns nil if bdd_store not in ctx" do assert Tilly.BDD.get_node_data(%{}, 0) == nil end end describe "is_false_node?/2 and is_true_node?/2" do setup do ctx = Tilly.BDD.init_bdd_store(%{}) leaf_structure = Node.mk_leaf("data") ops_mod = :leaf_ops {new_ctx, leaf_id_val} = Tilly.BDD.get_or_intern_node(ctx, leaf_structure, ops_mod) %{ctx: new_ctx, leaf_id: leaf_id_val} end test "is_false_node?/2", %{ctx: ctx, leaf_id: id} do assert Tilly.BDD.is_false_node?(ctx, Tilly.BDD.false_node_id()) == true assert Tilly.BDD.is_false_node?(ctx, Tilly.BDD.true_node_id()) == false assert Tilly.BDD.is_false_node?(ctx, id) == false assert Tilly.BDD.is_false_node?(ctx, 999) == false # Unknown ID end test "is_true_node?/2", %{ctx: ctx, leaf_id: id} do assert Tilly.BDD.is_true_node?(ctx, Tilly.BDD.true_node_id()) == true assert Tilly.BDD.is_true_node?(ctx, Tilly.BDD.false_node_id()) == false assert Tilly.BDD.is_true_node?(ctx, id) == false assert Tilly.BDD.is_true_node?(ctx, 999) == false # Unknown ID end end end