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