blob: 2bbf2397916b0cbb41d0f200adb72c1fd39084db [file] [log] [blame]
// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.
package database
import (
"context"
"math"
"strconv"
"testing"
"time"
"github.com/garyburd/redigo/redis"
"github.com/google/go-cmp/cmp"
"github.com/golang/gddo/doc"
)
func newDB(t *testing.T) *Database {
p := redis.NewPool(func() (redis.Conn, error) {
c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
if err != nil {
return nil, err
}
_, err = c.Do("SELECT", "9")
if err != nil {
c.Close()
return nil, err
}
return c, nil
}, 1)
c := p.Get()
defer c.Close()
n, err := redis.Int(c.Do("DBSIZE"))
if n != 0 || err != nil {
t.Errorf("DBSIZE returned %d, %v", n, err)
}
return &Database{Pool: p, RemoteClient: nil}
}
func closeDB(db *Database) {
c := db.Pool.Get()
c.Do("FLUSHDB")
c.Close()
}
func TestPutGet(t *testing.T) {
ctx := context.Background()
var nextCrawl = time.Unix(time.Now().Add(time.Hour).Unix(), 0).UTC()
db := newDB(t)
defer closeDB(db)
pdoc := &doc.Package{
ImportPath: "github.com/user/repo/foo/bar",
Name: "bar",
Synopsis: "hello",
ProjectRoot: "github.com/user/repo",
ProjectName: "foo",
Updated: time.Now().Add(-time.Hour),
Imports: []string{"C", "errors", "github.com/user/repo/foo/bar"}, // self import for testing convenience.
}
if err := db.Put(ctx, pdoc, nextCrawl, false); err != nil {
t.Errorf("db.Put() returned error %v", err)
}
if err := db.Put(ctx, pdoc, time.Time{}, false); err != nil {
t.Errorf("second db.Put() returned error %v", err)
}
actualPdoc, actualSubdirs, actualCrawl, err := db.Get(ctx, "github.com/user/repo/foo/bar")
if err != nil {
t.Fatalf("db.Get(.../foo/bar) returned %v", err)
}
if len(actualSubdirs) != 0 {
t.Errorf("db.Get(.../foo/bar) returned subdirs %v, want none", actualSubdirs)
}
if !cmp.Equal(actualPdoc, pdoc) {
t.Errorf("db.Get(.../foo/bar) returned doc %v, want %v", actualPdoc, pdoc)
}
if !nextCrawl.Equal(actualCrawl) {
t.Errorf("db.Get(.../foo/bar) returned crawl %v, want %v", actualCrawl, nextCrawl)
}
before := time.Now().Unix()
if err := db.BumpCrawl(pdoc.ProjectRoot); err != nil {
t.Errorf("db.BumpCrawl() returned %v", err)
}
after := time.Now().Unix()
_, _, actualCrawl, _ = db.Get(ctx, "github.com/user/repo/foo/bar")
if actualCrawl.Unix() < before || after < actualCrawl.Unix() {
t.Errorf("actualCrawl=%v, expect value between %v and %v", actualCrawl.Unix(), before, after)
}
// Popular
if err := db.IncrementPopularScore(pdoc.ImportPath); err != nil {
t.Errorf("db.IncrementPopularScore() returned %v", err)
}
// Get "-"
actualPdoc, _, _, err = db.Get(ctx, "-")
if err != nil {
t.Fatalf("db.Get(-) returned %v", err)
}
if !cmp.Equal(actualPdoc, pdoc) {
t.Errorf("db.Get(-) returned doc %v, want %v", actualPdoc, pdoc)
}
actualPdoc, actualSubdirs, _, err = db.Get(ctx, "github.com/user/repo/foo")
if err != nil {
t.Fatalf("db.Get(.../foo) returned %v", err)
}
if actualPdoc != nil {
t.Errorf("db.Get(.../foo) returned doc %v, want %v", actualPdoc, nil)
}
expectedSubdirs := []Package{{Path: "github.com/user/repo/foo/bar", Synopsis: "hello"}}
if !cmp.Equal(actualSubdirs, expectedSubdirs) {
t.Errorf("db.Get(.../foo) returned subdirs %v, want %v", actualSubdirs, expectedSubdirs)
}
actualImporters, err := db.Importers("github.com/user/repo/foo/bar")
if err != nil {
t.Fatalf("db.Importers() returned error %v", err)
}
expectedImporters := []Package{{Path: "github.com/user/repo/foo/bar", Synopsis: "hello"}}
if !cmp.Equal(actualImporters, expectedImporters) {
t.Errorf("db.Importers() = %v, want %v", actualImporters, expectedImporters)
}
actualImports, err := db.Packages(pdoc.Imports)
if err != nil {
t.Fatalf("db.Imports() returned error %v", err)
}
for i := range actualImports {
if actualImports[i].Path == "C" {
actualImports[i].Synopsis = ""
}
}
expectedImports := []Package{
{Path: "C", Synopsis: ""},
{Path: "errors", Synopsis: ""},
{Path: "github.com/user/repo/foo/bar", Synopsis: "hello"},
}
if !cmp.Equal(actualImports, expectedImports) {
t.Errorf("db.Imports() = %v, want %v", actualImports, expectedImports)
}
importerCount, _ := db.ImporterCount("github.com/user/repo/foo/bar")
if importerCount != 1 {
t.Errorf("db.ImporterCount() = %d, want %d", importerCount, 1)
}
if err := db.Delete(ctx, "github.com/user/repo/foo/bar"); err != nil {
t.Errorf("db.Delete() returned error %v", err)
}
db.Query("bar")
if err := db.Put(ctx, pdoc, time.Time{}, false); err != nil {
t.Errorf("db.Put() returned error %v", err)
}
if err := db.Block("github.com/user/repo"); err != nil {
t.Errorf("db.Block() returned error %v", err)
}
blocked, err := db.IsBlocked("github.com/user/repo/foo/bar")
if !blocked || err != nil {
t.Errorf("db.IsBlocked(github.com/user/repo/foo/bar) returned %v, %v, want true, nil", blocked, err)
}
blocked, err = db.IsBlocked("github.com/foo/bar")
if blocked || err != nil {
t.Errorf("db.IsBlocked(github.com/foo/bar) returned %v, %v, want false, nil", blocked, err)
}
c := db.Pool.Get()
defer c.Close()
c.Send("DEL", "maxQueryId")
c.Send("DEL", "maxPackageId")
c.Send("DEL", "block")
c.Send("DEL", "popular:0")
c.Send("DEL", "newCrawl")
keys, err := redis.Values(c.Do("HKEYS", "ids"))
for _, key := range keys {
t.Errorf("unexpected id %s", key)
}
keys, err = redis.Values(c.Do("KEYS", "*"))
for _, key := range keys {
t.Errorf("unexpected key %s", key)
}
}
const epsilon = 0.000001
func TestPopular(t *testing.T) {
db := newDB(t)
defer closeDB(db)
c := db.Pool.Get()
defer c.Close()
// Add scores for packages. On each iteration, add half-life to time and
// divide the score by two. All packages should have the same score.
now := time.Now()
score := float64(4048)
for id := 12; id >= 0; id-- {
path := "github.com/user/repo/p" + strconv.Itoa(id)
c.Do("HSET", "ids", path, id)
err := db.incrementPopularScoreInternal(path, score, now)
if err != nil {
t.Fatal(err)
}
now = now.Add(popularHalfLife)
score /= 2
}
values, _ := redis.Values(c.Do("ZRANGE", "popular", "0", "100000", "WITHSCORES"))
if len(values) != 26 {
t.Fatalf("Expected 26 values, got %d", len(values))
}
// Check for equal scores.
score, err := redis.Float64(values[1], nil)
if err != nil {
t.Fatal(err)
}
for i := 3; i < len(values); i += 2 {
s, _ := redis.Float64(values[i], nil)
if math.Abs(score-s)/score > epsilon {
t.Errorf("Bad score, score[1]=%g, score[%d]=%g", score, i, s)
}
}
}
func TestCounter(t *testing.T) {
db := newDB(t)
defer closeDB(db)
const key = "127.0.0.1"
now := time.Now()
n, err := db.incrementCounterInternal(key, 1, now)
if err != nil {
t.Fatal(err)
}
if math.Abs(n-1.0) > epsilon {
t.Errorf("1: got n=%g, want 1", n)
}
n, err = db.incrementCounterInternal(key, 1, now)
if err != nil {
t.Fatal(err)
}
if math.Abs(n-2.0)/2.0 > epsilon {
t.Errorf("2: got n=%g, want 2", n)
}
now = now.Add(counterHalflife)
n, err = db.incrementCounterInternal(key, 1, now)
if err != nil {
t.Fatal(err)
}
if math.Abs(n-2.0)/2.0 > epsilon {
t.Errorf("3: got n=%g, want 2", n)
}
}