// Copyright 2013 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 imports

import (
	"context"
	"flag"
	"fmt"
	"go/build"
	"log"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"sync"
	"sync/atomic"
	"testing"

	"golang.org/x/tools/internal/gocommand"
	"golang.org/x/tools/internal/packagestest"
	"golang.org/x/tools/internal/stdlib"
)

var testDebug = flag.Bool("debug", false, "enable debug output")

var tests = []struct {
	name       string
	formatOnly bool
	in, out    string
}{
	// Adding an import to an existing parenthesized import
	{
		name: "factored_imports_add",
		in: `package foo
import (
  "fmt"
)
func bar() {
var b bytes.Buffer
fmt.Println(b.String())
}
`,
		out: `package foo

import (
	"bytes"
	"fmt"
)

func bar() {
	var b bytes.Buffer
	fmt.Println(b.String())
}
`,
	},

	// Adding an import to an existing parenthesized import,
	// verifying it goes into the first section.
	{
		name: "factored_imports_add_first_sec",
		in: `package foo
import (
  "fmt"

  "github.com/golang/snappy"
)
func bar() {
var b bytes.Buffer
_ = snappy.ErrCorrupt
fmt.Println(b.String())
}
`,
		out: `package foo

import (
	"bytes"
	"fmt"

	"github.com/golang/snappy"
)

func bar() {
	var b bytes.Buffer
	_ = snappy.ErrCorrupt
	fmt.Println(b.String())
}
`,
	},

	// Adding an import to an existing parenthesized import,
	// verifying it goes into the first section. (test 2)
	{
		name: "factored_imports_add_first_sec_2",
		in: `package foo
import (
  "fmt"

  "github.com/golang/snappy"
)
func bar() {
_ = math.NaN
_ = fmt.Sprintf
_ = snappy.ErrCorrupt
}
`,
		out: `package foo

import (
	"fmt"
	"math"

	"github.com/golang/snappy"
)

func bar() {
	_ = math.NaN
	_ = fmt.Sprintf
	_ = snappy.ErrCorrupt
}
`,
	},

	// Adding a new import line, without parens
	{
		name: "add_import_section",
		in: `package foo
func bar() {
var b bytes.Buffer
}
`,
		out: `package foo

import "bytes"

func bar() {
	var b bytes.Buffer
}
`,
	},

	// Adding two new imports, which should make a parenthesized import decl.
	{
		name: "add_import_paren_section",
		in: `package foo
func bar() {
_, _ := bytes.Buffer, zip.NewReader
}
`,
		out: `package foo

import (
	"archive/zip"
	"bytes"
)

func bar() {
	_, _ := bytes.Buffer, zip.NewReader
}
`,
	},

	// Make sure we don't add things twice
	{
		name: "no_double_add",
		in: `package foo
func bar() {
_, _ := bytes.Buffer, bytes.NewReader
}
`,
		out: `package foo

import "bytes"

func bar() {
	_, _ := bytes.Buffer, bytes.NewReader
}
`,
	},

	// Make sure we don't add packages that don't have the right exports
	{
		name: "no_mismatched_add",
		in: `package foo

func bar() {
	_ := bytes.NonexistentSymbol
}
`,
		out: `package foo

func bar() {
	_ := bytes.NonexistentSymbol
}
`,
	},

	// Remove unused imports, 1 of a factored block
	{
		name: "remove_unused_1_of_2",
		in: `package foo
import (
"bytes"
"fmt"
)

func bar() {
_, _ := bytes.Buffer, bytes.NewReader
}
`,
		out: `package foo

import (
	"bytes"
)

func bar() {
	_, _ := bytes.Buffer, bytes.NewReader
}
`,
	},

	// Remove unused imports, 2 of 2
	{
		name: "remove_unused_2_of_2",
		in: `package foo
import (
"bytes"
"fmt"
)

func bar() {
}
`,
		out: `package foo

func bar() {
}
`,
	},

	// Remove unused imports, 1 of 1
	{
		name: "remove_unused_1_of_1",
		in: `package foo

import "fmt"

func bar() {
}
`,
		out: `package foo

func bar() {
}
`,
	},

	// Don't remove empty imports.
	{
		name: "dont_remove_empty_imports",
		in: `package foo
import (
_ "image/png"
_ "image/jpeg"
)
`,
		out: `package foo

import (
	_ "image/jpeg"
	_ "image/png"
)
`,
	},

	// Don't remove dot imports.
	{
		name: "dont_remove_dot_imports",
		in: `package foo
import (
. "foo"
. "bar"
)
`,
		out: `package foo

import (
	. "bar"
	. "foo"
)
`,
	},

	// Skip refs the parser can resolve.
	{
		name: "skip_resolved_refs",
		in: `package foo

func f() {
	type t struct{ Println func(string) }
	fmt := t{Println: func(string) {}}
	fmt.Println("foo")
}
`,
		out: `package foo

func f() {
	type t struct{ Println func(string) }
	fmt := t{Println: func(string) {}}
	fmt.Println("foo")
}
`,
	},

	// Do not add a package we already have a resolution for.
	{
		name: "skip_template",
		in: `package foo

import "html/template"

func f() { t = template.New("sometemplate") }
`,
		out: `package foo

import "html/template"

func f() { t = template.New("sometemplate") }
`,
	},

	// Don't touch cgo
	{
		name: "cgo",
		in: `package foo

/*
#include <foo.h>
*/
import "C"
`,
		out: `package foo

/*
#include <foo.h>
*/
import "C"
`,
	},

	// Put some things in their own section
	{
		name: "make_sections",
		in: `package foo

import (
"os"
)

func foo () {
_, _ = os.Args, fmt.Println
_, _ = snappy.ErrCorrupt, p.P
}
`,
		out: `package foo

import (
	"fmt"
	"os"

	"github.com/golang/snappy"
	"rsc.io/p"
)

func foo() {
	_, _ = os.Args, fmt.Println
	_, _ = snappy.ErrCorrupt, p.P
}
`,
	},
	// Merge import blocks, even when no additions are required.
	{
		name: "merge_import_blocks_no_fix",
		in: `package foo

import (
	"fmt"
)
import "os"

import (
	"rsc.io/p"
)

var _, _ = os.Args, fmt.Println
var _, _ = snappy.ErrCorrupt, p.P
`,
		out: `package foo

import (
	"fmt"
	"os"

	"github.com/golang/snappy"
	"rsc.io/p"
)

var _, _ = os.Args, fmt.Println
var _, _ = snappy.ErrCorrupt, p.P
`,
	},
	// Delete existing empty import block
	{
		name: "delete_empty_import_block",
		in: `package foo

import ()
`,
		out: `package foo
`,
	},

	// Use existing empty import block
	{
		name: "use_empty_import_block",
		in: `package foo

import ()

func f() {
	_ = fmt.Println
}
`,
		out: `package foo

import "fmt"

func f() {
	_ = fmt.Println
}
`,
	},

	// Blank line before adding new section.
	{
		name: "blank_line_before_new_group",
		in: `package foo

import (
	"fmt"
	"net"
)

func f() {
	_ = net.Dial
	_ = fmt.Printf
	_ = snappy.ErrCorrupt
}
`,
		out: `package foo

import (
	"fmt"
	"net"

	"github.com/golang/snappy"
)

func f() {
	_ = net.Dial
	_ = fmt.Printf
	_ = snappy.ErrCorrupt
}
`,
	},

	// Blank line between standard library and third-party stuff.
	{
		name: "blank_line_separating_std_and_third_party",
		in: `package foo

import (
	"github.com/golang/snappy"
	"fmt"
	"net"
)

func f() {
	_ = net.Dial
	_ = fmt.Printf
	_ = snappy.Foo
}
`,
		out: `package foo

import (
	"fmt"
	"net"

	"github.com/golang/snappy"
)

func f() {
	_ = net.Dial
	_ = fmt.Printf
	_ = snappy.Foo
}
`,
	},

	// golang.org/issue/6884
	{
		name: "new_imports_before_comment",
		in: `package main

// A comment
func main() {
	fmt.Println("Hello, world")
}
`,
		out: `package main

import "fmt"

// A comment
func main() {
	fmt.Println("Hello, world")
}
`,
	},

	// golang.org/issue/7132
	{
		name: "new_section_for_dotless_import",
		in: `package main

import (
"fmt"

"gu"
"manypackages.com/packagea"
)

var (
a = packagea.A
b = gu.A
c = fmt.Printf
)
`,
		out: `package main

import (
	"fmt"

	"gu"

	"manypackages.com/packagea"
)

var (
	a = packagea.A
	b = gu.A
	c = fmt.Printf
)
`,
	},

	{
		name: "fragment_with_main",
		in:   `func main(){fmt.Println("Hello, world")}`,
		out: `package main

import "fmt"

func main() { fmt.Println("Hello, world") }
`,
	},

	{
		name: "fragment_without_main",
		in:   `func notmain(){fmt.Println("Hello, world")}`,
		out: `import "fmt"

func notmain() { fmt.Println("Hello, world") }`,
	},

	// Remove first import within in a 2nd/3rd/4th/etc. section.
	// golang.org/issue/7679
	{
		name: "remove_first_import_in_section",
		in: `package main

import (
	"fmt"

	"manypackages.com/packagea"
	"manypackages.com/packageb"
)

func main() {
	var _ = fmt.Println
	//var _ = packagea.A
	var _ = packageb.B
}
`,
		out: `package main

import (
	"fmt"

	"manypackages.com/packageb"
)

func main() {
	var _ = fmt.Println
	//var _ = packagea.A
	var _ = packageb.B
}
`,
	},

	// Blank line can be added before all types of import declarations.
	// golang.org/issue/7866
	{
		name: "new_section_for_all_kinds_of_imports",
		in: `package main

import (
	"fmt"
	renamed_packagea "manypackages.com/packagea"

	. "manypackages.com/packageb"
	"io"

	_ "manypackages.com/packagec"
	"strings"
)

var _, _, _, _, _ = fmt.Errorf, io.Copy, strings.Contains, renamed_packagea.A, B
`,
		out: `package main

import (
	"fmt"

	renamed_packagea "manypackages.com/packagea"

	"io"

	. "manypackages.com/packageb"

	"strings"

	_ "manypackages.com/packagec"
)

var _, _, _, _, _ = fmt.Errorf, io.Copy, strings.Contains, renamed_packagea.A, B
`,
	},

	// Blank line can be added even when first import of group has comment with quote
	{
		name: "new_section_where_trailing_comment_has_quote",
		in: `package main

import (
	"context"
	bar "local.com/bar"
	baz "local.com/baz"
	buzz "local.com/buzz"
	"github.com/golang/snappy" // this is a "typical" import
)

var _, _, _, _, _ = context.Background, bar.B, baz.B, buzz.B, snappy.ErrCorrupt
`,
		out: `package main

import (
	"context"

	"github.com/golang/snappy" // this is a "typical" import

	bar "local.com/bar"
	baz "local.com/baz"
	buzz "local.com/buzz"
)

var _, _, _, _, _ = context.Background, bar.B, baz.B, buzz.B, snappy.ErrCorrupt
`,
	},

	// Non-idempotent comment formatting
	// golang.org/issue/8035
	{
		name: "comments_formatted",
		in: `package main

import (
	"fmt"                     // A
	"go/ast"                  // B
	_ "manypackages.com/packagec"    // C
)

func main() { _, _ = fmt.Print, ast.Walk }
`,
		out: `package main

import (
	"fmt"    // A
	"go/ast" // B

	_ "manypackages.com/packagec" // C
)

func main() { _, _ = fmt.Print, ast.Walk }
`,
	},

	// Failure to delete all duplicate imports
	// golang.org/issue/8459
	{
		name: "remove_duplicates",
		in: `package main

import (
	"fmt"
	"log"
	"log"
	"math"
)

func main() { fmt.Println("pi:", math.Pi) }
`,
		out: `package main

import (
	"fmt"
	"math"
)

func main() { fmt.Println("pi:", math.Pi) }
`,
	},

	// Too aggressive prefix matching
	// golang.org/issue/9961
	{
		name: "no_extra_groups",
		in: `package p

import (
	"zip"

	"rsc.io/p"
)

var (
	_ = fmt.Print
	_ = zip.Store
	_ p.P
	_ = regexp.Compile
)
`,
		out: `package p

import (
	"fmt"
	"regexp"
	"zip"

	"rsc.io/p"
)

var (
	_ = fmt.Print
	_ = zip.Store
	_ p.P
	_ = regexp.Compile
)
`,
	},

	// Unused named import is mistaken for unnamed import
	// golang.org/issue/8149
	{
		name: "named_import_doesnt_provide_package_name",
		in: `package main

import foo "fmt"

func main() { fmt.Println() }
`,
		out: `package main

import "fmt"

func main() { fmt.Println() }
`,
	},

	// Unused named import is mistaken for unnamed import
	// golang.org/issue/8149
	{
		name: "unused_named_import_removed",
		in: `package main

import (
	"fmt"
	x "fmt"
)

func main() { fmt.Println() }
`,
		out: `package main

import (
	"fmt"
)

func main() { fmt.Println() }
`,
	},

	{
		name: "ignore_unexported_identifier",
		in: `package main
var _ = fmt.unexported`,
		out: `package main

var _ = fmt.unexported
`,
	},

	// FormatOnly
	{
		name:       "formatonly_works",
		formatOnly: true,
		in: `package main

import (
"fmt"
"manypackages.com/packagea"
)

func main() {}
`,
		out: `package main

import (
	"fmt"

	"manypackages.com/packagea"
)

func main() {}
`,
	},

	{
		name: "preserve_import_group",
		in: `package p

import (
	"bytes"
	"fmt"
)

var _ = fmt.Sprintf
`,
		out: `package p

import (
	"fmt"
)

var _ = fmt.Sprintf
`,
	},
	{
		name: "import_grouping_not_path_dependent_no_groups",
		in: `package main

import (
	"time"
)

func main() {
	_ = snappy.ErrCorrupt
	_ = p.P
	_ = time.Parse
}
`,
		out: `package main

import (
	"time"

	"github.com/golang/snappy"
	"rsc.io/p"
)

func main() {
	_ = snappy.ErrCorrupt
	_ = p.P
	_ = time.Parse
}
`,
	},

	{
		name: "import_grouping_not_path_dependent_existing_group",
		in: `package main

import (
	"time"

	"github.com/golang/snappy"
)

func main() {
	_ = snappy.ErrCorrupt
	_ = p.P
	_ = time.Parse
}
`,
		out: `package main

import (
	"time"

	"github.com/golang/snappy"
	"rsc.io/p"
)

func main() {
	_ = snappy.ErrCorrupt
	_ = p.P
	_ = time.Parse
}
`,
	},

	// golang.org/issue/12097
	{
		name: "package_statement_insertion_preserves_comments",
		in: `// a
// b
// c

func main() {
    _ = fmt.Println
}`,
		out: `package main

import "fmt"

// a
// b
// c

func main() {
	_ = fmt.Println
}
`,
	},

	{
		name: "import_comment_stays_on_import",
		in: `package main

import (
	"math" // fun
)

func main() {
	x := math.MaxInt64
	fmt.Println(strings.Join(",", []string{"hi"}), x)
}`,
		out: `package main

import (
	"fmt"
	"math" // fun
	"strings"
)

func main() {
	x := math.MaxInt64
	fmt.Println(strings.Join(",", []string{"hi"}), x)
}
`,
	},

	{
		name: "no_blank_after_comment",
		in: `package main

import (
	_ "io"
	_ "net/http"
	_ "net/http/pprof" // install the pprof http handlers
	_ "strings"
)

func main() {
}
`,
		out: `package main

import (
	_ "io"
	_ "net/http"
	_ "net/http/pprof" // install the pprof http handlers
	_ "strings"
)

func main() {
}
`,
	},

	{
		name: "no_blank_after_comment_reordered",
		in: `package main

import (
	_ "io"
	_ "net/http/pprof" // install the pprof http handlers
	_ "net/http"
	_ "strings"
)

func main() {
}
`,
		out: `package main

import (
	_ "io"
	_ "net/http"
	_ "net/http/pprof" // install the pprof http handlers
	_ "strings"
)

func main() {
}
`,
	},

	{
		name: "no_blank_after_comment_unnamed",
		in: `package main

import (
	"encoding/json"
	"io"
	"net/http"
	_ "net/http/pprof" // install the pprof http handlers
	"strings"

	"manypackages.com/packagea"
)

func main() {
	_ = strings.ToUpper("hello")
	_ = io.EOF
	var (
		_ json.Number
		_ *http.Request
		_ packagea.A
	)
}
`,
		out: `package main

import (
	"encoding/json"
	"io"
	"net/http"
	_ "net/http/pprof" // install the pprof http handlers
	"strings"

	"manypackages.com/packagea"
)

func main() {
	_ = strings.ToUpper("hello")
	_ = io.EOF
	var (
		_ json.Number
		_ *http.Request
		_ packagea.A
	)
}
`,
	},

	{
		name: "blank_after_package_statement_with_comment",
		in: `package p // comment

import "math"

var _ = fmt.Printf
`,
		out: `package p // comment

import "fmt"

var _ = fmt.Printf
`,
	},

	{
		name: "blank_after_package_statement_no_comment",
		in: `package p

import "math"

var _ = fmt.Printf
`,
		out: `package p

import "fmt"

var _ = fmt.Printf
`,
	},

	{
		name: "cryptorand_preferred_easy_possible",
		in: `package p

var _ = rand.Read
`,
		out: `package p

import "crypto/rand"

var _ = rand.Read
`,
	},

	{
		name: "cryptorand_preferred_easy_impossible",
		in: `package p

var _ = rand.NewZipf
`,
		out: `package p

import "math/rand/v2"

var _ = rand.NewZipf
`,
	},

	{
		name: "cryptorand_preferred_complex_possible",
		in: `package p

var _, _ = rand.Read, rand.Prime
`,
		out: `package p

import "crypto/rand"

var _, _ = rand.Read, rand.Prime
`,
	},

	{
		name: "cryptorand_preferred_complex_impossible",
		in: `package p

var _, _ = rand.Read, rand.NewZipf
`,
		out: `package p

import "math/rand"

var _, _ = rand.Read, rand.NewZipf
`,
	},
	{
		name: "unused_duplicate_imports_remove",
		in: `package main

import (
	"errors"

	"github.com/pkg/errors"
)
`,
		out: `package main
`,
	},
}

func TestSimpleCases(t *testing.T) {
	const localPrefix = "local.com,github.com/local"
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testConfig{
				modules: []packagestest.Module{
					{
						Name:  "golang.org/fake",
						Files: fm{"x.go": tt.in},
					},
					// Skeleton non-stdlib packages for use during testing.
					// Each includes one arbitrary symbol, e.g. the first declaration in the first file.
					// Try not to add more without a good reason.
					// DO NOT USE PACKAGES NOT LISTED HERE -- they will be downloaded!
					{
						Name:  "rsc.io",
						Files: fm{"p/x.go": "package p\nfunc P(){}\n"},
					},
					{
						Name:  "github.com/golang/snappy",
						Files: fm{"x.go": "package snappy\nvar ErrCorrupt error\n"},
					},
					{
						Name: "manypackages.com",
						Files: fm{
							"packagea/x.go": "package packagea\nfunc A(){}\n",
							"packageb/x.go": "package packageb\nfunc B(){}\n",
							"packagec/x.go": "package packagec\nfunc C(){}\n",
							"packaged/x.go": "package packaged\nfunc D(){}\n",
						},
					},
					{
						Name:  "local.com",
						Files: fm{"foo/x.go": "package foo\nfunc Foo(){}\n"},
					},
					{
						Name:  "github.com/local",
						Files: fm{"bar/x.go": "package bar\nfunc Bar(){}\n"},
					},
				},
			}.test(t, func(t *goimportTest) {
				options := &Options{
					LocalPrefix: localPrefix,
					TabWidth:    8,
					TabIndent:   true,
					Comments:    true,
					Fragment:    true,
					FormatOnly:  tt.formatOnly,
				}
				t.assertProcessEquals("golang.org/fake", "x.go", nil, options, tt.out)
			})

		})
	}
}

func TestAppengine(t *testing.T) {
	const input = `package p

var _, _, _ = fmt.Printf, appengine.Main, datastore.ErrInvalidEntityType
`

	const want = `package p

import (
	"fmt"

	"appengine"
	"appengine/datastore"
)

var _, _, _ = fmt.Printf, appengine.Main, datastore.ErrInvalidEntityType
`

	testConfig{
		gopathOnly: true, // can't create a module named appengine, so no module tests.
		modules: []packagestest.Module{
			{
				Name:  "golang.org/fake",
				Files: fm{"x.go": input},
			},
			{
				Name: "appengine",
				Files: fm{
					"x.go":           "package appengine\nfunc Main(){}\n",
					"datastore/x.go": "package datastore\nvar ErrInvalidEntityType error\n",
				},
			},
		},
	}.processTest(t, "golang.org/fake", "x.go", nil, nil, want)
}

func TestReadFromFilesystem(t *testing.T) {
	tests := []struct {
		name    string
		in, out string
	}{
		{
			name: "works",
			in: `package foo
func bar() {
fmt.Println("hi")
}
`,
			out: `package foo

import "fmt"

func bar() {
	fmt.Println("hi")
}
`,
		},
		{
			name: "missing_package",
			in: `
func bar() {
fmt.Println("hi")
}
`,
			out: `
import "fmt"

func bar() {
	fmt.Println("hi")
}
`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			options := &Options{
				TabWidth:  8,
				TabIndent: true,
				Comments:  true,
				Fragment:  true,
			}
			testConfig{
				module: packagestest.Module{
					Name:  "golang.org/fake",
					Files: fm{"x.go": tt.in},
				},
			}.processTest(t, "golang.org/fake", "x.go", nil, options, tt.out)
		})
	}

}

// Test support for packages in GOPATH that are actually symlinks.
// Also test that a symlink loop does not block the process.
func TestImportSymlinks(t *testing.T) {
	const input = `package p

var (
	_ = fmt.Print
	_ = mypkg.Foo
)
`
	const want = `package p

import (
	"fmt"

	"golang.org/fake/x/y/mypkg"
)

var (
	_ = fmt.Print
	_ = mypkg.Foo
)
`

	testConfig{
		module: packagestest.Module{
			Name: "golang.org/fake",
			Files: fm{
				"target/f.go":                "package mypkg\nvar Foo = 123\n",
				"x/y/mypkg":                  packagestest.Symlink("../../target"), // valid symlink
				"x/y/apkg":                   packagestest.Symlink(".."),           // symlink loop
				"myotherpackage/toformat.go": input,
			},
		},
	}.processTest(t, "golang.org/fake", "myotherpackage/toformat.go", nil, nil, want)
}

// Test support for packages in GOPATH whose files are symlinks.
func TestImportSymlinkFiles(t *testing.T) {
	const input = `package p

var (
	_ = fmt.Print
	_ = mypkg.Foo
)
`
	const want = `package p

import (
	"fmt"

	"golang.org/fake/x/y/mypkg"
)

var (
	_ = fmt.Print
	_ = mypkg.Foo
)
`

	testConfig{
		module: packagestest.Module{
			Name: "golang.org/fake",
			Files: fm{
				"target/f.go":                "package mypkg\nvar Foo = 123\n",
				"x/y/mypkg/f.go":             packagestest.Symlink("../../../target/f.go"),
				"myotherpackage/toformat.go": input,
			},
		},
	}.processTest(t, "golang.org/fake", "myotherpackage/toformat.go", nil, nil, want)
}

func TestImportSymlinksWithIgnore(t *testing.T) {
	const input = `package p

var (
	_ = fmt.Print
	_ = mypkg.Foo
)
`
	const want = `package p

import "fmt"

var (
	_ = fmt.Print
	_ = mypkg.Foo
)
`

	testConfig{
		gopathOnly: true,
		module: packagestest.Module{
			Name: "golang.org/fake",
			Files: fm{
				"target/f.go":            "package mypkg\nvar Foo = 123\n",
				"x/y/mypkg":              packagestest.Symlink("../../target"), // valid symlink
				"x/y/apkg":               packagestest.Symlink(".."),           // symlink loop
				"myotherpkg/toformat.go": input,
				"../../.goimportsignore": "golang.org/fake/x/y/mypkg\n" +
					"golang.org/fake/x/y/apkg\n",
			},
		},
	}.processTest(t, "golang.org/fake", "myotherpkg/toformat.go", nil, nil, want)
}

// Test for x/y/v2 convention for package y.
func TestModuleVersion(t *testing.T) {
	const input = `package p

import (
	"fmt"

	"github.com/foo/v2"
)

var (
	_ = fmt.Print
	_ = foo.Foo
)
`

	testConfig{
		modules: []packagestest.Module{
			{
				Name:  "mypkg.com/outpkg",
				Files: fm{"toformat.go": input},
			},
			{
				Name:  "github.com/foo/v2",
				Files: fm{"x.go": "package foo\n func Foo(){}\n"},
			},
		},
	}.processTest(t, "mypkg.com/outpkg", "toformat.go", nil, nil, input)
}

// Test for correctly identifying the name of a vendored package when it
// differs from its directory name. In this test, the import line
// "mypkg.com/mypkg_v1" would be removed if goimports wasn't able to detect
// that the package name is "mypkg".
func TestVendorPackage(t *testing.T) {
	const input = `package p
import (
	"fmt"
	"mypkg.com/mypkg_v1"
)
var _, _ = fmt.Print, mypkg.Foo
`

	const want = `package p

import (
	"fmt"

	mypkg "mypkg.com/mypkg_v1"
)

var _, _ = fmt.Print, mypkg.Foo
`

	testConfig{
		gopathOnly: true,
		module: packagestest.Module{
			Name: "mypkg.com/outpkg",
			Files: fm{
				"vendor/mypkg.com/mypkg_v1/f.go": "package mypkg\nvar Foo = 123\n",
				"toformat.go":                    input,
			},
		},
	}.processTest(t, "mypkg.com/outpkg", "toformat.go", nil, nil, want)
}

func TestInternal(t *testing.T) {
	const input = `package bar

var _ = race.Acquire
`
	const importAdded = `package bar

import "foo.com/internal/race"

var _ = race.Acquire
`

	// Packages under the same directory should be able to use internal packages.
	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"internal/race/x.go": "package race\n func Acquire(){}\n",
				"bar/x.go":           input,
			},
		},
	}.processTest(t, "foo.com", "bar/x.go", nil, nil, importAdded)

	// Packages outside the same directory should not.
	testConfig{
		modules: []packagestest.Module{
			{
				Name:  "foo.com",
				Files: fm{"internal/race/x.go": "package race\n func Acquire(){}\n"},
			},
			{
				Name:  "bar.com",
				Files: fm{"x.go": input},
			},
		},
	}.processTest(t, "bar.com", "x.go", nil, nil, input)
}

func TestProcessVendor(t *testing.T) {
	const input = `package p

var _ = hpack.HuffmanDecode
`
	const want = `package p

import "golang.org/x/net/http2/hpack"

var _ = hpack.HuffmanDecode
`
	testConfig{
		gopathOnly: true,
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"vendor/golang.org/x/net/http2/hpack/huffman.go": "package hpack\nfunc HuffmanDecode() { }\n",
				"bar/x.go": input,
			},
		},
	}.processTest(t, "foo.com", "bar/x.go", nil, nil, want)
}

func TestFindStdlib(t *testing.T) {
	tests := []struct {
		pkg     string
		symbols []string
		want    string
	}{
		{"http", []string{"Get"}, "net/http"},
		{"http", []string{"Get", "Post"}, "net/http"},
		{"http", []string{"Get", "Foo"}, ""},
		{"bytes", []string{"Buffer"}, "bytes"},
		{"ioutil", []string{"Discard"}, "io/ioutil"},
	}
	for _, tt := range tests {
		input := "package p\n"
		for _, sym := range tt.symbols {
			input += fmt.Sprintf("var _ = %s.%s\n", tt.pkg, sym)
		}
		testConfig{
			module: packagestest.Module{
				Name:  "foo.com",
				Files: fm{"x.go": input},
			},
		}.test(t, func(t *goimportTest) {
			buf, err := t.process("foo.com", "x.go", nil, nil)
			if err != nil {
				t.Fatal(err)
			}
			if got := string(buf); !strings.Contains(got, tt.want) {
				t.Errorf("Process(%q) = %q, wanted it to contain %q", input, buf, tt.want)
			}
		})
	}
}

// https://golang.org/issue/31814
func TestStdlibNotPrefixed(t *testing.T) {
	const input = `package p
var _ = bytes.Buffer
`
	const want = `package p

import "bytes"

var _ = bytes.Buffer
`
	// Force a scan of the stdlib.
	savedStdlib := stdlib.PackageSymbols
	defer func() { stdlib.PackageSymbols = savedStdlib }()
	stdlib.PackageSymbols = nil

	testConfig{
		module: packagestest.Module{
			Name:  "ignored.com",
			Files: fm{"x.go": "package x"},
		},
	}.test(t, func(t *goimportTest) {
		// Run in GOROOT/src so that the std module shows up in go list -m all.
		t.env.WorkingDir = filepath.Join(t.goroot, "src")
		got, err := t.processNonModule(filepath.Join(t.goroot, "src/x.go"), []byte(input), nil)
		if err != nil {
			t.Fatalf("Process() = %v", err)
		}
		if string(got) != want {
			t.Errorf("Got:\n%s\nWant:\n%s", got, want)
		}
	})
}

func TestStdlibSelfImports(t *testing.T) {
	const input = `package rc4

var _ = rc4.NewCipher
`

	testConfig{
		module: packagestest.Module{
			Name:  "ignored.com",
			Files: fm{"x.go": "package x"},
		},
	}.test(t, func(t *goimportTest) {
		got, err := t.processNonModule(filepath.Join(t.goroot, "src/crypto/rc4/foo.go"), []byte(input), nil)
		if err != nil {
			t.Fatalf("Process() = %v", err)
		}
		if string(got) != input {
			t.Errorf("Got:\n%s\nWant:\n%s", got, input)
		}
	})
}

type testConfig struct {
	gopathOnly bool
	module     packagestest.Module
	modules    []packagestest.Module
}

// fm is the type for a packagestest.Module's Files, abbreviated for shorter lines.
type fm map[string]any

func (c testConfig) test(t *testing.T, fn func(*goimportTest)) {
	t.Helper()

	if c.module.Name != "" {
		c.modules = []packagestest.Module{c.module}
	}

	for _, exporter := range packagestest.All {
		t.Run(exporter.Name(), func(t *testing.T) {
			t.Helper()
			if c.gopathOnly && exporter.Name() == "Modules" {
				t.Skip("test marked GOPATH-only")
			}
			exported := packagestest.Export(t, exporter, c.modules)
			defer exported.Cleanup()

			env := map[string]string{}
			for _, kv := range exported.Config.Env {
				split := strings.SplitN(kv, "=", 2)
				env[split[0]] = split[1]
			}
			it := &goimportTest{
				T: t,
				env: &ProcessEnv{
					Env:         env,
					WorkingDir:  exported.Config.Dir,
					GocmdRunner: &gocommand.Runner{},
				},
				exported: exported,
			}
			if *testDebug {
				it.env.Logf = log.Printf
			}
			// packagestest clears out GOROOT to work around golang/go#32849,
			// which isn't relevant here. Fill it back in so we can find the standard library.
			it.env.Env["GOROOT"] = build.Default.GOROOT
			it.goroot = build.Default.GOROOT

			fn(it)
		})
	}
}

func (c testConfig) processTest(t *testing.T, module, file string, contents []byte, opts *Options, want string) {
	t.Helper()
	c.test(t, func(t *goimportTest) {
		t.Helper()
		t.assertProcessEquals(module, file, contents, opts, want)
	})
}

type goimportTest struct {
	*testing.T
	goroot   string
	env      *ProcessEnv
	exported *packagestest.Exported
}

func (t *goimportTest) process(module, file string, contents []byte, opts *Options) ([]byte, error) {
	t.Helper()
	f := t.exported.File(module, file)
	if f == "" {
		t.Fatalf("%v not found in exported files (typo in filename?)", file)
	}
	return t.processNonModule(f, contents, opts)
}

func (t *goimportTest) processNonModule(file string, contents []byte, opts *Options) ([]byte, error) {
	if contents == nil {
		var err error
		contents, err = os.ReadFile(file)
		if err != nil {
			return nil, err
		}
	}
	if opts == nil {
		opts = &Options{Comments: true, TabIndent: true, TabWidth: 8}
	}
	// ProcessEnv is not safe for concurrent use. Make a copy.
	opts.Env = t.env.CopyConfig()
	return Process(file, contents, opts)
}

func (t *goimportTest) assertProcessEquals(module, file string, contents []byte, opts *Options, want string) {
	buf, err := t.process(module, file, contents, opts)
	if err != nil {
		t.Fatalf("Process() = %v", err)
	}
	if string(buf) != want {
		t.Errorf("Got:\n'%s'\nWant:\n'%s'", buf, want) // 's show empty lines
	}
}

// Tests that added imports are renamed when the import path's base doesn't
// match its package name.
func TestRenameWhenPackageNameMismatch(t *testing.T) {
	const input = `package main
 const Y = bar.X`

	const want = `package main

import bar "foo.com/foo/bar/baz"

const Y = bar.X
`
	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"foo/bar/baz/x.go": "package bar \n const X = 1",
				"test/t.go":        input,
			},
		},
	}.processTest(t, "foo.com", "test/t.go", nil, nil, want)
}

func TestPanicAstutils(t *testing.T) {
	t.Skip("panic in ast/astutil/imports.go, should be PostionFor(,false) at lines 273, 274, at least")
	const input = `package main
//line mah.go:600

import (
"foo.com/a.thing"
"foo.com/surprise"
"foo.com/v1"
"foo.com/other/v2"
"foo.com/other/v3"
)
`

	const want = `package main

//line mah.go:600

import (
	"foo.com/a.thing"
	"foo.com/go-thing"
	gow "foo.com/go-wrong"
	v2 "foo.com/other/v2"
	"foo.com/other/v3"
	bar "foo.com/surprise"
	v1 "foo.com/v1"
)

`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"test/t.go": input,
			},
		},
	}.processTest(t, "foo.com", "test/t.go", nil, nil, want)
}

// without PositionFor in sortImports this test panics
func TestPanic51916(t *testing.T) {
	const input = `package main
//line mah.go:600

import (
"foo.com/a.thing"
"foo.com/surprise"
"foo.com/v1"
"foo.com/other/v2"
"foo.com/other/v3"
"foo.com/go-thing"
"foo.com/go-wrong"
)

var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong}`

	const want = `package main

//line mah.go:600

import (
	"foo.com/a.thing"
	"foo.com/go-thing"
	gow "foo.com/go-wrong"
	v2 "foo.com/other/v2"
	"foo.com/other/v3"
	bar "foo.com/surprise"
	v1 "foo.com/v1"
)

var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong}
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"a.thing/a.go":  "package a \n const A = 1",
				"surprise/x.go": "package bar \n const X = 1",
				"v1/x.go":       "package v1 \n const Y = 1",
				"other/v2/y.go": "package v2 \n const V2 = 1",
				"other/v3/z.go": "package other \n const V3 = 1",
				"go-thing/b.go": "package thing \n const Thing = 1",
				"go-wrong/b.go": "package gow \n const Wrong = 1",
				"test/t.go":     input,
			},
		},
	}.processTest(t, "foo.com", "test/t.go", nil, nil, want)
}

// Tests that an existing import with badly mismatched path/name has its name
// correctly added. See #28645 and #29041.
// and check that //line directives are ignored (#51916)
func TestAddNameToMismatchedImport(t *testing.T) {
	const input = `package main

import (
"foo.com/a.thing"
"foo.com/surprise"
"foo.com/v1"
"foo.com/other/v2"
"foo.com/other/v3"
"foo.com/go-thing"
"foo.com/go-wrong"
)

var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong}`

	const want = `package main

import (
	"foo.com/a.thing"
	"foo.com/go-thing"
	gow "foo.com/go-wrong"
	v2 "foo.com/other/v2"
	"foo.com/other/v3"
	bar "foo.com/surprise"
	v1 "foo.com/v1"
)

var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong}
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"a.thing/a.go":  "package a \n const A = 1",
				"surprise/x.go": "package bar \n const X = 1",
				"v1/x.go":       "package v1 \n const Y = 1",
				"other/v2/y.go": "package v2 \n const V2 = 1",
				"other/v3/z.go": "package other \n const V3 = 1",
				"go-thing/b.go": "package thing \n const Thing = 1",
				"go-wrong/b.go": "package gow \n const Wrong = 1",
				"test/t.go":     input,
			},
		},
	}.processTest(t, "foo.com", "test/t.go", nil, nil, want)
}

// Tests that the LocalPrefix option causes imports
// to be added into a later group (num=3).
func TestLocalPrefix(t *testing.T) {
	tests := []struct {
		name        string
		modules     []packagestest.Module
		localPrefix string
		src         string
		want        string
	}{
		{
			name: "one_local",
			modules: []packagestest.Module{
				{
					Name: "foo.com",
					Files: fm{
						"bar/bar.go": "package bar \n const X = 1",
					},
				},
			},
			localPrefix: "foo.com/",
			src:         "package main \n const Y = bar.X \n const _ = runtime.GOOS",
			want: `package main

import (
	"runtime"

	"foo.com/bar"
)

const Y = bar.X
const _ = runtime.GOOS
`,
		},
		{
			name: "two_local",
			modules: []packagestest.Module{
				{
					Name: "foo.com",
					Files: fm{
						"foo/foo.go":     "package foo \n const X = 1",
						"foo/bar/bar.go": "package bar \n const X = 1",
					},
				},
			},
			localPrefix: "foo.com/foo",
			src:         "package main \n const Y = bar.X \n const Z = foo.X \n const _ = runtime.GOOS",
			want: `package main

import (
	"runtime"

	"foo.com/foo"
	"foo.com/foo/bar"
)

const Y = bar.X
const Z = foo.X
const _ = runtime.GOOS
`,
		},
		{
			name: "three_prefixes",
			modules: []packagestest.Module{
				{
					Name:  "example.org/pkg",
					Files: fm{"pkg.go": "package pkg \n const A = 1"},
				},
				{
					Name:  "foo.com",
					Files: fm{"bar/bar.go": "package bar \n const B = 1"},
				},
				{
					Name:  "code.org/r/p",
					Files: fm{"expproj/expproj.go": "package expproj \n const C = 1"},
				},
			},
			localPrefix: "example.org/pkg,foo.com/,code.org",
			src:         "package main \n const X = pkg.A \n const Y = bar.B \n const Z = expproj.C \n const _ = runtime.GOOS",
			want: `package main

import (
	"runtime"

	"code.org/r/p/expproj"
	"example.org/pkg"
	"foo.com/bar"
)

const X = pkg.A
const Y = bar.B
const Z = expproj.C
const _ = runtime.GOOS
`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testConfig{
				// The module being processed has to be first so it's the primary module.
				modules: append([]packagestest.Module{{
					Name:  "test.com",
					Files: fm{"t.go": tt.src},
				}}, tt.modules...),
			}.test(t, func(t *goimportTest) {
				options := &Options{
					LocalPrefix: tt.localPrefix,
					TabWidth:    8,
					TabIndent:   true,
					Comments:    true,
					Fragment:    true,
				}
				t.assertProcessEquals("test.com", "t.go", nil, options, tt.want)
			})
		})
	}
}

// Tests that "package documentation" files are ignored.
func TestIgnoreDocumentationPackage(t *testing.T) {
	const input = `package x

const Y = foo.X
`
	const want = `package x

import "foo.com/foo"

const Y = foo.X
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"foo/foo.go": "package foo\nconst X = 1\n",
				"foo/doc.go": "package documentation \n // just to confuse things\n",
				"x/x.go":     input,
			},
		},
	}.processTest(t, "foo.com", "x/x.go", nil, nil, want)
}

// Tests importPathToNameGoPathParse and in particular that it stops
// after finding the first non-documentation package name, not
// reporting an error on inconsistent package names (since it should
// never make it that far).
func TestImportPathToNameGoPathParse(t *testing.T) {
	testConfig{
		module: packagestest.Module{
			Name: "example.net/pkg",
			Files: fm{
				"doc.go": "package documentation\n", // ignored
				"gen.go": "package main\n",          // also ignored
				"pkg.go": "package the_pkg_name_to_find\n  and this syntax error is ignored because of parser.PackageClauseOnly",
				"z.go":   "package inconsistent\n", // inconsistent but ignored
			},
		},
	}.test(t, func(t *goimportTest) {
		if strings.Contains(t.Name(), "GoPackages") {
			t.Skip("go/packages does not ignore package main")
		}
		r, err := t.env.GetResolver()
		if err != nil {
			t.Fatal(err)
		}
		srcDir := filepath.Dir(t.exported.File("example.net/pkg", "z.go"))
		names, err := r.loadPackageNames([]string{"example.net/pkg"}, srcDir)
		if err != nil {
			t.Fatal(err)
		}
		const want = "the_pkg_name_to_find"
		if got := names["example.net/pkg"]; got != want {
			t.Errorf("loadPackageNames(..) = %q; want %q", got, want)
		}
	})
}

func TestIgnoreConfiguration(t *testing.T) {
	const input = `package x

const _ = pkg.X
`
	const want = `package x

import "foo.com/otherwise-longer-so-worse-example/foo/pkg"

const _ = pkg.X
`

	testConfig{
		gopathOnly: true,
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"../.goimportsignore":                              "# comment line\n\n foo.com/example", // tests comment, blank line, whitespace trimming
				"example/pkg/pkg.go":                               "package pkg\nconst X = 1",
				"otherwise-longer-so-worse-example/foo/pkg/pkg.go": "package pkg\nconst X = 1",
				"x/x.go": input,
			},
		},
	}.processTest(t, "foo.com", "x/x.go", nil, nil, want)
}

// Skip "node_modules" directory.
func TestSkipNodeModules(t *testing.T) {
	const input = `package x

const _ = pkg.X
`
	const want = `package x

import "foo.com/otherwise-longer/not_modules/pkg"

const _ = pkg.X
`

	testConfig{
		gopathOnly: true,
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"example/node_modules/pkg/a.go":         "package pkg\nconst X = 1",
				"otherwise-longer/not_modules/pkg/a.go": "package pkg\nconst X = 1",
				"x/x.go":                                input,
			},
		},
	}.processTest(t, "foo.com", "x/x.go", nil, nil, want)
}

// Tests that package global variables with the same name and function name as
// a function in a separate package do not result in an import which masks
// the global variable
func TestGlobalImports(t *testing.T) {
	const usesGlobal = `package pkg

func doSomething() {
	t := time.Now()
}
`

	const declaresGlobal = `package pkg

type Time struct{}

func (t Time) Now() Time {
	return Time{}
}

var time Time
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"pkg/uses.go":   usesGlobal,
				"pkg/global.go": declaresGlobal,
			},
		},
	}.processTest(t, "foo.com", "pkg/uses.go", nil, nil, usesGlobal)
}

// Some people put multiple packages' files in the same directory. Globals
// declared in other packages should be ignored.
func TestGlobalImports_DifferentPackage(t *testing.T) {
	const declaresGlobal = `package main
var fmt int
`
	const input = `package pkg
var _ = fmt.Printf
`
	const want = `package pkg

import "fmt"

var _ = fmt.Printf
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"pkg/main.go": declaresGlobal,
				"pkg/uses.go": input,
			},
		},
	}.processTest(t, "foo.com", "pkg/uses.go", nil, nil, want)
}

func TestGlobalImports_MultipleMains(t *testing.T) {
	const declaresGlobal = `package main
var fmt int
`
	const input = `package main
import "fmt"
var _, _ = fmt.Printf, bytes.Equal
`
	const want = `package main

import (
	"bytes"
	"fmt"
)

var _, _ = fmt.Printf, bytes.Equal
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"pkg/main.go": declaresGlobal,
				"pkg/uses.go": input,
			},
		},
	}.processTest(t, "foo.com", "pkg/uses.go", nil, nil, want)
}

// Tests that sibling files - other files in the same package - can provide an
// import that may not be the default one otherwise.
func TestSiblingImports(t *testing.T) {

	// provide is the sibling file that provides the desired import.
	const provide = `package siblingimporttest

import "local/log"
import "my/bytes"
import renamed "fmt"

func LogSomething() {
	log.Print("Something")
	bytes.SomeFunc()
	renamed.Println("Something")
}
`

	// need is the file being tested that needs the import.
	const need = `package siblingimporttest

var _ = bytes.Buffer{}

func LogSomethingElse() {
	log.Print("Something else")
	renamed.Println("Yet another")
}
`

	// want is the expected result file
	const want = `package siblingimporttest

import (
	"bytes"
	renamed "fmt"
	"local/log"
)

var _ = bytes.Buffer{}

func LogSomethingElse() {
	log.Print("Something else")
	renamed.Println("Yet another")
}
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"p/needs_import.go":    need,
				"p/provides_import.go": provide,
			},
		},
	}.processTest(t, "foo.com", "p/needs_import.go", nil, nil, want)
}

// Tests #29180: a sibling import of the right package with the wrong name is used.
func TestSiblingImport_Misnamed(t *testing.T) {
	const sibling = `package main
import renamed "fmt"
var _ = renamed.Printf
`
	const input = `package pkg
var _ = fmt.Printf
`
	const want = `package pkg

import "fmt"

var _ = fmt.Printf
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"pkg/main.go": sibling,
				"pkg/uses.go": input,
			},
		},
	}.processTest(t, "foo.com", "pkg/uses.go", nil, nil, want)

}

// Tests that an input file's own package is ignored.
func TestIgnoreOwnPackage(t *testing.T) {
	const input = `package pkg

const _ = pkg.X
`
	const want = `package pkg

const _ = pkg.X
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"pkg/a.go": "package pkg\nconst X = 1",
				"pkg/b.go": input,
			},
		},
	}.processTest(t, "foo.com", "pkg/b.go", nil, nil, want)
}

func TestExternalTestImportsPackageUnderTest(t *testing.T) {
	const provide = `package pkg
func DoIt(){}
`
	const input = `package pkg_test

var _ = pkg.DoIt`

	const want = `package pkg_test

import "foo.com/pkg"

var _ = pkg.DoIt
`

	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"pkg/provide.go": provide,
				"pkg/x_test.go":  input,
			},
		},
	}.processTest(t, "foo.com", "pkg/x_test.go", nil, nil, want)
}

func TestPkgIsCandidate(t *testing.T) {
	tests := []struct {
		name     string
		filename string
		pkgIdent string
		pkg      *pkg
		want     bool
	}{
		{
			name:     "normal_match",
			filename: "/gopath/src/my/pkg/pkg.go",
			pkgIdent: "client",
			pkg: &pkg{
				dir:             "/gopath/src/client",
				importPathShort: "client",
			},
			want: true,
		},
		{
			name:     "no_match",
			filename: "/gopath/src/my/pkg/pkg.go",
			pkgIdent: "zzz",
			pkg: &pkg{
				dir:             "/gopath/src/client",
				importPathShort: "client",
			},
			want: false,
		},
		{
			name:     "match_too_early",
			filename: "/gopath/src/my/pkg/pkg.go",
			pkgIdent: "client",
			pkg: &pkg{
				dir:             "/gopath/src/client/foo/foo/foo",
				importPathShort: "client/foo/foo",
			},
			want: false,
		},
		{
			name:     "substring_match",
			filename: "/gopath/src/my/pkg/pkg.go",
			pkgIdent: "client",
			pkg: &pkg{
				dir:             "/gopath/src/foo/go-client",
				importPathShort: "foo/go-client",
			},
			want: true,
		},
		{
			name:     "hidden_internal",
			filename: "/gopath/src/my/pkg/pkg.go",
			pkgIdent: "client",
			pkg: &pkg{
				dir:             "/gopath/src/foo/internal/client",
				importPathShort: "foo/internal/client",
			},
			want: false,
		},
		{
			name:     "visible_internal",
			filename: "/gopath/src/foo/bar.go",
			pkgIdent: "client",
			pkg: &pkg{
				dir:             "/gopath/src/foo/internal/client",
				importPathShort: "foo/internal/client",
			},
			want: true,
		},
		{
			name:     "invisible_vendor",
			filename: "/gopath/src/foo/bar.go",
			pkgIdent: "client",
			pkg: &pkg{
				dir:             "/gopath/src/other/vendor/client",
				importPathShort: "client",
			},
			want: false,
		},
		{
			name:     "visible_vendor",
			filename: "/gopath/src/foo/bar.go",
			pkgIdent: "client",
			pkg: &pkg{
				dir:             "/gopath/src/foo/vendor/client",
				importPathShort: "client",
			},
			want: true,
		},
		{
			name:     "match_with_hyphens",
			filename: "/gopath/src/foo/bar.go",
			pkgIdent: "socketio",
			pkg: &pkg{
				dir:             "/gopath/src/foo/socket-io",
				importPathShort: "foo/socket-io",
			},
			want: true,
		},
		{
			name:     "match_with_mixed_case",
			filename: "/gopath/src/foo/bar.go",
			pkgIdent: "fooprod",
			pkg: &pkg{
				dir:             "/gopath/src/foo/FooPROD",
				importPathShort: "foo/FooPROD",
			},
			want: true,
		},
		{
			name:     "matches_with_hyphen_and_caps",
			filename: "/gopath/src/foo/bar.go",
			pkgIdent: "fooprod",
			pkg: &pkg{
				dir:             "/gopath/src/foo/Foo-PROD",
				importPathShort: "foo/Foo-PROD",
			},
			want: true,
		},
	}
	for i, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			refs := References{tt.pkgIdent: nil}
			got := pkgIsCandidate(tt.filename, refs, tt.pkg)
			if got != tt.want {
				t.Errorf("test %d. pkgIsCandidate(%q, %q, %+v) = %v; want %v",
					i, tt.filename, tt.pkgIdent, *tt.pkg, got, tt.want)
			}
		})
	}
}

// Issue 20941: this used to panic on Windows.
func TestProcessStdin(t *testing.T) {
	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
		},
	}.test(t, func(t *goimportTest) {
		got, err := t.processNonModule("<standard input>", []byte("package main\nfunc main() {\n\tfmt.Println(123)\n}\n"), nil)
		if err != nil {
			t.Fatal(err)
		}
		if !strings.Contains(string(got), `"fmt"`) {
			t.Errorf("expected fmt import; got: %s", got)
		}
	})
}

// Tests LocalPackagePromotion when there is a local package that matches, it
// should be the closest match.
// https://golang.org/issues/17557
func TestLocalPackagePromotion(t *testing.T) {
	const input = `package main
var c = &config.SystemConfig{}
`
	const want = `package main

import "mycompany.net/tool/config"

var c = &config.SystemConfig{}
`

	testConfig{
		modules: []packagestest.Module{
			{
				Name:  "config.net/config",
				Files: fm{"config.go": "package config\n type SystemConfig struct {}"}, // Will match but should not be first choice
			},
			{
				Name:  "mycompany.net/config",
				Files: fm{"config.go": "package config\n type SystemConfig struct {}"}, // Will match but should not be first choice
			},
			{
				Name: "mycompany.net/tool",
				Files: fm{
					"config/config.go": "package config\n type SystemConfig struct {}", // Local package should be promoted over shorter package
					"main.go":          input,
				},
			},
		},
	}.processTest(t, "mycompany.net/tool", "main.go", nil, nil, want)
}

// Tests FindImportInLocalGoFiles looks at the import lines for other Go files in the
// local directory, since the user is likely to import the same packages in the current
// Go file.  If an import is found that satisfies the need, it should be used over the
// standard library.
// https://golang.org/issues/17557
func TestFindImportInLocalGoFiles(t *testing.T) {
	const input = `package main
 var _ = &bytes.Buffer{}`

	const want = `package main

import "bytes.net/bytes"

var _ = &bytes.Buffer{}
`
	testConfig{
		modules: []packagestest.Module{
			{
				Name: "mycompany.net/tool",
				Files: fm{
					"io.go":   "package main\n import \"bytes.net/bytes\"\n var _ = &bytes.Buffer{}", // Contains package import that will cause stdlib to be ignored
					"main.go": input,
				},
			},
			{
				Name:  "bytes.net/bytes",
				Files: fm{"bytes.go": "package bytes\n type Buffer struct {}"}, // Should be selected over standard library
			},
		},
	}.processTest(t, "mycompany.net/tool", "main.go", nil, nil, want)
}

func TestInMemoryFile(t *testing.T) {
	const input = `package main
 var _ = &bytes.Buffer{}`

	const want = `package main

import "bytes"

var _ = &bytes.Buffer{}
`
	testConfig{
		module: packagestest.Module{
			Name:  "foo.com",
			Files: fm{"x.go": "package x\n"},
		},
	}.processTest(t, "foo.com", "x.go", []byte(input), nil, want)
}

func TestImportNoGoFiles(t *testing.T) {
	const input = `package main
 var _ = &bytes.Buffer{}`

	const want = `package main

import "bytes"

var _ = &bytes.Buffer{}
`
	testConfig{
		module: packagestest.Module{
			Name: "mycompany.net",
		},
	}.test(t, func(t *goimportTest) {
		buf, err := t.processNonModule("mycompany.net/tool/main.go", []byte(input), nil)
		if err != nil {
			t.Fatalf("Process() = %v", err)
		}
		if string(buf) != want {
			t.Errorf("Got:\n%s\nWant:\n%s", buf, want)
		}
	})

}

// Ensures a token as large as 500000 bytes can be handled
// https://golang.org/issues/18201
func TestProcessLargeToken(t *testing.T) {
	largeString := strings.Repeat("x", 500000)

	input := `package testimports

import (
	"bytes"
)

const s = fmt.Sprintf("%s", "` + largeString + `")
var _ = bytes.Buffer{}

// end
`

	want := `package testimports

import (
	"bytes"
	"fmt"
)

const s = fmt.Sprintf("%s", "` + largeString + `")

var _ = bytes.Buffer{}

// end
`

	testConfig{
		module: packagestest.Module{
			Name:  "foo.com",
			Files: fm{"foo.go": input},
		},
	}.processTest(t, "foo.com", "foo.go", nil, nil, want)
}

// Tests that an external test package will import the package under test if it
// also uses symbols exported only in test files.
// https://golang.org/issues/29979
func TestExternalTest(t *testing.T) {
	const input = `package a_test
func TestX() {
	a.X()
	a.Y()
}
`
	const want = `package a_test

import "foo.com/a"

func TestX() {
	a.X()
	a.Y()
}
`

	testConfig{
		modules: []packagestest.Module{
			{
				Name: "foo.com/a",
				Files: fm{
					"a.go":           "package a\n func X() {}",
					"export_test.go": "package a\n func Y() {}",
					"a_test.go":      input,
				},
			},
		},
	}.processTest(t, "foo.com/a", "a_test.go", nil, nil, want)
}

// TestGetCandidates tests that get packages finds packages
// with correct priorities.
func TestGetCandidates(t *testing.T) {
	type res struct {
		relevance  float64
		name, path string
	}
	want := []res{
		{0, "bytes", "bytes"},
		{0, "http", "net/http"},
		{0, "rand", "crypto/rand"},
		{0, "bar", "bar.com/bar"},
		{0, "foo", "foo.com/foo"},
	}

	testConfig{
		modules: []packagestest.Module{
			{
				Name:  "bar.com",
				Files: fm{"bar/bar.go": "package bar\n"},
			},
			{
				Name:  "foo.com",
				Files: fm{"foo/foo.go": "package foo\n"},
			},
		},
	}.test(t, func(t *goimportTest) {
		var mu sync.Mutex
		var got []res
		add := func(c ImportFix) {
			mu.Lock()
			defer mu.Unlock()
			for _, w := range want {
				if c.StmtInfo.ImportPath == w.path {
					got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath})
				}
			}
		}
		if err := GetAllCandidates(context.Background(), add, "", "x.go", "x", t.env); err != nil {
			t.Fatalf("GetAllCandidates() = %v", err)
		}
		// Sort, then clear out relevance so it doesn't mess up the DeepEqual.
		sort.Slice(got, func(i, j int) bool {
			ri, rj := got[i], got[j]
			if ri.relevance != rj.relevance {
				return ri.relevance > rj.relevance // Highest first.
			}
			return ri.name < rj.name
		})
		for i := range got {
			got[i].relevance = 0
		}
		if !reflect.DeepEqual(want, got) {
			t.Errorf("wanted results in order %v, got %v", want, got)
		}
	})
}

func TestGetImportPaths(t *testing.T) {
	type res struct {
		relevance  float64
		name, path string
	}
	want := []res{
		{0, "http", "net/http"},
		{0, "net", "net"},
		{0, "neta", "neta.com/neta"},
	}

	testConfig{
		modules: []packagestest.Module{
			{
				Name:  "neta.com",
				Files: fm{"neta/neta.go": "package neta\n"},
			},
		},
	}.test(t, func(t *goimportTest) {
		var mu sync.Mutex
		var got []res
		add := func(c ImportFix) {
			mu.Lock()
			defer mu.Unlock()
			for _, w := range want {
				if c.StmtInfo.ImportPath == w.path {
					got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath})
				}
			}
		}
		if err := GetImportPaths(context.Background(), add, "ne", "x.go", "x", t.env); err != nil {
			t.Fatalf("GetImportPaths() = %v", err)
		}
		// Sort, then clear out relevance so it doesn't mess up the DeepEqual.
		sort.Slice(got, func(i, j int) bool {
			ri, rj := got[i], got[j]
			if ri.relevance != rj.relevance {
				return ri.relevance > rj.relevance // Highest first.
			}
			return ri.name < rj.name
		})
		for i := range got {
			got[i].relevance = 0
		}
		if !reflect.DeepEqual(want, got) {
			t.Errorf("wanted results in order %v, got %v", want, got)
		}
	})
}

func TestGetPackageCompletions(t *testing.T) {
	type res struct {
		relevance          float64
		name, path, symbol string
	}
	want := []res{
		{0, "rand", "math/rand", "Seed"},
		{0, "rand", "bar.com/rand", "Bar"},
	}

	testConfig{
		modules: []packagestest.Module{
			{
				Name:  "bar.com",
				Files: fm{"rand/bar.go": "package rand\nvar Bar int\n"},
			},
		},
	}.test(t, func(t *goimportTest) {
		var mu sync.Mutex
		var got []res
		add := func(c PackageExport) {
			mu.Lock()
			defer mu.Unlock()
			for _, csym := range c.Exports {
				for _, w := range want {
					if c.Fix.StmtInfo.ImportPath == w.path && csym.Name == w.symbol {
						got = append(got, res{c.Fix.Relevance, c.Fix.IdentName, c.Fix.StmtInfo.ImportPath, csym.Name})
					}
				}
			}
		}
		if err := GetPackageExports(context.Background(), add, "rand", "x.go", "x", t.env); err != nil {
			t.Fatalf("getPackageCompletions() = %v", err)
		}
		// Sort, then clear out relevance so it doesn't mess up the DeepEqual.
		sort.Slice(got, func(i, j int) bool {
			ri, rj := got[i], got[j]
			if ri.relevance != rj.relevance {
				return ri.relevance > rj.relevance // Highest first.
			}
			return ri.name < rj.name
		})
		for i := range got {
			got[i].relevance = 0
		}
		if !reflect.DeepEqual(want, got) {
			t.Errorf("wanted results in order %v, got %v", want, got)
		}
	})
}

// Tests #34895: process should not panic on concurrent calls.
func TestConcurrentProcess(t *testing.T) {
	testConfig{
		module: packagestest.Module{
			Name: "foo.com",
			Files: fm{
				"p/first.go": `package foo

func _() {
	fmt.Println()
}
`,
				"p/second.go": `package foo

import "fmt"

func _() {
	fmt.Println()
	imports.Bar() // not imported.
}
`,
			},
		},
	}.test(t, func(t *goimportTest) {
		var (
			n  = 10
			wg sync.WaitGroup
		)
		wg.Add(n)
		for range n {
			go func() {
				defer wg.Done()
				_, err := t.process("foo.com", "p/first.go", nil, nil)
				if err != nil {
					t.Error(err)
				}
			}()
		}
		wg.Wait()
	})
}

func TestNonlocalDot(t *testing.T) {
	const input = `package main
import (
	"fmt"
)
var _, _ = fmt.Sprintf, dot.Dot
`
	const want = `package main

import (
	"fmt"
	"noninternet/dot.v1/dot"
)

var _, _ = fmt.Sprintf, dot.Dot
`
	testConfig{
		modules: []packagestest.Module{
			{
				Name:  "golang.org/fake",
				Files: fm{"x.go": input},
			},
			{
				Name: "noninternet/dot.v1",
				Files: fm{
					"dot/dot.go": "package dot\nfunc Dot(){}\n",
				},
			},
		},
		gopathOnly: true, // our modules testing setup doesn't allow modules without dots.
	}.processTest(t, "golang.org/fake", "x.go", nil, nil, want)
}

func TestSymbolSearchStarvation(t *testing.T) {
	// This test verifies the fix for golang/go#67923: searching through
	// candidates should not starve when the context is cancelled.
	//
	// To reproduce the conditions that led to starvation, cancel the context
	// half way through the search, by leveraging the loadExports callback.
	const candCount = 100
	var loaded atomic.Int32
	ctx, cancel := context.WithCancel(context.Background())
	searcher := symbolSearcher{
		logf:   t.Logf,
		srcDir: "/path/to/foo",
		loadExports: func(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) {
			if loaded.Add(1) > candCount/2 {
				cancel()
			}
			return "bar", []stdlib.Symbol{
				{Name: "A", Kind: stdlib.Var},
				{Name: "B", Kind: stdlib.Var},
				// Missing: "C", so that none of these packages match.
			}, nil
		},
	}

	var candidates []pkgDistance
	for i := range candCount {
		name := fmt.Sprintf("bar%d", i)
		candidates = append(candidates, pkgDistance{
			pkg: &pkg{
				dir:             path.Join(searcher.srcDir, name),
				importPathShort: "foo/" + name,
				packageName:     name,
				relevance:       1,
			},
			distance: 1,
		})
	}

	// We don't actually care what happens, as long as it doesn't deadlock!
	_, err := searcher.search(ctx, candidates, "bar", map[string]bool{"A": true, "B": true, "C": true})
	t.Logf("search completed with err: %v", err)
}

func TestMatchesPath(t *testing.T) {
	tests := []struct {
		ident string
		path  string
		want  bool
	}{
		// degenerate cases
		{"", "", true},
		{"", "x", false},
		{"x", "", false},

		// full segment matching
		{"x", "x", true},
		{"x", "y", false},
		{"x", "wx", false},
		{"x", "path/to/x", true},
		{"mypkg", "path/to/mypkg", true},
		{"x", "path/to/xy", false},
		{"x", "path/to/x/y", true},
		{"mypkg", "path/to/mypkg/y", true},
		{"x", "path/to/x/v3", true},

		// subsegment matching
		{"x", "path/to/x-go", true},
		{"foo", "path/to/go-foo", true},
		{"go", "path/to/go-foo", true},
		{"gofoo", "path/to/go-foo", true},
		{"gofoo", "path/to/go-foo-bar", false},
		{"foo", "path/to/go-foo-bar", true},
		{"bar", "path/to/go-foo-bar", true},
		{"gofoobar", "path/to/go-foo-bar", true},
		{"x", "path/to/x.v3", true},
		{"x", "path/to/xy.v3", false},
		{"x", "path/to/wx.v3", false},

		// case insensitivity
		{"MyPkg", "path/to/mypkg", true},
		{"myPkg", "path/to/MyPkg", true},

		// multi-byte runes
		{"世界", "path/to/世界", true},
		{"世界", "path/to/世界/foo", true},
		{"世界", "path/to/go-世界/foo", true},
		{"世界", "path/to/世/foo", false},
	}

	for _, test := range tests {
		if got := matchesPath(test.ident, test.path); got != test.want {
			t.Errorf("matchesPath(%q, %q) = %v, want %v", test.ident, test.path, got, test.want)
		}
	}
}

func BenchmarkMatchesPath(b *testing.B) {
	// A collection of calls that exercise different kinds of matching.
	tests := map[string][]struct {
		ident string
		path  string
		want  bool
	}{
		"easy": { // lower case ascii
			{"mypkg", "path/to/mypkg/y", true},
			{"foo", "path/to/go-foo-bar", true},
			{"gofoo", "path/to/go-foo-bar-baz", false},
		},
		"hard": {
			{"MyPkg", "path/to/mypkg", true},
			{"世界", "path/to/go-世界-pkg/foo", true},
			{"longpkgname", "cloud.google.com/Go/Spanner/Admin/Database/longpkgname", true},
		},
	}

	for name, tests := range tests {
		b.Run(name, func(b *testing.B) {
			for b.Loop() {
				for _, test := range tests {
					if got := matchesPath(test.ident, test.path); got != test.want {
						b.Errorf("matchesPath(%q, %q) = %v, want %v", test.ident, test.path, got, test.want)
					}
				}
			}
		})
	}
}
