blob: 46e157b2a0605f5533086bcc8f0a22a38d52a77a [file]
// Copyright 2019 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 postgres
import (
"context"
"encoding/json"
"errors"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/source"
"golang.org/x/pkgsite/internal/testing/sample"
)
func TestGetNestedModules(t *testing.T) {
t.Parallel()
ctx := context.Background()
for _, test := range []struct {
name string
path string
modules []*internal.Module
wantModulePaths []string
}{
{
name: "Nested Modules in cloud.google.com/go that have the same module prefix path",
path: "cloud.google.com/go",
modules: []*internal.Module{
sample.Module("cloud.google.com/go", "v0.46.2", "storage", "spanner", "pubsub"),
sample.Module("cloud.google.com/go/storage", "v1.10.0", sample.Suffix),
sample.Module("cloud.google.com/go/spanner", "v1.9.0", sample.Suffix),
sample.Module("cloud.google.com/go/pubsub", "v1.6.1", sample.Suffix),
},
wantModulePaths: []string{
"cloud.google.com/go/pubsub",
"cloud.google.com/go/spanner",
"cloud.google.com/go/storage",
},
},
{
name: "Nested Modules in cloud.google.com/go that have multiple major versions",
path: "cloud.google.com/go",
modules: []*internal.Module{
sample.Module("cloud.google.com/go", "v0.46.2", "storage", "spanner", "pubsub"),
sample.Module("cloud.google.com/go/storage", "v1.10.0", sample.Suffix),
sample.Module("cloud.google.com/go/storage/v9", "v9.0.0", sample.Suffix),
sample.Module("cloud.google.com/go/storage/v11", "v11.0.0", sample.Suffix),
},
wantModulePaths: []string{
"cloud.google.com/go/storage/v11",
},
},
{
name: "Nested Modules in golang.org/x/tools/v2 that have the same module prefix path",
path: "golang.org/x/tools/v2",
modules: []*internal.Module{
sample.Module("golang.org/x/tools", "v0.0.1", sample.Suffix),
sample.Module("golang.org/x/tools/gopls", "v0.5.1", sample.Suffix),
},
wantModulePaths: []string{
"golang.org/x/tools/gopls",
},
},
} {
t.Run(test.name, func(t *testing.T) {
testDB, release := acquire(t)
defer release()
for _, v := range test.modules {
testDB.MustInsertModule(t, v)
}
gotModules, err := testDB.GetNestedModules(ctx, test.path)
if err != nil {
t.Fatal(err)
}
var gotModulePaths []string
for _, mod := range gotModules {
gotModulePaths = append(gotModulePaths, mod.ModulePath)
}
if diff := cmp.Diff(test.wantModulePaths, gotModulePaths); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestGetNestedModules_Excluded(t *testing.T) {
t.Parallel()
testDB, release := acquire(t)
defer release()
ctx := context.Background()
test := struct {
name string
path string
modules []*internal.Module
wantModulePaths []string
}{
name: "Nested Modules in cloud.google.com/go that have the same module prefix path",
path: "cloud.google.com/go",
modules: []*internal.Module{
sample.Module("cloud.google.com/go", "v0.46.2", "storage", "spanner", "pubsub"),
// cloud.google.com/storage will be excluded below.
sample.Module("cloud.google.com/go/storage", "v1.10.0", sample.Suffix),
sample.Module("cloud.google.com/go/pubsub", "v1.6.1", sample.Suffix),
sample.Module("cloud.google.com/go/spanner", "v1.9.0", sample.Suffix),
},
wantModulePaths: []string{
"cloud.google.com/go/pubsub",
"cloud.google.com/go/spanner",
},
}
for _, m := range test.modules {
testDB.MustInsertModule(t, m)
}
if err := testDB.InsertExcludedPattern(ctx, "cloud.google.com/go/storage", "postgres", "test"); err != nil {
t.Fatal(err)
}
gotModules, err := testDB.GetNestedModules(ctx, "cloud.google.com/go")
if err != nil {
t.Fatal(err)
}
var gotModulePaths []string
for _, mod := range gotModules {
gotModulePaths = append(gotModulePaths, mod.ModulePath)
}
if diff := cmp.Diff(test.wantModulePaths, gotModulePaths); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
}
func TestPostgres_GetModuleInfo(t *testing.T) {
t.Parallel()
testDB, release := acquire(t)
defer release()
ctx := context.Background()
testCases := []struct {
name, path, version string
modules []*internal.Module
wantIndex int // index into versions
wantErr error
}{
{
name: "version present",
path: "mod.1",
version: "v1.0.2",
modules: []*internal.Module{
sample.Module("mod.1", "v1.1.0", sample.Suffix),
sample.Module("mod.1", "v1.0.2", sample.Suffix),
sample.Module("mod.1", "v1.0.0", sample.Suffix),
},
wantIndex: 1,
},
{
name: "version not present",
path: "mod.2",
version: "v1.0.3",
modules: []*internal.Module{
sample.Module("mod.2", "v1.1.0", sample.Suffix),
sample.Module("mod.2", "v1.0.2", sample.Suffix),
sample.Module("mod.2", "v1.0.0", sample.Suffix),
},
wantErr: derrors.NotFound,
},
{
name: "no versions",
path: "mod3",
version: "v1.2.3",
wantErr: derrors.NotFound,
},
}
latestGoodVersions := map[string]string{
"mod.1": "v1.1.0",
"mod.2": "v1.1.0",
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
for _, v := range test.modules {
testDB.MustInsertModule(t, v)
}
gotVI, err := testDB.GetModuleInfo(ctx, test.path, test.version)
if err != nil {
if test.wantErr == nil {
t.Fatalf("got unexpected error %v", err)
}
if !errors.Is(err, test.wantErr) {
t.Fatalf("got error %v, want Is(%v)", err, test.wantErr)
}
return
}
if test.wantIndex >= len(test.modules) {
t.Fatal("wantIndex too large")
}
wantVI := &test.modules[test.wantIndex].ModuleInfo
wantVI.LatestVersion = latestGoodVersions[wantVI.ModulePath]
if diff := cmp.Diff(wantVI, gotVI,
cmpopts.EquateEmpty(),
cmp.AllowUnexported(source.Info{}),
); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestGetImportedBy(t *testing.T) {
t.Parallel()
ctx := context.Background()
var (
m1 = sample.Module("path.to/foo", "v1.1.0", "bar")
m2 = sample.Module("path2.to/foo", "v1.2.0", "bar2")
m3 = sample.Module("path3.to/foo", "v1.3.0", "bar3")
testModules = []*internal.Module{m1, m2, m3}
pkg1 = m1.Packages()[0]
pkg2 = m2.Packages()[0]
pkg3 = m3.Packages()[0]
)
pkg1.Imports = nil
pkg2.Imports = []string{pkg1.Path}
pkg3.Imports = []string{pkg2.Path, pkg1.Path}
for _, test := range []struct {
name, path, modulePath, version string
wantImports []string
wantImportedBy []string
}{
{
name: "multiple imports no imported by",
path: pkg3.Path,
modulePath: m3.ModulePath,
version: "v1.3.0",
wantImports: pkg3.Imports,
wantImportedBy: nil,
},
{
name: "one import one imported by",
path: pkg2.Path,
modulePath: m2.ModulePath,
version: "v1.2.0",
wantImports: pkg2.Imports,
wantImportedBy: []string{pkg3.Path},
},
{
name: "no imports two imported by",
path: pkg1.Path,
modulePath: m1.ModulePath,
version: "v1.1.0",
wantImports: nil,
wantImportedBy: []string{pkg2.Path, pkg3.Path},
},
{
name: "no imports one imported by",
path: pkg1.Path,
modulePath: m2.ModulePath, // should cause pkg2 to be excluded.
version: "v1.1.0",
wantImports: nil,
wantImportedBy: []string{pkg3.Path},
},
} {
t.Run(test.name, func(t *testing.T) {
testDB, release := acquire(t)
defer release()
for _, v := range testModules {
testDB.MustInsertModule(t, v)
}
gotImportedBy, err := testDB.GetImportedBy(ctx, test.path, test.modulePath, "", 100)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(test.wantImportedBy, gotImportedBy); diff != "" {
t.Errorf("testDB.GetImportedBy(%q, %q) mismatch (-want +got):\n%s", test.path, test.modulePath, diff)
}
})
}
}
func TestJSONBScanner(t *testing.T) {
t.Parallel()
type S struct{ A int }
want := &S{1}
val, err := json.Marshal(want)
if err != nil {
t.Fatal(err)
}
var got *S
js := jsonbScanner{&got}
if err := js.Scan(val); err != nil {
t.Fatal(err)
}
if *got != *want {
t.Errorf("got %+v, want %+v", *got, *want)
}
var got2 *S
js = jsonbScanner{&got2}
if err := js.Scan(nil); err != nil {
t.Fatal(err)
}
if got2 != nil {
t.Errorf("got %#v, want nil", got2)
}
}
func TestGetImportedByPagination(t *testing.T) {
t.Parallel()
testDB, release := acquire(t)
defer release()
ctx := t.Context()
m0 := sample.Module("path.to/foo", "v1.0.0", "pkg0")
pkg0 := m0.Packages()[0]
pkg0.Imports = nil
m1 := sample.Module("path.to/a", "v1.0.0", "pkg1")
pkg1 := m1.Packages()[0]
pkg1.Imports = []string{pkg0.Path}
m2 := sample.Module("path.to/b", "v1.0.0", "pkg2")
pkg2 := m2.Packages()[0]
pkg2.Imports = []string{pkg0.Path}
m3 := sample.Module("path.to/c", "v1.0.0", "pkg3")
pkg3 := m3.Packages()[0]
pkg3.Imports = []string{pkg0.Path}
m4 := sample.Module("path.to/d", "v1.0.0", "pkg4")
pkg4 := m4.Packages()[0]
pkg4.Imports = []string{pkg0.Path}
m5 := sample.Module("path.to/e", "v1.0.0", "pkg5")
pkg5 := m5.Packages()[0]
pkg5.Imports = []string{pkg0.Path}
testModules := []*internal.Module{m0, m1, m2, m3, m4, m5}
for _, v := range testModules {
testDB.MustInsertModule(t, v)
}
wantImporters := []string{pkg1.Path, pkg2.Path, pkg3.Path, pkg4.Path, pkg5.Path}
// Page 1: limit 2, start ""
page1, err := testDB.GetImportedBy(ctx, pkg0.Path, m0.ModulePath, "", 2)
if err != nil {
t.Fatal(err)
}
if len(page1) != 2 {
t.Fatalf("expected 2 items, got %d", len(page1))
}
if diff := cmp.Diff(wantImporters[:2], page1); diff != "" {
t.Errorf("page 1 mismatch (-want +got):\n%s", diff)
}
// Page 2: limit 2, start page1[1]
page2, err := testDB.GetImportedBy(ctx, pkg0.Path, m0.ModulePath, page1[1]+"a", 2)
if err != nil {
t.Fatal(err)
}
if len(page2) != 2 {
t.Fatalf("expected 2 items, got %d", len(page2))
}
if diff := cmp.Diff(wantImporters[2:4], page2); diff != "" {
t.Errorf("page 2 mismatch (-want +got):\n%s", diff)
}
// Page 3: limit 2, start page2[1]
page3, err := testDB.GetImportedBy(ctx, pkg0.Path, m0.ModulePath, page2[1]+"a", 2)
if err != nil {
t.Fatal(err)
}
if len(page3) != 1 {
t.Fatalf("expected 1 item, got %d", len(page3))
}
if diff := cmp.Diff(wantImporters[4:], page3); diff != "" {
t.Errorf("page 3 mismatch (-want +got):\n%s", diff)
}
}