// Copyright 2018 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.

package load

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"go/ast"
	"go/build"
	"go/doc"
	"go/parser"
	"go/token"
	"internal/lazytemplate"
	"maps"
	"path/filepath"
	"slices"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"

	"cmd/go/internal/fsys"
	"cmd/go/internal/modload"
	"cmd/go/internal/str"
	"cmd/go/internal/trace"
)

var TestMainDeps = []string{
	// Dependencies for testmain.
	"os",
	"reflect",
	"testing",
	"testing/internal/testdeps",
}

type TestCover struct {
	Mode  string
	Local bool
	Pkgs  []*Package
	Paths []string
}

// TestPackagesFor is like TestPackagesAndErrors but it returns
// the package containing an error if the test packages or
// their dependencies have errors.
// Only test packages without errors are returned.
func TestPackagesFor(loaderstate *modload.State, ctx context.Context, opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest, perr *Package) {
	pmain, ptest, pxtest = TestPackagesAndErrors(loaderstate, ctx, nil, opts, p, cover)
	for _, p1 := range []*Package{ptest, pxtest, pmain} {
		if p1 == nil {
			// pxtest may be nil
			continue
		}
		if p1.Error != nil {
			perr = p1
			break
		}
		if p1.Incomplete {
			ps := PackageList([]*Package{p1})
			for _, p := range ps {
				if p.Error != nil {
					perr = p
					break
				}
			}
			break
		}
	}
	if pmain.Error != nil || pmain.Incomplete {
		pmain = nil
	}
	if ptest.Error != nil || ptest.Incomplete {
		ptest = nil
	}
	if pxtest != nil && (pxtest.Error != nil || pxtest.Incomplete) {
		pxtest = nil
	}
	return pmain, ptest, pxtest, perr
}

// TestPackagesAndErrors returns three packages:
//   - pmain, the package main corresponding to the test binary (running tests in ptest and pxtest).
//   - ptest, the package p compiled with added "package p" test files.
//   - pxtest, the result of compiling any "package p_test" (external) test files.
//
// If the package has no "package p_test" test files, pxtest will be nil.
// If the non-test compilation of package p can be reused
// (for example, if there are no "package p" test files and
// package p need not be instrumented for coverage or any other reason),
// then the returned ptest == p.
//
// If done is non-nil, TestPackagesAndErrors will finish filling out the returned
// package structs in a goroutine and call done once finished. The members of the
// returned packages should not be accessed until done is called.
//
// The caller is expected to have checked that len(p.TestGoFiles)+len(p.XTestGoFiles) > 0,
// or else there's no point in any of this.
func TestPackagesAndErrors(loaderstate *modload.State, ctx context.Context, done func(), opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package) {
	ctx, span := trace.StartSpan(ctx, "load.TestPackagesAndErrors")
	defer span.Done()

	pre := newPreload()
	defer pre.flush()
	allImports := append([]string{}, p.TestImports...)
	allImports = append(allImports, p.XTestImports...)
	pre.preloadImports(loaderstate, ctx, opts, allImports, p.Internal.Build)

	var ptestErr, pxtestErr *PackageError
	var imports, ximports []*Package
	var stk ImportStack
	var testEmbed, xtestEmbed map[string][]string
	var incomplete bool
	stk.Push(ImportInfo{Pkg: p.ImportPath + " (test)"})
	rawTestImports := str.StringList(p.TestImports)
	for i, path := range p.TestImports {
		p1, err := loadImport(loaderstate, ctx, opts, pre, path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], ResolveImport)
		if err != nil && ptestErr == nil {
			ptestErr = err
			incomplete = true
		}
		if p1.Incomplete {
			incomplete = true
		}
		p.TestImports[i] = p1.ImportPath
		imports = append(imports, p1)
	}
	var err error
	p.TestEmbedFiles, testEmbed, err = resolveEmbed(p.Dir, p.TestEmbedPatterns)
	if err != nil {
		ptestErr = &PackageError{
			ImportStack: stk.Copy(),
			Err:         err,
		}
		incomplete = true
		embedErr := err.(*EmbedError)
		ptestErr.setPos(p.Internal.Build.TestEmbedPatternPos[embedErr.Pattern])
	}
	stk.Pop()

	stk.Push(ImportInfo{Pkg: p.ImportPath + "_test"})
	pxtestNeedsPtest := false
	var pxtestIncomplete bool
	rawXTestImports := str.StringList(p.XTestImports)
	for i, path := range p.XTestImports {
		p1, err := loadImport(loaderstate, ctx, opts, pre, path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], ResolveImport)
		if err != nil && pxtestErr == nil {
			pxtestErr = err
		}
		if p1.Incomplete {
			pxtestIncomplete = true
		}
		if p1.ImportPath == p.ImportPath {
			pxtestNeedsPtest = true
		} else {
			ximports = append(ximports, p1)
		}
		p.XTestImports[i] = p1.ImportPath
	}
	p.XTestEmbedFiles, xtestEmbed, err = resolveEmbed(p.Dir, p.XTestEmbedPatterns)
	if err != nil && pxtestErr == nil {
		pxtestErr = &PackageError{
			ImportStack: stk.Copy(),
			Err:         err,
		}
		embedErr := err.(*EmbedError)
		pxtestErr.setPos(p.Internal.Build.XTestEmbedPatternPos[embedErr.Pattern])
	}
	pxtestIncomplete = pxtestIncomplete || pxtestErr != nil
	stk.Pop()

	// Test package.
	if len(p.TestGoFiles) > 0 || p.Name == "main" || cover != nil && cover.Local {
		ptest = new(Package)
		*ptest = *p
		if ptest.Error == nil {
			ptest.Error = ptestErr
		}
		ptest.Incomplete = ptest.Incomplete || incomplete
		ptest.ForTest = p.ImportPath
		ptest.GoFiles = nil
		ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...)
		ptest.GoFiles = append(ptest.GoFiles, p.TestGoFiles...)
		ptest.Target = ""
		// Note: The preparation of the vet config requires that common
		// indexes in ptest.Imports and ptest.Internal.RawImports
		// all line up (but RawImports can be shorter than the others).
		// That is, for 0 ≤ i < len(RawImports),
		// RawImports[i] is the import string in the program text, and
		// Imports[i] is the expanded import string (vendoring applied or relative path expanded away).
		// Any implicitly added imports appear in Imports and Internal.Imports
		// but not RawImports (because they were not in the source code).
		// We insert TestImports, imports, and rawTestImports at the start of
		// these lists to preserve the alignment.
		// Note that p.Internal.Imports may not be aligned with p.Imports/p.Internal.RawImports,
		// but we insert at the beginning there too just for consistency.
		ptest.Imports = str.StringList(p.TestImports, p.Imports)
		ptest.Internal.Imports = append(imports, p.Internal.Imports...)
		ptest.Internal.RawImports = str.StringList(rawTestImports, p.Internal.RawImports)
		ptest.Internal.ForceLibrary = true
		ptest.Internal.BuildInfo = nil
		ptest.Internal.Build = new(build.Package)
		*ptest.Internal.Build = *p.Internal.Build
		m := map[string][]token.Position{}
		for k, v := range p.Internal.Build.ImportPos {
			m[k] = append(m[k], v...)
		}
		for k, v := range p.Internal.Build.TestImportPos {
			m[k] = append(m[k], v...)
		}
		ptest.Internal.Build.ImportPos = m
		if testEmbed == nil && len(p.Internal.Embed) > 0 {
			testEmbed = map[string][]string{}
		}
		maps.Copy(testEmbed, p.Internal.Embed)
		ptest.Internal.Embed = testEmbed
		ptest.EmbedFiles = str.StringList(p.EmbedFiles, p.TestEmbedFiles)
		ptest.Internal.OrigImportPath = p.Internal.OrigImportPath
		ptest.Internal.PGOProfile = p.Internal.PGOProfile
		ptest.Internal.Build.Directives = append(slices.Clip(p.Internal.Build.Directives), p.Internal.Build.TestDirectives...)
	} else {
		ptest = p
	}

	// External test package.
	if len(p.XTestGoFiles) > 0 {
		pxtest = &Package{
			PackagePublic: PackagePublic{
				Name:       p.Name + "_test",
				ImportPath: p.ImportPath + "_test",
				Root:       p.Root,
				Dir:        p.Dir,
				Goroot:     p.Goroot,
				GoFiles:    p.XTestGoFiles,
				Imports:    p.XTestImports,
				ForTest:    p.ImportPath,
				Module:     p.Module,
				Error:      pxtestErr,
				Incomplete: pxtestIncomplete,
				EmbedFiles: p.XTestEmbedFiles,
			},
			Internal: PackageInternal{
				LocalPrefix: p.Internal.LocalPrefix,
				Build: &build.Package{
					ImportPos:  p.Internal.Build.XTestImportPos,
					Directives: p.Internal.Build.XTestDirectives,
				},
				Imports:    ximports,
				RawImports: rawXTestImports,

				Asmflags:       p.Internal.Asmflags,
				Gcflags:        p.Internal.Gcflags,
				Ldflags:        p.Internal.Ldflags,
				Gccgoflags:     p.Internal.Gccgoflags,
				Embed:          xtestEmbed,
				OrigImportPath: p.Internal.OrigImportPath,
				PGOProfile:     p.Internal.PGOProfile,
			},
		}
		if pxtestNeedsPtest {
			pxtest.Internal.Imports = append(pxtest.Internal.Imports, ptest)
		}
	}

	// Arrange for testing.Testing to report true.
	ldflags := append(p.Internal.Ldflags, "-X", "testing.testBinary=1")
	gccgoflags := append(p.Internal.Gccgoflags, "-Wl,--defsym,testing.gccgoTestBinary=1")

	// Build main package.
	pmain = &Package{
		PackagePublic: PackagePublic{
			Name:       "main",
			Dir:        p.Dir,
			GoFiles:    []string{"_testmain.go"},
			ImportPath: p.ImportPath + ".test",
			Root:       p.Root,
			Imports:    str.StringList(TestMainDeps),
			Module:     p.Module,
		},
		Internal: PackageInternal{
			Build:          &build.Package{Name: "main"},
			BuildInfo:      p.Internal.BuildInfo,
			Asmflags:       p.Internal.Asmflags,
			Gcflags:        p.Internal.Gcflags,
			Ldflags:        ldflags,
			Gccgoflags:     gccgoflags,
			OrigImportPath: p.Internal.OrigImportPath,
			PGOProfile:     p.Internal.PGOProfile,
		},
	}

	pb := p.Internal.Build
	pmain.DefaultGODEBUG = defaultGODEBUG(loaderstate, pmain, pb.Directives, pb.TestDirectives, pb.XTestDirectives)
	if pmain.Internal.BuildInfo == nil || pmain.DefaultGODEBUG != p.DefaultGODEBUG {
		// Either we didn't generate build info for the package under test (because it wasn't package main), or
		// the DefaultGODEBUG used to build the test main package is different from the DefaultGODEBUG
		// used to build the package under test. If we didn't set build info for the package under test
		// pmain won't have buildinfo set (since we copy it from the package under test). If the default GODEBUG
		// used for the package under test is different from that of the test main, the BuildInfo assigned above from the package
		// under test incorrect for the test main package. Either set or correct pmain's build info.
		pmain.setBuildInfo(ctx, loaderstate.Fetcher(), opts.AutoVCS)
	}

	// The generated main also imports testing, regexp, and os.
	// Also the linker introduces implicit dependencies reported by LinkerDeps.
	stk.Push(ImportInfo{Pkg: "testmain"})
	deps := TestMainDeps // cap==len, so safe for append
	if cover != nil {
		deps = append(deps, "internal/coverage/cfile")
	}
	ldDeps, err := LinkerDeps(loaderstate, p)
	if err != nil && pmain.Error == nil {
		pmain.Error = &PackageError{Err: err}
	}
	for _, d := range ldDeps {
		deps = append(deps, d)
	}
	for _, dep := range deps {
		if dep == ptest.ImportPath {
			pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
		} else {
			p1, err := loadImport(loaderstate, ctx, opts, pre, dep, "", nil, &stk, nil, 0)
			if err != nil && pmain.Error == nil {
				pmain.Error = err
				pmain.Incomplete = true
			}
			pmain.Internal.Imports = append(pmain.Internal.Imports, p1)
		}
	}
	stk.Pop()

	parallelizablePart := func() {
		allTestImports := make([]*Package, 0, len(pmain.Internal.Imports)+len(imports)+len(ximports))
		allTestImports = append(allTestImports, pmain.Internal.Imports...)
		allTestImports = append(allTestImports, imports...)
		allTestImports = append(allTestImports, ximports...)
		setToolFlags(loaderstate, allTestImports...)

		// Do initial scan for metadata needed for writing _testmain.go
		// Use that metadata to update the list of imports for package main.
		// The list of imports is used by recompileForTest and by the loop
		// afterward that gathers t.Cover information.
		t, err := loadTestFuncs(p)
		if err != nil && pmain.Error == nil {
			pmain.setLoadPackageDataError(err, p.ImportPath, &stk, nil)
		}
		t.Cover = cover
		if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 {
			pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
			pmain.Imports = append(pmain.Imports, ptest.ImportPath)
			t.ImportTest = true
		}
		if pxtest != nil {
			pmain.Internal.Imports = append(pmain.Internal.Imports, pxtest)
			pmain.Imports = append(pmain.Imports, pxtest.ImportPath)
			t.ImportXtest = true
		}

		// Sort and dedup pmain.Imports.
		// Only matters for go list -test output.
		sort.Strings(pmain.Imports)
		w := 0
		for _, path := range pmain.Imports {
			if w == 0 || path != pmain.Imports[w-1] {
				pmain.Imports[w] = path
				w++
			}
		}
		pmain.Imports = pmain.Imports[:w]
		pmain.Internal.RawImports = str.StringList(pmain.Imports)

		// Replace pmain's transitive dependencies with test copies, as necessary.
		cycleErr := recompileForTest(pmain, p, ptest, pxtest)
		if cycleErr != nil {
			ptest.Error = cycleErr
			ptest.Incomplete = true
		}

		if cover != nil {
			// Here ptest needs to inherit the proper coverage mode (since
			// it contains p's Go files), whereas pmain contains only
			// test harness code (don't want to instrument it, and
			// we don't want coverage hooks in the pkg init).
			ptest.Internal.Cover.Mode = p.Internal.Cover.Mode
			pmain.Internal.Cover.Mode = "testmain"

			// Should we apply coverage analysis locally, only for this
			// package and only for this test? Yes, if -cover is on but
			// -coverpkg has not specified a list of packages for global
			// coverage.
			if cover.Local {
				ptest.Internal.Cover.Mode = cover.Mode
			}
		}

		data, err := formatTestmain(t)
		if err != nil && pmain.Error == nil {
			pmain.Error = &PackageError{Err: err}
			pmain.Incomplete = true
		}
		// Set TestmainGo even if it is empty: the presence of a TestmainGo
		// indicates that this package is, in fact, a test main.
		pmain.Internal.TestmainGo = &data
	}

	if done != nil {
		go func() {
			parallelizablePart()
			done()
		}()
	} else {
		parallelizablePart()
	}

	return pmain, ptest, pxtest
}

// recompileForTest copies and replaces certain packages in pmain's dependency
// graph. This is necessary for two reasons. First, if ptest is different than
// preal, packages that import the package under test should get ptest instead
// of preal. This is particularly important if pxtest depends on functionality
// exposed in test sources in ptest. Second, if there is a main package
// (other than pmain) anywhere, we need to set p.Internal.ForceLibrary and
// clear p.Internal.BuildInfo in the test copy to prevent link conflicts.
// This may happen if both -coverpkg and the command line patterns include
// multiple main packages.
func recompileForTest(pmain, preal, ptest, pxtest *Package) *PackageError {
	// The "test copy" of preal is ptest.
	// For each package that depends on preal, make a "test copy"
	// that depends on ptest. And so on, up the dependency tree.
	testCopy := map[*Package]*Package{preal: ptest}
	for _, p := range PackageList([]*Package{pmain}) {
		if p == preal {
			continue
		}
		// Copy on write.
		didSplit := p == pmain || p == pxtest || p == ptest
		split := func() {
			if didSplit {
				return
			}
			didSplit = true
			if testCopy[p] != nil {
				panic("recompileForTest loop")
			}
			p1 := new(Package)
			testCopy[p] = p1
			*p1 = *p
			p1.ForTest = preal.ImportPath
			p1.Internal.Imports = make([]*Package, len(p.Internal.Imports))
			copy(p1.Internal.Imports, p.Internal.Imports)
			p1.Imports = make([]string, len(p.Imports))
			copy(p1.Imports, p.Imports)
			p = p1
			p.Target = ""
			p.Internal.BuildInfo = nil
			p.Internal.ForceLibrary = true
			p.Internal.PGOProfile = preal.Internal.PGOProfile
		}

		// Update p.Internal.Imports to use test copies.
		for i, imp := range p.Internal.Imports {
			if p1 := testCopy[imp]; p1 != nil && p1 != imp {
				split()

				// If the test dependencies cause a cycle with pmain, this is
				// where it is introduced.
				// (There are no cycles in the graph until this assignment occurs.)
				p.Internal.Imports[i] = p1
			}
		}

		// Force main packages the test imports to be built as libraries.
		// Normal imports of main packages are forbidden by the package loader,
		// but this can still happen if -coverpkg patterns include main packages:
		// covered packages are imported by pmain. Linking multiple packages
		// compiled with '-p main' causes duplicate symbol errors.
		// See golang.org/issue/30907, golang.org/issue/34114.
		if p.Name == "main" && p != pmain && p != ptest {
			split()
		}
		// Split and attach PGO information to test dependencies if preal
		// is built with PGO.
		if preal.Internal.PGOProfile != "" && p.Internal.PGOProfile == "" {
			split()
		}
	}

	// Do search to find cycle.
	// importerOf maps each import path to its importer nearest to p.
	importerOf := map[*Package]*Package{}
	for _, p := range ptest.Internal.Imports {
		importerOf[p] = nil
	}

	// q is a breadth-first queue of packages to search for target.
	// Every package added to q has a corresponding entry in pathTo.
	//
	// We search breadth-first for two reasons:
	//
	// 	1. We want to report the shortest cycle.
	//
	// 	2. If p contains multiple cycles, the first cycle we encounter might not
	// 	   contain target. To ensure termination, we have to break all cycles
	// 	   other than the first.
	q := slices.Clip(ptest.Internal.Imports)
	for len(q) > 0 {
		p := q[0]
		q = q[1:]
		if p == ptest {
			// The stack is supposed to be in the order x imports y imports z.
			// We collect in the reverse order: z is imported by y is imported
			// by x, and then we reverse it.
			var stk ImportStack
			for p != nil {
				importer, ok := importerOf[p]
				if importer == nil && ok { // we set importerOf[p] == nil for the initial set of packages p that are imports of ptest
					importer = ptest
				}
				stk = append(stk, ImportInfo{
					Pkg: p.ImportPath,
					Pos: extractFirstImport(importer.Internal.Build.ImportPos[p.ImportPath]),
				})
				p = importerOf[p]
			}
			// complete the cycle: we set importer[p] = nil to break the cycle
			// in importerOf, it's an implicit importerOf[p] == pTest. Add it
			// back here since we reached nil in the loop above to demonstrate
			// the cycle as (for example) package p imports package q imports package r
			// imports package p.
			stk = append(stk, ImportInfo{
				Pkg: ptest.ImportPath,
			})
			slices.Reverse(stk)
			return &PackageError{
				ImportStack:   stk,
				Err:           errors.New("import cycle not allowed in test"),
				IsImportCycle: true,
			}
		}
		for _, dep := range p.Internal.Imports {
			if _, ok := importerOf[dep]; !ok {
				importerOf[dep] = p
				q = append(q, dep)
			}
		}
	}

	return nil
}

// isTestFunc tells whether fn has the type of a testing function. arg
// specifies the parameter type we look for: B, F, M or T.
func isTestFunc(fn *ast.FuncDecl, arg string) bool {
	if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
		fn.Type.Params.List == nil ||
		len(fn.Type.Params.List) != 1 ||
		len(fn.Type.Params.List[0].Names) > 1 {
		return false
	}
	ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
	if !ok {
		return false
	}
	// We can't easily check that the type is *testing.M
	// because we don't know how testing has been imported,
	// but at least check that it's *M or *something.M.
	// Same applies for B, F and T.
	if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
		return true
	}
	if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
		return true
	}
	return false
}

// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
	if !strings.HasPrefix(name, prefix) {
		return false
	}
	if len(name) == len(prefix) { // "Test" is ok
		return true
	}
	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
	return !unicode.IsLower(rune)
}

// loadTestFuncs returns the testFuncs describing the tests that will be run.
// The returned testFuncs is always non-nil, even if an error occurred while
// processing test files.
func loadTestFuncs(ptest *Package) (*testFuncs, error) {
	t := &testFuncs{
		Package: ptest,
	}
	var err error
	for _, file := range ptest.TestGoFiles {
		if lerr := t.load(filepath.Join(ptest.Dir, file), "_test", &t.ImportTest, &t.NeedTest); lerr != nil && err == nil {
			err = lerr
		}
	}
	for _, file := range ptest.XTestGoFiles {
		if lerr := t.load(filepath.Join(ptest.Dir, file), "_xtest", &t.ImportXtest, &t.NeedXtest); lerr != nil && err == nil {
			err = lerr
		}
	}
	return t, err
}

// formatTestmain returns the content of the _testmain.go file for t.
func formatTestmain(t *testFuncs) ([]byte, error) {
	var buf bytes.Buffer
	tmpl := testmainTmpl
	if err := tmpl.Execute(&buf, t); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

type testFuncs struct {
	Tests       []testFunc
	Benchmarks  []testFunc
	FuzzTargets []testFunc
	Examples    []testFunc
	TestMain    *testFunc
	Package     *Package
	ImportTest  bool
	NeedTest    bool
	ImportXtest bool
	NeedXtest   bool
	Cover       *TestCover
}

// ImportPath returns the import path of the package being tested, if it is within GOPATH.
// This is printed by the testing package when running benchmarks.
func (t *testFuncs) ImportPath() string {
	pkg := t.Package.ImportPath
	if strings.HasPrefix(pkg, "_/") {
		return ""
	}
	if pkg == "command-line-arguments" {
		return ""
	}
	return pkg
}

func (t *testFuncs) ModulePath() string {
	m := t.Package.Module
	if m == nil {
		return ""
	}
	return m.Path
}

// Covered returns a string describing which packages are being tested for coverage.
// If the covered package is the same as the tested package, it returns the empty string.
// Otherwise it is a comma-separated human-readable list of packages beginning with
// " in", ready for use in the coverage message.
func (t *testFuncs) Covered() string {
	if t.Cover == nil || t.Cover.Paths == nil {
		return ""
	}
	return " in " + strings.Join(t.Cover.Paths, ", ")
}

func (t *testFuncs) CoverSelectedPackages() string {
	if t.Cover == nil || t.Cover.Paths == nil {
		return `[]string{"` + t.Package.ImportPath + `"}`
	}
	var sb strings.Builder
	fmt.Fprintf(&sb, "[]string{")
	for k, p := range t.Cover.Pkgs {
		if k != 0 {
			sb.WriteString(", ")
		}
		fmt.Fprintf(&sb, `"%s"`, p.ImportPath)
	}
	sb.WriteString("}")
	return sb.String()
}

// Tested returns the name of the package being tested.
func (t *testFuncs) Tested() string {
	return t.Package.Name
}

type testFunc struct {
	Package   string // imported package name (_test or _xtest)
	Name      string // function name
	Output    string // output, for examples
	Unordered bool   // output is allowed to be unordered.
}

var testFileSet = token.NewFileSet()

func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
	// Pass in the overlaid source if we have an overlay for this file.
	src, err := fsys.Open(filename)
	if err != nil {
		return err
	}
	defer src.Close()
	f, err := parser.ParseFile(testFileSet, filename, src, parser.ParseComments|parser.SkipObjectResolution)
	if err != nil {
		return err
	}
	for _, d := range f.Decls {
		n, ok := d.(*ast.FuncDecl)
		if !ok {
			continue
		}
		if n.Recv != nil {
			continue
		}
		name := n.Name.String()
		switch {
		case name == "TestMain":
			if isTestFunc(n, "T") {
				t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
				*doImport, *seen = true, true
				continue
			}
			err := checkTestFunc(n, "M")
			if err != nil {
				return err
			}
			if t.TestMain != nil {
				return errors.New("multiple definitions of TestMain")
			}
			t.TestMain = &testFunc{pkg, name, "", false}
			*doImport, *seen = true, true
		case isTest(name, "Test"):
			err := checkTestFunc(n, "T")
			if err != nil {
				return err
			}
			t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
			*doImport, *seen = true, true
		case isTest(name, "Benchmark"):
			err := checkTestFunc(n, "B")
			if err != nil {
				return err
			}
			t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
			*doImport, *seen = true, true
		case isTest(name, "Fuzz"):
			err := checkTestFunc(n, "F")
			if err != nil {
				return err
			}
			t.FuzzTargets = append(t.FuzzTargets, testFunc{pkg, name, "", false})
			*doImport, *seen = true, true
		}
	}
	ex := doc.Examples(f)
	sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
	for _, e := range ex {
		*doImport = true // import test file whether executed or not
		if e.Output == "" && !e.EmptyOutput {
			// Don't run examples with no output.
			continue
		}
		t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
		*seen = true
	}
	return nil
}

func checkTestFunc(fn *ast.FuncDecl, arg string) error {
	var why string
	if !isTestFunc(fn, arg) {
		why = fmt.Sprintf("must be: func %s(%s *testing.%s)", fn.Name.String(), strings.ToLower(arg), arg)
	}
	if fn.Type.TypeParams.NumFields() > 0 {
		why = "test functions cannot have type parameters"
	}
	if why != "" {
		pos := testFileSet.Position(fn.Pos())
		return fmt.Errorf("%s: wrong signature for %s, %s", pos, fn.Name.String(), why)
	}
	return nil
}

var testmainTmpl = lazytemplate.New("main", `
// Code generated by 'go test'. DO NOT EDIT.

package main

import (
	"os"
{{if .TestMain}}
	"reflect"
{{end}}
	"testing"
	"testing/internal/testdeps"
{{if .Cover}}
	"internal/coverage/cfile"
{{end}}

{{if .ImportTest}}
	{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
	{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
)

var tests = []testing.InternalTest{
{{range .Tests}}
	{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}

var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
	{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}

var fuzzTargets = []testing.InternalFuzzTarget{
{{range .FuzzTargets}}
	{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}

var examples = []testing.InternalExample{
{{range .Examples}}
	{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}

func init() {
{{if .Cover}}
	testdeps.CoverMode = {{printf "%q" .Cover.Mode}}
	testdeps.Covered = {{printf "%q" .Covered}}
	testdeps.CoverSelectedPackages = {{printf "%s" .CoverSelectedPackages}}
	testdeps.CoverSnapshotFunc = cfile.Snapshot
	testdeps.CoverProcessTestDirFunc = cfile.ProcessCoverTestDir
	testdeps.CoverMarkProfileEmittedFunc = cfile.MarkProfileEmitted

{{end}}
	testdeps.ModulePath = {{.ModulePath | printf "%q"}}
	testdeps.ImportPath = {{.ImportPath | printf "%q"}}
}

func main() {
	m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
{{with .TestMain}}
	{{.Package}}.{{.Name}}(m)
	os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
{{else}}
	os.Exit(m.Run())
{{end}}
}

`)
