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