mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 18:51:59 +00:00
371 lines
12 KiB
Go
371 lines
12 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cockroachdb/errors/errbase"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/jackc/pgconn"
|
|
"github.com/keegancsmith/sqlf"
|
|
|
|
"github.com/sourcegraph/log/logtest"
|
|
|
|
"github.com/sourcegraph/sourcegraph/internal/database/dbtest"
|
|
"github.com/sourcegraph/sourcegraph/internal/types"
|
|
)
|
|
|
|
func TestExecutorsList(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip()
|
|
}
|
|
|
|
logger := logtest.Scoped(t)
|
|
db := NewDB(logger, dbtest.NewDB(t))
|
|
store := db.Executors().(*executorStore)
|
|
ctx := context.Background()
|
|
|
|
executors := []types.Executor{
|
|
{Hostname: "h1", QueueName: "q1", OS: "win", Architecture: "amd", DockerVersion: "d1", ExecutorVersion: "e1", GitVersion: "g1", IgniteVersion: "i1", SrcCliVersion: "s1"}, // id=1
|
|
{Hostname: "h2", QueueName: "q2", OS: "win", Architecture: "x86", DockerVersion: "d2", ExecutorVersion: "e2", GitVersion: "g2", IgniteVersion: "i2", SrcCliVersion: "s2"}, // id=2
|
|
{Hostname: "h3", QueueName: "q3", OS: "win", Architecture: "amd", DockerVersion: "d3", ExecutorVersion: "e3", GitVersion: "g3", IgniteVersion: "i3", SrcCliVersion: "s3"}, // id=3
|
|
{Hostname: "h4", QueueName: "q4", OS: "win", Architecture: "x86", DockerVersion: "d1", ExecutorVersion: "e4", GitVersion: "g4", IgniteVersion: "i4", SrcCliVersion: "s4"}, // id=4
|
|
{Hostname: "h5", QueueName: "q5", OS: "win", Architecture: "amd", DockerVersion: "d2", ExecutorVersion: "e1", GitVersion: "g5", IgniteVersion: "i5", SrcCliVersion: "s5"}, // id=5
|
|
{Hostname: "h6", QueueName: "q6", OS: "mac", Architecture: "x86", DockerVersion: "d3", ExecutorVersion: "e2", GitVersion: "g1", IgniteVersion: "i6", SrcCliVersion: "s6"}, // id=6
|
|
{Hostname: "h7", QueueName: "q7", OS: "mac", Architecture: "amd", DockerVersion: "d1", ExecutorVersion: "e3", GitVersion: "g2", IgniteVersion: "i1", SrcCliVersion: "s7"}, // id=7
|
|
{Hostname: "h8", QueueName: "q8", OS: "mac", Architecture: "x86", DockerVersion: "d2", ExecutorVersion: "e4", GitVersion: "g3", IgniteVersion: "i2", SrcCliVersion: "s1"}, // id=8
|
|
{Hostname: "h9", QueueName: "q9", OS: "mac", Architecture: "amd", DockerVersion: "d3", ExecutorVersion: "e1", GitVersion: "g4", IgniteVersion: "i3", SrcCliVersion: "s2"}, // id=9
|
|
{Hostname: "h0", QueueName: "q0", OS: "mac", Architecture: "x86", DockerVersion: "d1", ExecutorVersion: "e2", GitVersion: "g5", IgniteVersion: "i4", SrcCliVersion: "s3"}, // id=10
|
|
}
|
|
|
|
for _, executor := range executors {
|
|
db.Executors().UpsertHeartbeat(ctx, executor)
|
|
}
|
|
|
|
now := time.Unix(1587396557, 0).UTC()
|
|
t1 := now.Add(-time.Minute * 10) // active
|
|
t2 := now.Add(-time.Minute * 45) // inactive
|
|
|
|
lastSeenAtByID := map[int]time.Time{
|
|
1: t1,
|
|
2: t1,
|
|
3: t1,
|
|
4: t1,
|
|
5: t1,
|
|
6: t2,
|
|
7: t2,
|
|
8: t2,
|
|
9: t2,
|
|
10: t2,
|
|
}
|
|
for id, lastSeenAt := range lastSeenAtByID {
|
|
q := sqlf.Sprintf(`UPDATE executor_heartbeats SET last_seen_at = %s WHERE id = %s`, lastSeenAt, id)
|
|
if _, err := db.Handle().ExecContext(ctx, q.Query(sqlf.PostgresBindVar), q.Args()...); err != nil {
|
|
t.Fatalf("failed to set up executors for test: %s", err)
|
|
}
|
|
}
|
|
|
|
type testCase struct {
|
|
query string
|
|
active bool
|
|
expectedIDs []int
|
|
}
|
|
testCases := []testCase{
|
|
{expectedIDs: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}},
|
|
{query: "win", expectedIDs: []int{5, 4, 3, 2, 1}}, // test search by OS
|
|
{query: "x86", expectedIDs: []int{10, 8, 6, 4, 2}}, // test search by architecture
|
|
{query: "d2", expectedIDs: []int{8, 5, 2}}, // test search by docker version
|
|
{query: "e2", expectedIDs: []int{10, 6, 2}}, // test search by executor version
|
|
{query: "g2", expectedIDs: []int{7, 2}}, // test search by git version
|
|
{query: "i2", expectedIDs: []int{8, 2}}, // test search by ignite version
|
|
{query: "s2", expectedIDs: []int{9, 2}}, // test search by src-cli version
|
|
{active: true, expectedIDs: []int{5, 4, 3, 2, 1}},
|
|
}
|
|
|
|
runTest := func(testCase testCase, lo, hi int) (errors int) {
|
|
name := fmt.Sprintf(
|
|
"query=%q active=%v offset=%d",
|
|
testCase.query,
|
|
testCase.active,
|
|
lo,
|
|
)
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
opts := ExecutorStoreListOptions{
|
|
Query: testCase.query,
|
|
Active: testCase.active,
|
|
Limit: 3,
|
|
Offset: lo,
|
|
}
|
|
executors, err := store.list(ctx, opts, now)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error getting executors: %s", err)
|
|
}
|
|
totalCount, err := store.count(ctx, opts, now)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error counting executors: %s", err)
|
|
}
|
|
if totalCount != len(testCase.expectedIDs) {
|
|
t.Errorf("unexpected total count. want=%d have=%d", len(testCase.expectedIDs), totalCount)
|
|
errors++
|
|
}
|
|
|
|
if totalCount != 0 {
|
|
var ids []int
|
|
for _, executor := range executors {
|
|
ids = append(ids, executor.ID)
|
|
}
|
|
if diff := cmp.Diff(testCase.expectedIDs[lo:hi], ids); diff != "" {
|
|
t.Errorf("unexpected executor ids at offset %d (-want +got):\n%s", lo, diff)
|
|
errors++
|
|
}
|
|
}
|
|
})
|
|
|
|
return errors
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
if n := len(testCase.expectedIDs); n == 0 {
|
|
runTest(testCase, 0, 0)
|
|
} else {
|
|
for lo := range n {
|
|
if numErrors := runTest(testCase, lo, min(lo+3, n)); numErrors > 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExecutorsGetByID(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip()
|
|
}
|
|
|
|
logger := logtest.Scoped(t)
|
|
db := NewDB(logger, dbtest.NewDB(t))
|
|
store := db.Executors().(*executorStore)
|
|
ctx := context.Background()
|
|
|
|
// Executor does not exist initially
|
|
if _, exists, err := db.Executors().GetByID(ctx, 1); err != nil {
|
|
t.Fatalf("unexpected error getting executor: %s", err)
|
|
} else if exists {
|
|
t.Fatal("unexpected record")
|
|
}
|
|
|
|
now := time.Unix(1587396557, 0).UTC()
|
|
t1 := now.Add(-time.Minute * 15)
|
|
t2 := now.Add(-time.Minute * 45)
|
|
|
|
expected := types.Executor{
|
|
ID: 1,
|
|
Hostname: "test-hostname",
|
|
QueueName: "test-queue-name",
|
|
OS: "test-os",
|
|
Architecture: "test-architecture",
|
|
DockerVersion: "test-docker-version",
|
|
ExecutorVersion: "test-executor-version",
|
|
GitVersion: "test-git-version",
|
|
IgniteVersion: "test-ignite-version",
|
|
SrcCliVersion: "test-src-cli-version",
|
|
FirstSeenAt: t1,
|
|
LastSeenAt: t2,
|
|
}
|
|
|
|
// update first seen at
|
|
if err := store.upsertHeartbeat(ctx, expected, t1); err != nil {
|
|
t.Fatalf("unexpected error inserting heartbeat: %s", err)
|
|
}
|
|
|
|
expected.QueueName += "-changed"
|
|
expected.OS += "-changed"
|
|
expected.Architecture += "-changed"
|
|
expected.DockerVersion += "-changed"
|
|
expected.ExecutorVersion += "-changed"
|
|
expected.GitVersion += "-changed"
|
|
expected.IgniteVersion += "-changed"
|
|
expected.SrcCliVersion += "-changed"
|
|
|
|
// update values as well as last seen at
|
|
if err := store.upsertHeartbeat(ctx, expected, t2); err != nil {
|
|
t.Fatalf("unexpected error inserting heartbeat: %s", err)
|
|
}
|
|
|
|
if executor, exists, err := store.GetByID(ctx, 1); err != nil {
|
|
t.Fatalf("unexpected error getting executor: %s", err)
|
|
} else if !exists {
|
|
t.Fatal("expected record to exist")
|
|
} else if diff := cmp.Diff(expected, executor); diff != "" {
|
|
t.Errorf("unexpected executor (-want +got):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestExecutorsGetByHostname(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip()
|
|
}
|
|
|
|
logger := logtest.Scoped(t)
|
|
db := NewDB(logger, dbtest.NewDB(t))
|
|
store := db.Executors().(*executorStore)
|
|
ctx := context.Background()
|
|
|
|
hostname := "megahost-somuchfast"
|
|
|
|
// Executor does not exist initially
|
|
if _, exists, err := db.Executors().GetByHostname(ctx, hostname); err != nil {
|
|
t.Fatalf("unexpected error getting executor: %s", err)
|
|
} else if exists {
|
|
t.Fatal("unexpected record")
|
|
}
|
|
|
|
now := time.Unix(1587396557, 0).UTC()
|
|
t1 := now.Add(-time.Minute * 15)
|
|
t2 := now.Add(-time.Minute * 45)
|
|
|
|
expected := types.Executor{
|
|
ID: 1,
|
|
Hostname: hostname,
|
|
QueueName: "test-queue-name",
|
|
OS: "test-os",
|
|
Architecture: "test-architecture",
|
|
DockerVersion: "test-docker-version",
|
|
ExecutorVersion: "test-executor-version",
|
|
GitVersion: "test-git-version",
|
|
IgniteVersion: "test-ignite-version",
|
|
SrcCliVersion: "test-src-cli-version",
|
|
FirstSeenAt: t1,
|
|
LastSeenAt: t2,
|
|
}
|
|
|
|
// update first seen at
|
|
if err := store.upsertHeartbeat(ctx, expected, t1); err != nil {
|
|
t.Fatalf("unexpected error inserting heartbeat: %s", err)
|
|
}
|
|
|
|
expected.QueueName += "-changed"
|
|
expected.OS += "-changed"
|
|
expected.Architecture += "-changed"
|
|
expected.DockerVersion += "-changed"
|
|
expected.ExecutorVersion += "-changed"
|
|
expected.GitVersion += "-changed"
|
|
expected.IgniteVersion += "-changed"
|
|
expected.SrcCliVersion += "-changed"
|
|
|
|
// update values as well as last seen at
|
|
if err := store.upsertHeartbeat(ctx, expected, t2); err != nil {
|
|
t.Fatalf("unexpected error inserting heartbeat: %s", err)
|
|
}
|
|
|
|
if executor, exists, err := db.Executors().GetByHostname(ctx, hostname); err != nil {
|
|
t.Fatalf("unexpected error getting executor: %s", err)
|
|
} else if !exists {
|
|
t.Fatal("expected record to exist")
|
|
} else if diff := cmp.Diff(expected, executor); diff != "" {
|
|
t.Errorf("unexpected executor (-want +got):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestExecutorsDeleteInactiveHeartbeats(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip()
|
|
}
|
|
|
|
logger := logtest.Scoped(t)
|
|
db := NewDB(logger, dbtest.NewDB(t))
|
|
store := db.Executors().(*executorStore)
|
|
ctx := context.Background()
|
|
|
|
for i := range 10 {
|
|
db.Executors().UpsertHeartbeat(ctx, types.Executor{Hostname: fmt.Sprintf("h%02d", i+1), QueueName: "q1"})
|
|
}
|
|
|
|
now := time.Unix(1587396557, 0).UTC()
|
|
t1 := now.Add(-time.Minute * 10) // active
|
|
t2 := now.Add(-time.Minute * 45) // inactive
|
|
|
|
lastSeenAtByID := map[int]time.Time{
|
|
1: t1,
|
|
2: t1,
|
|
3: t1,
|
|
4: t1,
|
|
5: t1,
|
|
6: t2,
|
|
7: t2,
|
|
8: t2,
|
|
9: t2,
|
|
10: t2,
|
|
}
|
|
for id, lastSeenAt := range lastSeenAtByID {
|
|
q := sqlf.Sprintf(`UPDATE executor_heartbeats SET last_seen_at = %s WHERE id = %s`, lastSeenAt, id)
|
|
if _, err := db.ExecContext(ctx, q.Query(sqlf.PostgresBindVar), q.Args()...); err != nil {
|
|
t.Fatalf("failed to set up executors for test: %s", err)
|
|
}
|
|
}
|
|
|
|
if err := store.deleteInactiveHeartbeats(ctx, time.Minute*30, now); err != nil {
|
|
t.Fatalf("unexpected error deleting inactive heartbeats: %s", err)
|
|
}
|
|
|
|
if totalCount, err := db.Executors().Count(ctx, ExecutorStoreListOptions{}); err != nil {
|
|
t.Fatalf("unexpected error counting executors: %s", err)
|
|
} else if totalCount != 5 {
|
|
t.Fatalf("unexpected total count. want=%d have=%d", 5, totalCount)
|
|
}
|
|
}
|
|
|
|
func TestExecutorsUpsertHeartbeat(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip()
|
|
}
|
|
|
|
logger := logtest.Scoped(t)
|
|
db := NewDB(logger, dbtest.NewDB(t))
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
executor types.Executor
|
|
expectedErrorMessage string
|
|
}{
|
|
{
|
|
name: "Single queue defined",
|
|
executor: types.Executor{Hostname: "happy_single_queue", QueueName: "single", OS: "win", Architecture: "amd", DockerVersion: "d1", ExecutorVersion: "e1", GitVersion: "g1", IgniteVersion: "i1", SrcCliVersion: "s1"},
|
|
},
|
|
{
|
|
name: "Multiple queues defined",
|
|
executor: types.Executor{Hostname: "happy_multi_queue", QueueNames: []string{"multi1", "multi2"}, OS: "win", Architecture: "amd", DockerVersion: "d1", ExecutorVersion: "e1", GitVersion: "g1", IgniteVersion: "i1", SrcCliVersion: "s1"},
|
|
},
|
|
{
|
|
name: "Both single queue and multiple queues defined",
|
|
executor: types.Executor{Hostname: "sad_both_defined", QueueName: "single", QueueNames: []string{"multi1", "multi2"}, OS: "win", Architecture: "amd", DockerVersion: "d1", ExecutorVersion: "e1", GitVersion: "g1", IgniteVersion: "i1", SrcCliVersion: "s1"},
|
|
expectedErrorMessage: `new row for relation "executor_heartbeats" violates check constraint "one_of_queue_name_queue_names"`,
|
|
},
|
|
{
|
|
name: "No queues defined",
|
|
executor: types.Executor{Hostname: "sad_none_defined", OS: "win", Architecture: "amd", DockerVersion: "d1", ExecutorVersion: "e1", GitVersion: "g1", IgniteVersion: "i1", SrcCliVersion: "s1"},
|
|
expectedErrorMessage: `new row for relation "executor_heartbeats" violates check constraint "one_of_queue_name_queue_names"`,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := db.Executors().UpsertHeartbeat(ctx, test.executor)
|
|
if err != nil {
|
|
err = errbase.UnwrapAll(err)
|
|
pgErr, ok := err.(*pgconn.PgError)
|
|
if !ok {
|
|
t.Fatalf("unexpected error while upserting heartbeat: %s", err)
|
|
}
|
|
if pgErr.Message != test.expectedErrorMessage {
|
|
t.Errorf("Unexpected error while upserting heartbeat. expected=%s actual=%s", test.expectedErrorMessage, pgErr.Message)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|