Browse Source

db: add tests for LFS (#6087)

* Improve DB test setup

* Discard GORM logs in non-verbose mode

* Add tests to lfs

* Fix data race
ᴜɴᴋɴᴡᴏɴ 4 years ago
parent
commit
e077ecdd9d
5 changed files with 184 additions and 44 deletions
  1. 6 9
      internal/db/access_tokens.go
  2. 12 33
      internal/db/access_tokens_test.go
  3. 5 2
      internal/db/db.go
  4. 117 0
      internal/db/lfs_test.go
  5. 44 0
      internal/db/main_test.go

+ 6 - 9
internal/db/access_tokens.go

@@ -56,12 +56,12 @@ type AccessToken struct {
 
 // NOTE: This is a GORM create hook.
 func (t *AccessToken) BeforeCreate() {
-	t.CreatedUnix = t.Created.Unix()
+	t.CreatedUnix = gorm.NowFunc().Unix()
 }
 
 // NOTE: This is a GORM update hook.
 func (t *AccessToken) BeforeUpdate() {
-	t.UpdatedUnix = t.Updated.Unix()
+	t.UpdatedUnix = gorm.NowFunc().Unix()
 }
 
 // NOTE: This is a GORM query hook.
@@ -69,14 +69,13 @@ func (t *AccessToken) AfterFind() {
 	t.Created = time.Unix(t.CreatedUnix, 0).Local()
 	t.Updated = time.Unix(t.UpdatedUnix, 0).Local()
 	t.HasUsed = t.Updated.After(t.Created)
-	t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(time.Now())
+	t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(gorm.NowFunc())
 }
 
 var _ AccessTokensStore = (*accessTokens)(nil)
 
 type accessTokens struct {
 	*gorm.DB
-	clock func() time.Time
 }
 
 type ErrAccessTokenAlreadyExist struct {
@@ -101,10 +100,9 @@ func (db *accessTokens) Create(userID int64, name string) (*AccessToken, error)
 	}
 
 	token := &AccessToken{
-		UserID:  userID,
-		Name:    name,
-		Sha1:    tool.SHA1(gouuid.NewV4().String()),
-		Created: db.clock(),
+		UserID: userID,
+		Name:   name,
+		Sha1:   tool.SHA1(gouuid.NewV4().String()),
 	}
 	return token, db.DB.Create(token).Error
 }
@@ -150,6 +148,5 @@ func (db *accessTokens) List(userID int64) ([]*AccessToken, error) {
 }
 
 func (db *accessTokens) Save(t *AccessToken) error {
-	t.Updated = db.clock()
 	return db.DB.Save(t).Error
 }

+ 12 - 33
internal/db/access_tokens_test.go

@@ -5,15 +5,12 @@
 package db
 
 import (
-	"fmt"
-	"os"
-	"path/filepath"
 	"testing"
 	"time"
 
+	"github.com/jinzhu/gorm"
 	"github.com/stretchr/testify/assert"
 
-	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/errutil"
 )
 
@@ -24,34 +21,10 @@ func Test_accessTokens(t *testing.T) {
 
 	t.Parallel()
 
-	dbpath := filepath.Join(os.TempDir(), fmt.Sprintf("gogs-%d.db", time.Now().Unix()))
-	gdb, err := openDB(conf.DatabaseOpts{
-		Type: "sqlite3",
-		Path: dbpath,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Cleanup(func() {
-		_ = gdb.Close()
-
-		if t.Failed() {
-			t.Logf("Database %q left intact for inspection", dbpath)
-			return
-		}
-
-		_ = os.Remove(dbpath)
-	})
-
-	err = gdb.AutoMigrate(new(AccessToken)).Error
-	if err != nil {
-		t.Fatal(err)
+	db := &accessTokens{
+		DB: initTestDB(t, "accessTokens", new(AccessToken)),
 	}
 
-	now := time.Now().Truncate(time.Second)
-	clock := func() time.Time { return now }
-	db := &accessTokens{DB: gdb, clock: clock}
-
 	for _, tc := range []struct {
 		name string
 		test func(*testing.T, *accessTokens)
@@ -64,7 +37,7 @@ func Test_accessTokens(t *testing.T) {
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Cleanup(func() {
-				err := deleteTables(gdb, new(AccessToken))
+				err := deleteTables(db.DB, new(AccessToken))
 				if err != nil {
 					t.Fatal(err)
 				}
@@ -84,7 +57,13 @@ func test_accessTokens_Create(t *testing.T, db *accessTokens) {
 	assert.Equal(t, int64(1), token.UserID)
 	assert.Equal(t, "Test", token.Name)
 	assert.Equal(t, 40, len(token.Sha1), "sha1 length")
-	assert.Equal(t, db.clock(), token.Created)
+
+	// Get it back and check the Created field
+	token, err = db.GetBySHA(token.Sha1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, gorm.NowFunc().Format(time.RFC3339), token.Created.Format(time.RFC3339))
 
 	// Try create second access token with same name should fail
 	_, err = db.Create(token.UserID, token.Name)
@@ -197,5 +176,5 @@ func test_accessTokens_Save(t *testing.T, db *accessTokens) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	assert.Equal(t, db.clock(), token.Updated)
+	assert.Equal(t, gorm.NowFunc().Format(time.RFC3339), token.Updated.Format(time.RFC3339))
 }

+ 5 - 2
internal/db/db.go

@@ -163,9 +163,12 @@ func Init() error {
 		return errors.Wrap(err, "migrate schemes")
 	}
 
-	clock := func() time.Time {return time.Now().UTC().Truncate(time.Microsecond)}
+	gorm.NowFunc = func() time.Time {
+		return time.Now().UTC().Truncate(time.Microsecond)
+	}
+
 	// Initialize stores, sorted in alphabetical order.
-	AccessTokens = &accessTokens{DB: db, clock: clock}
+	AccessTokens = &accessTokens{DB: db}
 	LoginSources = &loginSources{DB: db}
 	LFS = &lfs{DB: db}
 	Perms = &perms{DB: db}

+ 117 - 0
internal/db/lfs_test.go

@@ -0,0 +1,117 @@
+// Copyright 2020 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package db
+
+import (
+	"testing"
+	"time"
+
+	"github.com/jinzhu/gorm"
+	"github.com/stretchr/testify/assert"
+
+	"gogs.io/gogs/internal/errutil"
+	"gogs.io/gogs/internal/lfsutil"
+)
+
+func Test_lfs(t *testing.T) {
+	if testing.Short() {
+		t.Skip()
+	}
+
+	t.Parallel()
+
+	db := &lfs{
+		DB: initTestDB(t, "lfs", new(LFSObject)),
+	}
+
+	for _, tc := range []struct {
+		name string
+		test func(*testing.T, *lfs)
+	}{
+		{"CreateObject", test_lfs_CreateObject},
+		{"GetObjectByOID", test_lfs_GetObjectByOID},
+		{"GetObjectsByOIDs", test_lfs_GetObjectsByOIDs},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Cleanup(func() {
+				err := deleteTables(db.DB, new(LFSObject))
+				if err != nil {
+					t.Fatal(err)
+				}
+			})
+			tc.test(t, db)
+		})
+	}
+}
+
+func test_lfs_CreateObject(t *testing.T, db *lfs) {
+	// Create first LFS object
+	repoID := int64(1)
+	oid := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
+	err := db.CreateObject(repoID, oid, 12, lfsutil.StorageLocal)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Get it back and check the CreatedAt field
+	object, err := db.GetObjectByOID(repoID, oid)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, gorm.NowFunc().Format(time.RFC3339), object.CreatedAt.Format(time.RFC3339))
+
+	// Try create second LFS object with same oid should fail
+	err = db.CreateObject(repoID, oid, 12, lfsutil.StorageLocal)
+	assert.Error(t, err)
+}
+
+func test_lfs_GetObjectByOID(t *testing.T, db *lfs) {
+	// Create a LFS object
+	repoID := int64(1)
+	oid := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
+	err := db.CreateObject(repoID, oid, 12, lfsutil.StorageLocal)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// We should be able to get it back
+	_, err = db.GetObjectByOID(repoID, oid)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Try to get a non-existent object
+	_, err = db.GetObjectByOID(repoID, "bad_oid")
+	expErr := ErrLFSObjectNotExist{args: errutil.Args{"repoID": repoID, "oid": lfsutil.OID("bad_oid")}}
+	assert.Equal(t, expErr, err)
+}
+
+func test_lfs_GetObjectsByOIDs(t *testing.T, db *lfs) {
+	// Create two LFS objects
+	repoID := int64(1)
+	oid1 := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
+	oid2 := lfsutil.OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64g")
+	err := db.CreateObject(repoID, oid1, 12, lfsutil.StorageLocal)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = db.CreateObject(repoID, oid2, 12, lfsutil.StorageLocal)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// We should be able to get them back and ignore non-existent ones
+	objects, err := db.GetObjectsByOIDs(repoID, oid1, oid2, "bad_oid")
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, 2, len(objects), "number of objects")
+
+	assert.Equal(t, repoID, objects[0].RepoID)
+	assert.Equal(t, oid1, objects[0].OID)
+
+	assert.Equal(t, repoID, objects[1].RepoID)
+	assert.Equal(t, oid2, objects[1].OID)
+}

+ 44 - 0
internal/db/main_test.go

@@ -7,12 +7,17 @@ package db
 import (
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"os"
+	"path/filepath"
 	"testing"
+	"time"
 
 	"github.com/jinzhu/gorm"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
+	"gogs.io/gogs/internal/dbutil"
 	"gogs.io/gogs/internal/testutil"
 )
 
@@ -27,6 +32,10 @@ func TestMain(m *testing.M) {
 			os.Exit(1)
 		}
 	}
+
+	now := time.Now().Truncate(time.Second)
+	gorm.NowFunc = func() time.Time { return now }
+
 	os.Exit(m.Run())
 }
 
@@ -39,3 +48,38 @@ func deleteTables(db *gorm.DB, tables ...interface{}) error {
 	}
 	return nil
 }
+
+func initTestDB(t *testing.T, suite string, tables ...interface{}) *gorm.DB {
+	t.Helper()
+
+	dbpath := filepath.Join(os.TempDir(), fmt.Sprintf("gogs-%s-%d.db", suite, time.Now().Unix()))
+	db, err := openDB(conf.DatabaseOpts{
+		Type: "sqlite3",
+		Path: dbpath,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Cleanup(func() {
+		_ = db.Close()
+
+		if t.Failed() {
+			t.Logf("Database %q left intact for inspection", dbpath)
+			return
+		}
+
+		_ = os.Remove(dbpath)
+	})
+
+	db.SingularTable(true)
+	if !testing.Verbose() {
+		db.SetLogger(&dbutil.Writer{Writer: ioutil.Discard})
+	}
+
+	err = db.AutoMigrate(tables...).Error
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return db
+}