mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-18 19:11:06 +00:00
chore: clean up tests (#37715)
1. use MockVariableValue as much as possible 2. use wg.Go as much as possible instead of Add/Done 3. simplify global lock's DefaultLocker logic to make it easier to test 4. introduce a general approach for getting external service config in CI 5. remove unclear & unnecessary "t.Skip" 6. use modern generic syntax for remaining "DecodeJSON" calls 7. clarify test result for "list gitignore templates" and "list licenses"
This commit is contained in:
@@ -6,31 +6,39 @@ package globallock
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLocker Locker
|
||||
initOnce sync.Once
|
||||
initFunc = func() {
|
||||
switch setting.GlobalLock.ServiceType {
|
||||
case "redis":
|
||||
defaultLocker = NewRedisLocker(setting.GlobalLock.ServiceConnStr)
|
||||
case "memory":
|
||||
fallthrough
|
||||
default:
|
||||
defaultLocker = NewMemoryLocker()
|
||||
}
|
||||
} // define initFunc as a variable to make it possible to change it in tests
|
||||
defaultLocker atomic.Pointer[Locker]
|
||||
defaultMutex sync.Mutex
|
||||
)
|
||||
|
||||
func initDefaultLocker() Locker {
|
||||
switch setting.GlobalLock.ServiceType {
|
||||
case "redis":
|
||||
return NewRedisLocker(setting.GlobalLock.ServiceConnStr)
|
||||
default: // "memory"
|
||||
return NewMemoryLocker()
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultLocker returns the default locker.
|
||||
func DefaultLocker() Locker {
|
||||
initOnce.Do(func() {
|
||||
initFunc()
|
||||
})
|
||||
return defaultLocker
|
||||
ptr := defaultLocker.Load()
|
||||
if ptr == nil {
|
||||
defaultMutex.Lock()
|
||||
ptr = defaultLocker.Load()
|
||||
if ptr == nil {
|
||||
ptr = new(initDefaultLocker())
|
||||
defaultLocker.Store(ptr)
|
||||
}
|
||||
defaultMutex.Unlock()
|
||||
ptr = defaultLocker.Load()
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// Lock tries to acquire a lock for the given key, it uses the default locker.
|
||||
|
||||
@@ -5,7 +5,6 @@ package globallock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -15,50 +14,13 @@ import (
|
||||
|
||||
func TestLockAndDo(t *testing.T) {
|
||||
t.Run("redis", func(t *testing.T) {
|
||||
url := "redis://127.0.0.1:6379/0"
|
||||
if os.Getenv("CI") == "" {
|
||||
// Make it possible to run tests against a local redis instance
|
||||
url = os.Getenv("TEST_REDIS_URL")
|
||||
if url == "" {
|
||||
t.Skip("TEST_REDIS_URL not set and not running in CI")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
oldDefaultLocker := defaultLocker
|
||||
oldInitFunc := initFunc
|
||||
defer func() {
|
||||
defaultLocker = oldDefaultLocker
|
||||
initFunc = oldInitFunc
|
||||
if defaultLocker == nil {
|
||||
initOnce = sync.Once{}
|
||||
}
|
||||
}()
|
||||
|
||||
initOnce = sync.Once{}
|
||||
initFunc = func() {
|
||||
defaultLocker = NewRedisLocker(url)
|
||||
}
|
||||
|
||||
locker := newTestRedisLocker(t)
|
||||
defaultLocker.Store(new(locker))
|
||||
testLockAndDo(t)
|
||||
require.NoError(t, defaultLocker.(*redisLocker).Close())
|
||||
require.NoError(t, locker.(*redisLocker).Close())
|
||||
})
|
||||
t.Run("memory", func(t *testing.T) {
|
||||
oldDefaultLocker := defaultLocker
|
||||
oldInitFunc := initFunc
|
||||
defer func() {
|
||||
defaultLocker = oldDefaultLocker
|
||||
initFunc = oldInitFunc
|
||||
if defaultLocker == nil {
|
||||
initOnce = sync.Once{}
|
||||
}
|
||||
}()
|
||||
|
||||
initOnce = sync.Once{}
|
||||
initFunc = func() {
|
||||
defaultLocker = NewMemoryLocker()
|
||||
}
|
||||
|
||||
defaultLocker.Store(new(NewMemoryLocker()))
|
||||
testLockAndDo(t)
|
||||
})
|
||||
}
|
||||
@@ -69,13 +31,10 @@ func testLockAndDo(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
count := 0
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(concurrency)
|
||||
for range concurrency {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
err := LockAndDo(ctx, "test", func(ctx context.Context) error {
|
||||
count++
|
||||
|
||||
// It's impossible to acquire the lock inner the function
|
||||
ok, err := TryLockAndDo(ctx, "test", func(ctx context.Context) error {
|
||||
assert.Fail(t, "should not acquire the lock")
|
||||
@@ -83,13 +42,11 @@ func testLockAndDo(t *testing.T) {
|
||||
})
|
||||
assert.False(t, ok)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, concurrency, count)
|
||||
|
||||
@@ -10,29 +10,30 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/go-redsync/redsync/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newTestRedisLocker(t *testing.T) Locker {
|
||||
t.Helper()
|
||||
redisURL := util.IfZero(os.Getenv("TEST_REDIS_URL"), "redis://127.0.0.1:6379/0")
|
||||
rl := NewRedisLocker(redisURL).(*redisLocker)
|
||||
err := rl.conn.Ping(t.Context()).Err()
|
||||
if err != nil && test.AllowSkipExternalService() {
|
||||
t.Skip("no redis server for testing, skipped")
|
||||
}
|
||||
require.NoError(t, err, "redis error for testing: %v", err)
|
||||
return rl
|
||||
}
|
||||
|
||||
func TestLocker(t *testing.T) {
|
||||
t.Run("redis", func(t *testing.T) {
|
||||
url := "redis://127.0.0.1:6379/0"
|
||||
if os.Getenv("CI") == "" {
|
||||
// Make it possible to run tests against a local redis instance
|
||||
url = os.Getenv("TEST_REDIS_URL")
|
||||
if url == "" {
|
||||
t.Skip("TEST_REDIS_URL not set and not running in CI")
|
||||
return
|
||||
}
|
||||
}
|
||||
oldExpiry := redisLockExpiry
|
||||
redisLockExpiry = 5 * time.Second // make it shorter for testing
|
||||
defer func() {
|
||||
redisLockExpiry = oldExpiry
|
||||
}()
|
||||
|
||||
locker := NewRedisLocker(url)
|
||||
defer test.MockVariableValue(&redisLockExpiry, 5*time.Second)() // make it shorter for testing
|
||||
locker := newTestRedisLocker(t)
|
||||
testLocker(t, locker)
|
||||
testRedisLocker(t, locker.(*redisLocker))
|
||||
require.NoError(t, locker.(*redisLocker).Close())
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/go-redsync/redsync/v4"
|
||||
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const redisLockKeyPrefix = "gitea:globallock:"
|
||||
@@ -23,7 +24,8 @@ const redisLockKeyPrefix = "gitea:globallock:"
|
||||
var redisLockExpiry = 30 * time.Second
|
||||
|
||||
type redisLocker struct {
|
||||
rs *redsync.Redsync
|
||||
conn redis.UniversalClient
|
||||
rs *redsync.Redsync
|
||||
|
||||
mutexM sync.Map
|
||||
closed atomic.Bool
|
||||
@@ -33,17 +35,13 @@ type redisLocker struct {
|
||||
var _ Locker = &redisLocker{}
|
||||
|
||||
func NewRedisLocker(connection string) Locker {
|
||||
conn := nosql.GetManager().GetRedisClient(connection)
|
||||
l := &redisLocker{
|
||||
rs: redsync.New(
|
||||
goredis.NewPool(
|
||||
nosql.GetManager().GetRedisClient(connection),
|
||||
),
|
||||
),
|
||||
conn: conn,
|
||||
rs: redsync.New(goredis.NewPool(conn)),
|
||||
}
|
||||
|
||||
l.extendWg.Add(1)
|
||||
l.startExtend()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
|
||||
@@ -4,24 +4,19 @@
|
||||
package elasticsearch
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newRealIndexer(t *testing.T) *Indexer {
|
||||
t.Helper()
|
||||
url := "http://elasticsearch:9200"
|
||||
if os.Getenv("CI") == "" {
|
||||
url = os.Getenv("TEST_ELASTICSEARCH_URL")
|
||||
if url == "" {
|
||||
t.Skip("TEST_ELASTICSEARCH_URL not set and not running in CI")
|
||||
}
|
||||
}
|
||||
esURL := test.ExternalServiceHTTP(t, "TEST_ELASTICSEARCH_URL", "http://elasticsearch:9200")
|
||||
indexName := "gitea_test_" + strings.ReplaceAll(strings.ToLower(t.Name()), "/", "_")
|
||||
ix := NewIndexer(url, indexName, 1, `{"mappings":{"properties":{"x":{"type":"keyword"}}}}`)
|
||||
ix := NewIndexer(esURL, indexName, 1, `{"mappings":{"properties":{"x":{"type":"keyword"}}}}`)
|
||||
_, err := ix.Init(t.Context())
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(ix.Close)
|
||||
|
||||
@@ -7,27 +7,18 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/indexer/issues/internal/tests"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestElasticsearchIndexer(t *testing.T) {
|
||||
// The elasticsearch instance started by pull-db-tests.yml > test-unit > services > elasticsearch
|
||||
rawURL := "http://elastic:changeme@elasticsearch:9200"
|
||||
|
||||
if os.Getenv("CI") == "" {
|
||||
// Make it possible to run tests against a local elasticsearch instance
|
||||
rawURL = os.Getenv("TEST_ELASTICSEARCH_URL")
|
||||
if rawURL == "" {
|
||||
t.Skip("TEST_ELASTICSEARCH_URL not set and not running in CI")
|
||||
return
|
||||
}
|
||||
}
|
||||
rawURL := test.ExternalServiceHTTP(t, "TEST_ELASTICSEARCH_URL", "http://elastic:changeme@elasticsearch:9200")
|
||||
|
||||
// Go's net/http does not auto-attach URL userinfo as Basic Auth, so extract
|
||||
// it and set the header explicitly; otherwise auth-enforced clusters answer
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/indexer/issues/internal"
|
||||
"code.gitea.io/gitea/modules/indexer/issues/internal/tests"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -21,18 +22,8 @@ import (
|
||||
|
||||
func TestMeilisearchIndexer(t *testing.T) {
|
||||
// The meilisearch instance started by pull-db-tests.yml > test-unit > services > meilisearch
|
||||
url := "http://meilisearch:7700"
|
||||
key := "" // auth has been disabled in test environment
|
||||
|
||||
if os.Getenv("CI") == "" {
|
||||
// Make it possible to run tests against a local meilisearch instance
|
||||
url = os.Getenv("TEST_MEILISEARCH_URL")
|
||||
if url == "" {
|
||||
t.Skip("TEST_MEILISEARCH_URL not set and not running in CI")
|
||||
return
|
||||
}
|
||||
key = os.Getenv("TEST_MEILISEARCH_KEY")
|
||||
}
|
||||
url := test.ExternalServiceHTTP(t, "TEST_MEILISEARCH_URL", "http://meilisearch:7700")
|
||||
key := os.Getenv("TEST_MEILISEARCH_KEY")
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
resp, err := http.Get(url)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/nosql"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -57,10 +58,10 @@ func TestBaseRedis(t *testing.T) {
|
||||
}()
|
||||
if !waitRedisReady("redis://127.0.0.1:6379/0", 0) {
|
||||
redisServer = redisServerCmd(t)
|
||||
if redisServer == nil && os.Getenv("CI") == "" {
|
||||
t.Skip("redis-server not found")
|
||||
return
|
||||
if redisServer == nil && test.AllowSkipExternalService() {
|
||||
t.Skip("redis server command not found, skipped")
|
||||
}
|
||||
require.NotNil(t, redisServer)
|
||||
assert.NoError(t, redisServer.Start())
|
||||
require.True(t, waitRedisReady("redis://127.0.0.1:6379/0", 5*time.Second), "start redis-server")
|
||||
}
|
||||
|
||||
@@ -5,25 +5,22 @@ package storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAzureBlobStorage(t *testing.T) {
|
||||
if os.Getenv("CI") == "" {
|
||||
t.Skip("azureBlobStorage not present outside of CI")
|
||||
return
|
||||
}
|
||||
endpoint := test.ExternalServiceHTTP(t, "TEST_AZURESTORAGE_ENDPOINT", "http://devstoreaccount1.azurite.local:10000")
|
||||
storageType := setting.AzureBlobStorageType
|
||||
config := &setting.Storage{
|
||||
AzureBlobConfig: setting.AzureBlobStorageConfig{
|
||||
// https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#ip-style-url
|
||||
Endpoint: "http://devstoreaccount1.azurite.local:10000",
|
||||
Endpoint: endpoint,
|
||||
// https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#well-known-storage-account-and-key
|
||||
AccountName: "devstoreaccount1",
|
||||
AccountKey: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
|
||||
@@ -77,15 +74,11 @@ func TestAzureBlobStoragePath(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_azureBlobObject(t *testing.T) {
|
||||
if os.Getenv("CI") == "" {
|
||||
t.Skip("azureBlobStorage not present outside of CI")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint := test.ExternalServiceHTTP(t, "TEST_AZURESTORAGE_ENDPOINT", "http://devstoreaccount1.azurite.local:10000")
|
||||
s, err := NewStorage(setting.AzureBlobStorageType, &setting.Storage{
|
||||
AzureBlobConfig: setting.AzureBlobStorageConfig{
|
||||
// https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#ip-style-url
|
||||
Endpoint: "http://devstoreaccount1.azurite.local:10000",
|
||||
Endpoint: endpoint,
|
||||
// https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#well-known-storage-account-and-key
|
||||
AccountName: "devstoreaccount1",
|
||||
AccountKey: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
|
||||
|
||||
@@ -11,20 +11,18 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMinioStorage(t *testing.T) {
|
||||
if os.Getenv("CI") == "" {
|
||||
t.Skip("minioStorage not present outside of CI")
|
||||
return
|
||||
}
|
||||
endpoint := test.ExternalServiceHTTP(t, "TEST_MINIO_ENDPOINT", "minio:9000")
|
||||
storageType := setting.MinioStorageType
|
||||
config := &setting.Storage{
|
||||
MinioConfig: setting.MinioStorageConfig{
|
||||
Endpoint: "minio:9000",
|
||||
Endpoint: endpoint,
|
||||
AccessKeyID: "123456",
|
||||
SecretAccessKey: "12345678",
|
||||
Bucket: "gitea",
|
||||
|
||||
@@ -11,7 +11,10 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -141,3 +144,41 @@ func CompressGzip(content string) *bytes.Buffer {
|
||||
_ = cw.Close()
|
||||
return buf
|
||||
}
|
||||
|
||||
var AllowSkipExternalService = sync.OnceValue(func() bool {
|
||||
isLocalTesting := os.Getenv("CI") == ""
|
||||
ciSkipExternal, _ := strconv.ParseBool(os.Getenv("GITEA_TEST_CI_SKIP_EXTERNAL"))
|
||||
return isLocalTesting || ciSkipExternal
|
||||
})
|
||||
|
||||
type TestingT interface {
|
||||
Helper()
|
||||
Skipf(format string, args ...any)
|
||||
Errorf(format string, args ...any)
|
||||
Fatalf(format string, args ...any)
|
||||
}
|
||||
|
||||
func ExternalServiceHTTP(t TestingT, envVarName, def string) string {
|
||||
t.Helper()
|
||||
val := util.IfZero(os.Getenv(envVarName), def)
|
||||
if val == "" {
|
||||
if AllowSkipExternalService() {
|
||||
t.Skipf("skipping test because %s is not set", envVarName)
|
||||
} else {
|
||||
t.Fatalf("%s is not set, but skipping is not allowed in CI", envVarName)
|
||||
}
|
||||
}
|
||||
// minio's endpoint is "host:port" pattern
|
||||
testURL := util.Iif(strings.Contains(val, "://"), val, "http://"+val)
|
||||
resp, err := http.Get(testURL)
|
||||
if err != nil {
|
||||
if AllowSkipExternalService() {
|
||||
t.Skipf("skipping test because %s is not ready", val)
|
||||
} else {
|
||||
t.Fatalf("%s is not ready, but skipping is not allowed in CI", val)
|
||||
}
|
||||
} else {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user