Source file src/cmd/gofmt/gofmt_test.go

     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  package main
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  	"text/scanner"
    17  )
    18  
    19  var update = flag.Bool("update", false, "update .golden files")
    20  
    21  // gofmtFlags looks for a comment of the form
    22  //
    23  //	//gofmt flags
    24  //
    25  // within the first maxLines lines of the given file,
    26  // and returns the flags string, if any. Otherwise it
    27  // returns the empty string.
    28  func gofmtFlags(filename string, maxLines int) string {
    29  	f, err := os.Open(filename)
    30  	if err != nil {
    31  		return "" // ignore errors - they will be found later
    32  	}
    33  	defer f.Close()
    34  
    35  	// initialize scanner
    36  	var s scanner.Scanner
    37  	s.Init(f)
    38  	s.Error = func(*scanner.Scanner, string) {}       // ignore errors
    39  	s.Mode = scanner.GoTokens &^ scanner.SkipComments // want comments
    40  
    41  	// look for //gofmt comment
    42  	for s.Line <= maxLines {
    43  		switch s.Scan() {
    44  		case scanner.Comment:
    45  			const prefix = "//gofmt "
    46  			if t := s.TokenText(); strings.HasPrefix(t, prefix) {
    47  				return strings.TrimSpace(t[len(prefix):])
    48  			}
    49  		case scanner.EOF:
    50  			return ""
    51  		}
    52  	}
    53  
    54  	return ""
    55  }
    56  
    57  func runTest(t *testing.T, in, out string) {
    58  	// process flags
    59  	*simplifyAST = false
    60  	*rewriteRule = ""
    61  	info, err := os.Lstat(in)
    62  	if err != nil {
    63  		t.Error(err)
    64  		return
    65  	}
    66  	for _, flag := range strings.Split(gofmtFlags(in, 20), " ") {
    67  		elts := strings.SplitN(flag, "=", 2)
    68  		name := elts[0]
    69  		value := ""
    70  		if len(elts) == 2 {
    71  			value = elts[1]
    72  		}
    73  		switch name {
    74  		case "":
    75  			// no flags
    76  		case "-r":
    77  			*rewriteRule = value
    78  		case "-s":
    79  			*simplifyAST = true
    80  		case "-stdin":
    81  			// fake flag - pretend input is from stdin
    82  			info = nil
    83  		default:
    84  			t.Errorf("unrecognized flag name: %s", name)
    85  		}
    86  	}
    87  
    88  	initParserMode()
    89  	initRewrite()
    90  
    91  	const maxWeight = 2 << 20
    92  	var buf, errBuf bytes.Buffer
    93  	s := newSequencer(maxWeight, &buf, &errBuf)
    94  	s.Add(fileWeight(in, info), func(r *reporter) error {
    95  		return processFile(in, info, nil, r)
    96  	})
    97  	if errBuf.Len() > 0 {
    98  		t.Logf("%q", errBuf.Bytes())
    99  	}
   100  	if s.GetExitCode() != 0 {
   101  		t.Fail()
   102  	}
   103  
   104  	expected, err := os.ReadFile(out)
   105  	if err != nil {
   106  		t.Error(err)
   107  		return
   108  	}
   109  
   110  	if got := buf.Bytes(); !bytes.Equal(got, expected) {
   111  		if *update {
   112  			if in != out {
   113  				if err := os.WriteFile(out, got, 0666); err != nil {
   114  					t.Error(err)
   115  				}
   116  				return
   117  			}
   118  			// in == out: don't accidentally destroy input
   119  			t.Errorf("WARNING: -update did not rewrite input file %s", in)
   120  		}
   121  
   122  		t.Errorf("(gofmt %s) != %s (see %s.gofmt)", in, out, in)
   123  		d, err := diffWithReplaceTempFile(expected, got, in)
   124  		if err == nil {
   125  			t.Errorf("%s", d)
   126  		}
   127  		if err := os.WriteFile(in+".gofmt", got, 0666); err != nil {
   128  			t.Error(err)
   129  		}
   130  	}
   131  }
   132  
   133  // TestRewrite processes testdata/*.input files and compares them to the
   134  // corresponding testdata/*.golden files. The gofmt flags used to process
   135  // a file must be provided via a comment of the form
   136  //
   137  //	//gofmt flags
   138  //
   139  // in the processed file within the first 20 lines, if any.
   140  func TestRewrite(t *testing.T) {
   141  	// determine input files
   142  	match, err := filepath.Glob("testdata/*.input")
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  
   147  	// add larger examples
   148  	match = append(match, "gofmt.go", "gofmt_test.go")
   149  
   150  	for _, in := range match {
   151  		out := in // for files where input and output are identical
   152  		if strings.HasSuffix(in, ".input") {
   153  			out = in[:len(in)-len(".input")] + ".golden"
   154  		}
   155  		runTest(t, in, out)
   156  		if in != out {
   157  			// Check idempotence.
   158  			runTest(t, out, out)
   159  		}
   160  	}
   161  }
   162  
   163  // Test case for issue 3961.
   164  func TestCRLF(t *testing.T) {
   165  	const input = "testdata/crlf.input"   // must contain CR/LF's
   166  	const golden = "testdata/crlf.golden" // must not contain any CR's
   167  
   168  	data, err := os.ReadFile(input)
   169  	if err != nil {
   170  		t.Error(err)
   171  	}
   172  	if !bytes.Contains(data, []byte("\r\n")) {
   173  		t.Errorf("%s contains no CR/LF's", input)
   174  	}
   175  
   176  	data, err = os.ReadFile(golden)
   177  	if err != nil {
   178  		t.Error(err)
   179  	}
   180  	if bytes.Contains(data, []byte("\r")) {
   181  		t.Errorf("%s contains CR's", golden)
   182  	}
   183  }
   184  
   185  func TestBackupFile(t *testing.T) {
   186  	dir, err := os.MkdirTemp("", "gofmt_test")
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	defer os.RemoveAll(dir)
   191  	name, err := backupFile(filepath.Join(dir, "foo.go"), []byte("  package main"), 0644)
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  	t.Logf("Created: %s", name)
   196  }
   197  
   198  func TestDiff(t *testing.T) {
   199  	if _, err := exec.LookPath("diff"); err != nil {
   200  		t.Skipf("skip test on %s: diff command is required", runtime.GOOS)
   201  	}
   202  	in := []byte("first\nsecond\n")
   203  	out := []byte("first\nthird\n")
   204  	filename := "difftest.txt"
   205  	b, err := diffWithReplaceTempFile(in, out, filename)
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  
   210  	if runtime.GOOS == "windows" {
   211  		b = bytes.ReplaceAll(b, []byte{'\r', '\n'}, []byte{'\n'})
   212  	}
   213  
   214  	bs := bytes.SplitN(b, []byte{'\n'}, 3)
   215  	line0, line1 := bs[0], bs[1]
   216  
   217  	if prefix := "--- difftest.txt.orig"; !bytes.HasPrefix(line0, []byte(prefix)) {
   218  		t.Errorf("diff: first line should start with `%s`\ngot: %s", prefix, line0)
   219  	}
   220  
   221  	if prefix := "+++ difftest.txt"; !bytes.HasPrefix(line1, []byte(prefix)) {
   222  		t.Errorf("diff: second line should start with `%s`\ngot: %s", prefix, line1)
   223  	}
   224  
   225  	want := `@@ -1,2 +1,2 @@
   226   first
   227  -second
   228  +third
   229  `
   230  
   231  	if got := string(bs[2]); got != want {
   232  		t.Errorf("diff: got:\n%s\nwant:\n%s", got, want)
   233  	}
   234  }
   235  
   236  func TestReplaceTempFilename(t *testing.T) {
   237  	diff := []byte(`--- /tmp/tmpfile1	2017-02-08 00:53:26.175105619 +0900
   238  +++ /tmp/tmpfile2	2017-02-08 00:53:38.415151275 +0900
   239  @@ -1,2 +1,2 @@
   240   first
   241  -second
   242  +third
   243  `)
   244  	want := []byte(`--- path/to/file.go.orig	2017-02-08 00:53:26.175105619 +0900
   245  +++ path/to/file.go	2017-02-08 00:53:38.415151275 +0900
   246  @@ -1,2 +1,2 @@
   247   first
   248  -second
   249  +third
   250  `)
   251  	// Check path in diff output is always slash regardless of the
   252  	// os.PathSeparator (`/` or `\`).
   253  	sep := string(os.PathSeparator)
   254  	filename := strings.Join([]string{"path", "to", "file.go"}, sep)
   255  	got, err := replaceTempFilename(diff, filename)
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	if !bytes.Equal(got, want) {
   260  		t.Errorf("os.PathSeparator='%s': replacedDiff:\ngot:\n%s\nwant:\n%s", sep, got, want)
   261  	}
   262  }
   263  

View as plain text