| // Copyright 2014 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. |
| |
| // No testdata on Android. |
| |
| //go:build !android |
| // +build !android |
| |
| package eg_test |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "go/constant" |
| "go/format" |
| "go/types" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "golang.org/x/tools/go/packages" |
| "golang.org/x/tools/internal/testenv" |
| "golang.org/x/tools/internal/testfiles" |
| "golang.org/x/tools/refactor/eg" |
| "golang.org/x/tools/txtar" |
| ) |
| |
| // TODO(adonovan): more tests: |
| // - of command-line tool |
| // - of all parts of syntax |
| // - of applying a template to a package it imports: |
| // the replacement syntax should use unqualified names for its objects. |
| |
| var ( |
| updateFlag = flag.Bool("update", false, "update the golden files") |
| verboseFlag = flag.Bool("verbose", false, "show matcher information") |
| ) |
| |
| func Test(t *testing.T) { |
| testenv.NeedsGoPackages(t) |
| |
| switch runtime.GOOS { |
| case "windows": |
| t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS) |
| } |
| |
| // Each txtar defines a package example.com/template and zero |
| // or more input packages example.com/in/... on which to apply |
| // it. The outputs are compared with the corresponding files |
| // in example.com/out/... |
| for _, filename := range []string{ |
| "testdata/a.txtar", |
| "testdata/b.txtar", |
| "testdata/c.txtar", |
| "testdata/d.txtar", |
| "testdata/e.txtar", |
| "testdata/f.txtar", |
| "testdata/g.txtar", |
| "testdata/h.txtar", |
| "testdata/i.txtar", |
| "testdata/j.txtar", |
| "testdata/bad_type.txtar", |
| "testdata/no_before.txtar", |
| "testdata/no_after_return.txtar", |
| "testdata/type_mismatch.txtar", |
| "testdata/expr_type_mismatch.txtar", |
| } { |
| t.Run(filename, func(t *testing.T) { |
| // Extract and load packages from test archive. |
| dir := testfiles.ExtractTxtarFileToTmp(t, filename) |
| cfg := packages.Config{ |
| Mode: packages.LoadAllSyntax, |
| Dir: dir, |
| } |
| pkgs, err := packages.Load(&cfg, "example.com/template", "example.com/in/...") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if packages.PrintErrors(pkgs) > 0 { |
| t.Fatal("Load: there were errors") |
| } |
| |
| // Find and compile the template. |
| var template *packages.Package |
| var inputs []*packages.Package |
| for _, pkg := range pkgs { |
| if pkg.Types.Name() == "template" { |
| template = pkg |
| } else { |
| inputs = append(inputs, pkg) |
| } |
| } |
| if template == nil { |
| t.Fatal("no template package") |
| } |
| shouldFail, _ := template.Types.Scope().Lookup("shouldFail").(*types.Const) |
| xform, err := eg.NewTransformer(template.Fset, template.Types, template.Syntax[0], template.TypesInfo, *verboseFlag) |
| if err != nil { |
| if shouldFail == nil { |
| t.Errorf("NewTransformer(%s): %s", filename, err) |
| } else if want := constant.StringVal(shouldFail.Val()); !strings.Contains(normalizeAny(err.Error()), want) { |
| t.Errorf("NewTransformer(%s): got error %q, want error %q", filename, err, want) |
| } |
| } else if shouldFail != nil { |
| t.Errorf("NewTransformer(%s) succeeded unexpectedly; want error %q", |
| filename, shouldFail.Val()) |
| } |
| |
| // Apply template to each input package. |
| updated := make(map[string][]byte) |
| for _, pkg := range inputs { |
| for _, file := range pkg.Syntax { |
| filename, err := filepath.Rel(dir, pkg.Fset.File(file.FileStart).Name()) |
| if err != nil { |
| t.Fatalf("can't relativize filename: %v", err) |
| } |
| |
| // Apply the transform and reformat. |
| n := xform.Transform(pkg.TypesInfo, pkg.Types, file) |
| if n == 0 { |
| t.Fatalf("%s: no replacements", filename) |
| } |
| var got []byte |
| { |
| var out bytes.Buffer |
| format.Node(&out, pkg.Fset, file) // ignore error |
| got = out.Bytes() |
| } |
| |
| // Compare formatted output with out/<filename> |
| // Errors here are not fatal, so we can proceed to -update. |
| outfile := strings.Replace(filename, "in", "out", 1) |
| updated[outfile] = got |
| want, err := os.ReadFile(filepath.Join(dir, outfile)) |
| if err != nil { |
| t.Errorf("can't read output file: %v", err) |
| } else if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("Unexpected output:\n%s\n\ngot %s:\n%s\n\nwant %s:\n%s", |
| diff, |
| filename, got, outfile, want) |
| } |
| } |
| } |
| |
| // -update: replace the .txtar. |
| if *updateFlag { |
| ar, err := txtar.ParseFile(filename) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var new bytes.Buffer |
| new.Write(ar.Comment) |
| for _, file := range ar.Files { |
| data, ok := updated[file.Name] |
| if !ok { |
| data = file.Data |
| } |
| fmt.Fprintf(&new, "-- %s --\n%s", file.Name, data) |
| } |
| t.Logf("Updating %s...", filename) |
| os.Remove(filename + ".bak") // ignore error |
| os.Rename(filename, filename+".bak") // ignore error |
| if err := os.WriteFile(filename, new.Bytes(), 0666); err != nil { |
| t.Fatal(err) |
| } |
| } |
| }) |
| } |
| } |
| |
| // normalizeAny replaces occurrences of interface{} with any, for consistent |
| // output. |
| func normalizeAny(s string) string { |
| return strings.ReplaceAll(s, "interface{}", "any") |
| } |