blob: 48d6a683bc4f2cc0061f11607711b506c870a7f6 [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"
"fmt"
"strings"
"testing"
"time"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/testing/sample"
)
func TestUpdateSearchDocumentsImportedByCount(t *testing.T) {
// Dont' run in parallel because it changes countBatchSize.
ctx := context.Background()
pkgPath := func(m *internal.Module) string { return m.Packages()[0].Path }
// insert package with suffix at version, return the module
insertPackageVersion := func(t *testing.T, db *DB, suffix, version string, imports []string) *internal.Module {
t.Helper()
m := sample.Module("mod.com/"+suffix, version, suffix)
// Units[0] is the module itself.
pkg := m.Units[1]
pkg.Imports = nil
for _, imp := range imports {
pkg.Imports = append(pkg.Imports, fmt.Sprintf("mod.com/%s/%[1]s", imp))
}
db.MustInsertModule(t, m)
return m
}
updateImportedByCount := func(db *DB, batchSize int) {
t.Helper()
if _, err := db.UpdateSearchDocumentsImportedByCount(ctx, batchSize); err != nil {
t.Fatal(err)
}
}
validateImportedByCountAndGetSearchDocument := func(t *testing.T, db *DB, path string, count int) *searchDocument {
t.Helper()
sd, err := getSearchDocument(ctx, db, path)
if err != nil {
t.Fatalf("testDB.getSearchDocument(ctx, %q): %v", path, err)
}
if sd.importedByCount > 0 && sd.importedByCountUpdatedAt.IsZero() {
t.Fatalf("importedByCountUpdatedAt for package %q should not be empty if count > 0", path)
}
if count != sd.importedByCount {
t.Fatalf("importedByCount for package %q = %d; want = %d", path, sd.importedByCount, count)
}
return sd
}
t.Run("basic", func(t *testing.T) {
testDB, release := acquire(t)
defer release()
// Test imported_by_count = 0 when only pkgA is added.
mA := insertPackageVersion(t, testDB, "A", "v1.0.0", nil)
updateImportedByCount(testDB, 100)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mA), 0)
// Test imported_by_count = 1 for pkgA when pkgB is added.
mB := insertPackageVersion(t, testDB, "B", "v1.0.0", []string{"A"})
updateImportedByCount(testDB, 100)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mA), 1)
sdB := validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mB), 0)
wantSearchDocBUpdatedAt := sdB.importedByCountUpdatedAt
// Test imported_by_count = 2 for pkgA, when C is added.
mC := insertPackageVersion(t, testDB, "C", "v1.0.0", []string{"A"})
updateImportedByCount(testDB, 100)
sdA := validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mA), 2)
sdC := validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mC), 0)
// Nothing imports C, so it has never been updated.
if !sdC.importedByCountUpdatedAt.IsZero() {
t.Fatalf("pkgC imported_by_count_updated_at should be zero, but is %v", sdC.importedByCountUpdatedAt)
}
if sdA.importedByCountUpdatedAt.IsZero() {
t.Fatal("pkgA imported_by_count_updated_at should be non-zero, but is zero")
}
// Test imported_by_count_updated_at for B has not changed.
sdB = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mB), 0)
if sdB.importedByCountUpdatedAt != wantSearchDocBUpdatedAt {
t.Fatalf("expected imported_by_count_updated_at for pkgB not to have changed; old = %v, new = %v",
wantSearchDocBUpdatedAt, sdB.importedByCountUpdatedAt)
}
// When an older version of A imports D, nothing happens to the counts,
// because imports_unique only records the latest version of each package.
mD := insertPackageVersion(t, testDB, "D", "v1.0.0", nil)
insertPackageVersion(t, testDB, "A", "v0.9.0", []string{"D"})
updateImportedByCount(testDB, 100)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mA), 2)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mD), 0)
// When a newer version of A imports D, however, the counts do change.
insertPackageVersion(t, testDB, "A", "v1.1.0", []string{"D"})
updateImportedByCount(testDB, 100)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mA), 2)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mD), 1)
})
t.Run("alternative", func(t *testing.T) {
// Test with alternative modules that are removed from search_documents.
testDB, release := acquire(t)
defer release()
insertPackageVersion(t, testDB, "B", "v1.0.0", nil)
// Insert a package with the canonical module path.
canonicalModule := insertPackageVersion(t, testDB, "A", "v1.0.0", []string{"B"})
// Imagine we see a package with an alternative path at v1.2.0.
// We add that information to module_version_states.
alternativeModulePath := strings.ToLower(canonicalModule.ModulePath)
alternativeStatus := derrors.ToStatus(derrors.AlternativeModule)
mvs := &ModuleVersionStateForUpdate{
ModulePath: alternativeModulePath,
Version: "v1.2.0",
Timestamp: time.Now(),
Status: alternativeStatus,
GoModPath: canonicalModule.ModulePath,
}
if err := testDB.InsertIndexVersions(ctx, []*internal.IndexVersion{
{
Path: mvs.ModulePath,
Version: mvs.Version,
Timestamp: mvs.Timestamp,
},
}); err != nil {
t.Fatal(err)
}
if err := testDB.UpdateModuleVersionState(ctx, mvs); err != nil {
t.Fatal(err)
}
// Now we see an earlier version of that package, without a go.mod file, so we insert it.
// It should not get inserted into search_documents.
mAlt := sample.Module(alternativeModulePath, "v1.0.0", "A")
mAlt.Packages()[0].Imports = []string{"B"}
testDB.MustInsertModule(t, mAlt)
// Although B is imported by two packages, only one is in search_documents, so its
// imported-by count is 1.
updateImportedByCount(testDB, 100)
validateImportedByCountAndGetSearchDocument(t, testDB, "mod.com/B/B", 1)
})
t.Run("multiple", func(t *testing.T) {
testDB, release := acquire(t)
defer release()
// Two modules with importers.
mA := insertPackageVersion(t, testDB, "A", "v1.0.0", nil)
mB := insertPackageVersion(t, testDB, "B", "v1.0.0", nil)
insertPackageVersion(t, testDB, "C", "v1.0.0", []string{"A"})
insertPackageVersion(t, testDB, "D", "v1.0.0", []string{"A"})
insertPackageVersion(t, testDB, "E", "v1.0.0", []string{"B"})
updateImportedByCount(testDB, 1)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mA), 2)
_ = validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mB), 1)
})
t.Run("module_sharing", func(t *testing.T) {
testDB, release := acquire(t)
defer release()
mA := insertPackageVersion(t, testDB, "A", "v1.0.0", nil)
// Module C has two packages, C1 and C2, both importing A.
mC := sample.Module("mod.com/C", "v1.0.0", "C1", "C2")
for _, u := range mC.Units {
if u.Path != "mod.com/C" {
u.Imports = []string{pkgPath(mA)}
}
}
testDB.MustInsertModule(t, mC)
updateImportedByCount(testDB, 100)
sdA := validateImportedByCountAndGetSearchDocument(t, testDB, pkgPath(mA), 2)
if sdA.importedByModuleCount != 1 {
t.Errorf("importedByModuleCount for A = %d, want 1", sdA.importedByModuleCount)
}
if sdA.importedByModuleCountUpdatedAt.IsZero() {
t.Error("importedByModuleCountUpdatedAt for A is zero, want non-zero")
}
})
t.Run("same_module", func(t *testing.T) {
testDB, release := acquire(t)
defer release()
// Module A has two packages, pkg1 and pkg2.
// pkg1 imports pkg2 within the same module.
mA := sample.Module("mod.com/A", "v1.0.0", "pkg1", "pkg2")
for _, u := range mA.Units {
if u.Path == "mod.com/A/pkg1" {
u.Imports = []string{"mod.com/A/pkg2"}
} else if u.IsPackage() {
u.Imports = nil
}
}
testDB.MustInsertModule(t, mA)
updateImportedByCount(testDB, 100)
sdPkg2 := validateImportedByCountAndGetSearchDocument(t, testDB, "mod.com/A/pkg2", 0)
if sdPkg2.importedByModuleCount != 1 {
t.Errorf("importedByModuleCount for pkg2 = %d, want 1", sdPkg2.importedByModuleCount)
}
if sdPkg2.importedByModuleCountUpdatedAt.IsZero() {
t.Error("importedByModuleCountUpdatedAt for pkg2 is zero, want non-zero")
}
})
}