1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package format
16
17
18
19 import (
20 "bytes"
21 "fmt"
22 "os"
23 "path/filepath"
24 "testing"
25 "time"
26
27 "cuelang.org/go/cue/ast"
28 "cuelang.org/go/cue/errors"
29 "cuelang.org/go/cue/parser"
30 "cuelang.org/go/cue/token"
31 "cuelang.org/go/internal"
32 "cuelang.org/go/internal/cuetest"
33 )
34
35 var (
36 defaultConfig = newConfig([]Option{})
37 Fprint = defaultConfig.fprint
38 )
39
40 const (
41 dataDir = "testdata"
42 )
43
44 type checkMode uint
45
46 const (
47 _ checkMode = 1 << iota
48 idempotent
49 simplify
50 sortImps
51 )
52
53
54
55
56 func format(src []byte, mode checkMode) ([]byte, error) {
57
58 opts := []Option{TabIndent(true)}
59 if mode&simplify != 0 {
60 opts = append(opts, Simplify())
61 }
62 if mode&sortImps != 0 {
63 opts = append(opts, sortImportsOption())
64 }
65
66 res, err := Source(src, opts...)
67 if err != nil {
68 return nil, err
69 }
70
71
72 if _, err := parser.ParseFile("", res, parser.AllErrors); err != nil {
73 return nil, errors.Append(err.(errors.Error),
74 errors.Newf(token.NoPos, "re-parse failed: %s", res))
75 }
76
77 return res, nil
78 }
79
80
81 func lineAt(text []byte, offs int) []byte {
82 i := offs
83 for i < len(text) && text[i] != '\n' {
84 i++
85 }
86 return text[offs:i]
87 }
88
89
90 func diff(aname, bname string, a, b []byte) error {
91 var buf bytes.Buffer
92
93
94 if len(a) != len(b) {
95 fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b))
96 }
97
98
99 line := 1
100 offs := 1
101 for i := 0; i < len(a) && i < len(b); i++ {
102 ch := a[i]
103 if ch != b[i] {
104 fmt.Fprintf(&buf, "\n%s:%d:%d: %s", aname, line, i-offs+1, lineAt(a, offs))
105 fmt.Fprintf(&buf, "\n%s:%d:%d: %s", bname, line, i-offs+1, lineAt(b, offs))
106 fmt.Fprintf(&buf, "\n\n")
107 break
108 }
109 if ch == '\n' {
110 line++
111 offs = i + 1
112 }
113 }
114
115 if buf.Len() > 0 {
116 return errors.New(buf.String())
117 }
118 return nil
119 }
120
121 func runcheck(t *testing.T, source, golden string, mode checkMode) {
122 src, err := os.ReadFile(source)
123 if err != nil {
124 t.Error(err)
125 return
126 }
127
128 res, err := format(src, mode)
129 if err != nil {
130 b := &bytes.Buffer{}
131 errors.Print(b, err, nil)
132 t.Error(b.String())
133 return
134 }
135
136
137 if cuetest.UpdateGoldenFiles {
138 if err := os.WriteFile(golden, res, 0644); err != nil {
139 t.Error(err)
140 }
141 return
142 }
143
144
145 gld, err := os.ReadFile(golden)
146 if err != nil {
147 t.Error(err)
148 return
149 }
150
151
152 if err := diff(source, golden, res, gld); err != nil {
153 t.Error(err)
154 return
155 }
156
157 if mode&idempotent != 0 {
158
159
160
161 res, err = format(gld, mode)
162 if err != nil {
163 t.Fatal(err)
164 }
165 if err := diff(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil {
166 t.Errorf("golden is not idempotent: %s", err)
167 }
168 }
169 }
170
171 func check(t *testing.T, source, golden string, mode checkMode) {
172
173 cc := make(chan int)
174 go func() {
175 runcheck(t, source, golden, mode)
176 cc <- 0
177 }()
178
179
180 select {
181 case <-time.After(100000 * time.Second):
182
183 t.Errorf("%s: running too slowly", source)
184 case <-cc:
185
186 }
187 }
188
189 type entry struct {
190 source, golden string
191 mode checkMode
192 }
193
194
195 var data = []entry{
196 {"comments.input", "comments.golden", simplify},
197 {"simplify.input", "simplify.golden", simplify},
198 {"expressions.input", "expressions.golden", 0},
199 {"values.input", "values.golden", 0},
200 {"imports.input", "imports.golden", sortImps},
201 }
202
203 func TestFiles(t *testing.T) {
204 t.Parallel()
205 for _, e := range data {
206 source := filepath.Join(dataDir, e.source)
207 golden := filepath.Join(dataDir, e.golden)
208 mode := e.mode
209 t.Run(e.source, func(t *testing.T) {
210 t.Parallel()
211 check(t, source, golden, mode)
212
213
214 })
215 }
216 }
217
218
219 func init() {
220 const name = "foobar"
221 b, err := Fprint(&ast.Ident{Name: name})
222 if err != nil {
223 panic(err)
224 }
225
226
227 if s := string(b); !debug && s != name {
228 panic("got " + s + ", want " + name)
229 }
230 }
231
232
233
234 func TestNodes(t *testing.T) {
235 testCases := []struct {
236 name string
237 in ast.Node
238 out string
239 }{{
240 name: "old-style octal numbers",
241 in: ast.NewLit(token.INT, "0123"),
242 out: "0o123",
243 }, {
244 name: "labels with multi-line strings",
245 in: &ast.Field{
246 Label: ast.NewLit(token.STRING,
247 `"""
248 foo
249 bar
250 """`,
251 ),
252 Value: ast.NewIdent("goo"),
253 },
254 out: `"foo\nbar": goo`,
255 }, {
256 name: "foo",
257 in: func() ast.Node {
258 st := ast.NewStruct("version", ast.NewString("foo"))
259 st = ast.NewStruct("info", st)
260 ast.AddComment(st.Elts[0], internal.NewComment(true, "FOO"))
261 return st
262 }(),
263 out: `{
264 // FOO
265 info: {
266 version: "foo"
267 }
268 }`,
269 }}
270 for _, tc := range testCases {
271 t.Run(tc.name, func(t *testing.T) {
272 b, err := Node(tc.in, Simplify())
273 if err != nil {
274 t.Fatal(err)
275 }
276 if got := string(b); got != tc.out {
277 t.Errorf("\ngot: %v; want: %v", got, tc.out)
278 }
279 })
280 }
281
282 }
283
284
285 func TestBadNodes(t *testing.T) {
286 const src = "package p\n("
287 const res = "package p\n\n(_|_)\n"
288 f, err := parser.ParseFile("", src, parser.ParseComments)
289 if err == nil {
290 t.Error("expected illegal program")
291 }
292 b, _ := Fprint(f)
293 if string(b) != res {
294 t.Errorf("got %q, expected %q", string(b), res)
295 }
296 }
297 func TestPackage(t *testing.T) {
298 f := &ast.File{
299 Decls: []ast.Decl{
300 &ast.Package{Name: ast.NewIdent("foo")},
301 &ast.EmbedDecl{
302 Expr: &ast.BasicLit{
303 Kind: token.INT,
304 ValuePos: token.NoSpace.Pos(),
305 Value: "1",
306 },
307 },
308 },
309 }
310 b, err := Node(f)
311 if err != nil {
312 t.Fatal(err)
313 }
314 const want = "package foo\n\n1\n"
315 if got := string(b); got != want {
316 t.Errorf("got %q, expected %q", got, want)
317 }
318 }
319
320
321 func idents(f *ast.File) <-chan *ast.Ident {
322 v := make(chan *ast.Ident)
323 go func() {
324 ast.Walk(f, func(n ast.Node) bool {
325 if ident, ok := n.(*ast.Ident); ok {
326 v <- ident
327 }
328 return true
329 }, nil)
330 close(v)
331 }()
332 return v
333 }
334
335
336 func identCount(f *ast.File) int {
337 n := 0
338 for range idents(f) {
339 n++
340 }
341 return n
342 }
343
344
345
346
347 func TestSourcePos(t *testing.T) {
348 const src = `package p
349
350 import (
351 "go/printer"
352 "math"
353 "regexp"
354 )
355
356 let pi = 3.14
357 let xx = 0
358 t: {
359 x: int
360 y: int
361 z: int
362 u: number
363 v: number
364 w: number
365 }
366 e: a*t.x + b*t.y
367
368 // two extra lines here // ...
369 e2: c*t.z
370 `
371
372
373 f1, err := parser.ParseFile("src", src, parser.ParseComments)
374 if err != nil {
375 t.Fatal(err)
376 }
377
378
379 b, err := (&config{UseSpaces: true, Tabwidth: 8}).fprint(f1)
380 if err != nil {
381 t.Fatal(err)
382 }
383
384
385
386 f2, err := parser.ParseFile("", b, parser.AllErrors, parser.ParseComments)
387 if err != nil {
388 t.Fatalf("%s\n%s", err, b)
389 }
390
391
392
393
394
395 n1 := identCount(f1)
396 n2 := identCount(f2)
397 if n1 == 0 {
398 t.Fatal("got no idents")
399 }
400 if n2 != n1 {
401 t.Errorf("got %d idents; want %d", n2, n1)
402 }
403
404
405 i2range := idents(f2)
406 for i1 := range idents(f1) {
407 i2 := <-i2range
408
409 if i2 == nil || i1 == nil {
410 t.Fatal("non nil identifiers")
411 }
412 if i2.Name != i1.Name {
413 t.Errorf("got ident %s; want %s", i2.Name, i1.Name)
414 }
415
416 l1 := i1.Pos().Line()
417 l2 := i2.Pos().Line()
418 if l2 != l1 {
419 t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name)
420 }
421 }
422
423 if t.Failed() {
424 t.Logf("\n%s", b)
425 }
426 }
427
428 var decls = []string{
429 "package p\n\n" + `import "fmt"`,
430 "package p\n\n" + "let pi = 3.1415\nlet e = 2.71828\n\nlet x = pi",
431 }
432
433 func TestDeclLists(t *testing.T) {
434 for _, src := range decls {
435 file, err := parser.ParseFile("", src, parser.ParseComments)
436 if err != nil {
437 panic(err)
438 }
439
440 b, err := Fprint(file.Decls)
441 if err != nil {
442 panic(err)
443 }
444
445 out := string(b)
446
447 if out != src {
448 t.Errorf("\ngot : %q\nwant: %q\n", out, src)
449 }
450 }
451 }
452
453 func TestIncorrectIdent(t *testing.T) {
454 testCases := []struct {
455 ident string
456 out string
457 }{
458 {"foo", "foo"},
459 {"a.b.c", `"a.b.c"`},
460 {"for", "for"},
461 }
462 for _, tc := range testCases {
463 t.Run(tc.ident, func(t *testing.T) {
464 b, _ := Node(&ast.Field{Label: ast.NewIdent(tc.ident), Value: ast.NewIdent("A")})
465 if got, want := string(b), tc.out+`: A`; got != want {
466 t.Errorf("got %q; want %q", got, want)
467 }
468 })
469 }
470 }
471
472
473
474 func TestX(t *testing.T) {
475 t.Skip()
476 const src = `
477
478 `
479 b, err := format([]byte(src), simplify)
480 if err != nil {
481 t.Error(err)
482 }
483 _ = b
484 t.Error("\n", string(b))
485 }
486
View as plain text