causal_test.go raw

   1  package causal
   2  
   3  import (
   4  	"testing"
   5  
   6  	"git.mleku.dev/mleku/dendrite/pkg/axiom"
   7  )
   8  
   9  type elem struct {
  10  	tag string
  11  	val string
  12  }
  13  
  14  func (e elem) Type() string  { return e.tag }
  15  func (e elem) Value() any    { return e.val }
  16  
  17  func TestExtractDefines(t *testing.T) {
  18  	tests := []struct {
  19  		src  string
  20  		want []string
  21  	}{
  22  		{"x := 1", []string{"x"}},
  23  		{"x, y := foo()", []string{"x", "y"}},
  24  		{"var z int", []string{"z"}},
  25  		{"fmt.Println(x)", nil},
  26  		{"return x + 1", nil},
  27  	}
  28  	for _, tt := range tests {
  29  		defs := ExtractDefines(tt.src)
  30  		for _, w := range tt.want {
  31  			if !defs[w] {
  32  				t.Errorf("ExtractDefines(%q) missing %q", tt.src, w)
  33  			}
  34  		}
  35  		if tt.want == nil && len(defs) > 0 {
  36  			t.Errorf("ExtractDefines(%q) = %v, want empty", tt.src, defs)
  37  		}
  38  	}
  39  }
  40  
  41  func TestExtractUses(t *testing.T) {
  42  	tests := []struct {
  43  		src  string
  44  		want []string
  45  		not  []string
  46  	}{
  47  		{"x := foo()", []string{"foo"}, []string{"x"}},
  48  		{"fmt.Println(x)", []string{"fmt", "Println", "x"}, nil},
  49  		{"return x + 1", []string{"x"}, nil},
  50  		{"y := x + 1", []string{"x"}, []string{"y"}},
  51  	}
  52  	for _, tt := range tests {
  53  		uses := ExtractUses(tt.src)
  54  		for _, w := range tt.want {
  55  			if !uses[w] {
  56  				t.Errorf("ExtractUses(%q) missing %q", tt.src, w)
  57  			}
  58  		}
  59  		for _, n := range tt.not {
  60  			if uses[n] {
  61  				t.Errorf("ExtractUses(%q) should not contain %q", tt.src, n)
  62  			}
  63  		}
  64  	}
  65  }
  66  
  67  func TestCausalConstraintAdmitsInContext(t *testing.T) {
  68  	cc := CausalConstraint{ConstraintTag: "assign"}
  69  
  70  	// Element that defines x.
  71  	defX := elem{"assign", "main\x00x := 1"}
  72  	// Element that uses x.
  73  	useX := elem{"assign", "main\x00y := x + 1"}
  74  	// Element with no dependencies.
  75  	selfContained := elem{"assign", "main\x00a := 42"}
  76  
  77  	// With defX as neighbor, useX should be admitted.
  78  	neighbors := []axiom.Element{defX}
  79  	if !cc.AdmitsInContext(useX, neighbors) {
  80  		t.Error("should admit element when neighbor defines used identifier")
  81  	}
  82  
  83  	// Self-contained element should always be admitted.
  84  	if !cc.AdmitsInContext(selfContained, nil) {
  85  		t.Error("should admit self-contained element with no neighbors")
  86  	}
  87  
  88  	// Even with no matching neighbors, should still admit (lenient).
  89  	noMatchNeighbors := []axiom.Element{elem{"assign", "main\x00z := 99"}}
  90  	if !cc.AdmitsInContext(useX, noMatchNeighbors) {
  91  		t.Error("should admit element even without matching neighbor (lenient)")
  92  	}
  93  
  94  	// Wrong type should be rejected.
  95  	wrongType := elem{"return", "main\x00return x"}
  96  	if cc.AdmitsInContext(wrongType, neighbors) {
  97  		t.Error("should reject element with wrong type tag")
  98  	}
  99  }
 100  
 101  func TestScopeConstraint(t *testing.T) {
 102  	sc := ScopeConstraint{ConstraintTag: "assign"}
 103  
 104  	defX := elem{"assign", "main\x00x := 1"}
 105  	useX := elem{"assign", "main\x00y := x + 1"}
 106  	selfContained := elem{"assign", "main\x00a := 42"}
 107  	pureConsumer := elem{"expr", "main\x00fmt.Println(x)"}
 108  
 109  	// With defining neighbor, use should be admitted.
 110  	if !sc.AdmitsInContext(useX, []axiom.Element{defX}) {
 111  		t.Error("should admit when same-scope neighbor defines used identifier")
 112  	}
 113  
 114  	// Self-contained element (defines something) always admitted.
 115  	if !sc.AdmitsInContext(selfContained, []axiom.Element{defX}) {
 116  		t.Error("should admit self-contained element")
 117  	}
 118  
 119  	// No neighbors — seed bonding, always admitted.
 120  	if !sc.AdmitsInContext(useX, nil) {
 121  		t.Error("should admit with no neighbors (seed bonding)")
 122  	}
 123  
 124  	// Pure consumer with no matching definitions — rejected by ScopeConstraint.
 125  	scExpr := ScopeConstraint{ConstraintTag: "expr"}
 126  	unrelated := elem{"assign", "main\x00z := 99"}
 127  	if scExpr.AdmitsInContext(pureConsumer, []axiom.Element{unrelated}) {
 128  		t.Error("should reject pure consumer when no neighbor defines used identifiers")
 129  	}
 130  
 131  	// Different parent scope — should not count as scope neighbor.
 132  	diffScope := elem{"assign", "other\x00x := 1"}
 133  	if !sc.AdmitsInContext(useX, []axiom.Element{diffScope}) {
 134  		t.Error("should admit when only neighbors are from different scope (treated as no scope neighbor)")
 135  	}
 136  }
 137  
 138  func TestUniqueDefConstraint(t *testing.T) {
 139  	uc := UniqueDefConstraint{ConstraintTag: "assign"}
 140  
 141  	defX := elem{"assign", "main\x00x := 1"}
 142  	defXAgain := elem{"assign", "main\x00x := 2"}
 143  	defY := elem{"assign", "main\x00y := 3"}
 144  
 145  	// No conflict — different identifiers.
 146  	if !uc.AdmitsInContext(defY, []axiom.Element{defX}) {
 147  		t.Error("should admit when no duplicate definitions")
 148  	}
 149  
 150  	// Conflict — same identifier in same scope.
 151  	if uc.AdmitsInContext(defXAgain, []axiom.Element{defX}) {
 152  		t.Error("should reject duplicate definition of same identifier in same scope")
 153  	}
 154  
 155  	// Different scope — no conflict.
 156  	defXOther := elem{"assign", "other\x00x := 1"}
 157  	if !uc.AdmitsInContext(defXAgain, []axiom.Element{defXOther}) {
 158  		t.Error("should admit when duplicate is in different scope")
 159  	}
 160  
 161  	// No definitions — always admitted.
 162  	nodef := elem{"expr", "main\x00fmt.Println(x)"}
 163  	ucExpr := UniqueDefConstraint{ConstraintTag: "expr"}
 164  	if !ucExpr.AdmitsInContext(nodef, []axiom.Element{defX}) {
 165  		t.Error("should admit element with no definitions")
 166  	}
 167  
 168  	// No neighbors — always admitted.
 169  	if !uc.AdmitsInContext(defX, nil) {
 170  		t.Error("should admit with no neighbors")
 171  	}
 172  }
 173