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
39 // format parses src, prints the corresponding AST, verifies the resulting
40 // src is syntactically correct, and returns the resulting src or an error
42 func format(src
[]byte, mode checkMode
) ([]byte, error
) {
44 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
46 return nil, fmt
.Errorf("parse: %s\n%s", err
, src
)
49 // filter exports if necessary
51 ast
.FileExports(f
) // ignore result
52 f
.Comments
= nil // don't print comments that are not in AST
55 // determine printer configuration
56 cfg
:= Config
{Tabwidth
: tabwidth
}
57 if mode
&rawFormat
!= 0 {
63 if err
:= cfg
.Fprint(&buf
, fset
, f
); err
!= nil {
64 return nil, fmt
.Errorf("print: %s", err
)
67 // make sure formatted output is syntactically correct
69 if _
, err
:= parser
.ParseFile(fset
, "", res
, 0); err
!= nil {
70 return nil, fmt
.Errorf("re-parse: %s\n%s", err
, buf
.Bytes())
76 // lineAt returns the line in text starting at offset offs.
77 func lineAt(text
[]byte, offs
int) []byte {
79 for i
< len(text
) && text
[i
] != '\n' {
85 // diff compares a and b.
86 func diff(aname
, bname
string, a
, b
[]byte) error
{
87 var buf bytes
.Buffer
// holding long error message
91 fmt
.Fprintf(&buf
, "\nlength changed: len(%s) = %d, len(%s) = %d", aname
, len(a
), bname
, len(b
))
97 for i
:= 0; i
< len(a
) && i
< len(b
); i
++ {
100 fmt
.Fprintf(&buf
, "\n%s:%d:%d: %s", aname
, line
, i
-offs
+1, lineAt(a
, offs
))
101 fmt
.Fprintf(&buf
, "\n%s:%d:%d: %s", bname
, line
, i
-offs
+1, lineAt(b
, offs
))
102 fmt
.Fprintf(&buf
, "\n\n")
112 return errors
.New(buf
.String())
117 func runcheck(t
*testing
.T
, source
, golden
string, mode checkMode
) {
118 src
, err
:= ioutil
.ReadFile(source
)
124 res
, err
:= format(src
, mode
)
130 // update golden files if necessary
132 if err
:= ioutil
.WriteFile(golden
, res
, 0644); err
!= nil {
139 gld
, err
:= ioutil
.ReadFile(golden
)
145 // formatted source and golden must be the same
146 if err
:= diff(source
, golden
, res
, gld
); err
!= nil {
151 if mode
&idempotent
!= 0 {
152 // formatting golden must be idempotent
153 // (This is very difficult to achieve in general and for now
154 // it is only checked for files explicitly marked as such.)
155 res
, err
= format(gld
, mode
)
156 if err
:= diff(golden
, fmt
.Sprintf("format(%s)", golden
), gld
, res
); err
!= nil {
157 t
.Errorf("golden is not idempotent: %s", err
)
162 func check(t
*testing
.T
, source
, golden
string, mode checkMode
) {
166 runcheck(t
, source
, golden
, mode
)
172 case <-time
.After(10 * time
.Second
): // plenty of a safety margin, even for very slow machines
173 // test running past time out
174 t
.Errorf("%s: running too slowly", source
)
176 // test finished within allotted time margin
181 source
, golden
string
185 // Use go test -update to create/update the respective golden files.
187 {"empty.input", "empty.golden", idempotent
},
188 {"comments.input", "comments.golden", 0},
189 {"comments.input", "comments.x", export
},
190 {"comments2.input", "comments2.golden", idempotent
},
191 {"linebreaks.input", "linebreaks.golden", idempotent
},
192 {"expressions.input", "expressions.golden", idempotent
},
193 {"expressions.input", "expressions.raw", rawFormat | idempotent
},
194 {"declarations.input", "declarations.golden", 0},
195 {"statements.input", "statements.golden", 0},
196 {"slow.input", "slow.golden", idempotent
},
199 func TestFiles(t
*testing
.T
) {
201 for _
, e
:= range data
{
202 source
:= filepath
.Join(dataDir
, e
.source
)
203 golden
:= filepath
.Join(dataDir
, e
.golden
)
205 t
.Run(e
.source
, func(t
*testing
.T
) {
207 check(t
, source
, golden
, mode
)
208 // TODO(gri) check that golden is idempotent
209 //check(t, golden, golden, e.mode)
214 // TestLineComments, using a simple test case, checks that consecutive line
215 // comments are properly terminated with a newline even if the AST position
216 // information is incorrect.
218 func TestLineComments(t
*testing
.T
) {
219 const src
= `// comment 1
225 fset
:= token
.NewFileSet()
226 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
228 panic(err
) // error in test
232 fset
= token
.NewFileSet() // use the wrong file set
233 Fprint(&buf
, fset
, f
)
236 for _
, ch
:= range buf
.Bytes() {
243 if nlines
< expected
{
244 t
.Errorf("got %d, expected %d\n", nlines
, expected
)
245 t
.Errorf("result:\n%s", buf
.Bytes())
249 // Verify that the printer can be invoked during initialization.
251 const name
= "foobar"
253 if err
:= Fprint(&buf
, fset
, &ast
.Ident
{Name
: name
}); err
!= nil {
254 panic(err
) // error in test
256 // in debug mode, the result contains additional information;
258 if s
:= buf
.String(); !debug
&& s
!= name
{
259 panic("got " + s
+ ", want " + name
)
263 // Verify that the printer doesn't crash if the AST contains BadXXX nodes.
264 func TestBadNodes(t
*testing
.T
) {
265 const src
= "package p\n("
266 const res
= "package p\nBadDecl\n"
267 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
269 t
.Error("expected illegal program") // error in test
272 Fprint(&buf
, fset
, f
)
273 if buf
.String() != res
{
274 t
.Errorf("got %q, expected %q", buf
.String(), res
)
278 // testComment verifies that f can be parsed again after printing it
279 // with its first comment set to comment at any possible source offset.
280 func testComment(t
*testing
.T
, f
*ast
.File
, srclen
int, comment
*ast
.Comment
) {
281 f
.Comments
[0].List
[0] = comment
283 for offs
:= 0; offs
<= srclen
; offs
++ {
285 // Printing f should result in a correct program no
286 // matter what the (incorrect) comment position is.
287 if err
:= Fprint(&buf
, fset
, f
); err
!= nil {
290 if _
, err
:= parser
.ParseFile(fset
, "", buf
.Bytes(), 0); err
!= nil {
291 t
.Fatalf("incorrect program for pos = %d:\n%s", comment
.Slash
, buf
.String())
293 // Position information is just an offset.
294 // Move comment one byte down in the source.
299 // Verify that the printer produces a correct program
300 // even if the position information of comments introducing newlines
302 func TestBadComments(t
*testing
.T
) {
305 // first comment - text and position changed by test
308 const pi = 3.14 // rough circle
310 x, y, z int = 1, 2, 3
315 return n /* seed values */
317 return fibo(n-1) + fibo(n-2)
321 f
, err
:= parser
.ParseFile(fset
, "", src
, parser
.ParseComments
)
323 t
.Error(err
) // error in test
326 comment
:= f
.Comments
[0].List
[0]
328 if fset
.Position(pos
).Offset
!= 1 {
329 t
.Error("expected offset 1") // error in test
332 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "//-style comment"})
333 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "/*-style comment */"})
334 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "/*-style \n comment */"})
335 testComment(t
, f
, len(src
), &ast
.Comment
{Slash
: pos
, Text
: "/*-style comment \n\n\n */"})
338 type visitor
chan *ast
.Ident
340 func (v visitor
) Visit(n ast
.Node
) (w ast
.Visitor
) {
341 if ident
, ok
:= n
.(*ast
.Ident
); ok
{
347 // idents is an iterator that returns all idents in f via the result channel.
348 func idents(f
*ast
.File
) <-chan *ast
.Ident
{
357 // identCount returns the number of identifiers found in f.
358 func identCount(f
*ast
.File
) int {
360 for range idents(f
) {
366 // Verify that the SourcePos mode emits correct //line directives
367 // by testing that position information for matching identifiers
369 func TestSourcePos(t
*testing
.T
) {
372 import ( "go/printer"; "math" )
373 const pi = 3.14; var x = 0
374 type t struct{ x, y, z int; u, v, w float32 }
375 func (t *t) foo(a, b, c int) int {
376 return a*t.x + b*t.y +
377 // two extra lines here
384 f1
, err
:= parser
.ParseFile(fset
, "src", src
, parser
.ParseComments
)
389 // pretty-print original
391 err
= (&Config
{Mode
: UseSpaces | SourcePos
, Tabwidth
: 8}).Fprint(&buf
, fset
, f1
)
396 // parse pretty printed original
397 // (//line directives must be interpreted even w/o parser.ParseComments set)
398 f2
, err
:= parser
.ParseFile(fset
, "", buf
.Bytes(), 0)
400 t
.Fatalf("%s\n%s", err
, buf
.Bytes())
403 // At this point the position information of identifiers in f2 should
404 // match the position information of corresponding identifiers in f1.
406 // number of identifiers must be > 0 (test should run) and must match
410 t
.Fatal("got no idents")
413 t
.Errorf("got %d idents; want %d", n2
, n1
)
416 // verify that all identifiers have correct line information
417 i2range
:= idents(f2
)
418 for i1
:= range idents(f1
) {
421 if i2
.Name
!= i1
.Name
{
422 t
.Errorf("got ident %s; want %s", i2
.Name
, i1
.Name
)
425 l1
:= fset
.Position(i1
.Pos()).Line
426 l2
:= fset
.Position(i2
.Pos()).Line
428 t
.Errorf("got line %d; want %d for %s", l2
, l1
, i1
.Name
)
433 t
.Logf("\n%s", buf
.Bytes())
437 // Verify that the SourcePos mode doesn't emit unnecessary //line directives
438 // before empty lines.
439 func TestIssue5945(t
*testing
.T
) {
442 func f() {} // line 3
451 const want
= `//line src.go:2
465 f1
, err
:= parser
.ParseFile(fset
, "src.go", orig
, 0)
470 // pretty-print original
472 err
= (&Config
{Mode
: UseSpaces | SourcePos
, Tabwidth
: 8}).Fprint(&buf
, fset
, f1
)
478 // compare original with desired output
480 t
.Errorf("got:\n%s\nwant:\n%s\n", got
, want
)
484 var decls
= []string{
486 "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi",
487 "func sum(x, y int) int\t{ return x + y }",
490 func TestDeclLists(t
*testing
.T
) {
491 for _
, src
:= range decls
{
492 file
, err
:= parser
.ParseFile(fset
, "", "package p;"+src
, parser
.ParseComments
)
494 panic(err
) // error in test
498 err
= Fprint(&buf
, fset
, file
.Decls
) // only print declarations
500 panic(err
) // error in test
505 t
.Errorf("\ngot : %q\nwant: %q\n", out
, src
)
510 var stmts
= []string{
512 "select {}\nvar a, b = 1, 2\nreturn a + b",
513 "go f()\ndefer func() {}()",
516 func TestStmtLists(t
*testing
.T
) {
517 for _
, src
:= range stmts
{
518 file
, err
:= parser
.ParseFile(fset
, "", "package p; func _() {"+src
+"}", parser
.ParseComments
)
520 panic(err
) // error in test
524 err
= Fprint(&buf
, fset
, file
.Decls
[0].(*ast
.FuncDecl
).Body
.List
) // only print statements
526 panic(err
) // error in test
531 t
.Errorf("\ngot : %q\nwant: %q\n", out
, src
)
536 func TestBaseIndent(t
*testing
.T
) {
538 // The testfile must not contain multi-line raw strings since those
539 // are not indented (because their values must not change) and make
541 const filename
= "printer.go"
542 src
, err
:= ioutil
.ReadFile(filename
)
544 panic(err
) // error in test
547 file
, err
:= parser
.ParseFile(fset
, filename
, src
, 0)
549 panic(err
) // error in test
552 for indent
:= 0; indent
< 4; indent
++ {
554 t
.Run(fmt
.Sprint(indent
), func(t
*testing
.T
) {
557 (&Config
{Tabwidth
: tabwidth
, Indent
: indent
}).Fprint(&buf
, fset
, file
)
558 // all code must be indented by at least 'indent' tabs
559 lines
:= bytes
.Split(buf
.Bytes(), []byte{'\n'})
560 for i
, line
:= range lines
{
562 continue // empty lines don't have indentation
565 for j
, b
:= range line
{
567 // end of indentation
573 t
.Errorf("line %d: got only %d tabs; want at least %d: %q", i
, n
, indent
, line
)
580 // TestFuncType tests that an ast.FuncType with a nil Params field
581 // can be printed (per go/ast specification). Test case for issue 3870.
582 func TestFuncType(t
*testing
.T
) {
584 Name
: &ast
.Ident
{Name
: "p"},
587 Name
: &ast
.Ident
{Name
: "f"},
588 Type
: &ast
.FuncType
{},
594 if err
:= Fprint(&buf
, fset
, src
); err
!= nil {
599 const want
= `package p
605 t
.Fatalf("got:\n%s\nwant:\n%s\n", got
, want
)
609 type limitWriter
struct {
614 func (l
*limitWriter
) Write(buf
[]byte) (n
int, err error
) {
616 if n
>= l
.remaining
{
625 // Test whether the printer stops writing after the first error
626 func TestWriteErrors(t
*testing
.T
) {
628 const filename
= "printer.go"
629 src
, err
:= ioutil
.ReadFile(filename
)
631 panic(err
) // error in test
633 file
, err
:= parser
.ParseFile(fset
, filename
, src
, 0)
635 panic(err
) // error in test
637 for i
:= 0; i
< 20; i
++ {
638 lw
:= &limitWriter
{remaining
: i
}
639 err
:= (&Config
{Mode
: RawFormat
}).Fprint(lw
, fset
, file
)
641 t
.Fatal("Writes continued after first error returned")
643 // We expect errCount be 1 iff err is set
644 if (lw
.errCount
!= 0) != (err
!= nil) {
645 t
.Fatal("Expected err when errCount != 0")
650 // TextX is a skeleton test that can be filled in for debugging one-off cases.
652 func TestX(t
*testing
.T
) {
657 _
, err
:= format([]byte(src
), 0)
663 func TestCommentedNode(t
*testing
.T
) {
665 input
= `package main
668 // comment inside func
672 type bar int // comment2
677 // comment inside func
680 bar
= `// leading comment
681 type bar int // comment2
685 fset
:= token
.NewFileSet()
686 f
, err
:= parser
.ParseFile(fset
, "input.go", input
, parser
.ParseComments
)
693 err
= Fprint(&buf
, fset
, &CommentedNode
{Node
: f
.Decls
[0], Comments
: f
.Comments
})
698 if buf
.String() != foo
{
699 t
.Errorf("got %q, want %q", buf
.String(), foo
)
704 err
= Fprint(&buf
, fset
, &CommentedNode
{Node
: f
.Decls
[1], Comments
: f
.Comments
})
709 if buf
.String() != bar
{
710 t
.Errorf("got %q, want %q", buf
.String(), bar
)