# defmodule Tilly.BDD.OpsTest do # use ExUnit.Case, async: true # # alias Tilly.BDD # alias Tilly.BDD.Node # alias Tilly.BDD.Ops # alias Tilly.BDD.IntegerBoolOps # Using a concrete ops_module for testing # # setup do # typing_ctx = BDD.init_bdd_store(%{}) # # Pre-intern some common elements for tests if needed, e.g., integers # # For now, rely on ops to intern elements as they are used. # %{initial_ctx: typing_ctx} # end # # describe "leaf/3" do # test "interning an empty leaf value returns predefined false_id", %{initial_ctx: ctx} do # {new_ctx, node_id} = Ops.leaf(ctx, false, IntegerBoolOps) # assert node_id == BDD.false_node_id() # assert new_ctx.bdd_store.ops_cache == ctx.bdd_store.ops_cache # Cache not used for this path # end # # test "interning a full leaf value returns predefined true_id", %{initial_ctx: ctx} do # {new_ctx, node_id} = Ops.leaf(ctx, true, IntegerBoolOps) # assert node_id == BDD.true_node_id() # assert new_ctx.bdd_store.ops_cache == ctx.bdd_store.ops_cache # end # # @tag :skip # test "interning a new 'other' leaf value returns a new ID", %{initial_ctx: _ctx} do # # Assuming IntegerBoolOps.test_leaf_value/1 would return :other for non-booleans # # For this test, we'd need an ops_module where e.g. an integer is an :other leaf. # # Let's simulate with a mock or by extending IntegerBoolOps if it were not read-only. # # For now, this test is conceptual for boolean leaves. # # If IntegerBoolOps was extended: # # defmodule MockIntegerOps do # # defdelegate compare_elements(e1, e2), to: IntegerBoolOps # # defdelegate equal_element?(e1, e2), to: IntegerBoolOps # # # ... other delegates # # def test_leaf_value(10), do: :other # Treat 10 as a specific leaf # # def test_leaf_value(true), do: :full # # def test_leaf_value(false), do: :empty # # end # # {ctx_after_intern, node_id} = Ops.leaf(ctx, 10, MockIntegerOps) # # assert node_id != BDD.true_node_id() and node_id != BDD.false_node_id() # # assert BDD.get_node_data(ctx_after_intern, node_id).structure == Node.mk_leaf(10) # # Placeholder for more complex leaf types. Test is skipped. # end # end # # describe "split/6 basic simplifications" do # test "if i_id is true, returns true_id", %{initial_ctx: ctx} do # {_p_ctx, p_id} = Ops.leaf(ctx, false, IntegerBoolOps) # dummy # {_n_ctx, n_id} = Ops.leaf(ctx, false, IntegerBoolOps) # dummy # true_id = BDD.true_node_id() # # {new_ctx, result_id} = Ops.split(ctx, 10, p_id, true_id, n_id, IntegerBoolOps) # assert result_id == true_id # assert new_ctx == ctx # No new nodes or cache entries expected for this rule # end # # test "if p_id == n_id and p_id == i_id, returns p_id", %{initial_ctx: ctx} do # {ctx, p_id} = BDD.get_or_intern_node(ctx, Node.mk_leaf(false), IntegerBoolOps) # some leaf # i_id = p_id # n_id = p_id # # {_new_ctx, result_id} = Ops.split(ctx, 10, p_id, i_id, n_id, IntegerBoolOps) # assert result_id == p_id # # Cache might be touched if union_bdds was called, but this rule is direct. # # For p_id == i_id, it's direct. # end # # test "if p_id == n_id and p_id != i_id, returns union(p_id, i_id)", %{initial_ctx: ctx} do # {ctx, p_id} = BDD.get_or_intern_node(ctx, Node.mk_leaf(false), IntegerBoolOps) # {ctx, i_id} = BDD.get_or_intern_node(ctx, Node.mk_leaf(true), IntegerBoolOps) # different leaf # n_id = p_id # # # Expected union of p_id (false_leaf) and i_id (true_leaf) is true_id # # This relies on union_bdds working. # {_new_ctx, result_id} = Ops.split(ctx, 10, p_id, i_id, n_id, IntegerBoolOps) # expected_union_id = BDD.true_node_id() # Union of false_leaf and true_leaf # assert result_id == expected_union_id # end # # test "interns a new split node if no simplification rule applies", %{initial_ctx: ctx} do # {ctx, p_id} = Ops.leaf(ctx, false, IntegerBoolOps) # false_node_id # {ctx, i_id} = Ops.leaf(ctx, false, IntegerBoolOps) # false_node_id # {ctx, n_id} = Ops.leaf(ctx, true, IntegerBoolOps) # true_node_id (different from p_id) # # element = 20 # {new_ctx, split_node_id} = Ops.split(ctx, element, p_id, i_id, n_id, IntegerBoolOps) # # assert split_node_id != p_id and split_node_id != i_id and split_node_id != n_id # assert split_node_id != BDD.true_node_id() and split_node_id != BDD.false_node_id() # # node_data = BDD.get_node_data(new_ctx, split_node_id) # assert node_data.structure == Node.mk_split(element, p_id, i_id, n_id) # assert node_data.ops_module == IntegerBoolOps # assert new_ctx.bdd_store.next_node_id > ctx.bdd_store.next_node_id # end # end # # describe "union_bdds/3" do # test "A U A = A", %{initial_ctx: ctx} do # {ctx, a_id} = Ops.leaf(ctx, false, IntegerBoolOps) # false_node_id # {new_ctx, result_id} = Ops.union_bdds(ctx, a_id, a_id) # assert result_id == a_id # assert Map.has_key?(new_ctx.bdd_store.ops_cache, {:union, a_id, a_id}) # end # # test "A U True = True", %{initial_ctx: ctx} do # {ctx, a_id} = Ops.leaf(ctx, false, IntegerBoolOps) # true_id = BDD.true_node_id() # {_new_ctx, result_id} = Ops.union_bdds(ctx, a_id, true_id) # assert result_id == true_id # end # # test "A U False = A", %{initial_ctx: ctx} do # {ctx, a_id} = Ops.leaf(ctx, true, IntegerBoolOps) # true_node_id # false_id = BDD.false_node_id() # {_new_ctx, result_id} = Ops.union_bdds(ctx, a_id, false_id) # assert result_id == a_id # end # # test "union of two distinct leaves", %{initial_ctx: ctx} do # # leaf(false) U leaf(true) = leaf(true OR false) = leaf(true) -> true_node_id # {ctx, leaf_false_id} = Ops.leaf(ctx, false, IntegerBoolOps) # {ctx, leaf_true_id} = Ops.leaf(ctx, true, IntegerBoolOps) # This is BDD.true_node_id() # # {_new_ctx, result_id} = Ops.union_bdds(ctx, leaf_false_id, leaf_true_id) # assert result_id == BDD.true_node_id() # end # # test "union of two simple split nodes with same element", %{initial_ctx: ctx} do # # BDD1: split(10, True, False, False) # # BDD2: split(10, False, True, False) # # Union: split(10, True U False, False U True, False U False) # # = split(10, True, True, False) # # true_id = BDD.true_node_id() # false_id = BDD.false_node_id() # # {ctx, bdd1_id} = Ops.split(ctx, 10, true_id, false_id, false_id, IntegerBoolOps) # {ctx, bdd2_id} = Ops.split(ctx, 10, false_id, true_id, false_id, IntegerBoolOps) # # {final_ctx, union_id} = Ops.union_bdds(ctx, bdd1_id, bdd2_id) # # # Expected structure # {_final_ctx, expected_bdd_id} = Ops.split(final_ctx, 10, true_id, true_id, false_id, IntegerBoolOps) # assert union_id == expected_bdd_id # end # # test "union of two simple split nodes with different elements (x1 < x2)", %{initial_ctx: ctx} do # # BDD1: split(10, True, False, False) # # BDD2: split(20, False, True, False) # # Union (x1 < x2): split(10, p1, i1 U BDD2, n1) # # = split(10, True, False U BDD2, False) # # = split(10, True, BDD2, False) # # {ctx, bdd1_p1_id} = Ops.leaf(ctx, true, IntegerBoolOps) # {ctx, bdd1_i1_id} = Ops.leaf(ctx, false, IntegerBoolOps) # {ctx, bdd1_n1_id} = Ops.leaf(ctx, false, IntegerBoolOps) # {ctx, bdd1_id} = Ops.split(ctx, 10, bdd1_p1_id, bdd1_i1_id, bdd1_n1_id, IntegerBoolOps) # # {ctx, bdd2_p2_id} = Ops.leaf(ctx, false, IntegerBoolOps) # {ctx, bdd2_i2_id} = Ops.leaf(ctx, true, IntegerBoolOps) # {ctx, bdd2_n2_id} = Ops.leaf(ctx, false, IntegerBoolOps) # {ctx, bdd2_id} = Ops.split(ctx, 20, bdd2_p2_id, bdd2_i2_id, bdd2_n2_id, IntegerBoolOps) # # {final_ctx, union_id} = Ops.union_bdds(ctx, bdd1_id, bdd2_id) # # # Expected structure: split(10, True, BDD2, False) # {_final_ctx, expected_bdd_id} = Ops.split(final_ctx, 10, bdd1_p1_id, bdd2_id, bdd1_n1_id, IntegerBoolOps) # assert union_id == expected_bdd_id # end # # test "uses cache for repeated union operations", %{initial_ctx: ctx} do # {ctx, a_id} = Ops.leaf(ctx, false, IntegerBoolOps) # {ctx, b_id} = Ops.leaf(ctx, true, IntegerBoolOps) # # {ctx_after_first_union, _result1_id} = Ops.union_bdds(ctx, a_id, b_id) # cache_after_first = ctx_after_first_union.bdd_store.ops_cache # # {ctx_after_second_union, _result2_id} = Ops.union_bdds(ctx_after_first_union, a_id, b_id) # # The BDD store itself (nodes, next_id) should not change on a cache hit. # # The ops_cache map reference will be the same if the result was cached. # assert ctx_after_second_union.bdd_store.ops_cache == cache_after_first # assert ctx_after_second_union.bdd_store.next_node_id == ctx_after_first_union.bdd_store.next_node_id # end # end # end