blob: eb54f0b3f95af212fb7c2e8ce88c92fb23b01ad8 [file] [log] [blame]
// 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")
}