返回 DeepSeek-Reasonix
dependency_test.go
根目录 / internal / extension / dependency_test.go
1 package extension
2
3 import (
4 "errors"
5 "strings"
6 "testing"
7
8 "reasonix/internal/extensioncontract"
9 )
10
11 func cap(ns, kind, id, ver, hash string) extensioncontract.Capability {
12 return extensioncontract.Capability{
13 Key: extensioncontract.CapabilityKey{Namespace: ns, Kind: kind, ID: id},
14 Version: ver,
15 SchemaHash: hash,
16 }
17 }
18
19 func req(ns, kind, id, rangeExpr string, optional bool) extensioncontract.Requirement {
20 return extensioncontract.Requirement{
21 Capability: extensioncontract.Capability{Key: extensioncontract.CapabilityKey{Namespace: ns, Kind: kind, ID: id}},
22 VersionRange: rangeExpr,
23 Optional: optional,
24 }
25 }
26
27 func TestDependencyGraphExactAndRange(t *testing.T) {
28 g, err := BuildDependencyGraph([]ComponentDescriptor{
29 {ID: "host", Provides: []extensioncontract.Capability{cap("reasonix", "provider", "deepseek/v4", "1.2.0", "sha256:p")}},
30 {ID: "plug", Requires: []extensioncontract.Requirement{req("reasonix", "provider", "deepseek/v4", ">=1.0.0", false)},
31 Provides: []extensioncontract.Capability{cap("plugin/ex", "tool", "t", "1.0.0", "sha256:t")}},
32 })
33 if err != nil {
34 t.Fatal(err)
35 }
36 order := g.ActivateOrder()
37 if len(order) != 2 || order[0] != "host" || order[1] != "plug" {
38 t.Fatalf("activate order = %v", order)
39 }
40 drain := g.DrainOrder()
41 if len(drain) != 2 || drain[0] != "plug" || drain[1] != "host" {
42 t.Fatalf("drain order = %v", drain)
43 }
44 }
45
46 func TestDependencyGraphSchemaMismatch(t *testing.T) {
47 _, err := BuildDependencyGraph([]ComponentDescriptor{
48 {ID: "host", Provides: []extensioncontract.Capability{cap("reasonix", "provider", "p", "1.0.0", "sha256:a")}},
49 {ID: "plug", Requires: []extensioncontract.Requirement{{
50 Capability: extensioncontract.Capability{
51 Key: extensioncontract.CapabilityKey{Namespace: "reasonix", Kind: "provider", ID: "p"},
52 SchemaHash: "sha256:b",
53 },
54 VersionRange: ">=1.0.0",
55 }}},
56 })
57 if err == nil || !strings.Contains(err.Error(), "dependency_unsatisfied") {
58 t.Fatalf("err = %v", err)
59 }
60 }
61
62 func TestDependencyGraphOptionalMissing(t *testing.T) {
63 g, err := BuildDependencyGraph([]ComponentDescriptor{
64 {ID: "plug", Requires: []extensioncontract.Requirement{req("reasonix", "provider", "missing", ">=1.0.0", true)}},
65 })
66 if err != nil {
67 t.Fatal(err)
68 }
69 if len(g.Diagnostics) == 0 {
70 t.Fatal("expected optional diagnostic")
71 }
72 }
73
74 func TestDependencyGraphDuplicateProvider(t *testing.T) {
75 _, err := BuildDependencyGraph([]ComponentDescriptor{
76 {ID: "a", Provides: []extensioncontract.Capability{cap("ns", "provider", "p", "1.0.0", "sha256:x")}},
77 {ID: "b", Provides: []extensioncontract.Capability{cap("ns", "provider", "p", "1.0.0", "sha256:x")}},
78 {ID: "c", Requires: []extensioncontract.Requirement{req("ns", "provider", "p", ">=1.0.0", false)}},
79 })
80 if err == nil || !strings.Contains(err.Error(), "duplicate_provider") {
81 t.Fatalf("err = %v", err)
82 }
83 }
84
85 func TestDependencyGraphRequiredCycle(t *testing.T) {
86 _, err := BuildDependencyGraph([]ComponentDescriptor{
87 {ID: "a", Requires: []extensioncontract.Requirement{req("ns", "x", "b", "", false)},
88 Provides: []extensioncontract.Capability{cap("ns", "x", "a", "1.0.0", "")}},
89 {ID: "b", Requires: []extensioncontract.Requirement{req("ns", "x", "a", "", false)},
90 Provides: []extensioncontract.Capability{cap("ns", "x", "b", "1.0.0", "")}},
91 })
92 if err == nil {
93 t.Fatal("cycle accepted")
94 }
95 var ge *GraphError
96 if !errors.As(err, &ge) || ge.Reason != "dependency_cycle" || len(ge.Cycle) < 2 {
97 t.Fatalf("err = %#v", err)
98 }
99 }
100
101 func TestDependencyGraphDeterministicOrder(t *testing.T) {
102 comps := []ComponentDescriptor{
103 {ID: "z", Priority: 1, Source: ContributionSource{Scope: ScopePlugin}},
104 {ID: "a", Priority: 1, Source: ContributionSource{Scope: ScopePlugin}},
105 {ID: "m", Priority: 10, Source: ContributionSource{Scope: ScopePlugin}},
106 }
107 g1, err := BuildDependencyGraph(comps)
108 if err != nil {
109 t.Fatal(err)
110 }
111 g2, err := BuildDependencyGraph([]ComponentDescriptor{comps[2], comps[0], comps[1]})
112 if err != nil {
113 t.Fatal(err)
114 }
115 o1, o2 := g1.ActivateOrder(), g2.ActivateOrder()
116 if strings.Join(idsToStrings(o1), ",") != strings.Join(idsToStrings(o2), ",") {
117 t.Fatalf("order not deterministic: %v vs %v", o1, o2)
118 }
119 // Higher priority first among independent nodes.
120 if o1[0] != "m" {
121 t.Fatalf("priority sort failed: %v", o1)
122 }
123 }
124
125 func idsToStrings(ids []ComponentID) []string {
126 out := make([]string, len(ids))
127 for i, id := range ids {
128 out[i] = string(id)
129 }
130 return out
131 }
132
132 lines GO