1// Copyright 2011 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.
4
5// This test applies gofmt to all Go files under -root.
6// To test specific files provide a list of comma-separated
7// filenames via the -files flag: go test -files=gofmt.go .
8
9package main
10
11import (
12	"bytes"
13	"flag"
14	"fmt"
15	"go/ast"
16	"go/printer"
17	"go/token"
18	"io"
19	"os"
20	"path/filepath"
21	"runtime"
22	"strings"
23	"testing"
24)
25
26var (
27	root    = flag.String("root", runtime.GOROOT(), "test root directory")
28	files   = flag.String("files", "", "comma-separated list of files to test")
29	ngo     = flag.Int("n", runtime.NumCPU(), "number of goroutines used")
30	verbose = flag.Bool("verbose", false, "verbose mode")
31	nfiles  int // number of files processed
32)
33
34func gofmt(fset *token.FileSet, filename string, src *bytes.Buffer) error {
35	f, _, _, err := parse(fset, filename, src.Bytes(), false)
36	if err != nil {
37		return err
38	}
39	ast.SortImports(fset, f)
40	src.Reset()
41	return (&printer.Config{Mode: printerMode, Tabwidth: tabWidth}).Fprint(src, fset, f)
42}
43
44func testFile(t *testing.T, b1, b2 *bytes.Buffer, filename string) {
45	// open file
46	f, err := os.Open(filename)
47	if err != nil {
48		t.Error(err)
49		return
50	}
51
52	// read file
53	b1.Reset()
54	_, err = io.Copy(b1, f)
55	f.Close()
56	if err != nil {
57		t.Error(err)
58		return
59	}
60
61	// exclude files w/ syntax errors (typically test cases)
62	fset := token.NewFileSet()
63	if _, _, _, err = parse(fset, filename, b1.Bytes(), false); err != nil {
64		if *verbose {
65			fmt.Fprintf(os.Stderr, "ignoring %s\n", err)
66		}
67		return
68	}
69
70	// gofmt file
71	if err = gofmt(fset, filename, b1); err != nil {
72		t.Errorf("1st gofmt failed: %v", err)
73		return
74	}
75
76	// make a copy of the result
77	b2.Reset()
78	b2.Write(b1.Bytes())
79
80	// gofmt result again
81	if err = gofmt(fset, filename, b2); err != nil {
82		t.Errorf("2nd gofmt failed: %v", err)
83		return
84	}
85
86	// the first and 2nd result should be identical
87	if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
88		// A known instance of gofmt not being idempotent
89		// (see Issue #24472)
90		if strings.HasSuffix(filename, "issue22662.go") {
91			t.Log("known gofmt idempotency bug (Issue #24472)")
92			return
93		}
94		t.Errorf("gofmt %s not idempotent", filename)
95	}
96}
97
98func testFiles(t *testing.T, filenames <-chan string, done chan<- int) {
99	b1 := new(bytes.Buffer)
100	b2 := new(bytes.Buffer)
101	for filename := range filenames {
102		testFile(t, b1, b2, filename)
103	}
104	done <- 0
105}
106
107func genFilenames(t *testing.T, filenames chan<- string) {
108	defer close(filenames)
109
110	handleFile := func(filename string, fi os.FileInfo, err error) error {
111		if err != nil {
112			t.Error(err)
113			return nil
114		}
115		if isGoFile(fi) {
116			filenames <- filename
117			nfiles++
118		}
119		return nil
120	}
121
122	// test Go files provided via -files, if any
123	if *files != "" {
124		for _, filename := range strings.Split(*files, ",") {
125			fi, err := os.Stat(filename)
126			handleFile(filename, fi, err)
127		}
128		return // ignore files under -root
129	}
130
131	// otherwise, test all Go files under *root
132	filepath.Walk(*root, handleFile)
133}
134
135func TestAll(t *testing.T) {
136	if testing.Short() {
137		return
138	}
139
140	if *ngo < 1 {
141		*ngo = 1 // make sure test is run
142	}
143	if *verbose {
144		fmt.Printf("running test using %d goroutines\n", *ngo)
145	}
146
147	// generate filenames
148	filenames := make(chan string, 32)
149	go genFilenames(t, filenames)
150
151	// launch test goroutines
152	done := make(chan int)
153	for i := 0; i < *ngo; i++ {
154		go testFiles(t, filenames, done)
155	}
156
157	// wait for all test goroutines to complete
158	for i := 0; i < *ngo; i++ {
159		<-done
160	}
161
162	if *verbose {
163		fmt.Printf("processed %d files\n", nfiles)
164	}
165}
166