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