| // 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") |
| } |
| }) |
| } |