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