blob: 98f6450499fdae58dda742d24d330e306b929d70 [file] [log] [blame]
// 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"
"net/http"
"sort"
"strconv"
"strings"
"testing"
"time"
"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/version"
)
func TestGetNextModulesToFetchAndUpdateModuleVersionStatesForReprocessing(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
defer ResetTestDB(testDB, t)
type testData struct {
modulePath, version string
numPackages, status int
}
var (
latest = "v1.2.0"
notLatest = "v1.0.0"
big = 2000
small = 100
versions = []string{notLatest, latest}
sizes = []int{small, big}
statuses = []int{
http.StatusOK,
derrors.ToHTTPStatus(derrors.HasIncompletePackages),
derrors.ToHTTPStatus(derrors.AlternativeModule),
derrors.ToHTTPStatus(derrors.BadModule),
http.StatusInternalServerError,
http.StatusBadRequest,
}
indexVersions []*internal.IndexVersion
now = time.Now()
)
testPath := func(m *testData) string {
return strings.ReplaceAll(fmt.Sprintf("%d/%d", m.numPackages, m.status), " ", "/")
}
generateMods := func(versions []string, sizes []int, statuses []int) []*testData {
var mods []*testData
for _, status := range statuses {
for _, size := range sizes {
for _, version := range versions {
m := &testData{
version: version,
numPackages: size,
status: status,
}
m.modulePath = testPath(m)
mods = append(mods, m)
}
}
}
sort.Slice(mods, func(i, j int) bool {
return mods[i].modulePath < mods[j].modulePath
})
return mods
}
mods := generateMods(versions, sizes, statuses)
for _, data := range mods {
indexVersions = append(indexVersions, &internal.IndexVersion{Path: data.modulePath, Version: data.version, Timestamp: now})
}
if err := testDB.InsertIndexVersions(ctx, indexVersions); err != nil {
t.Fatal(err)
}
checkNextToRequeue := func(wantData []*testData) {
t.Helper()
got, err := testDB.GetNextModulesToFetch(ctx, len(mods)+1)
if err != nil {
t.Fatal(err)
}
var want []*internal.ModuleVersionState
for _, data := range wantData {
m := &internal.ModuleVersionState{
ModulePath: data.modulePath,
Version: data.version,
Status: derrors.ToReprocessStatus(data.status),
}
if data.numPackages != 0 {
m.NumPackages = &data.numPackages
}
want = append(want, m)
}
if len(want) != len(got) {
t.Errorf("mismatch got = %d modules; want = %d", len(got), len(want))
}
ignore := cmpopts.IgnoreFields(
internal.ModuleVersionState{},
"AppVersion",
"CreatedAt",
"Error",
"GoModPath",
"IndexTimestamp",
"LastProcessedAt",
"NextProcessedAfter",
"TryCount",
)
if diff := cmp.Diff(want, got, ignore); diff != "" {
t.Fatalf("mismatch (-want, +got):\n%s", diff)
}
}
updateStates := func(wantData []*testData) {
for _, m := range wantData {
if err := upsertModuleVersionState(ctx, testDB.db, m.modulePath, m.version, "2020-04-29t14", &m.numPackages, now, m.status,
m.modulePath, derrors.FromHTTPStatus(m.status, "test string")); err != nil {
t.Fatal(err)
}
}
}
// All of the modules should have status = 0, so they should all be
// returned. At this point, we don't know the number of packages in each
// module.
want := generateMods([]string{latest}, []int{small, big}, statuses)
sort.Slice(want, func(i, j int) bool {
return want[i].modulePath < want[j].modulePath
})
want2 := generateMods([]string{notLatest}, []int{small, big}, statuses)
sort.Slice(want2, func(i, j int) bool {
return want2[i].modulePath < want2[j].modulePath
})
want = append(want, want2...)
for _, w := range want {
w.status = 0
w.numPackages = 0
}
checkNextToRequeue(want)
// Mark all modules for reprocessing.
for _, m := range mods {
if err := upsertModuleVersionState(ctx, testDB.db, m.modulePath, m.version, "2020-04-29t14", &m.numPackages, now, m.status, m.modulePath, derrors.FromHTTPStatus(m.status, "test string")); err != nil {
t.Fatal(err)
}
}
if err := testDB.UpdateModuleVersionStatesForReprocessing(ctx, "2020-04-30t14"); err != nil {
t.Fatal(err)
}
// The next modules to requeue should be only the latest version of
// not-large modules with errors derorrs.ReprocessStatusOK and
// derrors.ReprocessHasIncompletePackages.
want = generateMods([]string{latest}, []int{small}, []int{200, 290})
checkNextToRequeue(want)
updateStates(want)
// The next modules to requeue should be only the small latest version of
// not-large modules with errors derorrs.ReprocessAlternative and
// derrors.ReprocessBadModule.
want = generateMods([]string{latest}, []int{small}, []int{490, 491})
checkNextToRequeue(want)
updateStates(want)
// The next modules to requeue should be only the small non-latest
// version of modules with errors derorrs.ReprocessStatusOK and
// derrors.ReprocessHasIncompletePackages.
want = generateMods([]string{notLatest}, []int{small}, []int{200, 290})
checkNextToRequeue(want)
updateStates(want)
// The next modules to requeue should be only the small non-latest
// version of modules with errors derorrs.ReprocessAlternative and //
// derrors.ReprocessBadModule.
want = generateMods([]string{notLatest}, []int{small}, []int{490, 491})
checkNextToRequeue(want)
updateStates(want)
// Pick up all large modules. Modules with 520 > status >= 500
// have already been processsed.
tmp := generateMods(versions, []int{big}, []int{200, 290})
sort.Slice(tmp, func(i, j int) bool {
return tmp[i].version > tmp[j].version
})
want = tmp
tmp = generateMods(versions, []int{big}, []int{490, 491})
sort.Slice(tmp, func(i, j int) bool {
return tmp[i].version > tmp[j].version
})
want = append(want, tmp...)
checkNextToRequeue(want)
updateStates(want)
// At this point, everything should have been queued.
// Modules with status=500 and status=400 are not being picked up because
// their next_processed_at time > NOW.
checkNextToRequeue(nil)
}
func TestGetNextModulesToFetchOnlyPicksUpStatus0AndStatusGreaterThan500(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
defer ResetTestDB(testDB, t)
statuses := []int{
http.StatusOK,
derrors.ToHTTPStatus(derrors.HasIncompletePackages),
derrors.ToHTTPStatus(derrors.AlternativeModule),
derrors.ToHTTPStatus(derrors.BadModule),
http.StatusBadRequest,
http.StatusInternalServerError,
0,
}
for _, status := range statuses {
if _, err := testDB.db.Exec(ctx, `
INSERT INTO module_version_states AS mvs (
module_path,
version,
sort_version,
app_version,
index_timestamp,
status,
go_mod_path)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
strconv.Itoa(status),
"v1.0.0",
version.ForSorting("v1.0.0"),
"app-version",
time.Now(),
status,
strconv.Itoa(status),
); err != nil {
t.Fatal(err)
}
}
got, err := testDB.GetNextModulesToFetch(ctx, len(statuses))
if err != nil {
t.Fatal(err)
}
var (
want []*internal.ModuleVersionState
wantStatuses = []int{http.StatusInternalServerError, 0}
)
for _, status := range wantStatuses {
m := &internal.ModuleVersionState{
ModulePath: strconv.Itoa(status),
Version: "v1.0.0",
Status: status,
}
want = append(want, m)
}
compareModules(t, got, want)
}
func TestGetNextModulesToFetchLargeModulesLimit(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
defer ResetTestDB(testDB, t)
var (
modulePaths []string
status = http.StatusInternalServerError
v = "v1.0.0"
num500Mods = 10
)
for i := 0; i < num500Mods; i++ {
mp := strconv.Itoa(status) + strconv.Itoa(i)
modulePaths = append(modulePaths, mp)
if _, err := testDB.db.Exec(ctx, `
INSERT INTO module_version_states AS mvs (
module_path,
version,
sort_version,
app_version,
index_timestamp,
status,
go_mod_path)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
mp,
v,
version.ForSorting(v),
"app-version",
time.Now(),
status,
strconv.Itoa(status),
); err != nil {
t.Fatal(err)
}
}
largeModulesLimit = 5
got, err := testDB.GetNextModulesToFetch(ctx, num500Mods)
if err != nil {
t.Fatal(err)
}
sort.Strings(modulePaths)
var want []*internal.ModuleVersionState
for _, mp := range modulePaths[:largeModulesLimit] {
want = append(want, &internal.ModuleVersionState{
ModulePath: mp,
Version: v,
Status: status,
})
}
compareModules(t, got, want)
}
func compareModules(t *testing.T, got, want []*internal.ModuleVersionState) {
t.Helper()
ignore := cmpopts.IgnoreFields(
internal.ModuleVersionState{},
"AppVersion",
"CreatedAt",
"Error",
"GoModPath",
"IndexTimestamp",
"LastProcessedAt",
"NextProcessedAfter",
"TryCount",
)
sort.Slice(got, func(i, j int) bool {
return got[i].ModulePath < got[j].ModulePath
})
sort.Slice(want, func(i, j int) bool {
return want[i].ModulePath < want[j].ModulePath
})
if diff := cmp.Diff(want, got, ignore); diff != "" {
t.Fatalf("mismatch (-want, +got):\n%s", diff)
}
}