blob: 8b97d35e599ec1cce9e12cc915853320cece6798 [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"
"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"
)
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,
}
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 shuold have been queued.
checkNextToRequeue(nil)
}