1
2
3
4
5 package unify
6
7 import (
8 "bytes"
9 "fmt"
10 "iter"
11 "log"
12 "strings"
13 "testing"
14 "testing/fstest"
15
16 "gopkg.in/yaml.v3"
17 )
18
19 func mustParse(expr string) Closure {
20 var c Closure
21 if err := yaml.Unmarshal([]byte(expr), &c); err != nil {
22 panic(err)
23 }
24 return c
25 }
26
27 func oneValue(t *testing.T, c Closure) *Value {
28 t.Helper()
29 var v *Value
30 var i int
31 for v = range c.All() {
32 i++
33 }
34 if i != 1 {
35 t.Fatalf("expected 1 value, got %d", i)
36 }
37 return v
38 }
39
40 func printYaml(val any) {
41 fmt.Println(prettyYaml(val))
42 }
43
44 func prettyYaml(val any) string {
45 b, err := yaml.Marshal(val)
46 if err != nil {
47 panic(err)
48 }
49 var node yaml.Node
50 if err := yaml.Unmarshal(b, &node); err != nil {
51 panic(err)
52 }
53
54
55
56 lines := []int{-1, 0}
57 for pos := 0; pos < len(b); {
58 next := bytes.IndexByte(b[pos:], '\n')
59 if next == -1 {
60 break
61 }
62 pos += next + 1
63 lines = append(lines, pos)
64 }
65 lines = append(lines, len(b))
66
67
68 cleanYaml(&node, lines, len(b))
69
70 b, err = yaml.Marshal(&node)
71 if err != nil {
72 panic(err)
73 }
74 return string(b)
75 }
76
77 func cleanYaml(node *yaml.Node, lines []int, endPos int) {
78 node.HeadComment = ""
79 node.FootComment = ""
80 node.LineComment = ""
81
82 for i, n2 := range node.Content {
83 end2 := endPos
84 if i < len(node.Content)-1 {
85 end2 = lines[node.Content[i+1].Line]
86 }
87 cleanYaml(n2, lines, end2)
88 }
89
90
91 switch node.Kind {
92 case yaml.MappingNode, yaml.SequenceNode:
93 if endPos-lines[node.Line] < 40 {
94 node.Style = yaml.FlowStyle
95 }
96 }
97 }
98
99 func allYamlNodes(n *yaml.Node) iter.Seq[*yaml.Node] {
100 return func(yield func(*yaml.Node) bool) {
101 if !yield(n) {
102 return
103 }
104 for _, n2 := range n.Content {
105 for n3 := range allYamlNodes(n2) {
106 if !yield(n3) {
107 return
108 }
109 }
110 }
111 }
112 }
113
114 func TestRoundTripString(t *testing.T) {
115
116 const y = `!string test*`
117 t.Logf("input:\n%s", y)
118
119 v1 := oneValue(t, mustParse(y))
120 var buf1 strings.Builder
121 enc := yaml.NewEncoder(&buf1)
122 if err := enc.Encode(v1); err != nil {
123 log.Fatal(err)
124 }
125 enc.Close()
126 t.Logf("after parse 1:\n%s", buf1.String())
127
128 v2 := oneValue(t, mustParse(buf1.String()))
129 var buf2 strings.Builder
130 enc = yaml.NewEncoder(&buf2)
131 if err := enc.Encode(v2); err != nil {
132 log.Fatal(err)
133 }
134 enc.Close()
135 t.Logf("after parse 2:\n%s", buf2.String())
136
137 if buf1.String() != buf2.String() {
138 t.Fatal("parse 1 and parse 2 differ")
139 }
140 }
141
142 func TestEmptyString(t *testing.T) {
143
144
145 const y = `""`
146 t.Logf("input:\n%s", y)
147
148 v1 := oneValue(t, mustParse(y))
149 if !v1.Exact() {
150 t.Fatal("expected exact string")
151 }
152 }
153
154 func TestImport(t *testing.T) {
155
156 main := strings.NewReader("!import x/y.yaml")
157 fs := fstest.MapFS{
158
159 "x/y.yaml": {Data: []byte("!import y/*.yaml")},
160 "x/y/z.yaml": {Data: []byte("42")},
161 }
162 cl, err := Read(main, "x.yaml", ReadOpts{FS: fs})
163 if err != nil {
164 t.Fatal(err)
165 }
166 x := 42
167 checkDecode(t, oneValue(t, cl), &x)
168 }
169
170 func TestImportEscape(t *testing.T) {
171
172 main := strings.NewReader("!import x/y.yaml")
173 fs := fstest.MapFS{
174 "x/y.yaml": {Data: []byte("!import ../y/*.yaml")},
175 "y/z.yaml": {Data: []byte("42")},
176 }
177 _, err := Read(main, "x.yaml", ReadOpts{FS: fs})
178 if err == nil {
179 t.Fatal("relative !import should have failed")
180 }
181 if !strings.Contains(err.Error(), "must not contain") {
182 t.Fatalf("unexpected error %v", err)
183 }
184 }
185
186 func TestImportScope(t *testing.T) {
187
188 main := strings.NewReader("[!import y.yaml, !import y.yaml]")
189 fs := fstest.MapFS{
190 "y.yaml": {Data: []byte("$v")},
191 }
192 cl1, err := Read(main, "x.yaml", ReadOpts{FS: fs})
193 if err != nil {
194 t.Fatal(err)
195 }
196 cl2 := mustParse("[1, 2]")
197 res, err := Unify(cl1, cl2)
198 if err != nil {
199 t.Fatal(err)
200 }
201 checkDecode(t, oneValue(t, res), []int{1, 2})
202 }
203
View as plain text