1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
27 var update
= flag
.Bool("update", false, "update golden files")
29 var fset
= token
.NewFileSet()
34 export checkMode
= 1 << iota
41 // format parses src, prints the corresponding AST, verifies the resulting
42 // src is syntactically correct, and returns the resulting src or an error
44 func format(src
[]byte, mode checkMode
) ([]byte, error
) {
46 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
48 return nil, fmt
.Errorf("parse: %s\n%s", err
, src
)
51 // filter exports if necessary
53 ast
.FileExports(f
) // ignore result
54 f
.Comments
= nil // don't print comments that are not in AST
57 // determine printer configuration
58 cfg
:= Config
{Tabwidth
: tabwidth
}
59 if mode
&rawFormat
!= 0 {
62 if mode
&normNumber
!= 0 {
63 cfg
.Mode |
= normalizeNumbers
68 if err
:= cfg
.Fprint(&buf
, fset
, f
); err
!= nil {
69 return nil, fmt
.Errorf("print: %s", err
)
72 // make sure formatted output is syntactically correct
74 if _
, err
:= parser
.ParseFile(fset
, "", res
, parser
.ParseComments
); err
!= nil {
75 return nil, fmt
.Errorf("re-parse: %s\n%s", err
, buf
.Bytes())
81 // lineAt returns the line in text starting at offset offs.
82 func lineAt(text
[]byte, offs
int) []byte {
84 for i
< len(text
) && text
[i
] != '\n' {
90 // diff compares a and b.
91 func diff(aname
, bname
string, a
, b
[]byte) error
{
92 if bytes
.Equal(a
, b
) {
96 var buf bytes
.Buffer
// holding long error message
99 fmt
.Fprintf(&buf
, "\nlength changed: len(%s) = %d, len(%s) = %d", aname
, len(a
), bname
, len(b
))
105 for i
:= 0; i
< len(a
) && i
< len(b
); i
++ {
108 fmt
.Fprintf(&buf
, "\n%s:%d:%d: %s", aname
, line
, i
-offs
+1, lineAt(a
, offs
))
109 fmt
.Fprintf(&buf
, "\n%s:%d:%d: %s", bname
, line
, i
-offs
+1, lineAt(b
, offs
))
110 fmt
.Fprintf(&buf
, "\n\n")
119 fmt
.Fprintf(&buf
, "\n%s:\n%s\n%s:\n%s", aname
, a
, bname
, b
)
120 return errors
.New(buf
.String())
123 func runcheck(t
*testing
.T
, source
, golden
string, mode checkMode
) {
124 src
, err
:= os
.ReadFile(source
)
130 res
, err
:= format(src
, mode
)
136 // update golden files if necessary
138 if err
:= os
.WriteFile(golden
, res
, 0644); err
!= nil {
145 gld
, err
:= os
.ReadFile(golden
)
151 // formatted source and golden must be the same
152 if err
:= diff(source
, golden
, res
, gld
); err
!= nil {
157 if mode
&idempotent
!= 0 {
158 // formatting golden must be idempotent
159 // (This is very difficult to achieve in general and for now
160 // it is only checked for files explicitly marked as such.)
161 res
, err
= format(gld
, mode
)
166 if err
:= diff(golden
, fmt
.Sprintf("format(%s)", golden
), gld
, res
); err
!= nil {
167 t
.Errorf("golden is not idempotent: %s", err
)
172 func check(t
*testing
.T
, source
, golden
string, mode checkMode
) {
174 cc
:= make(chan int, 1)
176 runcheck(t
, source
, golden
, mode
)
182 case <-time
.After(10 * time
.Second
): // plenty of a safety margin, even for very slow machines
183 // test running past time out
184 t
.Errorf("%s: running too slowly", source
)
186 // test finished within allotted time margin
191 source
, golden
string
195 // Use go test -update to create/update the respective golden files.
197 {"empty.input", "empty.golden", idempotent
},
198 {"comments.input", "comments.golden", 0},
199 {"comments.input", "comments.x", export
},
200 {"comments2.input", "comments2.golden", idempotent
},
201 {"alignment.input", "alignment.golden", idempotent
},
202 {"linebreaks.input", "linebreaks.golden", idempotent
},
203 {"expressions.input", "expressions.golden", idempotent
},
204 {"expressions.input", "expressions.raw", rawFormat | idempotent
},
205 {"declarations.input", "declarations.golden", 0},
206 {"statements.input", "statements.golden", 0},
207 {"slow.input", "slow.golden", idempotent
},
208 {"complit.input", "complit.x", export
},
209 {"go2numbers.input", "go2numbers.golden", idempotent
},
210 {"go2numbers.input", "go2numbers.norm", normNumber | idempotent
},
211 {"generics.input", "generics.golden", idempotent | allowTypeParams
},
212 {"gobuild1.input", "gobuild1.golden", idempotent
},
213 {"gobuild2.input", "gobuild2.golden", idempotent
},
214 {"gobuild3.input", "gobuild3.golden", idempotent
},
215 {"gobuild4.input", "gobuild4.golden", idempotent
},
216 {"gobuild5.input", "gobuild5.golden", idempotent
},
217 {"gobuild6.input", "gobuild6.golden", idempotent
},
218 {"gobuild7.input", "gobuild7.golden", idempotent
},
221 func TestFiles(t
*testing
.T
) {
223 for _
, e
:= range data
{
224 source
:= filepath
.Join(dataDir
, e
.source
)
225 golden
:= filepath
.Join(dataDir
, e
.golden
)
227 t
.Run(e
.source
, func(t
*testing
.T
) {
229 check(t
, source
, golden
, mode
)
230 // TODO(gri) check that golden is idempotent
231 //check(t, golden, golden, e.mode)
236 // TestLineComments, using a simple test case, checks that consecutive line
237 // comments are properly terminated with a newline even if the AST position
238 // information is incorrect.
240 func TestLineComments(t
*testing
.T
) {
241 const src
= `// comment 1
247 fset
:= token
.NewFileSet()
248 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
250 panic(err
) // error in test
254 fset
= token
.NewFileSet() // use the wrong file set
255 Fprint(&buf
, fset
, f
)
258 for _
, ch
:= range buf
.Bytes() {
265 if nlines
< expected
{
266 t
.Errorf("got %d, expected %d\n", nlines
, expected
)
267 t
.Errorf("result:\n%s", buf
.Bytes())
271 // Verify that the printer can be invoked during initialization.
273 const name
= "foobar"
275 if err
:= Fprint(&buf
, fset
, &ast
.Ident
{Name
: name
}); err
!= nil {
276 panic(err
) // error in test
278 // in debug mode, the result contains additional information;
280 if s
:= buf
.String(); !debug
&& s
!= name
{
281 panic("got " + s
+ ", want " + name
)
285 // Verify that the printer doesn't crash if the AST contains BadXXX nodes.
286 func TestBadNodes(t
*testing
.T
) {
287 const src
= "package p\n("
288 const res
= "package p\nBadDecl\n"
289 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
291 t
.Error("expected illegal program") // error in test
294 Fprint(&buf
, fset
, f
)
295 if buf
.String() != res
{
296 t
.Errorf("got %q, expected %q", buf
.String(), res
)
300 // testComment verifies that f can be parsed again after printing it
301 // with its first comment set to comment at any possible source offset.
302 func testComment(t
*testing
.T
, f
*ast
.File
, srclen
int, comment
*ast
.Comment
) {
303 f
.Comments
[0].List
[0] = comment
305 for offs
:= 0; offs
<= srclen
; offs
++ {
307 // Printing f should result in a correct program no
308 // matter what the (incorrect) comment position is.
309 if err
:= Fprint(&buf
, fset
, f
); err
!= nil {
312 if _
, err
:= parser
.ParseFile(fset
, "", buf
.Bytes(), 0); err
!= nil {
313 t
.Fatalf("incorrect program for pos = %d:\n%s", comment
.Slash
, buf
.String())
315 // Position information is just an offset.
316 // Move comment one byte down in the source.
321 // Verify that the printer produces a correct program
322 // even if the position information of comments introducing newlines
324 func TestBadComments(t
*testing
.T
) {
327 // first comment - text and position changed by test
330 const pi = 3.14 // rough circle
332 x, y, z int = 1, 2, 3
337 return n /* seed values */
339 return fibo(n-1) + fibo(n-2)
343 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
345 t
.Error(err
) // error in test
348 comment
:= f
.Comments
[0].List
[0]
350 if fset
.PositionFor(pos
, false /* absolute position */).Offset
!= 1 {
351 t
.Error("expected offset 1") // error in test
354 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "//-style comment"})
355 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "/*-style comment */"})
356 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "/*-style \n comment */"})
357 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "/*-style comment \n\n\n */"})
360 type visitor
chan *ast
.Ident
362 func (v visitor
) Visit(n ast
.Node
) (w ast
.Visitor
) {
363 if ident
, ok
:= n
.(*ast
.Ident
); ok
{
369 // idents is an iterator that returns all idents in f via the result channel.
370 func idents(f
*ast
.File
) <-chan *ast
.Ident
{
379 // identCount returns the number of identifiers found in f.
380 func identCount(f
*ast
.File
) int {
382 for range idents(f
) {
388 // Verify that the SourcePos mode emits correct //line directives
389 // by testing that position information for matching identifiers
391 func TestSourcePos(t
*testing
.T
) {
394 import ( "go/printer"; "math" )
395 const pi = 3.14; var x = 0
396 type t struct{ x, y, z int; u, v, w float32 }
397 func (t *t) foo(a, b, c int) int {
398 return a*t.x + b*t.y +
399 // two extra lines here
406 f1
, err
:= parser
.ParseFile(fset
, "src", src
, parser
.ParseComments
)
411 // pretty-print original
413 err
= (&Config
{Mode
: UseSpaces | SourcePos
, Tabwidth
: 8}).Fprint(&buf
, fset
, f1
)
418 // parse pretty printed original
419 // (//line directives must be interpreted even w/o parser.ParseComments set)
420 f2
, err
:= parser
.ParseFile(fset
, "", buf
.Bytes(), 0)
422 t
.Fatalf("%s\n%s", err
, buf
.Bytes())
425 // At this point the position information of identifiers in f2 should
426 // match the position information of corresponding identifiers in f1.
428 // number of identifiers must be > 0 (test should run) and must match
432 t
.Fatal("got no idents")
435 t
.Errorf("got %d idents; want %d", n2
, n1
)
438 // verify that all identifiers have correct line information
439 i2range
:= idents(f2
)
440 for i1
:= range idents(f1
) {
443 if i2
.Name
!= i1
.Name
{
444 t
.Errorf("got ident %s; want %s", i2
.Name
, i1
.Name
)
447 // here we care about the relative (line-directive adjusted) positions
448 l1
:= fset
.Position(i1
.Pos()).Line
449 l2
:= fset
.Position(i2
.Pos()).Line
451 t
.Errorf("got line %d; want %d for %s", l2
, l1
, i1
.Name
)
456 t
.Logf("\n%s", buf
.Bytes())
460 // Verify that the SourcePos mode doesn't emit unnecessary //line directives
461 // before empty lines.
462 func TestIssue5945(t
*testing
.T
) {
465 func f() {} // line 3
474 const want
= `//line src.go:2
488 f1
, err
:= parser
.ParseFile(fset
, "src.go", orig
, 0)
493 // pretty-print original
495 err
= (&Config
{Mode
: UseSpaces | SourcePos
, Tabwidth
: 8}).Fprint(&buf
, fset
, f1
)
501 // compare original with desired output
503 t
.Errorf("got:\n%s\nwant:\n%s\n", got
, want
)
507 var decls
= []string{
509 "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi",
510 "func sum(x, y int) int\t{ return x + y }",
513 func TestDeclLists(t
*testing
.T
) {
514 for _
, src
:= range decls
{
515 file
, err
:= parser
.ParseFile(fset
, "", "package p;"+src
, parser
.ParseComments
)
517 panic(err
) // error in test
521 err
= Fprint(&buf
, fset
, file
.Decls
) // only print declarations
523 panic(err
) // error in test
528 t
.Errorf("\ngot : %q\nwant: %q\n", out
, src
)
533 var stmts
= []string{
535 "select {}\nvar a, b = 1, 2\nreturn a + b",
536 "go f()\ndefer func() {}()",
539 func TestStmtLists(t
*testing
.T
) {
540 for _
, src
:= range stmts
{
541 file
, err
:= parser
.ParseFile(fset
, "", "package p; func _() {"+src
+"}", parser
.ParseComments
)
543 panic(err
) // error in test
547 err
= Fprint(&buf
, fset
, file
.Decls
[0].(*ast
.FuncDecl
).Body
.List
) // only print statements
549 panic(err
) // error in test
554 t
.Errorf("\ngot : %q\nwant: %q\n", out
, src
)
559 func TestBaseIndent(t
*testing
.T
) {
561 // The testfile must not contain multi-line raw strings since those
562 // are not indented (because their values must not change) and make
564 const filename
= "printer.go"
565 src
, err
:= os
.ReadFile(filename
)
567 panic(err
) // error in test
570 file
, err
:= parser
.ParseFile(fset
, filename
, src
, 0)
572 panic(err
) // error in test
575 for indent
:= 0; indent
< 4; indent
++ {
577 t
.Run(fmt
.Sprint(indent
), func(t
*testing
.T
) {
580 (&Config
{Tabwidth
: tabwidth
, Indent
: indent
}).Fprint(&buf
, fset
, file
)
581 // all code must be indented by at least 'indent' tabs
582 lines
:= bytes
.Split(buf
.Bytes(), []byte{'\n'})
583 for i
, line
:= range lines
{
585 continue // empty lines don't have indentation
588 for j
, b
:= range line
{
590 // end of indentation
596 t
.Errorf("line %d: got only %d tabs; want at least %d: %q", i
, n
, indent
, line
)
603 // TestFuncType tests that an ast.FuncType with a nil Params field
604 // can be printed (per go/ast specification). Test case for issue 3870.
605 func TestFuncType(t
*testing
.T
) {
607 Name
: &ast
.Ident
{Name
: "p"},
610 Name
: &ast
.Ident
{Name
: "f"},
611 Type
: &ast
.FuncType
{},
617 if err
:= Fprint(&buf
, fset
, src
); err
!= nil {
622 const want
= `package p
628 t
.Fatalf("got:\n%s\nwant:\n%s\n", got
, want
)
632 type limitWriter
struct {
637 func (l
*limitWriter
) Write(buf
[]byte) (n
int, err error
) {
639 if n
>= l
.remaining
{
648 // Test whether the printer stops writing after the first error
649 func TestWriteErrors(t
*testing
.T
) {
651 const filename
= "printer.go"
652 src
, err
:= os
.ReadFile(filename
)
654 panic(err
) // error in test
656 file
, err
:= parser
.ParseFile(fset
, filename
, src
, 0)
658 panic(err
) // error in test
660 for i
:= 0; i
< 20; i
++ {
661 lw
:= &limitWriter
{remaining
: i
}
662 err
:= (&Config
{Mode
: RawFormat
}).Fprint(lw
, fset
, file
)
664 t
.Fatal("Writes continued after first error returned")
666 // We expect errCount be 1 iff err is set
667 if (lw
.errCount
!= 0) != (err
!= nil) {
668 t
.Fatal("Expected err when errCount != 0")
673 // TextX is a skeleton test that can be filled in for debugging one-off cases.
675 func TestX(t
*testing
.T
) {
680 _
, err
:= format([]byte(src
), 0)
686 func TestCommentedNode(t
*testing
.T
) {
688 input
= `package main
691 // comment inside func
695 type bar int // comment2
700 // comment inside func
703 bar
= `// leading comment
704 type bar int // comment2
708 fset
:= token
.NewFileSet()
709 f
, err
:= parser
.ParseFile(fset
, "input.go", input
, parser
.ParseComments
)
716 err
= Fprint(&buf
, fset
, &CommentedNode
{Node
: f
.Decls
[0], Comments
: f
.Comments
})
721 if buf
.String() != foo
{
722 t
.Errorf("got %q, want %q", buf
.String(), foo
)
727 err
= Fprint(&buf
, fset
, &CommentedNode
{Node
: f
.Decls
[1], Comments
: f
.Comments
})
732 if buf
.String() != bar
{
733 t
.Errorf("got %q, want %q", buf
.String(), bar
)
737 func TestIssue11151(t
*testing
.T
) {
738 const src
= "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n"
739 fset
:= token
.NewFileSet()
740 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
746 Fprint(&buf
, fset
, f
)
748 const want
= "package p\t/*/1*\r/2*\r/3*+/4*/\n" // \r following opening /* should be stripped
750 t
.Errorf("\ngot : %q\nwant: %q", got
, want
)
753 // the resulting program must be valid
754 _
, err
= parser
.ParseFile(fset
, "", got
, 0)
756 t
.Errorf("%v\norig: %q\ngot : %q", err
, src
, got
)
760 // If a declaration has multiple specifications, a parenthesized
761 // declaration must be printed even if Lparen is token.NoPos.
762 func TestParenthesizedDecl(t
*testing
.T
) {
763 // a package with multiple specs in a single declaration
764 const src
= "package p; var ( a float64; b int )"
765 fset
:= token
.NewFileSet()
766 f
, err
:= parser
.ParseFile(fset
, "", src
, 0)
771 // print the original package
773 err
= Fprint(&buf
, fset
, f
)
777 original
:= buf
.String()
779 // now remove parentheses from the declaration
780 for i
:= 0; i
!= len(f
.Decls
); i
++ {
781 f
.Decls
[i
].(*ast
.GenDecl
).Lparen
= token
.NoPos
784 err
= Fprint(&buf
, fset
, f
)
788 noparen
:= buf
.String()
790 if noparen
!= original
{
791 t
.Errorf("got %q, want %q", noparen
, original
)
795 // Verify that we don't print a newline between "return" and its results, as
796 // that would incorrectly cause a naked return.
797 func TestIssue32854(t
*testing
.T
) {
805 fset
:= token
.NewFileSet()
806 file
, err
:= parser
.ParseFile(fset
, "", src
, 0)
811 // Replace the result with call(), which is on the next line.
812 fd
:= file
.Decls
[0].(*ast
.FuncDecl
)
813 ret
:= fd
.Body
.List
[0].(*ast
.ReturnStmt
)
814 ret
.Results
[0] = ret
.Results
[0].(*ast
.CompositeLit
).Elts
[0]
817 if err
:= Fprint(&buf
, fset
, ret
); err
!= nil {
820 want
:= "return call()"
821 if got
:= buf
.String(); got
!= want
{
822 t
.Fatalf("got %q, want %q", got
, want
)