|  | // Copyright 2011 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // This test applies gofmt to all Go files under -root. | 
|  | // To test specific files provide a list of comma-separated | 
|  | // filenames via the -files flag: go test -files=gofmt.go . | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "flag" | 
|  | "fmt" | 
|  | "go/ast" | 
|  | "go/printer" | 
|  | "go/token" | 
|  | "internal/testenv" | 
|  | "io" | 
|  | "io/fs" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "runtime" | 
|  | "strings" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | var ( | 
|  | root    = flag.String("root", runtime.GOROOT(), "test root directory") | 
|  | files   = flag.String("files", "", "comma-separated list of files to test") | 
|  | ngo     = flag.Int("n", runtime.NumCPU(), "number of goroutines used") | 
|  | verbose = flag.Bool("verbose", false, "verbose mode") | 
|  | nfiles  int // number of files processed | 
|  | ) | 
|  |  | 
|  | func gofmt(fset *token.FileSet, filename string, src *bytes.Buffer) error { | 
|  | f, _, _, err := parse(fset, filename, src.Bytes(), false) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | ast.SortImports(fset, f) | 
|  | src.Reset() | 
|  | return (&printer.Config{Mode: printerMode, Tabwidth: tabWidth}).Fprint(src, fset, f) | 
|  | } | 
|  |  | 
|  | func testFile(t *testing.T, b1, b2 *bytes.Buffer, filename string) { | 
|  | // open file | 
|  | f, err := os.Open(filename) | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | return | 
|  | } | 
|  |  | 
|  | // read file | 
|  | b1.Reset() | 
|  | _, err = io.Copy(b1, f) | 
|  | f.Close() | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | return | 
|  | } | 
|  |  | 
|  | // exclude files w/ syntax errors (typically test cases) | 
|  | fset := token.NewFileSet() | 
|  | if _, _, _, err = parse(fset, filename, b1.Bytes(), false); err != nil { | 
|  | if *verbose { | 
|  | fmt.Fprintf(os.Stderr, "ignoring %s\n", err) | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | // gofmt file | 
|  | if err = gofmt(fset, filename, b1); err != nil { | 
|  | t.Errorf("1st gofmt failed: %v", err) | 
|  | return | 
|  | } | 
|  |  | 
|  | // make a copy of the result | 
|  | b2.Reset() | 
|  | b2.Write(b1.Bytes()) | 
|  |  | 
|  | // gofmt result again | 
|  | if err = gofmt(fset, filename, b2); err != nil { | 
|  | t.Errorf("2nd gofmt failed: %v", err) | 
|  | return | 
|  | } | 
|  |  | 
|  | // the first and 2nd result should be identical | 
|  | if !bytes.Equal(b1.Bytes(), b2.Bytes()) { | 
|  | // A known instance of gofmt not being idempotent | 
|  | // (see Issue #24472) | 
|  | if strings.HasSuffix(filename, "issue22662.go") { | 
|  | t.Log("known gofmt idempotency bug (Issue #24472)") | 
|  | return | 
|  | } | 
|  | t.Errorf("gofmt %s not idempotent", filename) | 
|  | } | 
|  | } | 
|  |  | 
|  | func testFiles(t *testing.T, filenames <-chan string, done chan<- int) { | 
|  | b1 := new(bytes.Buffer) | 
|  | b2 := new(bytes.Buffer) | 
|  | for filename := range filenames { | 
|  | testFile(t, b1, b2, filename) | 
|  | } | 
|  | done <- 0 | 
|  | } | 
|  |  | 
|  | func genFilenames(t *testing.T, filenames chan<- string) { | 
|  | defer close(filenames) | 
|  |  | 
|  | handleFile := func(filename string, d fs.DirEntry, err error) error { | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | return nil | 
|  | } | 
|  | // don't descend into testdata directories | 
|  | if isGoFile(d) && !strings.Contains(filepath.ToSlash(filename), "/testdata/") { | 
|  | filenames <- filename | 
|  | nfiles++ | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // test Go files provided via -files, if any | 
|  | if *files != "" { | 
|  | for _, filename := range strings.Split(*files, ",") { | 
|  | fi, err := os.Stat(filename) | 
|  | handleFile(filename, &statDirEntry{fi}, err) | 
|  | } | 
|  | return // ignore files under -root | 
|  | } | 
|  |  | 
|  | // otherwise, test all Go files under *root | 
|  | goroot := *root | 
|  | if goroot == "" { | 
|  | goroot = testenv.GOROOT(t) | 
|  | } | 
|  | filepath.WalkDir(goroot, handleFile) | 
|  | } | 
|  |  | 
|  | func TestAll(t *testing.T) { | 
|  | if testing.Short() { | 
|  | return | 
|  | } | 
|  |  | 
|  | if *ngo < 1 { | 
|  | *ngo = 1 // make sure test is run | 
|  | } | 
|  | if *verbose { | 
|  | fmt.Printf("running test using %d goroutines\n", *ngo) | 
|  | } | 
|  |  | 
|  | // generate filenames | 
|  | filenames := make(chan string, 32) | 
|  | go genFilenames(t, filenames) | 
|  |  | 
|  | // launch test goroutines | 
|  | done := make(chan int) | 
|  | for i := 0; i < *ngo; i++ { | 
|  | go testFiles(t, filenames, done) | 
|  | } | 
|  |  | 
|  | // wait for all test goroutines to complete | 
|  | for i := 0; i < *ngo; i++ { | 
|  | <-done | 
|  | } | 
|  |  | 
|  | if *verbose { | 
|  | fmt.Printf("processed %d files\n", nfiles) | 
|  | } | 
|  | } | 
|  |  | 
|  | type statDirEntry struct { | 
|  | info fs.FileInfo | 
|  | } | 
|  |  | 
|  | func (d *statDirEntry) Name() string               { return d.info.Name() } | 
|  | func (d *statDirEntry) IsDir() bool                { return d.info.IsDir() } | 
|  | func (d *statDirEntry) Type() fs.FileMode          { return d.info.Mode().Type() } | 
|  | func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil } |