directive_test.go raw
1 package organ
2
3 import (
4 "testing"
5
6 "git.mleku.dev/mleku/dendrite/pkg/axiom"
7 "git.mleku.dev/mleku/dendrite/pkg/dissolve"
8 "git.mleku.dev/mleku/dendrite/pkg/lattice"
9 "git.mleku.dev/mleku/dendrite/pkg/ratio"
10 )
11
12 func TestDirectiveElementInterface(t *testing.T) {
13 d := NewDirectiveElement("evolve faster")
14
15 // Implements axiom.Element.
16 var _ axiom.Element = d
17 if d.Type() != "directive" {
18 t.Errorf("expected type 'directive', got %q", d.Type())
19 }
20 if d.Value().(string) != "evolve faster" {
21 t.Errorf("expected value 'evolve faster', got %q", d.Value())
22 }
23
24 // Implements axiom.StickyElement.
25 var _ axiom.StickyElement = d
26 if !d.IsSticky() {
27 t.Error("default directive should be sticky")
28 }
29
30 // Non-sticky directive.
31 d2 := DirectiveElement{Directive: Directive{Text: "test", Sticky: false}}
32 if d2.IsSticky() {
33 t.Error("non-sticky directive should not be sticky")
34 }
35 }
36
37 func TestDirectiveConstraint(t *testing.T) {
38 c := DirectiveConstraint{}
39
40 if c.Tag() != "directive" {
41 t.Errorf("expected tag 'directive', got %q", c.Tag())
42 }
43
44 // Should admit DirectiveElement.
45 d := NewDirectiveElement("test")
46 if !c.Admits(d) {
47 t.Error("should admit DirectiveElement")
48 }
49
50 // Should not admit other element types.
51 other := mockElement{typ: "fragment", val: "foo"}
52 if c.Admits(other) {
53 t.Error("should not admit non-directive element")
54 }
55 }
56
57 // mockElement implements axiom.Element for testing.
58 type mockElement struct {
59 typ string
60 val any
61 }
62
63 func (m mockElement) Type() string { return m.typ }
64 func (m mockElement) Value() any { return m.val }
65
66 func TestTokenizeDirective(t *testing.T) {
67 tokens := TokenizeDirective("learn to compile Go code", true, ratio.One)
68
69 expected := []string{"learn", "to", "compile", "Go", "code"}
70 if len(tokens) != len(expected) {
71 t.Fatalf("expected %d tokens, got %d", len(expected), len(tokens))
72 }
73
74 for i, tok := range tokens {
75 if tok.Directive.Text != expected[i] {
76 t.Errorf("token %d: expected %q, got %q", i, expected[i], tok.Directive.Text)
77 }
78 if !tok.IsSticky() {
79 t.Errorf("token %d should be sticky", i)
80 }
81 if !tok.Directive.Priority.Equal(ratio.One) {
82 t.Errorf("token %d priority: expected 1/1, got %s", i, tok.Directive.Priority)
83 }
84 }
85
86 // Non-sticky tokens.
87 tokens = TokenizeDirective("soft hint", false, ratio.Half)
88 if len(tokens) != 2 {
89 t.Fatalf("expected 2 tokens, got %d", len(tokens))
90 }
91 for _, tok := range tokens {
92 if tok.IsSticky() {
93 t.Error("should not be sticky")
94 }
95 if !tok.Directive.Priority.Equal(ratio.Half) {
96 t.Errorf("expected priority 1/2, got %s", tok.Directive.Priority)
97 }
98 }
99 }
100
101 func TestTokenizeDirectiveEdgeCases(t *testing.T) {
102 // Empty string.
103 if tokens := TokenizeDirective("", true, ratio.One); len(tokens) != 0 {
104 t.Errorf("empty string should produce 0 tokens, got %d", len(tokens))
105 }
106
107 // Only whitespace.
108 if tokens := TokenizeDirective(" \t\n ", true, ratio.One); len(tokens) != 0 {
109 t.Errorf("whitespace should produce 0 tokens, got %d", len(tokens))
110 }
111
112 // Single word.
113 tokens := TokenizeDirective("optimize", true, ratio.One)
114 if len(tokens) != 1 || tokens[0].Directive.Text != "optimize" {
115 t.Errorf("single word: expected ['optimize'], got %v", tokens)
116 }
117
118 // Multiple spaces.
119 tokens = TokenizeDirective("a b c", true, ratio.One)
120 if len(tokens) != 3 {
121 t.Errorf("multiple spaces: expected 3 tokens, got %d", len(tokens))
122 }
123 }
124
125 func TestStickyDirectiveSurvivesDissolution(t *testing.T) {
126 l := lattice.New()
127
128 // Create a directive site with one constraint.
129 dirNode := l.AddNode([]axiom.Constraint{DirectiveConstraint{}})
130
131 // Bond a sticky directive.
132 d := NewDirectiveElement("persist")
133 if !dirNode.Bond(d) {
134 t.Fatal("directive should bond")
135 }
136
137 // Create a regular site with a weak constraint.
138 regNode := l.AddNode([]axiom.Constraint{mockConstraint{tag: "text", admitAll: true}})
139 regular := mockElement{typ: "text", val: "ephemeral"}
140 if !regNode.Bond(regular) {
141 t.Fatal("regular element should bond")
142 }
143
144 // Both nodes occupied.
145 if !dirNode.Occupied() || !regNode.Occupied() {
146 t.Fatal("both should be occupied")
147 }
148
149 // Run dissolution with a high threshold that would dissolve everything.
150 dissolved := make(chan axiom.Element, 10)
151 events := make(chan dissolve.Event, 10)
152 dissolve.ScanOnce(l, dissolve.Config{Threshold: ratio.FromInt(999)}, dissolved, events)
153
154 // The sticky directive should survive.
155 if !dirNode.Occupied() {
156 t.Error("sticky directive should survive dissolution")
157 }
158
159 // The regular element should be dissolved.
160 if regNode.Occupied() {
161 t.Error("regular element should be dissolved")
162 }
163
164 // One element should have been dissolved.
165 select {
166 case elem := <-dissolved:
167 if elem.Type() != "text" {
168 t.Errorf("expected dissolved element type 'text', got %q", elem.Type())
169 }
170 default:
171 t.Error("expected one dissolved element")
172 }
173 }
174
175 func TestNonStickyDirectiveDissolvable(t *testing.T) {
176 l := lattice.New()
177
178 dirNode := l.AddNode([]axiom.Constraint{DirectiveConstraint{}})
179
180 // Bond a non-sticky directive.
181 d := DirectiveElement{Directive: Directive{Text: "soft hint", Sticky: false}}
182 if !dirNode.Bond(d) {
183 t.Fatal("directive should bond")
184 }
185
186 // High threshold — should dissolve non-sticky.
187 dissolved := make(chan axiom.Element, 10)
188 events := make(chan dissolve.Event, 10)
189 dissolve.ScanOnce(l, dissolve.Config{Threshold: ratio.FromInt(999)}, dissolved, events)
190
191 if dirNode.Occupied() {
192 t.Error("non-sticky directive should be dissolved")
193 }
194 }
195
196 // mockConstraint is a simple constraint for testing.
197 type mockConstraint struct {
198 tag string
199 admitAll bool
200 }
201
202 func (c mockConstraint) Tag() string { return c.tag }
203 func (c mockConstraint) Admits(e axiom.Element) bool { return c.admitAll }
204