github.com/golangci/gofmt@v0.0.0-20231018234816-f50ced29576e/gofmt/long_test.go (about)

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