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:
wxiaoguang
2026-05-15 22:26:36 +08:00
committed by GitHub
parent cf0f25b798
commit 59db4154eb
39 changed files with 208 additions and 313 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -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())

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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")
}

View File

@@ -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==",

View File

@@ -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",

View File

@@ -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
}