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