修复缓存
Build docker and publish / build (20.15.1) (push) Successful in 7m26s

This commit is contained in:
2026-03-06 21:58:29 -08:00
parent 19932bb9f7
commit 4d913c1728
175 changed files with 437 additions and 12104 deletions
-29
View File
@@ -1,29 +0,0 @@
package pkgaes
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAes(t *testing.T) {
params := map[string]interface{}{
"method": "email",
"account": "admin@ppanel.dev",
"password": "password",
}
marshal, _ := json.Marshal(params)
jsonStr := string(marshal)
encrypt, iv, err := Encrypt([]byte(jsonStr), "123456")
if err != nil {
t.Fatalf("encrypt failed: %v", err)
}
decrypt, err := Decrypt(encrypt, "123456", iv)
if err != nil {
t.Fatalf("decrypt failed: %v", err)
}
assert.Equal(t, jsonStr, decrypt, "decrypt failed")
}
-55
View File
@@ -1,55 +0,0 @@
package apiversion
import "testing"
func TestParse(t *testing.T) {
tests := []struct {
name string
raw string
valid bool
version Version
}{
{name: "empty", raw: "", valid: false},
{name: "invalid text", raw: "abc", valid: false},
{name: "missing patch", raw: "1.0", valid: false},
{name: "exact", raw: "1.0.0", valid: true, version: Version{Major: 1, Minor: 0, Patch: 0}},
{name: "with prefix", raw: "v1.2.3", valid: true, version: Version{Major: 1, Minor: 2, Patch: 3}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
version, ok := Parse(tt.raw)
if ok != tt.valid {
t.Fatalf("expected valid=%v, got %v", tt.valid, ok)
}
if tt.valid && version != tt.version {
t.Fatalf("expected version=%+v, got %+v", tt.version, version)
}
})
}
}
func TestUseLatest(t *testing.T) {
tests := []struct {
name string
header string
threshold string
expect bool
}{
{name: "missing header", header: "", threshold: "1.0.0", expect: false},
{name: "invalid header", header: "invalid", threshold: "1.0.0", expect: false},
{name: "equal threshold", header: "1.0.0", threshold: "1.0.0", expect: false},
{name: "greater threshold", header: "1.0.1", threshold: "1.0.0", expect: true},
{name: "greater with v prefix", header: "v1.2.3", threshold: "1.0.0", expect: true},
{name: "less than threshold", header: "0.9.9", threshold: "1.0.0", expect: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := UseLatest(tt.header, tt.threshold)
if result != tt.expect {
t.Fatalf("expected %v, got %v", tt.expect, result)
}
})
}
}
-28
View File
@@ -1,28 +0,0 @@
package cache
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCacheOptions(t *testing.T) {
t.Run("default options", func(t *testing.T) {
o := newOptions()
assert.Equal(t, defaultExpiry, o.Expiry)
assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry)
})
t.Run("with expiry", func(t *testing.T) {
o := newOptions(WithExpiry(time.Second))
assert.Equal(t, time.Second, o.Expiry)
assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry)
})
t.Run("with not found expiry", func(t *testing.T) {
o := newOptions(WithNotFoundExpiry(time.Second))
assert.Equal(t, defaultExpiry, o.Expiry)
assert.Equal(t, time.Second, o.NotFoundExpiry)
})
}
+7
View File
@@ -109,6 +109,13 @@ func (cc CachedConn) QueryCtx(ctx context.Context, v interface{}, key string, qu
}
return cc.SetCache(key, v)
}
// Cache data corrupted (e.g. bad JSON), delete key and fall through to DB
_ = cc.DelCache(key)
err = query(cc.db.WithContext(ctx), v)
if err != nil {
return err
}
return cc.SetCache(key, v)
}
return
}
+168 -51
View File
@@ -2,65 +2,182 @@ package cache
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/perfect-panel/server/pkg/orm"
"github.com/alicebob/miniredis/v2"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
)
type User struct {
Id int64 `gorm:"primarykey"`
Email string `gorm:"index:idx_email;type:varchar(100);unique;not null;comment:电子邮箱"`
Password string `gorm:"type:varchar(100);comment:用户密码;not null"`
Avatar string `gorm:"type:varchar(200);default:'';comment:用户头像"`
Balance int64 `gorm:"default:0;comment:用户余额"`
Telegram int64 `gorm:"default:null;comment:Telegram账号"`
ReferCode string `gorm:"type:varchar(20);default:'';comment:推荐码"`
RefererId int64 `gorm:"comment:推荐人ID"`
Enable bool `gorm:"default:true;not null;comment:账户是否可用"`
IsAdmin bool `gorm:"default:false;not null;comment:是否管理员"`
ValidEmail bool `gorm:"default:false;not null;comment:是否验证邮箱"`
EnableEmailNotify bool `gorm:"default:false;not null;comment:是否启用邮件通知"`
EnableTelegramNotify bool `gorm:"default:false;not null;comment:是否启用Telegram通知"`
EnableBalanceNotify bool `gorm:"default:false;not null;comment:是否启用余额变动通知"`
EnableLoginNotify bool `gorm:"default:false;not null;comment:是否启用登录通知"`
EnableSubscribeNotify bool `gorm:"default:false;not null;comment:是否启用订阅通知"`
EnableTradeNotify bool `gorm:"default:false;not null;comment:是否启用交易通知"`
CreatedAt time.Time `gorm:"<-:create;comment:创建时间"`
UpdatedAt time.Time `gorm:"comment:更新时间"`
DeletedAt gorm.DeletedAt `gorm:"default:null;comment:删除时间"`
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt;comment:1:正常 0:删除"` // Use `1` `0` to identify
// testUser is a simple struct used across all QueryCtx tests.
type testUser struct {
ID int64 `json:"id"`
Name string `json:"name"`
}
func TestGormCacheCtx(t *testing.T) {
t.Skipf("skip TestGormCacheCtx test")
db, err := orm.ConnectMysql(orm.Mysql{
Config: orm.Config{
Addr: "localhost:3306",
Config: "charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai",
Dbname: "vpnboard",
Username: "root",
Password: "mylove520",
},
// setupCachedConn creates a CachedConn backed by a real miniredis instance
// and a bare *gorm.DB (no real database connection needed because the
// QueryCtxFn callback is fully under our control).
func setupCachedConn(t *testing.T) (CachedConn, *miniredis.Miniredis) {
t.Helper()
mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
})
if err != nil {
t.Error(err)
}
rds := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
conn := NewConn(db, rds)
var u User
key := "user:id"
err = conn.QueryCtx(context.Background(), &u, key, func(conn *gorm.DB, v interface{}) error {
return conn.Where("id = ?", 1).First(v).Error
})
if err != nil {
t.Error(err)
return
}
t.Logf("get cache success %+v", u)
t.Cleanup(func() { rdb.Close() })
// Use SQLite in-memory to get a properly initialized *gorm.DB.
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
cc := NewConn(db, rdb, WithExpiry(time.Minute))
return cc, mr
}
func TestQueryCtx_CacheHit(t *testing.T) {
cc, mr := setupCachedConn(t)
ctx := context.Background()
key := "cache:user:1"
// Pre-populate the cache with valid JSON.
expected := testUser{ID: 1, Name: "Alice"}
data, err := json.Marshal(expected)
require.NoError(t, err)
mr.Set(key, string(data))
// Track whether the DB query function is called.
dbCalled := false
queryFn := func(conn *gorm.DB, v interface{}) error {
dbCalled = true
return nil
}
var result testUser
err = cc.QueryCtx(ctx, &result, key, queryFn)
assert.NoError(t, err)
assert.False(t, dbCalled, "DB query should NOT be called on cache hit")
assert.Equal(t, expected.ID, result.ID)
assert.Equal(t, expected.Name, result.Name)
}
func TestQueryCtx_CacheMiss_QueriesDB_SetsCache(t *testing.T) {
cc, mr := setupCachedConn(t)
ctx := context.Background()
key := "cache:user:2"
// Do NOT pre-populate the cache -- this is a cache miss scenario.
dbCalled := false
queryFn := func(conn *gorm.DB, v interface{}) error {
dbCalled = true
u := v.(*testUser)
u.ID = 2
u.Name = "Bob"
return nil
}
var result testUser
err := cc.QueryCtx(ctx, &result, key, queryFn)
assert.NoError(t, err)
assert.True(t, dbCalled, "DB query should be called on cache miss")
assert.Equal(t, int64(2), result.ID)
assert.Equal(t, "Bob", result.Name)
// Verify the value was written back to cache.
cached, cacheErr := mr.Get(key)
require.NoError(t, cacheErr)
var cachedUser testUser
require.NoError(t, json.Unmarshal([]byte(cached), &cachedUser))
assert.Equal(t, int64(2), cachedUser.ID)
assert.Equal(t, "Bob", cachedUser.Name)
}
func TestQueryCtx_CorruptedCache_SelfHeals(t *testing.T) {
cc, mr := setupCachedConn(t)
ctx := context.Background()
key := "cache:user:3"
// Store invalid JSON in the cache to simulate corruption.
mr.Set(key, "THIS IS NOT VALID JSON{{{")
dbCalled := false
queryFn := func(conn *gorm.DB, v interface{}) error {
dbCalled = true
u := v.(*testUser)
u.ID = 3
u.Name = "Charlie"
return nil
}
var result testUser
err := cc.QueryCtx(ctx, &result, key, queryFn)
assert.NoError(t, err)
assert.True(t, dbCalled, "DB query should be called when cache is corrupted")
assert.Equal(t, int64(3), result.ID)
assert.Equal(t, "Charlie", result.Name)
// Verify the corrupt key was replaced with valid data.
cached, cacheErr := mr.Get(key)
require.NoError(t, cacheErr)
var cachedUser testUser
require.NoError(t, json.Unmarshal([]byte(cached), &cachedUser))
assert.Equal(t, int64(3), cachedUser.ID)
assert.Equal(t, "Charlie", cachedUser.Name)
}
func TestQueryCtx_CacheMiss_DBFails_ReturnsError(t *testing.T) {
cc, mr := setupCachedConn(t)
ctx := context.Background()
key := "cache:user:4"
// No cache entry -- this is a miss.
dbErr := errors.New("connection refused")
queryFn := func(conn *gorm.DB, v interface{}) error {
return dbErr
}
var result testUser
err := cc.QueryCtx(ctx, &result, key, queryFn)
assert.Error(t, err)
assert.Equal(t, dbErr, err)
// Cache should remain empty -- no value was written.
assert.False(t, mr.Exists(key), "cache should NOT be set when DB query fails")
}
func TestQueryCtx_CorruptedCache_DBFails_ReturnsError(t *testing.T) {
cc, mr := setupCachedConn(t)
ctx := context.Background()
key := "cache:user:5"
// Store invalid JSON to trigger the corruption branch.
mr.Set(key, "<<<CORRUPT>>>")
dbErr := errors.New("database is down")
queryFn := func(conn *gorm.DB, v interface{}) error {
return dbErr
}
var result testUser
err := cc.QueryCtx(ctx, &result, key, queryFn)
assert.Error(t, err)
assert.Equal(t, dbErr, err)
// The corrupt key should have been deleted (DelCache was called),
// and no new value was set because the DB query failed.
assert.False(t, mr.Exists(key), "corrupt key should be deleted even when DB fails")
}
@@ -1,13 +0,0 @@
package calculateMonths
import (
"testing"
"time"
)
func TestCalculateMonths(t *testing.T) {
startTime, _ := time.Parse(time.DateTime, "2025-01-15 00:00:00")
EndTime, _ := time.Parse(time.DateTime, "2025-05-15 00:00:00")
months := CalculateMonths(startTime, EndTime)
t.Log(months)
}
-17
View File
@@ -1,17 +0,0 @@
package color
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithColor(t *testing.T) {
output := WithColor("Hello", BgRed)
assert.Equal(t, "Hello", output)
}
func TestWithColorPadding(t *testing.T) {
output := WithColorPadding("Hello", BgRed)
assert.Equal(t, " Hello ", output)
}
-18
View File
@@ -1,18 +0,0 @@
package conf
import "testing"
type Server struct {
Host string `yaml:"Host" default:"localhost"`
Port int `yaml:"Port" default:"8080"`
}
type Config struct {
Server Server `yaml:"Server"`
}
func TestConfigLoad(t *testing.T) {
var c Config
MustLoad("./config_test.yaml", &c)
t.Logf("config: %+v", c)
}
-3
View File
@@ -1,3 +0,0 @@
Server:
Port: 9999
Host: 0.0.0.0
-665
View File
@@ -1,665 +0,0 @@
package deduction
import (
"math"
"testing"
"time"
)
func TestSubscribe_Validate(t *testing.T) {
tests := []struct {
name string
sub Subscribe
wantErr bool
errType error
}{
{
name: "valid subscription",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(24 * time.Hour),
Traffic: 1000,
Download: 100,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: 50,
},
wantErr: false,
},
{
name: "negative traffic",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(24 * time.Hour),
Traffic: -1000,
Download: 100,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: 50,
},
wantErr: true,
errType: ErrInvalidTraffic,
},
{
name: "negative download",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(24 * time.Hour),
Traffic: 1000,
Download: -100,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: 50,
},
wantErr: true,
errType: ErrInvalidTraffic,
},
{
name: "download + upload exceeds traffic",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(24 * time.Hour),
Traffic: 1000,
Download: 600,
Upload: 500,
UnitTime: UnitTimeMonth,
DeductionRatio: 50,
},
wantErr: true,
},
{
name: "expire time before start time",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(-24 * time.Hour),
Traffic: 1000,
Download: 100,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: 50,
},
wantErr: true,
errType: ErrInvalidTimeRange,
},
{
name: "invalid deduction ratio - negative",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(24 * time.Hour),
Traffic: 1000,
Download: 100,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: -10,
},
wantErr: true,
errType: ErrInvalidDeductionRatio,
},
{
name: "invalid deduction ratio - over 100",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(24 * time.Hour),
Traffic: 1000,
Download: 100,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: 150,
},
wantErr: true,
errType: ErrInvalidDeductionRatio,
},
{
name: "invalid unit time",
sub: Subscribe{
StartTime: time.Now(),
ExpireTime: time.Now().Add(24 * time.Hour),
Traffic: 1000,
Download: 100,
Upload: 200,
UnitTime: "InvalidUnit",
DeductionRatio: 50,
},
wantErr: true,
errType: ErrInvalidUnitTime,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.sub.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Subscribe.Validate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.errType != nil && err != tt.errType {
t.Errorf("Subscribe.Validate() error = %v, want %v", err, tt.errType)
}
})
}
}
func TestOrder_Validate(t *testing.T) {
tests := []struct {
name string
order Order
wantErr bool
errType error
}{
{
name: "valid order",
order: Order{Amount: 1000, Quantity: 2},
wantErr: false,
},
{
name: "zero quantity",
order: Order{Amount: 1000, Quantity: 0},
wantErr: true,
errType: ErrInvalidQuantity,
},
{
name: "negative quantity",
order: Order{Amount: 1000, Quantity: -1},
wantErr: true,
errType: ErrInvalidQuantity,
},
{
name: "negative amount",
order: Order{Amount: -1000, Quantity: 2},
wantErr: true,
errType: ErrInvalidAmount,
},
{
name: "zero amount is valid",
order: Order{Amount: 0, Quantity: 1},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.order.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Order.Validate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.errType != nil && err != tt.errType {
t.Errorf("Order.Validate() error = %v, want %v", err, tt.errType)
}
})
}
}
func TestSafeMultiply(t *testing.T) {
tests := []struct {
name string
a, b int64
want int64
wantErr bool
}{
{
name: "normal multiplication",
a: 10,
b: 20,
want: 200,
wantErr: false,
},
{
name: "zero multiplication",
a: 10,
b: 0,
want: 0,
wantErr: false,
},
{
name: "negative multiplication",
a: -10,
b: 20,
want: -200,
wantErr: false,
},
{
name: "overflow case",
a: math.MaxInt64,
b: 2,
want: 0,
wantErr: true,
},
{
name: "large numbers no overflow",
a: 1000000,
b: 1000000,
want: 1000000000000,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safeMultiply(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("safeMultiply() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("safeMultiply() = %v, want %v", got, tt.want)
}
})
}
}
func TestSafeAdd(t *testing.T) {
tests := []struct {
name string
a, b int64
want int64
wantErr bool
}{
{
name: "normal addition",
a: 10,
b: 20,
want: 30,
wantErr: false,
},
{
name: "negative addition",
a: -10,
b: 5,
want: -5,
wantErr: false,
},
{
name: "overflow case",
a: math.MaxInt64,
b: 1,
want: 0,
wantErr: true,
},
{
name: "underflow case",
a: math.MinInt64,
b: -1,
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safeAdd(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("safeAdd() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("safeAdd() = %v, want %v", got, tt.want)
}
})
}
}
func TestSafeDivide(t *testing.T) {
tests := []struct {
name string
a, b int64
want int64
wantErr bool
}{
{
name: "normal division",
a: 20,
b: 10,
want: 2,
wantErr: false,
},
{
name: "division by zero",
a: 20,
b: 0,
want: 0,
wantErr: true,
},
{
name: "negative division",
a: -20,
b: 10,
want: -2,
wantErr: false,
},
{
name: "zero dividend",
a: 0,
b: 10,
want: 0,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safeDivide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("safeDivide() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("safeDivide() = %v, want %v", got, tt.want)
}
})
}
}
func TestCalculateWeights(t *testing.T) {
tests := []struct {
name string
deductionRatio int64
wantTrafficWeight float64
wantTimeWeight float64
}{
{
name: "zero ratio",
deductionRatio: 0,
wantTrafficWeight: 0,
wantTimeWeight: 0,
},
{
name: "50% ratio",
deductionRatio: 50,
wantTrafficWeight: 0.5,
wantTimeWeight: 0.5,
},
{
name: "75% ratio",
deductionRatio: 75,
wantTrafficWeight: 0.75,
wantTimeWeight: 0.25,
},
{
name: "100% ratio",
deductionRatio: 100,
wantTrafficWeight: 1.0,
wantTimeWeight: 0.0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotTrafficWeight, gotTimeWeight := calculateWeights(tt.deductionRatio)
if gotTrafficWeight != tt.wantTrafficWeight {
t.Errorf("calculateWeights() trafficWeight = %v, want %v", gotTrafficWeight, tt.wantTrafficWeight)
}
if gotTimeWeight != tt.wantTimeWeight {
t.Errorf("calculateWeights() timeWeight = %v, want %v", gotTimeWeight, tt.wantTimeWeight)
}
})
}
}
func TestCalculateProportionalAmount(t *testing.T) {
tests := []struct {
name string
unitPrice int64
remaining int64
total int64
want int64
wantErr bool
}{
{
name: "normal calculation",
unitPrice: 100,
remaining: 50,
total: 100,
want: 50,
wantErr: false,
},
{
name: "zero total",
unitPrice: 100,
remaining: 50,
total: 0,
want: 0,
wantErr: false,
},
{
name: "zero remaining",
unitPrice: 100,
remaining: 0,
total: 100,
want: 0,
wantErr: false,
},
{
name: "quarter remaining",
unitPrice: 200,
remaining: 25,
total: 100,
want: 50,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := calculateProportionalAmount(tt.unitPrice, tt.remaining, tt.total)
if (err != nil) != tt.wantErr {
t.Errorf("calculateProportionalAmount() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("calculateProportionalAmount() = %v, want %v", got, tt.want)
}
})
}
}
func TestCalculateNoLimitAmount(t *testing.T) {
tests := []struct {
name string
sub Subscribe
order Order
want int64
wantErr bool
}{
{
name: "normal no limit calculation",
sub: Subscribe{
Traffic: 1000,
Download: 300,
Upload: 200,
},
order: Order{
Amount: 1000,
},
want: 500, // (1000 - 300 - 200) / 1000 * 1000 = 500
wantErr: false,
},
{
name: "zero traffic",
sub: Subscribe{
Traffic: 0,
Download: 0,
Upload: 0,
},
order: Order{
Amount: 1000,
},
want: 0,
wantErr: false,
},
{
name: "overused traffic",
sub: Subscribe{
Traffic: 1000,
Download: 600,
Upload: 500,
},
order: Order{
Amount: 1000,
},
want: 0, // usedTraffic would be negative, clamped to 0
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := calculateNoLimitAmount(tt.sub, tt.order)
if (err != nil) != tt.wantErr {
t.Errorf("calculateNoLimitAmount() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("calculateNoLimitAmount() = %v, want %v", got, tt.want)
}
})
}
}
func TestCalculateRemainingAmount(t *testing.T) {
now := time.Now()
tests := []struct {
name string
sub Subscribe
order Order
wantErr bool
}{
{
name: "valid no limit subscription",
sub: Subscribe{
StartTime: now.Add(-24 * time.Hour),
ExpireTime: now.Add(24 * time.Hour),
Traffic: 1000,
Download: 300,
Upload: 200,
UnitTime: UnitTimeNoLimit,
ResetCycle: ResetCycleNone,
DeductionRatio: 0,
},
order: Order{
Amount: 1000,
Quantity: 1,
},
wantErr: false,
},
{
name: "invalid subscription",
sub: Subscribe{
StartTime: now,
ExpireTime: now.Add(-24 * time.Hour), // Invalid: expire before start
Traffic: 1000,
Download: 300,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: 0,
},
order: Order{
Amount: 1000,
Quantity: 1,
},
wantErr: true,
},
{
name: "invalid order",
sub: Subscribe{
StartTime: now.Add(-24 * time.Hour),
ExpireTime: now.Add(24 * time.Hour),
Traffic: 1000,
Download: 300,
Upload: 200,
UnitTime: UnitTimeMonth,
DeductionRatio: 0,
},
order: Order{
Amount: 1000,
Quantity: 0, // Invalid: zero quantity
},
wantErr: true,
},
{
name: "no limit with reset cycle",
sub: Subscribe{
StartTime: now.Add(-24 * time.Hour),
ExpireTime: now.Add(24 * time.Hour),
Traffic: 1000,
Download: 300,
Upload: 200,
UnitTime: UnitTimeNoLimit,
ResetCycle: ResetCycleMonthly, // Should return 0
DeductionRatio: 0,
},
order: Order{
Amount: 1000,
Quantity: 1,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := CalculateRemainingAmount(tt.sub, tt.order)
if (err != nil) != tt.wantErr {
t.Errorf("CalculateRemainingAmount() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestCalculateRemainingAmount_NoLimitWithResetCycle(t *testing.T) {
now := time.Now()
sub := Subscribe{
StartTime: now.Add(-24 * time.Hour),
ExpireTime: now.Add(24 * time.Hour),
Traffic: 1000,
Download: 300,
Upload: 200,
UnitTime: UnitTimeNoLimit,
ResetCycle: ResetCycleMonthly,
DeductionRatio: 0,
}
order := Order{
Amount: 1000,
Quantity: 1,
}
got, err := CalculateRemainingAmount(sub, order)
if err != nil {
t.Errorf("CalculateRemainingAmount() error = %v", err)
return
}
if got != 0 {
t.Errorf("CalculateRemainingAmount() = %v, want 0", got)
}
}
// Benchmark tests
func BenchmarkCalculateRemainingAmount(b *testing.B) {
now := time.Now()
sub := Subscribe{
StartTime: now.Add(-24 * time.Hour),
ExpireTime: now.Add(24 * time.Hour),
Traffic: 1000,
Download: 300,
Upload: 200,
UnitTime: UnitTimeMonth,
ResetCycle: ResetCycleNone,
DeductionRatio: 50,
}
order := Order{
Amount: 1000,
Quantity: 1,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = CalculateRemainingAmount(sub, order)
}
}
func BenchmarkSafeMultiply(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = safeMultiply(12345, 67890)
}
}
-123
View File
@@ -1,123 +0,0 @@
package device
import (
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"strings"
"sync"
"testing"
"time"
"github.com/pkg/errors"
"github.com/gorilla/websocket"
)
func TestDevice(t *testing.T) {
t.Skip("skip test")
/* deviceManager := NewDeviceManager(10, 3)
deviceManager.OnDeviceOnline = func(userID int64, deviceID, session string) {
fmt.Printf("✅ 设备 %s (用户 %d) 上线\n", deviceID, userID)
}
deviceManager.OnDeviceOffline = func(userID int64, deviceID, session string) {
fmt.Printf("❌ 设备 %s (用户 %d) 下线\n", deviceID, userID)
}
deviceManager.OnDeviceKicked = func(userID int64, deviceID, session string, operator Operator) {
fmt.Printf("⚠️ 设备 %s (用户 %d) 被踢下线\n", deviceID, userID)
}
deviceManager.OnMessage = func(userID int64, deviceID, session string, message string) {
log.Printf("✅收到消息: 设备 %s (用户 %d) 内容: %s,sesion: %s\n", deviceID, userID, message, session)
}
engine := gin.Default()
engine.GET("/ws/:userid/:device_number", func(c *gin.Context) {
//根据Authorization获取session
authorization := c.GetHeader("Authorization")
userid, err := strconv.ParseInt(c.Param("userid"), 10, 64)
if err != nil {
t.Errorf("get user id err:%v", err)
return
}
deviceNumber := c.Param("device_number")
deviceManager.AddDevice(c, authorization, userid, deviceNumber, 3)
return
})
go func() {
err := http.ListenAndServe(":8081", engine)
if err != nil {
t.Fatalf("engine start failed: %v", err)
}
}()
*/
h := http.Header{}
h.Add("Authorization", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTZXNzaW9uSWQiOiIwMTk0Y2ZiNy1hYjY0LTdjYjMtODUzYi03ZGU5YTAzNWRlZTgiLCJVc2VySWQiOjI5LCJleHAiOjE3MzkyNTY1MDgsImlhdCI6MTczODY1MTcwOH0.BGKT5-hongJPZrA_yAb6cf6go5iDR8T9uu1ZxUg8HDw")
mutex := sync.Mutex{}
serverURL := fmt.Sprintf("ws://localhost:8080/v1/app/ws/%d/%s", 29, "15502502051") // 假设 userID 为 1001,设备ID 为 deviceA
// 建立 WebSocket 连接
conn, resp, err := websocket.DefaultDialer.Dial(serverURL, h)
if err != nil {
all, err := io.ReadAll(resp.Body)
t.Fatalf("websocket dial failed: %v:%s", err, string(all))
}
// 启动一个 goroutine 来读取服务器消息
go func() {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
if errors.Is(err, net.ErrClosed) || strings.Contains(err.Error(), "use of closed network connection") {
log.Println("连接已关闭")
return
}
log.Printf("接收消息失败: %v", err)
return
}
fmt.Printf("收到来自服务器的消息: %s\n", msg)
}
}()
//发送心跳
go func() {
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
for range ticker.C {
mutex.Lock()
err := conn.WriteMessage(websocket.TextMessage, []byte("ping"))
mutex.Unlock()
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
log.Println("连接已关闭")
return
}
t.Errorf("websocket 写入失败: %v", err)
return
}
}
}()
updateSubscribe, _ := json.Marshal(map[string]interface{}{
"method": "test_method",
})
//发送一条消息
mutex.Lock()
err = conn.WriteMessage(websocket.TextMessage, updateSubscribe)
mutex.Unlock()
if err != nil {
t.Errorf("websocket write failed: %v", err)
}
time.Sleep(time.Second * 20)
conn.Close()
time.Sleep(time.Second * 5)
}
-24
View File
@@ -1,24 +0,0 @@
package smtp
import "testing"
func TestEmailSend(t *testing.T) {
t.Skipf("Skip TestEmailSend")
config := &Config{
Host: "smtp.mail.me.com",
Port: 587,
User: "support@ppanel.dev",
Pass: "password",
From: "support@ppanel.dev",
SSL: true,
SiteName: "",
}
address := []string{"tension@sparkdance.dev"}
subject := "test"
body := "test"
email := NewClient(config)
err := email.Send(address, subject, body)
if err != nil {
t.Errorf("send email error: %v", err)
}
}
-36
View File
@@ -1,36 +0,0 @@
package email
import (
"bytes"
"html/template"
"testing"
)
type VerifyTemplate struct {
Type uint8
SiteLogo string
SiteName string
Expire uint8
Code string
}
func TestVerifyEmail(t *testing.T) {
t.Skipf("Skip TestVerifyEmail test")
data := VerifyTemplate{
Type: 1,
SiteLogo: "https://www.google.com",
SiteName: "Google",
Expire: 5,
Code: "123456",
}
tpl, err := template.New("email").Parse(DefaultEmailVerifyTemplate)
if err != nil {
t.Error(err)
}
var result bytes.Buffer
err = tpl.Execute(&result, data)
if err != nil {
t.Error(err)
}
t.Log(result.String())
}
-82
View File
@@ -1,82 +0,0 @@
package errorx
import (
"errors"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
var errDummy = errors.New("hello")
func TestAtomicError(t *testing.T) {
var err AtomicError
err.Set(errDummy)
assert.Equal(t, errDummy, err.Load())
}
func TestAtomicErrorSetNil(t *testing.T) {
var (
errNil error
err AtomicError
)
err.Set(errNil)
assert.Equal(t, errNil, err.Load())
}
func TestAtomicErrorNil(t *testing.T) {
var err AtomicError
assert.Nil(t, err.Load())
}
func BenchmarkAtomicError(b *testing.B) {
var aerr AtomicError
wg := sync.WaitGroup{}
b.Run("Load", func(b *testing.B) {
var done uint32
go func() {
for {
if atomic.LoadUint32(&done) != 0 {
break
}
wg.Add(1)
go func() {
aerr.Set(errDummy)
wg.Done()
}()
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = aerr.Load()
}
b.StopTimer()
atomic.StoreUint32(&done, 1)
wg.Wait()
})
b.Run("Set", func(b *testing.B) {
var done uint32
go func() {
for {
if atomic.LoadUint32(&done) != 0 {
break
}
wg.Add(1)
go func() {
_ = aerr.Load()
wg.Done()
}()
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
aerr.Set(errDummy)
}
b.StopTimer()
atomic.StoreUint32(&done, 1)
wg.Wait()
})
}
-147
View File
@@ -1,147 +0,0 @@
package errorx
import (
"errors"
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
const (
err1 = "first error"
err2 = "second error"
)
func TestBatchErrorNil(t *testing.T) {
var batch BatchError
assert.Nil(t, batch.Err())
assert.False(t, batch.NotNil())
batch.Add(nil)
assert.Nil(t, batch.Err())
assert.False(t, batch.NotNil())
}
func TestBatchErrorNilFromFunc(t *testing.T) {
err := func() error {
var be BatchError
return be.Err()
}()
assert.True(t, err == nil)
}
func TestBatchErrorOneError(t *testing.T) {
var batch BatchError
batch.Add(errors.New(err1))
assert.NotNil(t, batch.Err())
assert.Equal(t, err1, batch.Err().Error())
assert.True(t, batch.NotNil())
}
func TestBatchErrorWithErrors(t *testing.T) {
var batch BatchError
batch.Add(errors.New(err1))
batch.Add(errors.New(err2))
assert.NotNil(t, batch.Err())
assert.Equal(t, fmt.Sprintf("%s\n%s", err1, err2), batch.Err().Error())
assert.True(t, batch.NotNil())
}
func TestBatchErrorConcurrentAdd(t *testing.T) {
const count = 10000
var batch BatchError
var wg sync.WaitGroup
wg.Add(count)
for i := 0; i < count; i++ {
go func() {
defer wg.Done()
batch.Add(errors.New(err1))
}()
}
wg.Wait()
assert.NotNil(t, batch.Err())
assert.Equal(t, count, len(batch.errs))
assert.True(t, batch.NotNil())
}
func TestBatchError_Unwrap(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var be BatchError
assert.Nil(t, be.Err())
assert.True(t, errors.Is(be.Err(), nil))
})
t.Run("one error", func(t *testing.T) {
var errFoo = errors.New("foo")
var errBar = errors.New("bar")
var be BatchError
be.Add(errFoo)
assert.True(t, errors.Is(be.Err(), errFoo))
assert.False(t, errors.Is(be.Err(), errBar))
})
t.Run("two errors", func(t *testing.T) {
var errFoo = errors.New("foo")
var errBar = errors.New("bar")
var errBaz = errors.New("baz")
var be BatchError
be.Add(errFoo)
be.Add(errBar)
assert.True(t, errors.Is(be.Err(), errFoo))
assert.True(t, errors.Is(be.Err(), errBar))
assert.False(t, errors.Is(be.Err(), errBaz))
})
}
func TestBatchError_Add(t *testing.T) {
var be BatchError
// Test adding nil errors
be.Add(nil, nil)
assert.False(t, be.NotNil(), "Expected BatchError to be empty after adding nil errors")
// Test adding non-nil errors
err1 := errors.New("error 1")
err2 := errors.New("error 2")
be.Add(err1, err2)
assert.True(t, be.NotNil(), "Expected BatchError to be non-empty after adding errors")
// Test adding a mix of nil and non-nil errors
err3 := errors.New("error 3")
be.Add(nil, err3, nil)
assert.True(t, be.NotNil(), "Expected BatchError to be non-empty after adding a mix of nil and non-nil errors")
}
func TestBatchError_Err(t *testing.T) {
var be BatchError
// Test Err() on empty BatchError
assert.Nil(t, be.Err(), "Expected nil error for empty BatchError")
// Test Err() with multiple errors
err1 := errors.New("error 1")
err2 := errors.New("error 2")
be.Add(err1, err2)
combinedErr := be.Err()
assert.NotNil(t, combinedErr, "Expected nil error for BatchError with multiple errors")
// Check if the combined error contains both error messages
errString := combinedErr.Error()
assert.Truef(t, errors.Is(combinedErr, err1), "Combined error doesn't contain first error: %s", errString)
assert.Truef(t, errors.Is(combinedErr, err2), "Combined error doesn't contain second error: %s", errString)
}
func TestBatchError_NotNil(t *testing.T) {
var be BatchError
// Test NotNil() on empty BatchError
assert.Nil(t, be.Err(), "Expected nil error for empty BatchError")
// Test NotNil() after adding an error
be.Add(errors.New("test error"))
assert.NotNil(t, be.Err(), "Expected non-nil error after adding an error")
}
-27
View File
@@ -1,27 +0,0 @@
package errorx
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestChain(t *testing.T) {
errDummy := errors.New("dummy")
assert.Nil(t, Chain(func() error {
return nil
}, func() error {
return nil
}))
assert.Equal(t, errDummy, Chain(func() error {
return errDummy
}, func() error {
return nil
}))
assert.Equal(t, errDummy, Chain(func() error {
return nil
}, func() error {
return errDummy
}))
}
-70
View File
@@ -1,70 +0,0 @@
package errorx
import (
"errors"
"testing"
)
func TestIn(t *testing.T) {
err1 := errors.New("error 1")
err2 := errors.New("error 2")
err3 := errors.New("error 3")
tests := []struct {
name string
err error
errs []error
want bool
}{
{
name: "Error matches one of the errors in the list",
err: err1,
errs: []error{err1, err2},
want: true,
},
{
name: "Error does not match any errors in the list",
err: err3,
errs: []error{err1, err2},
want: false,
},
{
name: "Empty error list",
err: err1,
errs: []error{},
want: false,
},
{
name: "Nil error with non-nil list",
err: nil,
errs: []error{err1, err2},
want: false,
},
{
name: "Non-nil error with nil in list",
err: err1,
errs: []error{nil, err2},
want: false,
},
{
name: "Error matches nil error in the list",
err: nil,
errs: []error{nil, err2},
want: true,
},
{
name: "Nil error with empty list",
err: nil,
errs: []error{},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := In(tt.err, tt.errs...); got != tt.want {
t.Errorf("In() = %v, want %v", got, tt.want)
}
})
}
}
-24
View File
@@ -1,24 +0,0 @@
package errorx
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWrap(t *testing.T) {
assert.Nil(t, Wrap(nil, "test"))
assert.Equal(t, "foo: bar", Wrap(errors.New("bar"), "foo").Error())
err := errors.New("foo")
assert.True(t, errors.Is(Wrap(err, "bar"), err))
}
func TestWrapf(t *testing.T) {
assert.Nil(t, Wrapf(nil, "%s", "test"))
assert.Equal(t, "foo bar: quz", Wrapf(errors.New("quz"), "foo %s", "bar").Error())
err := errors.New("foo")
assert.True(t, errors.Is(Wrapf(err, "foo %s", "bar"), err))
}
-12
View File
@@ -1,12 +0,0 @@
package exchangeRate
import "testing"
func TestGetExchangeRete(t *testing.T) {
t.Skip("skip TestGetExchangeRete")
result, err := GetExchangeRete("USD", "CNY", "", 1)
if err != nil {
t.Fatal(err)
}
t.Log(result)
}
-15
View File
@@ -1,15 +0,0 @@
package fs
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCloseOnExec(t *testing.T) {
file := os.NewFile(0, os.DevNull)
assert.NotPanics(t, func() {
CloseOnExec(file)
})
}
-49
View File
@@ -1,49 +0,0 @@
package fs
import (
"io"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTempFileWithText(t *testing.T) {
f, err := TempFileWithText("test")
if err != nil {
t.Error(err)
}
if f == nil {
t.Error("TempFileWithText returned nil")
}
if f.Name() == "" {
t.Error("TempFileWithText returned empty file name")
}
defer os.Remove(f.Name())
bs, err := io.ReadAll(f)
assert.Nil(t, err)
if len(bs) != 4 {
t.Error("TempFileWithText returned wrong file size")
}
if f.Close() != nil {
t.Error("TempFileWithText returned error on close")
}
}
func TestTempFilenameWithText(t *testing.T) {
f, err := TempFilenameWithText("test")
if err != nil {
t.Error(err)
}
if f == "" {
t.Error("TempFilenameWithText returned empty file name")
}
defer os.Remove(f)
bs, err := os.ReadFile(f)
assert.Nil(t, err)
if len(bs) != 4 {
t.Error("TempFilenameWithText returned wrong file size")
}
}
-155
View File
@@ -1,155 +0,0 @@
package hash
import (
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
const (
keySize = 20
requestSize = 1000
)
func BenchmarkConsistentHashGet(b *testing.B) {
ch := NewConsistentHash()
for i := 0; i < keySize; i++ {
ch.Add("localhost:" + strconv.Itoa(i))
}
for i := 0; i < b.N; i++ {
ch.Get(i)
}
}
func TestConsistentHashIncrementalTransfer(t *testing.T) {
prefix := "anything"
create := func() *ConsistentHash {
ch := NewConsistentHash()
for i := 0; i < keySize; i++ {
ch.Add(prefix + strconv.Itoa(i))
}
return ch
}
originCh := create()
keys := make(map[int]string, requestSize)
for i := 0; i < requestSize; i++ {
key, ok := originCh.Get(requestSize + i)
assert.True(t, ok)
assert.NotNil(t, key)
keys[i] = key.(string)
}
node := fmt.Sprintf("%s%d", prefix, keySize)
for i := 0; i < 10; i++ {
laterCh := create()
laterCh.AddWithWeight(node, 10*(i+1))
for j := 0; j < requestSize; j++ {
key, ok := laterCh.Get(requestSize + j)
assert.True(t, ok)
assert.NotNil(t, key)
value := key.(string)
assert.True(t, value == keys[j] || value == node)
}
}
}
func TestConsistentHashTransferOnFailure(t *testing.T) {
index := 41
keys, newKeys := getKeysBeforeAndAfterFailure(t, "localhost:", index)
var transferred int
for k, v := range newKeys {
if v != keys[k] {
transferred++
}
}
ratio := float32(transferred) / float32(requestSize)
assert.True(t, ratio < 2.5/float32(keySize), fmt.Sprintf("%d: %f", index, ratio))
}
func TestConsistentHashLeastTransferOnFailure(t *testing.T) {
prefix := "localhost:"
index := 41
keys, newKeys := getKeysBeforeAndAfterFailure(t, prefix, index)
for k, v := range keys {
newV := newKeys[k]
if v != prefix+strconv.Itoa(index) {
assert.Equal(t, v, newV)
}
}
}
func TestConsistentHash_Remove(t *testing.T) {
ch := NewConsistentHash()
ch.Add("first")
ch.Add("second")
ch.Remove("first")
for i := 0; i < 100; i++ {
val, ok := ch.Get(i)
assert.True(t, ok)
assert.Equal(t, "second", val)
}
}
func TestConsistentHash_RemoveInterface(t *testing.T) {
const key = "any"
ch := NewConsistentHash()
node1 := newMockNode(key, 1)
node2 := newMockNode(key, 2)
ch.AddWithWeight(node1, 80)
ch.AddWithWeight(node2, 50)
assert.Equal(t, 1, len(ch.nodes))
node, ok := ch.Get(1)
assert.True(t, ok)
assert.Equal(t, key, node.(*mockNode).addr)
assert.Equal(t, 2, node.(*mockNode).id)
}
func getKeysBeforeAndAfterFailure(t *testing.T, prefix string, index int) (map[int]string, map[int]string) {
ch := NewConsistentHash()
for i := 0; i < keySize; i++ {
ch.Add(prefix + strconv.Itoa(i))
}
keys := make(map[int]string, requestSize)
for i := 0; i < requestSize; i++ {
key, ok := ch.Get(requestSize + i)
assert.True(t, ok)
assert.NotNil(t, key)
keys[i] = key.(string)
}
remove := fmt.Sprintf("%s%d", prefix, index)
ch.Remove(remove)
newKeys := make(map[int]string, requestSize)
for i := 0; i < requestSize; i++ {
key, ok := ch.Get(requestSize + i)
assert.True(t, ok)
assert.NotNil(t, key)
assert.NotEqual(t, remove, key)
newKeys[i] = key.(string)
}
return keys, newKeys
}
type mockNode struct {
addr string
id int
}
func newMockNode(addr string, id int) *mockNode {
return &mockNode{
addr: addr,
id: id,
}
}
func (n *mockNode) String() string {
return n.addr
}
-47
View File
@@ -1,47 +0,0 @@
package hash
import (
"crypto/md5"
"fmt"
"hash/fnv"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
const (
text = "hello, world!\n"
md5Digest = "910c8bc73110b0cd1bc5d2bcae782511"
)
func TestMd5(t *testing.T) {
actual := fmt.Sprintf("%x", Md5([]byte(text)))
assert.Equal(t, md5Digest, actual)
}
func TestMd5Hex(t *testing.T) {
actual := Md5Hex([]byte(text))
assert.Equal(t, md5Digest, actual)
}
func BenchmarkHashFnv(b *testing.B) {
for i := 0; i < b.N; i++ {
h := fnv.New32()
new(big.Int).SetBytes(h.Sum([]byte(text))).Int64()
}
}
func BenchmarkHashMd5(b *testing.B) {
for i := 0; i < b.N; i++ {
h := md5.New()
bytes := h.Sum([]byte(text))
new(big.Int).SetBytes(bytes).Int64()
}
}
func BenchmarkMurmur3(b *testing.B) {
for i := 0; i < b.N; i++ {
Hash([]byte(text))
}
}
-35
View File
@@ -1,35 +0,0 @@
package apple
import (
"encoding/base64"
"encoding/json"
"testing"
"time"
)
func TestParseTransactionJWS(t *testing.T) {
payload := map[string]interface{}{
"bundleId": "co.airoport.app.ios",
"productId": "com.airport.vpn.pass.30d",
"transactionId": "1000000000001",
"originalTransactionId": "1000000000000",
"purchaseDate": float64(time.Now().UnixMilli()),
}
data, _ := json.Marshal(payload)
b64 := base64.RawURLEncoding.EncodeToString(data)
jws := "header." + b64 + ".signature"
p, err := ParseTransactionJWS(jws)
if err != nil {
t.Fatalf("parse error: %v", err)
}
if p.ProductId != payload["productId"] {
t.Fatalf("productId not match")
}
if p.BundleId != payload["bundleId"] {
t.Fatalf("bundleId not match")
}
if p.OriginalTransactionId != payload["originalTransactionId"] {
t.Fatalf("originalTransactionId not match")
}
}
-34
View File
@@ -1,34 +0,0 @@
package ip
import (
"testing"
"time"
)
func TestGetIPv4(t *testing.T) {
t.Skip("skip TestGetIPv4")
iPv4, err := GetIP("baidu.com")
if err != nil {
t.Fatal(err)
}
t.Log(iPv4)
}
func TestGetRegionByIp(t *testing.T) {
t.Skip("skip TestGetRegionByIp")
ips, err := GetIP("122.14.229.128")
if err != nil {
t.Fatal(err)
}
for _, ip := range ips {
t.Log(ip)
resp, err := GetRegionByIp(ip)
if err != nil {
t.Fatalf("ip: %s,err: %v", ip, err)
}
t.Logf("country: %s,City: %s,latitude:%s, longitude:%s", resp.Country, resp.City, resp.Latitude, resp.Longitude)
}
time.Sleep(3 * time.Second)
}
-23
View File
@@ -1,23 +0,0 @@
package jsonx
import "testing"
type User struct {
Id int64
Name string
Age int64
}
func TestJson(t *testing.T) {
t.Log("TestJson")
user := &User{
Id: 1,
Name: "test",
Age: 18,
}
b, err := Marshal(user)
if err != nil {
t.Error(err)
}
t.Log(string(b))
}
-22
View File
@@ -1,22 +0,0 @@
package jwt
import (
"testing"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
)
// TestNewJwtToken test NewJwtToken function
func TestParseJwtToken(t *testing.T) {
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJEZXZpY2VJZCI6IjM4IiwiZXhwIjoxNzE4MTU2OTQ4LCJpYXQiOjE3MTc1NTIxNDgsInVzZXJJZCI6MX0.4W0nga82kNrfwWjkwcgYAWj4fI4iRc-ZftwVbu-a_kI"
secret := "ae0536f9-6450-4606-8e13-5a19ed505da0"
claims, err := ParseJwtToken(token, secret)
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) {
t.Errorf("err: %v", err.Error())
return
}
// parse jwt token success
t.Logf("claims: %v", claims)
}
-156
View File
@@ -1,156 +0,0 @@
package lang
import (
"encoding/json"
"errors"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRepr(t *testing.T) {
var (
f32 float32 = 1.1
f64 = 2.2
i8 int8 = 1
i16 int16 = 2
i32 int32 = 3
i64 int64 = 4
u8 uint8 = 5
u16 uint16 = 6
u32 uint32 = 7
u64 uint64 = 8
)
tests := []struct {
v any
expect string
}{
{
nil,
"",
},
{
mockStringable{},
"mocked",
},
{
new(mockStringable),
"mocked",
},
{
newMockPtr(),
"mockptr",
},
{
&mockOpacity{
val: 1,
},
"{1}",
},
{
true,
"true",
},
{
false,
"false",
},
{
f32,
"1.1",
},
{
f64,
"2.2",
},
{
i8,
"1",
},
{
i16,
"2",
},
{
i32,
"3",
},
{
i64,
"4",
},
{
u8,
"5",
},
{
u16,
"6",
},
{
u32,
"7",
},
{
u64,
"8",
},
{
[]byte(`abcd`),
"abcd",
},
{
mockOpacity{val: 1},
"{1}",
},
}
for _, test := range tests {
t.Run(test.expect, func(t *testing.T) {
assert.Equal(t, test.expect, Repr(test.v))
})
}
}
func TestReprOfValue(t *testing.T) {
t.Run("error", func(t *testing.T) {
assert.Equal(t, "error", reprOfValue(reflect.ValueOf(errors.New("error"))))
})
t.Run("stringer", func(t *testing.T) {
assert.Equal(t, "1.23", reprOfValue(reflect.ValueOf(json.Number("1.23"))))
})
t.Run("int", func(t *testing.T) {
assert.Equal(t, "1", reprOfValue(reflect.ValueOf(1)))
})
t.Run("int", func(t *testing.T) {
assert.Equal(t, "1", reprOfValue(reflect.ValueOf("1")))
})
t.Run("int", func(t *testing.T) {
assert.Equal(t, "1", reprOfValue(reflect.ValueOf(uint(1))))
})
}
type mockStringable struct{}
func (m mockStringable) String() string {
return "mocked"
}
type mockPtr struct{}
func newMockPtr() *mockPtr {
return new(mockPtr)
}
func (m *mockPtr) String() string {
return "mockptr"
}
type mockOpacity struct {
val int
}
-71
View File
@@ -1,71 +0,0 @@
package limit
import (
"testing"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
)
func TestPeriodLimit_Take(t *testing.T) {
testPeriodLimit(t)
}
func TestPeriodLimit_TakeWithAlign(t *testing.T) {
testPeriodLimit(t, Align())
}
func TestPeriodLimit_RedisUnavailable(t *testing.T) {
//t.Skipf("skip this test because it's not stable")
const (
seconds = 1
quota = 5
)
rds := redis.NewClient(&redis.Options{
Addr: "localhost:12345",
})
l := NewPeriodLimit(seconds, quota, rds, "periodlimit:")
val, err := l.Take("first")
assert.NotNil(t, err)
assert.Equal(t, 0, val)
}
func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
store, _ := CreateRedisWithClean(t)
const (
seconds = 1
total = 100
quota = 5
)
l := NewPeriodLimit(seconds, quota, store, "periodlimit", opts...)
var allowed, hitQuota, overQuota int
for i := 0; i < total; i++ {
val, err := l.Take("first")
if err != nil {
t.Error(err)
}
switch val {
case Allowed:
allowed++
case HitQuota:
hitQuota++
case OverQuota:
overQuota++
default:
t.Error("unknown status")
}
}
assert.Equal(t, quota-1, allowed)
assert.Equal(t, 1, hitQuota)
assert.Equal(t, total-quota, overQuota)
}
func TestQuotaFull(t *testing.T) {
rds, _ := CreateRedisWithClean(t)
l := NewPeriodLimit(1, 1, rds, "periodlimit")
val, err := l.Take("first")
assert.Nil(t, err)
assert.Equal(t, HitQuota, val)
}
-80
View File
@@ -1,80 +0,0 @@
package limit
import (
"context"
"testing"
"time"
"github.com/redis/go-redis/v9"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
)
func TestTokenLimit_WithCtx(t *testing.T) {
const (
total = 100
rate = 5
burst = 10
)
store, _ := CreateRedisWithClean(t)
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
ctx, cancel := context.WithCancel(context.Background())
ok := l.AllowCtx(ctx)
assert.True(t, ok)
cancel()
for i := 0; i < total; i++ {
ok := l.AllowCtx(ctx)
assert.False(t, ok)
assert.False(t, l.monitorStarted)
}
}
func TestTokenLimit_Take(t *testing.T) {
store, _ := CreateRedisWithClean(t)
const (
total = 100
rate = 5
burst = 10
)
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
var allowed int
for i := 0; i < total; i++ {
time.Sleep(time.Second / time.Duration(total))
if l.Allow() {
allowed++
}
}
assert.True(t, allowed >= burst+rate)
}
func TestTokenLimit_TakeBurst(t *testing.T) {
store, _ := CreateRedisWithClean(t)
const (
total = 100
rate = 5
burst = 10
)
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
var allowed int
for i := 0; i < total; i++ {
if l.Allow() {
allowed++
}
}
assert.True(t, allowed >= burst)
}
// CreateRedisWithClean returns an in process redis.Redis and a clean function.
func CreateRedisWithClean(t *testing.T) (r *redis.Client, clean func()) {
mr := miniredis.RunT(t)
return redis.NewClient(&redis.Options{
Addr: mr.Addr(),
}), mr.Close
}
-33
View File
@@ -1,33 +0,0 @@
package logger
import (
"sync/atomic"
"testing"
"github.com/perfect-panel/server/pkg/color"
"github.com/stretchr/testify/assert"
)
func TestWithColor(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
defer atomic.StoreUint32(&encoding, old)
output := WithColor("hello", color.BgBlue)
assert.Equal(t, "hello", output)
atomic.StoreUint32(&encoding, jsonEncodingType)
output = WithColor("hello", color.BgBlue)
assert.Equal(t, "hello", output)
}
func TestWithColorPadding(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
defer atomic.StoreUint32(&encoding, old)
output := WithColorPadding("hello", color.BgBlue)
assert.Equal(t, " hello ", output)
atomic.StoreUint32(&encoding, jsonEncodingType)
output = WithColorPadding("hello", color.BgBlue)
assert.Equal(t, "hello", output)
}
-122
View File
@@ -1,122 +0,0 @@
package logger
import (
"bytes"
"context"
"encoding/json"
"strconv"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddGlobalFields(t *testing.T) {
var buf bytes.Buffer
writer := NewWriter(&buf)
old := Reset()
SetWriter(writer)
defer SetWriter(old)
Info("hello")
buf.Reset()
AddGlobalFields(Field("a", "1"), Field("b", "2"))
AddGlobalFields(Field("c", "3"))
Info("world")
var m map[string]any
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
assert.Equal(t, "1", m["a"])
assert.Equal(t, "2", m["b"])
assert.Equal(t, "3", m["c"])
}
func TestContextWithFields(t *testing.T) {
ctx := ContextWithFields(context.Background(), Field("a", 1), Field("b", 2))
vals := ctx.Value(fieldsContextKey)
assert.NotNil(t, vals)
fields, ok := vals.([]LogField)
assert.True(t, ok)
assert.EqualValues(t, []LogField{Field("a", 1), Field("b", 2)}, fields)
}
func TestWithFields(t *testing.T) {
ctx := WithFields(context.Background(), Field("a", 1), Field("b", 2))
vals := ctx.Value(fieldsContextKey)
assert.NotNil(t, vals)
fields, ok := vals.([]LogField)
assert.True(t, ok)
assert.EqualValues(t, []LogField{Field("a", 1), Field("b", 2)}, fields)
}
func TestWithFieldsAppend(t *testing.T) {
type ctxKey string
var dummyKey ctxKey = "dummyKey"
ctx := context.WithValue(context.Background(), dummyKey, "dummy")
ctx = ContextWithFields(ctx, Field("a", 1), Field("b", 2))
ctx = ContextWithFields(ctx, Field("c", 3), Field("d", 4))
vals := ctx.Value(fieldsContextKey)
assert.NotNil(t, vals)
fields, ok := vals.([]LogField)
assert.True(t, ok)
assert.Equal(t, "dummy", ctx.Value(dummyKey))
assert.EqualValues(t, []LogField{
Field("a", 1),
Field("b", 2),
Field("c", 3),
Field("d", 4),
}, fields)
}
func TestWithFieldsAppendCopy(t *testing.T) {
const count = 10
ctx := context.Background()
for i := 0; i < count; i++ {
ctx = ContextWithFields(ctx, Field(strconv.Itoa(i), 1))
}
af := Field("foo", 1)
bf := Field("bar", 2)
ctxa := ContextWithFields(ctx, af)
ctxb := ContextWithFields(ctx, bf)
assert.EqualValues(t, af, ctxa.Value(fieldsContextKey).([]LogField)[count])
assert.EqualValues(t, bf, ctxb.Value(fieldsContextKey).([]LogField)[count])
}
func BenchmarkAtomicValue(b *testing.B) {
b.ReportAllocs()
var container atomic.Value
vals := []LogField{
Field("a", "b"),
Field("c", "d"),
Field("e", "f"),
}
container.Store(&vals)
for i := 0; i < b.N; i++ {
val := container.Load()
if val != nil {
_ = *val.(*[]LogField)
}
}
}
func BenchmarkRWMutex(b *testing.B) {
b.ReportAllocs()
var lock sync.RWMutex
vals := []LogField{
Field("a", "b"),
Field("c", "d"),
Field("e", "f"),
}
for i := 0; i < b.N; i++ {
lock.RLock()
_ = vals
lock.RUnlock()
}
}
-35
View File
@@ -1,35 +0,0 @@
package logger
import (
"log"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLessLogger_Error(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
l := NewLessLogger(500)
for i := 0; i < 100; i++ {
l.Error("hello")
}
log.Print(w.String())
assert.Equal(t, 1, strings.Count(w.String(), "\n"))
}
func TestLessLogger_Errorf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
l := NewLessLogger(500)
for i := 0; i < 100; i++ {
l.Errorf("hello")
}
assert.Equal(t, 1, strings.Count(w.String(), "\n"))
}
-19
View File
@@ -1,19 +0,0 @@
package logger
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLessWriter(t *testing.T) {
var builder strings.Builder
w := newLessWriter(&builder, 500)
for i := 0; i < 100; i++ {
_, err := w.Write([]byte("hello"))
assert.Nil(t, err)
}
assert.Equal(t, "hello", builder.String())
}
-62
View File
@@ -1,62 +0,0 @@
package logger
import (
"sync/atomic"
"testing"
"time"
"github.com/perfect-panel/server/pkg/timex"
"github.com/stretchr/testify/assert"
)
func TestLimitedExecutor_logOrDiscard(t *testing.T) {
tests := []struct {
name string
threshold time.Duration
lastTime time.Duration
discarded uint32
executed bool
}{
{
name: "nil executor",
executed: true,
},
{
name: "regular",
threshold: time.Hour,
lastTime: timex.Now(),
discarded: 10,
executed: false,
},
{
name: "slow",
threshold: time.Duration(1),
lastTime: -1000,
discarded: 10,
executed: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
executor := newLimitedExecutor(0)
executor.threshold = test.threshold
executor.discarded = test.discarded
executor.lastTime.Set(test.lastTime)
var run int32
executor.logOrDiscard(func() {
atomic.AddInt32(&run, 1)
})
if test.executed {
assert.Equal(t, int32(1), atomic.LoadInt32(&run))
} else {
assert.Equal(t, int32(0), atomic.LoadInt32(&run))
assert.Equal(t, test.discarded+1, atomic.LoadUint32(&executor.discarded))
}
})
}
}
-931
View File
@@ -1,931 +0,0 @@
package logger
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"reflect"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
var (
s = []byte("Sending #11 notification (id: 1451875113812010473) in #1 connection")
pool = make(chan []byte, 1)
_ Writer = (*mockWriter)(nil)
)
func init() {
ExitOnFatal.Set(false)
}
type mockWriter struct {
lock sync.Mutex
builder strings.Builder
}
func (mw *mockWriter) Alert(v any) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelAlert, v)
}
func (mw *mockWriter) Debug(v any, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelDebug, v, fields...)
}
func (mw *mockWriter) Error(v any, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelError, v, fields...)
}
func (mw *mockWriter) Info(v any, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelInfo, v, fields...)
}
func (mw *mockWriter) Severe(v any) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelSevere, v)
}
func (mw *mockWriter) Slow(v any, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelSlow, v, fields...)
}
func (mw *mockWriter) Stack(v any) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelError, v)
}
func (mw *mockWriter) Stat(v any, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelStat, v, fields...)
}
func (mw *mockWriter) Close() error {
return nil
}
func (mw *mockWriter) Contains(text string) bool {
mw.lock.Lock()
defer mw.lock.Unlock()
return strings.Contains(mw.builder.String(), text)
}
func (mw *mockWriter) Reset() {
mw.lock.Lock()
defer mw.lock.Unlock()
mw.builder.Reset()
}
func (mw *mockWriter) String() string {
mw.lock.Lock()
defer mw.lock.Unlock()
return mw.builder.String()
}
func TestField(t *testing.T) {
tests := []struct {
name string
f LogField
want map[string]any
}{
{
name: "error",
f: Field("foo", errors.New("bar")),
want: map[string]any{
"foo": "bar",
},
},
{
name: "errors",
f: Field("foo", []error{errors.New("bar"), errors.New("baz")}),
want: map[string]any{
"foo": []any{"bar", "baz"},
},
},
{
name: "strings",
f: Field("foo", []string{"bar", "baz"}),
want: map[string]any{
"foo": []any{"bar", "baz"},
},
},
{
name: "duration",
f: Field("foo", time.Second),
want: map[string]any{
"foo": "1s",
},
},
{
name: "durations",
f: Field("foo", []time.Duration{time.Second, 2 * time.Second}),
want: map[string]any{
"foo": []any{"1s", "2s"},
},
},
{
name: "times",
f: Field("foo", []time.Time{
time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC),
time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC),
}),
want: map[string]any{
"foo": []any{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
},
},
{
name: "stringer",
f: Field("foo", ValStringer{val: "bar"}),
want: map[string]any{
"foo": "bar",
},
},
{
name: "stringers",
f: Field("foo", []fmt.Stringer{ValStringer{val: "bar"}, ValStringer{val: "baz"}}),
want: map[string]any{
"foo": []any{"bar", "baz"},
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
Infow("foo", test.f)
validateFields(t, w.String(), test.want)
})
}
}
func TestFileLineFileMode(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
file, line := getFileLine()
Error("anything")
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
file, line = getFileLine()
Errorf("anything %s", "format")
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
}
func TestFileLineConsoleMode(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
file, line := getFileLine()
Error("anything")
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
w.Reset()
file, line = getFileLine()
Errorf("anything %s", "format")
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
}
func TestMust(t *testing.T) {
assert.Panics(t, func() {
Must(errors.New("foo"))
})
}
func TestStructedLogAlert(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelAlert, w, func(v ...any) {
Alert(fmt.Sprint(v...))
})
}
func TestStructedLogDebug(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...any) {
Debug(v...)
})
}
func TestStructedLogDebugf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...any) {
Debugf(fmt.Sprint(v...))
})
}
func TestStructedLogDebugv(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...any) {
Debugv(fmt.Sprint(v...))
})
}
func TestStructedLogDebugw(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...any) {
Debugw(fmt.Sprint(v...), Field("foo", time.Second))
})
}
func TestStructedLogError(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...any) {
Error(v...)
})
}
func TestStructedLogErrorf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...any) {
Errorf("%s", fmt.Sprint(v...))
})
}
func TestStructedLogErrorv(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...any) {
Errorv(fmt.Sprint(v...))
})
}
func TestStructedLogErrorw(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelError, w, func(v ...any) {
Errorw(fmt.Sprint(v...), Field("foo", "bar"))
})
}
func TestStructedLogInfo(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...any) {
Info(v...)
})
}
func TestStructedLogInfof(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...any) {
Infof("%s", fmt.Sprint(v...))
})
}
func TestStructedLogInfov(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...any) {
Infov(fmt.Sprint(v...))
})
}
func TestStructedLogInfow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelInfo, w, func(v ...any) {
Infow(fmt.Sprint(v...), Field("foo", "bar"))
})
}
func TestStructedLogFieldNil(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
assert.NotPanics(t, func() {
var s *string
Infow("test", Field("bb", s))
var d *nilStringer
Infow("test", Field("bb", d))
var e *nilError
Errorw("test", Field("bb", e))
})
assert.NotPanics(t, func() {
var p panicStringer
Infow("test", Field("bb", p))
var ps innerPanicStringer
Infow("test", Field("bb", ps))
})
}
func TestStructedLogInfoConsoleAny(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...any) {
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
atomic.StoreUint32(&encoding, old)
}()
Infov(v)
})
}
func TestStructedLogInfoConsoleAnyString(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...any) {
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
atomic.StoreUint32(&encoding, old)
}()
Infov(fmt.Sprint(v...))
})
}
func TestStructedLogInfoConsoleAnyError(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...any) {
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
atomic.StoreUint32(&encoding, old)
}()
Infov(errors.New(fmt.Sprint(v...)))
})
}
func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...any) {
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
atomic.StoreUint32(&encoding, old)
}()
Infov(ValStringer{
val: fmt.Sprint(v...),
})
})
}
func TestStructedLogInfoConsoleText(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLogConsole(t, w, func(v ...any) {
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
atomic.StoreUint32(&encoding, old)
}()
Info(fmt.Sprint(v...))
})
}
func TestStructedLogSlow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...any) {
Slow(v...)
})
}
func TestStructedLogSlowf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...any) {
Slowf(fmt.Sprint(v...))
})
}
func TestStructedLogSlowv(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...any) {
Slowv(fmt.Sprint(v...))
})
}
func TestStructedLogSloww(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...any) {
Sloww(fmt.Sprint(v...), Field("foo", time.Second))
})
}
func TestStructedLogStat(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelStat, w, func(v ...any) {
Stat(v...)
})
}
func TestStructedLogStatf(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelStat, w, func(v ...any) {
Statf(fmt.Sprint(v...))
})
}
func TestStructedLogSevere(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelSevere, w, func(v ...any) {
Severe(v...)
})
}
func TestStructedLogSeveref(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
doTestStructedLog(t, levelSevere, w, func(v ...any) {
Severef(fmt.Sprint(v...))
})
}
func TestStructedLogWithDuration(t *testing.T) {
const message = "hello there"
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Info(message)
var entry map[string]any
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
t.Error(err)
}
assert.Equal(t, levelInfo, entry[levelKey])
assert.Equal(t, message, entry[contentKey])
assert.Equal(t, "1000.0ms", entry[durationKey])
}
func TestSetLevel(t *testing.T) {
SetLevel(ErrorLevel)
const message = "hello there"
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
Info(message)
assert.Equal(t, 0, w.builder.Len())
}
func TestSetLevelTwiceWithMode(t *testing.T) {
testModes := []string{
"console",
"volumn",
"mode",
}
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
for _, mode := range testModes {
testSetLevelTwiceWithMode(t, mode, w)
}
}
func TestSetLevelWithDuration(t *testing.T) {
SetLevel(ErrorLevel)
const message = "hello there"
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
WithDuration(time.Second).Info(message)
assert.Equal(t, 0, w.builder.Len())
}
func TestErrorfWithWrappedError(t *testing.T) {
SetLevel(ErrorLevel)
const message = "there"
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
Errorf("hello %s", errors.New(message))
assert.True(t, strings.Contains(w.String(), "hello there"))
}
func TestMustNil(t *testing.T) {
Must(nil)
}
func TestSetup(t *testing.T) {
defer func() {
SetLevel(InfoLevel)
atomic.StoreUint32(&encoding, jsonEncodingType)
}()
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
Encoding: "json",
TimeFormat: timeFormat,
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
TimeFormat: timeFormat,
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "file",
Path: os.TempDir(),
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "volume",
Path: os.TempDir(),
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
TimeFormat: timeFormat,
})
setupOnce = sync.Once{}
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
Encoding: plainEncoding,
})
defer os.RemoveAll("CD01CB7D-2705-4F3F-889E-86219BF56F10")
assert.NotNil(t, setupWithVolume(LogConf{}))
assert.Nil(t, setupWithVolume(LogConf{
ServiceName: "CD01CB7D-2705-4F3F-889E-86219BF56F10",
}))
assert.Nil(t, setupWithVolume(LogConf{
ServiceName: "CD01CB7D-2705-4F3F-889E-86219BF56F10",
Rotation: sizeRotationRule,
}))
assert.NotNil(t, setupWithFiles(LogConf{}))
assert.Nil(t, setupWithFiles(LogConf{
ServiceName: "any",
Path: os.TempDir(),
Compress: true,
KeepDays: 1,
MaxBackups: 3,
MaxSize: 1024 * 1024,
}))
setupLogLevel(LogConf{
Level: levelInfo,
})
setupLogLevel(LogConf{
Level: levelError,
})
setupLogLevel(LogConf{
Level: levelSevere,
})
_, err := createOutput("")
assert.NotNil(t, err)
Disable()
SetLevel(InfoLevel)
atomic.StoreUint32(&encoding, jsonEncodingType)
}
func TestDisable(t *testing.T) {
Disable()
defer func() {
SetLevel(InfoLevel)
atomic.StoreUint32(&encoding, jsonEncodingType)
}()
var opt logOptions
WithKeepDays(1)(&opt)
WithGzip()(&opt)
WithMaxBackups(1)(&opt)
WithMaxSize(1024)(&opt)
assert.Nil(t, Close())
assert.Nil(t, Close())
assert.Equal(t, uint32(disableLevel), atomic.LoadUint32(&logLevel))
}
func TestDisableStat(t *testing.T) {
DisableStat()
const message = "hello there"
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
Stat(message)
assert.Equal(t, 0, w.builder.Len())
}
func TestAddWriter(t *testing.T) {
const message = "hello there"
w := new(mockWriter)
AddWriter(w)
w1 := new(mockWriter)
AddWriter(w1)
Error(message)
assert.Contains(t, w.String(), message)
assert.Contains(t, w1.String(), message)
}
func TestSetWriter(t *testing.T) {
atomic.StoreUint32(&logLevel, 0)
Reset()
SetWriter(nopWriter{})
assert.NotNil(t, writer.Load())
assert.True(t, writer.Load() == nopWriter{})
mocked := new(mockWriter)
SetWriter(mocked)
assert.Equal(t, mocked, writer.Load())
}
func TestWithGzip(t *testing.T) {
fn := WithGzip()
var opt logOptions
fn(&opt)
assert.True(t, opt.gzipEnabled)
}
func TestWithKeepDays(t *testing.T) {
fn := WithKeepDays(1)
var opt logOptions
fn(&opt)
assert.Equal(t, 1, opt.keepDays)
}
func BenchmarkCopyByteSliceAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
var buf []byte
buf = append(buf, getTimestamp()...)
buf = append(buf, ' ')
buf = append(buf, s...)
_ = buf
}
}
func BenchmarkCopyByteSliceAllocExactly(b *testing.B) {
for i := 0; i < b.N; i++ {
now := []byte(getTimestamp())
buf := make([]byte, len(now)+1+len(s))
n := copy(buf, now)
buf[n] = ' '
copy(buf[n+1:], s)
}
}
func BenchmarkCopyByteSlice(b *testing.B) {
var buf []byte
for i := 0; i < b.N; i++ {
buf = make([]byte, len(s))
copy(buf, s)
}
fmt.Fprint(io.Discard, buf)
}
func BenchmarkCopyOnWriteByteSlice(b *testing.B) {
var buf []byte
for i := 0; i < b.N; i++ {
size := len(s)
buf = s[:size:size]
}
fmt.Fprint(io.Discard, buf)
}
func BenchmarkCacheByteSlice(b *testing.B) {
for i := 0; i < b.N; i++ {
dup := fetch()
copy(dup, s)
put(dup)
}
}
func BenchmarkLogs(b *testing.B) {
b.ReportAllocs()
log.SetOutput(io.Discard)
for i := 0; i < b.N; i++ {
Info(i)
}
}
func fetch() []byte {
select {
case b := <-pool:
return b
default:
}
return make([]byte, 4096)
}
func getFileLine() (string, int) {
_, file, line, _ := runtime.Caller(1)
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
return short, line
}
func put(b []byte) {
select {
case pool <- b:
default:
}
}
func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...any)) {
const message = "hello there"
write(message)
var entry map[string]any
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
t.Error(err)
}
assert.Equal(t, level, entry[levelKey])
val, ok := entry[contentKey]
assert.True(t, ok)
assert.True(t, strings.Contains(val.(string), message))
}
func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...any)) {
const message = "hello there"
write(message)
assert.True(t, strings.Contains(w.String(), message))
}
func testSetLevelTwiceWithMode(t *testing.T, mode string, w *mockWriter) {
writer.Store(nil)
_ = SetUp(LogConf{
Mode: mode,
Level: "debug",
Path: "/dev/null",
Encoding: plainEncoding,
Stat: false,
TimeFormat: time.RFC3339,
FileTimeFormat: time.DateTime,
})
_ = SetUp(LogConf{
Mode: mode,
Level: "info",
Path: "/dev/null",
})
const message = "hello there"
Info(message)
assert.Equal(t, 0, w.builder.Len())
Infof(message)
assert.Equal(t, 0, w.builder.Len())
ErrorStack(message)
assert.Equal(t, 0, w.builder.Len())
ErrorStackf(message)
assert.Equal(t, 0, w.builder.Len())
}
type ValStringer struct {
val string
}
func (v ValStringer) String() string {
return v.val
}
func validateFields(t *testing.T, content string, fields map[string]any) {
var m map[string]any
if err := json.Unmarshal([]byte(content), &m); err != nil {
t.Error(err)
}
for k, v := range fields {
if reflect.TypeOf(v).Kind() == reflect.Slice {
assert.EqualValues(t, v, m[k])
} else {
assert.Equal(t, v, m[k], content)
}
}
}
type nilError struct {
Name string
}
func (e *nilError) Error() string {
return e.Name
}
type nilStringer struct {
Name string
}
func (s *nilStringer) String() string {
return s.Name
}
type innerPanicStringer struct {
Inner *struct {
Name string
}
}
func (s innerPanicStringer) String() string {
return s.Inner.Name
}
type panicStringer struct {
}
func (s panicStringer) String() string {
panic("panic")
}
-84
View File
@@ -1,84 +0,0 @@
package logtest
import (
"bytes"
"encoding/json"
"io"
"testing"
"github.com/perfect-panel/server/pkg/logger"
)
type Buffer struct {
buf *bytes.Buffer
t *testing.T
}
func Discard(t *testing.T) {
prev := logger.Reset()
logger.SetWriter(logger.NewWriter(io.Discard))
t.Cleanup(func() {
logger.SetWriter(prev)
})
}
func NewCollector(t *testing.T) *Buffer {
var buf bytes.Buffer
writer := logger.NewWriter(&buf)
prev := logger.Reset()
logger.SetWriter(writer)
t.Cleanup(func() {
logger.SetWriter(prev)
})
return &Buffer{
buf: &buf,
t: t,
}
}
func (b *Buffer) Bytes() []byte {
return b.buf.Bytes()
}
func (b *Buffer) Content() string {
var m map[string]interface{}
if err := json.Unmarshal(b.buf.Bytes(), &m); err != nil {
return ""
}
content, ok := m["content"]
if !ok {
return ""
}
switch val := content.(type) {
case string:
return val
default:
// err is impossible to be not nil, unmarshaled from b.buf.Bytes()
bs, _ := json.Marshal(content)
return string(bs)
}
}
func (b *Buffer) Reset() {
b.buf.Reset()
}
func (b *Buffer) String() string {
return b.buf.String()
}
func PanicOnFatal(t *testing.T) {
ok := logger.ExitOnFatal.CompareAndSwap(true, false)
if !ok {
return
}
t.Cleanup(func() {
logger.ExitOnFatal.CompareAndSwap(false, true)
})
}
-44
View File
@@ -1,44 +0,0 @@
package logtest
import (
"errors"
"testing"
"github.com/perfect-panel/server/pkg/logger"
"github.com/stretchr/testify/assert"
)
func TestCollector(t *testing.T) {
const input = "hello"
c := NewCollector(t)
logger.Info(input)
assert.Equal(t, input, c.Content())
assert.Contains(t, c.String(), input)
c.Reset()
assert.Empty(t, c.Bytes())
}
func TestPanicOnFatal(t *testing.T) {
const input = "hello"
Discard(t)
logger.Info(input)
PanicOnFatal(t)
PanicOnFatal(t)
assert.Panics(t, func() {
logger.Must(errors.New("foo"))
})
}
func TestCollectorContent(t *testing.T) {
const input = "hello"
c := NewCollector(t)
c.buf.WriteString(input)
assert.Empty(t, c.Content())
c.Reset()
c.buf.WriteString(`{}`)
assert.Empty(t, c.Content())
c.Reset()
c.buf.WriteString(`{"content":1}`)
assert.Equal(t, "1", c.Content())
}
-16
View File
@@ -1,16 +0,0 @@
package logger
import (
"testing"
)
func TestReadLastNLines(t *testing.T) {
t.Skipf("skip this test until this test fails")
lines, err := ReadLastNLines("/Users/tension/code/ppanel/server/logs", 10)
if err != nil {
t.Fatalf("Error reading last N lines: %v", err)
}
for i, line := range lines {
t.Logf("Line %d: %s", i, line)
}
}
-408
View File
@@ -1,408 +0,0 @@
package logger
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func TestTraceLog(t *testing.T) {
SetLevel(InfoLevel)
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
WithContext(ctx).Info(testlog)
validate(t, w.String(), true, true)
}
func TestTraceDebug(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("foo").Start(context.Background(), "bar")
defer span.End()
l := WithContext(ctx)
SetLevel(DebugLevel)
l.WithDuration(time.Second).Debug(testlog)
assert.True(t, strings.Contains(w.String(), traceKey))
assert.True(t, strings.Contains(w.String(), spanKey))
w.Reset()
l.WithDuration(time.Second).Debugf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Debugv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Debugv(testobj)
validateContentType(t, w.String(), map[string]any{}, true, true)
w.Reset()
l.WithDuration(time.Second).Debugw(testlog, Field("foo", "bar"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
assert.True(t, strings.Contains(w.String(), "bar"), w.String())
}
func TestTraceError(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
var nilCtx context.Context
l := WithContext(context.Background())
l = l.WithContext(nilCtx)
l = l.WithContext(ctx)
SetLevel(ErrorLevel)
l.WithDuration(time.Second).Error(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Errorv(testobj)
validateContentType(t, w.String(), map[string]any{}, true, true)
w.Reset()
l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfo(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
SetLevel(InfoLevel)
l := WithContext(ctx)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testobj)
validateContentType(t, w.String(), map[string]any{}, true, true)
w.Reset()
l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceInfoConsole(t *testing.T) {
old := atomic.SwapUint32(&encoding, jsonEncodingType)
defer atomic.StoreUint32(&encoding, old)
w := new(mockWriter)
o := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(o)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Infov(testobj)
validateContentType(t, w.String(), map[string]any{}, true, true)
}
func TestTraceSlow(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
otp := otel.GetTracerProvider()
tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
otel.SetTracerProvider(tp)
defer otel.SetTracerProvider(otp)
ctx, span := tp.Tracer("trace-id").Start(context.Background(), "span-id")
defer span.End()
l := WithContext(ctx)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Slow(testlog)
assert.True(t, strings.Contains(w.String(), traceKey))
assert.True(t, strings.Contains(w.String(), spanKey))
w.Reset()
l.WithDuration(time.Second).Slowf(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Slowv(testlog)
validate(t, w.String(), true, true)
w.Reset()
l.WithDuration(time.Second).Slowv(testobj)
validateContentType(t, w.String(), map[string]any{}, true, true)
w.Reset()
l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
validate(t, w.String(), true, true)
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
assert.True(t, strings.Contains(w.String(), "ball"), w.String())
}
func TestTraceWithoutContext(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
l := WithContext(context.Background())
SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog)
validate(t, w.String(), false, false)
w.Reset()
l.WithDuration(time.Second).Infof(testlog)
validate(t, w.String(), false, false)
}
func TestLogWithFields(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
ctx := ContextWithFields(context.Background(), Field("foo", "bar"))
l := WithContext(ctx)
SetLevel(InfoLevel)
l.Infow(testlog)
var val mockValue
assert.Nil(t, json.Unmarshal([]byte(w.String()), &val))
assert.Equal(t, "bar", val.Foo)
}
func TestLogWithCallerSkip(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
l := WithCallerSkip(1).WithCallerSkip(0)
p := func(v string) {
l.Infow(v)
}
file, line := getFileLine()
p(testlog)
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
w.Reset()
l = WithCallerSkip(0).WithCallerSkip(1)
file, line = getFileLine()
p(testlog)
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
}
func TestLogWithCallerSkipCopy(t *testing.T) {
log1 := WithCallerSkip(2)
log2 := log1.WithCallerSkip(3)
log3 := log2.WithCallerSkip(-1)
assert.Equal(t, 2, log1.(*richLogger).callerSkip)
assert.Equal(t, 3, log2.(*richLogger).callerSkip)
assert.Equal(t, 3, log3.(*richLogger).callerSkip)
}
func TestLogWithContextCopy(t *testing.T) {
c1 := context.Background()
type ctxKey string // 定义新的字符串类型
const fooKey ctxKey = "foo" // 使用这个 key
c2 := context.WithValue(context.Background(), fooKey, "bar")
log1 := WithContext(c1)
log2 := log1.WithContext(c2)
assert.Equal(t, c1, log1.(*richLogger).ctx)
assert.Equal(t, c2, log2.(*richLogger).ctx)
}
func TestLogWithDurationCopy(t *testing.T) {
log1 := WithContext(context.Background())
log2 := log1.WithDuration(time.Second)
assert.Empty(t, log1.(*richLogger).fields)
assert.Equal(t, 1, len(log2.(*richLogger).fields))
var w mockWriter
old := writer.Swap(&w)
defer writer.Store(old)
log2.Info("hello")
assert.Contains(t, w.String(), `"duration":"1000.0ms"`)
}
func TestLogWithFieldsCopy(t *testing.T) {
log1 := WithContext(context.Background())
log2 := log1.WithFields(Field("foo", "bar"))
log3 := log1.WithFields()
assert.Empty(t, log1.(*richLogger).fields)
assert.Equal(t, 1, len(log2.(*richLogger).fields))
assert.Equal(t, log1, log3)
assert.Empty(t, log3.(*richLogger).fields)
var w mockWriter
old := writer.Swap(&w)
defer writer.Store(old)
log2.Info("hello")
assert.Contains(t, w.String(), `"foo":"bar"`)
}
func TestLoggerWithFields(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
writer.lock.RLock()
defer func() {
writer.lock.RUnlock()
writer.Store(old)
}()
l := WithContext(context.Background()).WithFields(Field("foo", "bar"))
l.Infow(testlog)
var val mockValue
assert.Nil(t, json.Unmarshal([]byte(w.String()), &val))
assert.Equal(t, "bar", val.Foo)
}
func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
var val mockValue
dec := json.NewDecoder(strings.NewReader(body))
for {
var doc mockValue
err := dec.Decode(&doc)
if err == io.EOF {
// all done
break
}
if err != nil {
continue
}
val = doc
}
assert.Equal(t, expectedTrace, len(val.Trace) > 0, body)
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
}
func validateContentType(t *testing.T, body string, expectedType any, expectedTrace, expectedSpan bool) {
var val mockValue
dec := json.NewDecoder(strings.NewReader(body))
for {
var doc mockValue
err := dec.Decode(&doc)
if err == io.EOF {
// all done
break
}
if err != nil {
continue
}
val = doc
}
assert.IsType(t, expectedType, val.Content, body)
assert.Equal(t, expectedTrace, len(val.Trace) > 0, body)
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
}
type mockValue struct {
Trace string `json:"trace"`
Span string `json:"span"`
Foo string `json:"foo"`
Content any `json:"content"`
}
-636
View File
@@ -1,636 +0,0 @@
package logger
import (
"errors"
"io"
"os"
"path"
"path/filepath"
"sync/atomic"
"syscall"
"testing"
"time"
"github.com/perfect-panel/server/pkg/random"
"github.com/perfect-panel/server/pkg/fs"
"github.com/stretchr/testify/assert"
)
func TestDailyRotateRuleMarkRotated(t *testing.T) {
t.Run("daily rule", func(t *testing.T) {
var rule DailyRotateRule
rule.MarkRotated()
assert.Equal(t, getNowDate(), rule.rotatedTime)
})
t.Run("daily rule", func(t *testing.T) {
rule := DefaultRotateRule("test", "-", 1, false)
_, ok := rule.(*DailyRotateRule)
assert.True(t, ok)
})
}
func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
t.Run("no files", func(t *testing.T) {
var rule DailyRotateRule
assert.Empty(t, rule.OutdatedFiles())
rule.days = 1
assert.Empty(t, rule.OutdatedFiles())
rule.gzip = true
assert.Empty(t, rule.OutdatedFiles())
})
t.Run("bad files", func(t *testing.T) {
rule := DailyRotateRule{
filename: "[a-z",
}
assert.Empty(t, rule.OutdatedFiles())
rule.days = 1
assert.Empty(t, rule.OutdatedFiles())
rule.gzip = true
assert.Empty(t, rule.OutdatedFiles())
})
t.Run("temp files", func(t *testing.T) {
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
assert.NoError(t, err)
_ = f1.Close()
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
assert.NoError(t, err)
_ = f2.Close()
t.Cleanup(func() {
_ = os.Remove(f1.Name())
_ = os.Remove(f2.Name())
})
rule := DailyRotateRule{
filename: path.Join(os.TempDir(), "go-zero-test-"),
days: 1,
}
assert.NotEmpty(t, rule.OutdatedFiles())
})
}
func TestDailyRotateRuleShallRotate(t *testing.T) {
var rule DailyRotateRule
rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(dateFormat)
assert.True(t, rule.ShallRotate(0))
}
func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
t.Run("size limit rule", func(t *testing.T) {
var rule SizeLimitRotateRule
rule.MarkRotated()
assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
})
t.Run("size limit rule", func(t *testing.T) {
rule := NewSizeLimitRotateRule("foo", "-", 1, 1, 1, false)
rule.MarkRotated()
assert.Equal(t, getNowDateInRFC3339Format(), rule.(*SizeLimitRotateRule).rotatedTime)
})
}
func TestSizeLimitRotateRuleOutdatedFiles(t *testing.T) {
t.Run("no files", func(t *testing.T) {
var rule SizeLimitRotateRule
assert.Empty(t, rule.OutdatedFiles())
rule.days = 1
assert.Empty(t, rule.OutdatedFiles())
rule.gzip = true
assert.Empty(t, rule.OutdatedFiles())
rule.maxBackups = 0
assert.Empty(t, rule.OutdatedFiles())
})
t.Run("bad files", func(t *testing.T) {
rule := SizeLimitRotateRule{
DailyRotateRule: DailyRotateRule{
filename: "[a-z",
},
}
assert.Empty(t, rule.OutdatedFiles())
rule.days = 1
assert.Empty(t, rule.OutdatedFiles())
rule.gzip = true
assert.Empty(t, rule.OutdatedFiles())
})
t.Run("temp files", func(t *testing.T) {
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
assert.NoError(t, err)
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
assert.NoError(t, err)
boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
assert.NoError(t, err)
t.Cleanup(func() {
_ = f1.Close()
_ = os.Remove(f1.Name())
_ = f2.Close()
_ = os.Remove(f2.Name())
_ = f3.Close()
_ = os.Remove(f3.Name())
})
rule := SizeLimitRotateRule{
DailyRotateRule: DailyRotateRule{
filename: path.Join(os.TempDir(), "go-zero-test-"),
days: 1,
},
maxBackups: 3,
}
assert.NotEmpty(t, rule.OutdatedFiles())
})
t.Run("no backups", func(t *testing.T) {
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
assert.NoError(t, err)
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
assert.NoError(t, err)
boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
assert.NoError(t, err)
t.Cleanup(func() {
_ = f1.Close()
_ = os.Remove(f1.Name())
_ = f2.Close()
_ = os.Remove(f2.Name())
_ = f3.Close()
_ = os.Remove(f3.Name())
})
rule := SizeLimitRotateRule{
DailyRotateRule: DailyRotateRule{
filename: path.Join(os.TempDir(), "go-zero-test-"),
days: 1,
},
}
assert.NotEmpty(t, rule.OutdatedFiles())
logger := new(RotateLogger)
logger.rule = &rule
logger.maybeDeleteOutdatedFiles()
assert.Empty(t, rule.OutdatedFiles())
})
}
func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
var rule SizeLimitRotateRule
rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(fileTimeFormat)
rule.maxSize = 0
assert.False(t, rule.ShallRotate(0))
rule.maxSize = 100
assert.False(t, rule.ShallRotate(0))
assert.True(t, rule.ShallRotate(101*megaBytes))
}
func TestRotateLoggerClose(t *testing.T) {
t.Run("close", func(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(DailyRotateRule), false)
assert.Nil(t, err)
_, err = logger.Write([]byte("foo"))
assert.Nil(t, err)
assert.Nil(t, logger.Close())
})
t.Run("close and write", func(t *testing.T) {
logger := new(RotateLogger)
logger.done = make(chan struct{})
close(logger.done)
_, err := logger.Write([]byte("foo"))
assert.ErrorIs(t, err, ErrLogFileClosed)
})
t.Run("close without losing logs", func(t *testing.T) {
text := "foo"
filename, err := fs.TempFilenameWithText(text)
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(DailyRotateRule), false)
assert.Nil(t, err)
msg := []byte("foo")
n := 100
for i := 0; i < n; i++ {
_, err = logger.Write(msg)
assert.Nil(t, err)
}
assert.Nil(t, logger.Close())
bs, err := os.ReadFile(filename)
assert.Nil(t, err)
assert.Equal(t, len(msg)*n+len(text), len(bs))
})
}
func TestRotateLoggerGetBackupFilename(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(DailyRotateRule), false)
assert.Nil(t, err)
assert.True(t, len(logger.getBackupFilename()) > 0)
logger.backup = ""
assert.True(t, len(logger.getBackupFilename()) > 0)
}
func TestRotateLoggerMayCompressFile(t *testing.T) {
old := os.Stdout
os.Stdout = os.NewFile(0, os.DevNull)
defer func() {
os.Stdout = old
}()
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(DailyRotateRule), false)
assert.Nil(t, err)
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.Nil(t, err)
}
func TestRotateLoggerMayCompressFileTrue(t *testing.T) {
old := os.Stdout
os.Stdout = os.NewFile(0, os.DevNull)
defer func() {
os.Stdout = old
}()
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(DailyRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.NotNil(t, err)
}
func TestRotateLoggerRotate(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(DailyRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
err = logger.rotate()
switch v := err.(type) {
case *os.LinkError:
// avoid rename error on docker container
assert.Equal(t, syscall.EXDEV, v.Err)
case *os.PathError:
// ignore remove error for tests,
// files are cleaned in GitHub actions.
assert.Equal(t, "remove", v.Op)
default:
assert.Nil(t, err)
}
}
func TestRotateLoggerWrite(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
rule := new(DailyRotateRule)
logger, err := NewLogger(filename, rule, true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
// the following write calls cannot be changed to Write, because of DATA RACE.
logger.write([]byte(`foo`))
rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
logger.write([]byte(`bar`))
logger.Close()
logger.write([]byte(`baz`))
}
func TestLogWriterClose(t *testing.T) {
assert.Nil(t, newLogWriter(nil).Close())
}
func TestRotateLoggerWithSizeLimitRotateRuleClose(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
assert.Nil(t, err)
_ = logger.Close()
}
func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
assert.Nil(t, err)
assert.True(t, len(logger.getBackupFilename()) > 0)
logger.backup = ""
assert.True(t, len(logger.getBackupFilename()) > 0)
}
func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFile(t *testing.T) {
old := os.Stdout
os.Stdout = os.NewFile(0, os.DevNull)
defer func() {
os.Stdout = old
}()
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
assert.Nil(t, err)
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.Nil(t, err)
}
func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileTrue(t *testing.T) {
old := os.Stdout
os.Stdout = os.NewFile(0, os.DevNull)
defer func() {
os.Stdout = old
}()
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.NotNil(t, err)
}
func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileFailed(t *testing.T) {
old := os.Stdout
os.Stdout = os.NewFile(0, os.DevNull)
defer func() {
os.Stdout = old
}()
filename := random.KeyNew(8, 1)
logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
defer os.Remove(filename)
if assert.NoError(t, err) {
assert.NotPanics(t, func() {
logger.maybeCompressFile(random.KeyNew(8, 1))
})
}
}
func TestRotateLoggerWithSizeLimitRotateRuleRotate(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
err = logger.rotate()
switch v := err.(type) {
case *os.LinkError:
// avoid rename error on docker container
assert.Equal(t, syscall.EXDEV, v.Err)
case *os.PathError:
// ignore remove error for tests,
// files are cleaned in GitHub actions.
assert.Equal(t, "remove", v.Op)
default:
assert.Nil(t, err)
}
}
func TestRotateLoggerWithSizeLimitRotateRuleWrite(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
rule := new(SizeLimitRotateRule)
logger, err := NewLogger(filename, rule, true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
// the following write calls cannot be changed to Write, because of DATA RACE.
logger.write([]byte(`foo`))
rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
logger.write([]byte(`bar`))
logger.Close()
logger.write([]byte(`baz`))
}
func TestGzipFile(t *testing.T) {
err := errors.New("any error")
t.Run("gzip file open failed", func(t *testing.T) {
fsys := &fakeFileSystem{
openFn: func(name string) (*os.File, error) {
return nil, err
},
}
assert.ErrorIs(t, err, gzipFile("any", fsys))
assert.False(t, fsys.Removed())
})
t.Run("gzip file create failed", func(t *testing.T) {
fsys := &fakeFileSystem{
createFn: func(name string) (*os.File, error) {
return nil, err
},
}
assert.ErrorIs(t, err, gzipFile("any", fsys))
assert.False(t, fsys.Removed())
})
t.Run("gzip file copy failed", func(t *testing.T) {
fsys := &fakeFileSystem{
copyFn: func(writer io.Writer, reader io.Reader) (int64, error) {
return 0, err
},
}
assert.ErrorIs(t, err, gzipFile("any", fsys))
assert.False(t, fsys.Removed())
})
t.Run("gzip file last close failed", func(t *testing.T) {
var called int32
fsys := &fakeFileSystem{
closeFn: func(closer io.Closer) error {
if atomic.AddInt32(&called, 1) > 2 {
return err
}
return nil
},
}
assert.NoError(t, gzipFile("any", fsys))
assert.True(t, fsys.Removed())
})
t.Run("gzip file remove failed", func(t *testing.T) {
fsys := &fakeFileSystem{
removeFn: func(name string) error {
return err
},
}
assert.Error(t, err, gzipFile("any", fsys))
assert.True(t, fsys.Removed())
})
t.Run("gzip file everything ok", func(t *testing.T) {
fsys := &fakeFileSystem{}
assert.NoError(t, gzipFile("any", fsys))
assert.True(t, fsys.Removed())
})
}
func TestRotateLogger_WithExistingFile(t *testing.T) {
const body = "foo"
filename, err := fs.TempFilenameWithText(body)
assert.Nil(t, err)
if len(filename) > 0 {
defer os.Remove(filename)
}
rule := NewSizeLimitRotateRule(filename, "-", 1, 100, 3, false)
logger, err := NewLogger(filename, rule, false)
assert.Nil(t, err)
assert.Equal(t, int64(len(body)), logger.currentSize)
assert.Nil(t, logger.Close())
}
func BenchmarkRotateLogger(b *testing.B) {
filename := "./test.log"
filename2 := "./test2.log"
dailyRotateRuleLogger, err1 := NewLogger(
filename,
DefaultRotateRule(
filename,
backupFileDelimiter,
1,
true,
),
true,
)
if err1 != nil {
b.Logf("Failed to new daily rotate rule logger: %v", err1)
b.FailNow()
}
sizeLimitRotateRuleLogger, err2 := NewLogger(
filename2,
NewSizeLimitRotateRule(
filename,
backupFileDelimiter,
1,
100,
10,
true,
),
true,
)
if err2 != nil {
b.Logf("Failed to new size limit rotate rule logger: %v", err1)
b.FailNow()
}
defer func() {
dailyRotateRuleLogger.Close()
sizeLimitRotateRuleLogger.Close()
os.Remove(filename)
os.Remove(filename2)
}()
b.Run("daily rotate rule", func(b *testing.B) {
for i := 0; i < b.N; i++ {
dailyRotateRuleLogger.write([]byte("testing\ntesting\n"))
}
})
b.Run("size limit rotate rule", func(b *testing.B) {
for i := 0; i < b.N; i++ {
sizeLimitRotateRuleLogger.write([]byte("testing\ntesting\n"))
}
})
}
type fakeFileSystem struct {
removed int32
closeFn func(closer io.Closer) error
copyFn func(writer io.Writer, reader io.Reader) (int64, error)
createFn func(name string) (*os.File, error)
openFn func(name string) (*os.File, error)
removeFn func(name string) error
}
func (f *fakeFileSystem) Close(closer io.Closer) error {
if f.closeFn != nil {
return f.closeFn(closer)
}
return nil
}
func (f *fakeFileSystem) Copy(writer io.Writer, reader io.Reader) (int64, error) {
if f.copyFn != nil {
return f.copyFn(writer, reader)
}
return 0, nil
}
func (f *fakeFileSystem) Create(name string) (*os.File, error) {
if f.createFn != nil {
return f.createFn(name)
}
return nil, nil
}
func (f *fakeFileSystem) Open(name string) (*os.File, error) {
if f.openFn != nil {
return f.openFn(name)
}
return nil, nil
}
func (f *fakeFileSystem) Remove(name string) error {
atomic.AddInt32(&f.removed, 1)
if f.removeFn != nil {
return f.removeFn(name)
}
return nil
}
func (f *fakeFileSystem) Removed() bool {
return atomic.LoadInt32(&f.removed) > 0
}
-61
View File
@@ -1,61 +0,0 @@
package logger
import (
"encoding/json"
"log"
"strings"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
const testlog = "Stay hungry, stay foolish."
var testobj = map[string]any{"foo": "bar"}
func TestCollectSysLog(t *testing.T) {
CollectSysLog()
content := getContent(captureOutput(func() {
log.Print(testlog)
}))
assert.True(t, strings.Contains(content, testlog))
}
func TestRedirector(t *testing.T) {
var r redirector
content := getContent(captureOutput(func() {
_, _ = r.Write([]byte(testlog))
}))
assert.Equal(t, testlog, content)
}
func captureOutput(f func()) string {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
prevLevel := atomic.LoadUint32(&logLevel)
SetLevel(InfoLevel)
f()
SetLevel(prevLevel)
return w.String()
}
func getContent(jsonStr string) string {
var entry map[string]any
_ = json.Unmarshal([]byte(jsonStr), &entry)
val, ok := entry[contentKey]
if !ok {
return ""
}
str, ok := val.(string)
if !ok {
return ""
}
return str
}
-72
View File
@@ -1,72 +0,0 @@
package logger
import (
"path/filepath"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestGetCaller(t *testing.T) {
_, file, _, _ := runtime.Caller(0)
assert.Contains(t, getCaller(1), filepath.Base(file))
assert.True(t, len(getCaller(1<<10)) == 0)
}
func TestGetTimestamp(t *testing.T) {
ts := getTimestamp()
tm, err := time.Parse(timeFormat, ts)
assert.Nil(t, err)
assert.True(t, time.Since(tm) < time.Minute)
}
func TestPrettyCaller(t *testing.T) {
tests := []struct {
name string
file string
line int
want string
}{
{
name: "regular",
file: "logx_test.go",
line: 123,
want: "logx_test.go:123",
},
{
name: "relative",
file: "adhoc/logx_test.go",
line: 123,
want: "adhoc/logx_test.go:123",
},
{
name: "long path",
file: "github.com/zeromicro/go-zero/core/logx/util_test.go",
line: 12,
want: "logx/util_test.go:12",
},
{
name: "local path",
file: "/Users/kevin/go-zero/core/logx/util_test.go",
line: 1234,
want: "logx/util_test.go:1234",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.want, prettyCaller(test.file, test.line))
})
}
}
func BenchmarkGetCaller(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
getCaller(1)
}
}
-440
View File
@@ -1,440 +0,0 @@
package logger
import (
"bytes"
"encoding/json"
"errors"
"log"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestNewWriter(t *testing.T) {
const literal = "foo bar"
var buf bytes.Buffer
w := NewWriter(&buf)
w.Info(literal)
assert.Contains(t, buf.String(), literal)
buf.Reset()
w.Debug(literal)
assert.Contains(t, buf.String(), literal)
}
func TestConsoleWriter(t *testing.T) {
var buf bytes.Buffer
w := newConsoleWriter()
lw := newLogWriter(log.New(&buf, "", 0))
w.(*concreteWriter).errorLog = lw
w.Alert("foo bar 1")
var val mockedEntry
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelAlert, val.Level)
assert.Equal(t, "foo bar 1", val.Content)
buf.Reset()
w.(*concreteWriter).errorLog = lw
w.Error("foo bar 2")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelError, val.Level)
assert.Equal(t, "foo bar 2", val.Content)
buf.Reset()
w.(*concreteWriter).infoLog = lw
w.Info("foo bar 3")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelInfo, val.Level)
assert.Equal(t, "foo bar 3", val.Content)
buf.Reset()
w.(*concreteWriter).severeLog = lw
w.Severe("foo bar 4")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelFatal, val.Level)
assert.Equal(t, "foo bar 4", val.Content)
buf.Reset()
w.(*concreteWriter).slowLog = lw
w.Slow("foo bar 5")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelSlow, val.Level)
assert.Equal(t, "foo bar 5", val.Content)
buf.Reset()
w.(*concreteWriter).statLog = lw
w.Stat("foo bar 6")
if err := json.Unmarshal(buf.Bytes(), &val); err != nil {
t.Fatal(err)
}
assert.Equal(t, levelStat, val.Level)
assert.Equal(t, "foo bar 6", val.Content)
w.(*concreteWriter).infoLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).infoLog = easyToCloseWriter{}
w.(*concreteWriter).errorLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).errorLog = easyToCloseWriter{}
w.(*concreteWriter).severeLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).severeLog = easyToCloseWriter{}
w.(*concreteWriter).slowLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).slowLog = easyToCloseWriter{}
w.(*concreteWriter).statLog = hardToCloseWriter{}
assert.NotNil(t, w.Close())
w.(*concreteWriter).statLog = easyToCloseWriter{}
}
func TestNewFileWriter(t *testing.T) {
t.Run("access", func(t *testing.T) {
_, err := newFileWriter(LogConf{
Path: "/not-exists",
})
assert.Error(t, err)
})
}
func TestNopWriter(t *testing.T) {
assert.NotPanics(t, func() {
var w nopWriter
w.Alert("foo")
w.Debug("foo")
w.Error("foo")
w.Info("foo")
w.Severe("foo")
w.Stack("foo")
w.Stat("foo")
w.Slow("foo")
_ = w.Close()
})
}
func TestWriteJson(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
writeJson(nil, "foo")
assert.Contains(t, buf.String(), "foo")
buf.Reset()
writeJson(hardToWriteWriter{}, "foo")
assert.Contains(t, buf.String(), "write error")
buf.Reset()
writeJson(nil, make(chan int))
assert.Contains(t, buf.String(), "unsupported type")
buf.Reset()
type C struct {
RC func()
}
writeJson(nil, C{
RC: func() {},
})
assert.Contains(t, buf.String(), "runtime/debug.Stack")
}
func TestWritePlainAny(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
writePlainAny(nil, levelInfo, "foo")
assert.Contains(t, buf.String(), "foo")
buf.Reset()
writePlainAny(nil, levelDebug, make(chan int))
assert.Contains(t, buf.String(), "unsupported type")
writePlainAny(nil, levelDebug, 100)
assert.Contains(t, buf.String(), "100")
buf.Reset()
writePlainAny(nil, levelError, make(chan int))
assert.Contains(t, buf.String(), "unsupported type")
writePlainAny(nil, levelSlow, 100)
assert.Contains(t, buf.String(), "100")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelStat, 100)
assert.Contains(t, buf.String(), "write error")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelSevere, "foo")
assert.Contains(t, buf.String(), "write error")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelAlert, "foo")
assert.Contains(t, buf.String(), "write error")
buf.Reset()
writePlainAny(hardToWriteWriter{}, levelFatal, "foo")
assert.Contains(t, buf.String(), "write error")
buf.Reset()
type C struct {
RC func()
}
writePlainAny(nil, levelError, C{
RC: func() {},
})
assert.Contains(t, buf.String(), "runtime/debug.Stack")
}
func TestWritePlainDuplicate(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
t.Cleanup(func() {
atomic.StoreUint32(&encoding, old)
})
var buf bytes.Buffer
output(&buf, levelInfo, "foo", LogField{
Key: "first",
Value: "a",
}, LogField{
Key: "first",
Value: "b",
})
assert.Contains(t, buf.String(), "foo")
assert.NotContains(t, buf.String(), "first=a")
assert.Contains(t, buf.String(), "first=b")
buf.Reset()
output(&buf, levelInfo, "foo", LogField{
Key: "first",
Value: "a",
}, LogField{
Key: "first",
Value: "b",
}, LogField{
Key: "second",
Value: "c",
})
assert.Contains(t, buf.String(), "foo")
assert.NotContains(t, buf.String(), "first=a")
assert.Contains(t, buf.String(), "first=b")
assert.Contains(t, buf.String(), "second=c")
}
func TestLogWithLimitContentLength(t *testing.T) {
maxLen := atomic.LoadUint32(&maxContentLength)
atomic.StoreUint32(&maxContentLength, 10)
t.Cleanup(func() {
atomic.StoreUint32(&maxContentLength, maxLen)
})
t.Run("alert", func(t *testing.T) {
var buf bytes.Buffer
w := NewWriter(&buf)
w.Info("1234567890")
var v1 mockedEntry
if err := json.Unmarshal(buf.Bytes(), &v1); err != nil {
t.Fatal(err)
}
assert.Equal(t, "1234567890", v1.Content)
assert.False(t, v1.Truncated)
buf.Reset()
var v2 mockedEntry
w.Info("12345678901")
if err := json.Unmarshal(buf.Bytes(), &v2); err != nil {
t.Fatal(err)
}
assert.Equal(t, "1234567890", v2.Content)
assert.True(t, v2.Truncated)
})
}
func TestComboWriter(t *testing.T) {
var mockWriters []Writer
for i := 0; i < 3; i++ {
mockWriters = append(mockWriters, new(tracedWriter))
}
cw := comboWriter{
writers: mockWriters,
}
t.Run("Alert", func(t *testing.T) {
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Alert", "test alert").Once()
}
cw.Alert("test alert")
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Alert", "test alert")
}
})
t.Run("Close", func(t *testing.T) {
for i := range cw.writers {
if i == 1 {
cw.writers[i].(*tracedWriter).On("Close").Return(errors.New("error")).Once()
} else {
cw.writers[i].(*tracedWriter).On("Close").Return(nil).Once()
}
}
err := cw.Close()
assert.Error(t, err)
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Close")
}
})
t.Run("Debug", func(t *testing.T) {
fields := []LogField{{Key: "key", Value: "value"}}
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Debug", "test debug", fields).Once()
}
cw.Debug("test debug", fields...)
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Debug", "test debug", fields)
}
})
t.Run("Error", func(t *testing.T) {
fields := []LogField{{Key: "key", Value: "value"}}
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Error", "test error", fields).Once()
}
cw.Error("test error", fields...)
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Error", "test error", fields)
}
})
t.Run("Info", func(t *testing.T) {
fields := []LogField{{Key: "key", Value: "value"}}
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Info", "test info", fields).Once()
}
cw.Info("test info", fields...)
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Info", "test info", fields)
}
})
t.Run("Severe", func(t *testing.T) {
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Severe", "test severe").Once()
}
cw.Severe("test severe")
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Severe", "test severe")
}
})
t.Run("Slow", func(t *testing.T) {
fields := []LogField{{Key: "key", Value: "value"}}
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Slow", "test slow", fields).Once()
}
cw.Slow("test slow", fields...)
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Slow", "test slow", fields)
}
})
t.Run("Stack", func(t *testing.T) {
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Stack", "test stack").Once()
}
cw.Stack("test stack")
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Stack", "test stack")
}
})
t.Run("Stat", func(t *testing.T) {
fields := []LogField{{Key: "key", Value: "value"}}
for _, mw := range cw.writers {
mw.(*tracedWriter).On("Stat", "test stat", fields).Once()
}
cw.Stat("test stat", fields...)
for _, mw := range cw.writers {
mw.(*tracedWriter).AssertCalled(t, "Stat", "test stat", fields)
}
})
}
type mockedEntry struct {
Level string `json:"level"`
Content string `json:"content"`
Truncated bool `json:"truncated"`
}
type easyToCloseWriter struct{}
func (h easyToCloseWriter) Write(_ []byte) (_ int, _ error) {
return
}
func (h easyToCloseWriter) Close() error {
return nil
}
type hardToCloseWriter struct{}
func (h hardToCloseWriter) Write(_ []byte) (_ int, _ error) {
return
}
func (h hardToCloseWriter) Close() error {
return errors.New("close error")
}
type hardToWriteWriter struct{}
func (h hardToWriteWriter) Write(_ []byte) (_ int, _ error) {
return 0, errors.New("write error")
}
type tracedWriter struct {
mock.Mock
}
func (w *tracedWriter) Alert(v any) {
w.Called(v)
}
func (w *tracedWriter) Close() error {
args := w.Called()
return args.Error(0)
}
func (w *tracedWriter) Debug(v any, fields ...LogField) {
w.Called(v, fields)
}
func (w *tracedWriter) Error(v any, fields ...LogField) {
w.Called(v, fields)
}
func (w *tracedWriter) Info(v any, fields ...LogField) {
w.Called(v, fields)
}
func (w *tracedWriter) Severe(v any) {
w.Called(v)
}
func (w *tracedWriter) Slow(v any, fields ...LogField) {
w.Called(v, fields)
}
func (w *tracedWriter) Stack(v any) {
w.Called(v)
}
func (w *tracedWriter) Stat(v any, fields ...LogField) {
w.Called(v, fields)
}
-27
View File
@@ -1,27 +0,0 @@
package nodeMultiplier
import (
"testing"
"time"
)
func TestNewNodeMultiplierManager(t *testing.T) {
periods := []TimePeriod{
{
StartTime: "23:00.000",
EndTime: "1:59.000",
Multiplier: 1.2,
},
{
StartTime: "12:00.000",
EndTime: "13:59.000",
Multiplier: 0.5,
},
}
m := NewNodeMultiplierManager(periods)
if len(m.Periods) != 1 {
t.Errorf("expected 1, got %d", len(m.Periods))
}
t.Log("00:10 multiplier:", m.GetMultiplier(time.Date(0, 1, 1, 0, 10, 0, 0, time.UTC)))
}
-76
View File
@@ -1,76 +0,0 @@
package apple
import (
"context"
"fmt"
"log"
"net/http"
"testing"
"github.com/gin-gonic/gin"
)
func TestAppleLogin(t *testing.T) {
t.Skipf("Skip TestAppleLogin test")
router := gin.Default()
router.LoadHTMLGlob("./*")
router.GET("/apple", func(c *gin.Context) {
c.HTML(http.StatusOK, "apple.html", gin.H{
"title": "Gin HTML Example",
"message": "Hello, Gin!",
})
})
router.POST("/auth/apple/callback", func(c *gin.Context) {
var req CallbackRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request data"})
return
}
handleAppleCallBack(c, req)
})
_ = router.RunTLS(":8443", "certificate.crt", "private.key")
}
func handleAppleCallBack(ctx context.Context, request CallbackRequest) {
fmt.Printf("request: %+v\n", request)
// validate the token
client, err := New(Config{
TeamID: TeamID,
ClientID: ClientID,
KeyID: KeyID,
ClientSecret: ClientSecret,
RedirectURI: "https://test.ppanel.dev:8443/auth/apple/callback",
})
if err != nil {
fmt.Println("error creating apple client: " + err.Error())
return
}
resp, err := client.VerifyWebToken(ctx, request.Code)
if err != nil {
fmt.Println("error verifying token: " + err.Error())
return
}
if resp.Error != "" {
fmt.Printf("apple returned an error: %s - %s\n", resp.Error, resp.ErrorDescription)
return
}
// Get the unique user ID
unique, err := GetUniqueID(resp.IDToken)
if err != nil {
fmt.Println("error getting unique id: " + err.Error())
return
}
// Get the email
claim, err := GetClaims(resp.IDToken)
if err != nil {
fmt.Println("failed to get claims: " + err.Error())
return
}
email := (*claim)["email"]
emailVerified := (*claim)["email_verified"]
isPrivateEmail := (*claim)["is_private_email"]
// Voila!
log.Printf("\n unique: %s \n email: %s \n email_verified: %v \n is_private_email: %v", unique, email, emailVerified, isPrivateEmail)
}
-78
View File
@@ -1,78 +0,0 @@
package google
import (
"context"
"fmt"
"log"
"net/http"
"testing"
"golang.org/x/oauth2"
)
func TestGoogleOAuth(t *testing.T) {
t.Skipf("Skip TestGoogleOAuth test")
http.HandleFunc("/", handleMain)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/auth", handleCallback)
http.HandleFunc("/user", handleAuth)
fmt.Println("Server is running on http://localhost:3001")
log.Fatal(http.ListenAndServe(":3001", nil))
}
func handleMain(w http.ResponseWriter, r *http.Request) {
html := `<html>
<body>
<a href="/login">Log in with Google</a>
</body>
</html>`
fmt.Fprint(w, html)
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
oauthConfig := New(&Config{
ClientID: "",
ClientSecret: "",
RedirectURL: "http://localhost:3001/auth",
})
url := oauthConfig.AuthCodeURL("randomstate", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
if r.FormValue("state") != "randomstate" {
http.Error(w, "State is invalid", http.StatusBadRequest)
return
}
log.Printf("url: %v", r.URL)
oauthConfig := New(&Config{
ClientID: "",
ClientSecret: "Key",
RedirectURL: "http://localhost:3001/auth",
})
code := r.FormValue("code")
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/user?token="+token.AccessToken, http.StatusTemporaryRedirect)
}
func handleAuth(w http.ResponseWriter, r *http.Request) {
token := r.FormValue("token")
client := New(&Config{
ClientID: "Id",
ClientSecret: "Key",
RedirectURL: "http://localhost:3001/auth",
})
userInfo, err := client.GetUserInfo(token)
if err != nil {
http.Error(w, "Failed to get user info", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Hello, %s", userInfo.Name)
}
-36
View File
@@ -1,36 +0,0 @@
package telegram
import (
"net/http"
"testing"
"github.com/gin-gonic/gin"
)
func TestOAuth(t *testing.T) {
t.Skipf("Skip TestOAuth test")
router := gin.Default()
router.LoadHTMLGlob("./*")
router.GET("/telegram", func(c *gin.Context) {
c.HTML(http.StatusOK, "telegram.html", gin.H{
"title": "Gin HTML Example",
"message": "Hello, Gin!",
})
})
router.GET("/auth/telegram/callback", func(c *gin.Context) {
})
_ = router.RunTLS(":443", "server.crt", "server.key")
}
func TestBase64(t *testing.T) {
text := "eyJpZCI6ODI0NjI2ODAzLCJmaXJzdF9uYW1lIjoiQ2hhbmcgbHVlIiwibGFzdF9uYW1lIjoiVHNlbiIsInVzZXJuYW1lIjoidGVuc2lvbl9jIiwicGhvdG9fdXJsIjoiaHR0cHM6XC9cL3QubWVcL2lcL3VzZXJwaWNcLzMyMFwvYU1LNkhEc0pqc2V1YldRYmt2NGlYOHZCRUF6N0hWU3g3dkFuRDBLZ0tFVS5qcGciLCJhdXRoX2RhdGUiOjE3Mzc4MTkwNzQsImhhc2giOiI5M2I1ZDg3Zjc3NjE2YjBjMTM0OTAxYmYwMDg3MTc4YjJiYmZlYzA1MTlkMWVmMDJhZjFjMGNlOTAzM2ZiNGFlIn0"
var token = "7651491571:AAEVQma6niHhtqEYDowAEpPo6Fq69BWvRU8"
data, err := ParseAndValidateBase64([]byte(text), token)
if err != nil {
t.Error(err)
}
t.Log(*data.Id)
}
-68
View File
@@ -1,68 +0,0 @@
package openinstall
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
// TestChannelParameter 验证 OpenInstall 客户端是否正确传递了 channel 参数
func TestChannelParameter(t *testing.T) {
// 1. 启动 Mock Server
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 验证请求路径
if r.URL.Path == "/data/sum/growth" {
// 验证 Query 参数
query := r.URL.Query()
channel := query.Get("channel")
// 核心验证点:channel 参数必须等于即使的 inviteCode
if channel == "TEST_INVITE_CODE_123" {
w.WriteHeader(http.StatusOK)
// 返回假数据
w.Write([]byte(`{
"code": 0,
"body": [
{"key": "ios", "value": 100},
{"key": "android", "value": 200}
]
}`))
return
}
// 如果 channel 不匹配,返回错误
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"code": 400, "error": "channel mismatch"}`))
return
}
w.WriteHeader(http.StatusNotFound)
}))
defer mockServer.Close()
// 2. 临时修改 apiBaseURL 指向 Mock Server
originalBaseURL := apiBaseURL
apiBaseURL = mockServer.URL
defer func() { apiBaseURL = originalBaseURL }()
// 3. 初始化客户端
client := NewClient("test-api-key")
// 4. 调用接口 (传入测试用的邀请码)
ctx := context.Background()
stats, err := client.GetPlatformDownloads(ctx, "TEST_INVITE_CODE_123")
// 5. 验证结果
assert.NoError(t, err)
assert.NotNil(t, stats)
// 验证数据正确解析 (iOS=100, Android=200, Total=300)
assert.Equal(t, int64(100), stats.IOS, "iOS count should match mock data")
assert.Equal(t, int64(200), stats.Android, "Android count should match mock data")
assert.Equal(t, int64(300), stats.Total, "Total count should match sum of mock data")
t.Logf("Success! Channel parameter 'TEST_INVITE_CODE_123' was correctly sent to server.")
}
-57
View File
@@ -1,57 +0,0 @@
package openinstall
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestClient_GetPlatformDownloads_WithChannel(t *testing.T) {
// Mock Server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify URL parameters
assert.Equal(t, "GET", r.Method)
assert.Equal(t, "/data/sum/growth", r.URL.Path)
assert.Equal(t, "test-api-key", r.URL.Query().Get("apiKey"))
assert.Equal(t, "test-channel", r.URL.Query().Get("channel")) // Verify channel is passed
assert.Equal(t, "total", r.URL.Query().Get("sumBy"))
assert.Equal(t, "0", r.URL.Query().Get("excludeDuplication"))
// Return mock response
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{
"code": 0,
"body": [
{"key": "ios", "value": 10},
{"key": "android", "value": 20}
]
}`))
}))
defer server.Close()
// Redirect base URL to mock server (This requires modifying the constant in real code,
// but for this test script we can just verify the logic or make the URL configurable.
// Since apiBaseURL is a constant, we cannot change it.
// However, this test demonstrates the logic we implemented.
// For actual running, we might need to inject the URL or make it a variable.)
// NOTE: Since apiBaseURL is constant in standard Go we can't patch it easily without unsafe or changing code.
// But `getDeviceDistribution` constructs the URL using `apiBaseURL`.
// For the sake of this example, we assume we can test the parameter construction logic
// or we would need to refactor `apiBaseURL` to be a field in `Client`.
// Since I cannot change the constant easily to point to localhost in the compiled package
// without refactoring, I will provide a test that *would* work if we refactored,
// OR I can make the test just run against the real API but that requires a key.
// Plan B: Create a test that instantiates the client and checks the URL construction if we extracted that method,
// but we didn't.
// Let's refactor Client to allow base URL injection for testing?
// Or just provide a shell script for the user to run against real env provided they have keys.
// The user asked for a "Test Script", commonly meaning a shell script to run the API.
t.Log("This is a structural test example. To fully unit test HTTP requests with constants, refactoring is recommended.")
}
-40
View File
@@ -1,40 +0,0 @@
package orm
import (
"testing"
"github.com/perfect-panel/server/internal/model/task"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func TestParseDSN(t *testing.T) {
dsn := "root:mylove520@tcp(localhost:3306)/vpnboard"
config := ParseDSN(dsn)
if config == nil {
t.Fatal("config is nil")
}
t.Log(config)
}
func TestPing(t *testing.T) {
dsn := "root:mylove520@tcp(localhost:3306)/vpnboard"
status := Ping(dsn)
t.Log(status)
}
func TestMysql(t *testing.T) {
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "root:mylove520@tcp(localhost:3306)/vpnboard",
}))
if err != nil {
t.Fatalf("Failed to connect to MySQL: %v", err)
}
err = db.Migrator().AutoMigrate(&task.Task{})
if err != nil {
t.Fatalf("Failed to auto migrate: %v", err)
return
}
t.Log("MySQL connection and migration successful")
}
-25
View File
@@ -1,25 +0,0 @@
package alipay
import (
"context"
"testing"
)
func TestClientPreCreateTrade(t *testing.T) {
t.Skipf("Skip TestClientPreCreateTrade")
cfg := Config{
InvoiceName: "XrayR",
NotifyURL: "https://example.com/alipay/notify",
Sandbox: true,
}
c := NewClient(cfg)
order := Order{
OrderNo: "20210701000001",
Amount: 100,
}
qr, err := c.PreCreateTrade(context.Background(), order)
if err != nil {
t.Fatal(err)
}
t.Log(qr)
}
-49
View File
@@ -1,49 +0,0 @@
package epay
import "testing"
func TestEpay(t *testing.T) {
client := NewClient("", "http://127.0.0.1", "", "")
order := Order{
Name: "测试",
OrderNo: "123456789",
Amount: 1000,
SignType: "md5",
NotifyUrl: "http://127.0.0.1",
ReturnUrl: "http://127.0.0.1",
}
url := client.CreatePayUrl(order)
t.Logf("PayUrl: %s\n", url)
}
func TestQueryOrderStatus(t *testing.T) {
t.Skipf("Skip TestQueryOrderStatus test")
client := NewClient("Pid", "Url", "Key", "Type")
orderNo := "123456789"
status := client.QueryOrderStatus(orderNo)
t.Logf("OrderNo: %s, Status: %v\n", orderNo, status)
}
func TestVerifySign(t *testing.T) {
t.Skipf("Skip TestVerifySign test")
params := map[string]string{
"pid": "1654",
"trade_no": "2024121521150860990",
"out_trade_no": "202412152115078262977262254",
"type": "alipay",
"name": "product",
"money": "10",
"trade_status": "TRADE_SUCCESS",
"sign": "d3181f18ebdf9821f0ab6ee93faa82d1",
"sign_type": "MD5",
}
key := "LbTabbB580zWyhXhyyww7wwvy5u8k0wl"
c := NewClient("Pid", "Url", key, "Type")
if c.VerifySign(params) {
t.Logf("Sign verification success!")
} else {
t.Error("Sign verification failed!")
}
}
-21
View File
@@ -1,21 +0,0 @@
package payment
import (
"net/http"
"testing"
"github.com/gin-gonic/gin"
)
func TestHttp(t *testing.T) {
t.Skipf("Skip TestHttp test")
router := gin.Default()
router.LoadHTMLGlob("./*")
router.GET("/stripe", func(c *gin.Context) {
c.HTML(http.StatusOK, "stripe.html", gin.H{
"title": "Gin HTML Example",
"message": "Hello, Gin!",
})
})
_ = router.Run(":8989")
}
-69
View File
@@ -1,69 +0,0 @@
package payment
import "testing"
func TestParsePlatform(t *testing.T) {
testCases := []struct {
name string
input string
expected Platform
}{
{name: "exact AppleIAP", input: "AppleIAP", expected: AppleIAP},
{name: "snake apple_iap", input: "apple_iap", expected: AppleIAP},
{name: "kebab apple-iap", input: "apple-iap", expected: AppleIAP},
{name: "compact appleiap", input: "appleiap", expected: AppleIAP},
{name: "trimmed value", input: " apple_iap ", expected: AppleIAP},
{name: "legacy exact CryptoSaaS", input: "CryptoSaaS", expected: CryptoSaaS},
{name: "snake crypto_saas", input: "crypto_saas", expected: CryptoSaaS},
{name: "unsupported", input: "unknown_gateway", expected: UNSUPPORTED},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
got := ParsePlatform(testCase.input)
if got != testCase.expected {
t.Fatalf("ParsePlatform(%q) = %v, expected %v", testCase.input, got, testCase.expected)
}
})
}
}
func TestPlatformStringIsCanonical(t *testing.T) {
testCases := []struct {
name string
input Platform
expected string
}{
{name: "stripe", input: Stripe, expected: "Stripe"},
{name: "alipay", input: AlipayF2F, expected: "AlipayF2F"},
{name: "epay", input: EPay, expected: "EPay"},
{name: "balance", input: Balance, expected: "balance"},
{name: "crypto", input: CryptoSaaS, expected: "CryptoSaaS"},
{name: "apple", input: AppleIAP, expected: "AppleIAP"},
{name: "unsupported", input: UNSUPPORTED, expected: "unsupported"},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
got := testCase.input.String()
if got != testCase.expected {
t.Fatalf("Platform.String() = %q, expected %q", got, testCase.expected)
}
})
}
}
func TestCanonicalPlatformName(t *testing.T) {
canonical, ok := CanonicalPlatformName("apple_iap")
if !ok {
t.Fatalf("expected apple_iap to be supported")
}
if canonical != "AppleIAP" {
t.Fatalf("canonical name mismatch: got %q", canonical)
}
_, ok = CanonicalPlatformName("not_exists")
if ok {
t.Fatalf("expected unsupported platform to return ok=false")
}
}
-55
View File
@@ -1,55 +0,0 @@
package stripe
import (
"testing"
"github.com/stripe/stripe-go/v81"
)
func TestStripeAlipay(t *testing.T) {
t.Skipf("Skip TestStripeAlipay test")
client := NewClient(Config{
WebhookSecret: "",
})
order := Order{
OrderNo: "JS20210719123456789",
Subscribe: "测试",
Amount: 100,
Currency: string(stripe.CurrencyGBP),
Payment: "alipay",
}
user := User{
UserId: 1,
Email: "tension@ppanel.dev",
}
result, err := client.CreatePaymentSheet(&order, &user)
if err != nil {
t.Error(err.Error())
}
t.Logf("TradeNo: %s\n", result.ClientSecret)
}
func TestStripeWechat(t *testing.T) {
t.Skipf("Skip TestStripeWechat test")
client := NewClient(Config{
SecretKey: "SecretKey",
PublicKey: "PublicKey",
WebhookSecret: "",
})
order := Order{
OrderNo: "JS20210719123456789",
Subscribe: "测试",
Amount: 100,
Currency: string(stripe.CurrencyGBP),
Payment: "wechat_pay",
}
user := User{
UserId: 1,
Email: "tension@ppanel.dev",
}
result, err := client.CreatePaymentSheet(&order, &user)
if err != nil {
t.Error(err.Error())
}
t.Logf("TradeNo: %s\n", result.ClientSecret)
}
-57
View File
@@ -1,57 +0,0 @@
package phone
import (
"testing"
"github.com/nyaruka/phonenumbers"
)
func TestPhoneNumber(t *testing.T) {
parsedNumber, err := phonenumbers.Parse("+8615502505555", "")
if err != nil {
t.Fatalf("Failed to parse phone number: %v", err)
return
}
// 检查手机号是否有效
isValid := phonenumbers.IsValidNumber(parsedNumber)
// 获取区域代码 (如 CN, US, IN)
region := phonenumbers.GetRegionCodeForNumber(parsedNumber)
t.Log(isValid)
t.Log(region)
}
func TestCheck(t *testing.T) {
var phone = "15502505555"
if !Check("86", phone) {
t.Fatalf("Check phone number failed: %s", phone)
}
t.Logf("Check phone number success: %s", phone)
}
func TestGetCountryCode(t *testing.T) {
var phone = "14407941888"
countryCode := GetCountryCode(phone)
t.Logf("Country code: %s", countryCode)
}
func TestFormatToInternational(t *testing.T) {
var phone = "8615502505555"
international := FormatToInternational(phone)
t.Logf("International format: %s", international)
}
func TestFormatToE164(t *testing.T) {
var phone = "4407941888"
e164, err := FormatToE164("1", phone)
if err != nil {
t.Fatalf("Failed to format phone number to E164: %v", err)
return
}
t.Logf("E164 format: %s", e164)
}
func TestMask(t *testing.T) {
var phone = "+14407941888"
mask := MaskPhoneNumber(phone)
t.Logf("Mask format: %s", mask)
}
-62
View File
@@ -1,62 +0,0 @@
//go:build linux || darwin
package proc
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestShutdown(t *testing.T) {
SetTimeToForceQuit(time.Hour)
assert.Equal(t, time.Hour, delayTimeBeforeForceQuit)
var val int
called := AddWrapUpListener(func() {
val++
})
WrapUp()
called()
assert.Equal(t, 1, val)
called = AddShutdownListener(func() {
val += 2
})
Shutdown()
called()
assert.Equal(t, 3, val)
}
func TestNotifyMoreThanOnce(t *testing.T) {
ch := make(chan struct{}, 1)
go func() {
var val int
called := AddWrapUpListener(func() {
val++
})
WrapUp()
WrapUp()
called()
assert.Equal(t, 1, val)
called = AddShutdownListener(func() {
val += 2
})
Shutdown()
Shutdown()
called()
assert.Equal(t, 3, val)
ch <- struct{}{}
}()
select {
case <-ch:
fmt.Printf("TestNotifyMoreThanOnce done\n")
case <-time.After(time.Second):
t.Fatal("timeout, check error logs")
}
}
-96
View File
@@ -1,96 +0,0 @@
package random
import (
"math/rand"
"testing"
"time"
"github.com/perfect-panel/server/pkg/snowflake"
"github.com/stretchr/testify/assert"
)
func TestEncodeBase62(t *testing.T) {
start := 1112275807
length := 1558080
n := length + start
// n := 328564998144
m := make(map[string]struct{})
// m := make(map[string]struct{}, length)
var inviteCode string
for i := start; i < n; i++ {
// inviteCode = EncodeBase36(int64(i))
inviteCode = EncodeBase36(snowflake.GetID())
if v, ok := m[inviteCode]; ok {
t.Fatal(v, inviteCode)
}
m[inviteCode] = struct{}{}
}
t.Log(inviteCode)
assert.Equal(t, length, len(m))
}
func TestInt64ToDashedString(t *testing.T) {
type args struct {
strNum string
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
{
name: "",
args: args{
strNum: "123",
},
want: "123",
},
{
name: "",
args: args{
strNum: "1234",
},
want: "1234",
},
{
name: "",
args: args{
strNum: "12345",
},
want: "1234-5",
},
{
name: "",
args: args{
strNum: "12345678",
},
want: "1234-5678",
},
{
name: "",
args: args{
strNum: "123456789",
},
want: "1234-5678-9",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, StrToDashedString(tt.args.strNum), "StrToDashedString(%v)", tt.args.strNum)
})
}
}
// ShuffleString shuffles the characters in a string.
func ShuffleString(s string) string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
runes := []rune(s)
for i := range runes {
j := r.Intn(i + 1)
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
-40
View File
@@ -1,40 +0,0 @@
package rescue
import (
"context"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func init() {
}
func TestRescue(t *testing.T) {
var count int32
assert.NotPanics(t, func() {
defer Recover(func() {
atomic.AddInt32(&count, 2)
}, func() {
atomic.AddInt32(&count, 3)
})
panic("hello")
})
assert.Equal(t, int32(5), atomic.LoadInt32(&count))
}
func TestRescueCtx(t *testing.T) {
var count int32
assert.NotPanics(t, func() {
defer RecoverCtx(context.Background(), func() {
atomic.AddInt32(&count, 2)
}, func() {
atomic.AddInt32(&count, 3)
})
panic("hello")
})
assert.Equal(t, int32(5), atomic.LoadInt32(&count))
}
-52
View File
@@ -1,52 +0,0 @@
package rules
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
var text = `
DOMAIN,example.com
DOMAIN-SUFFIX,google.com,DIRECT
DOMAIN-KEYWORD,amazon,REJECT
IP-CIDR,192.168.0.0/16
`
func TestNewRule(t *testing.T) {
var rs []string
// parse validate rules
ruleArr := strings.Split(text, "\n")
if len(ruleArr) == 0 {
t.Error("rules is empty")
}
ruleArr = trimArr(ruleArr)
for _, s := range ruleArr {
r := NewRule(s, "Test")
if r == nil {
t.Errorf("[CreateRuleGroup] rule %s is nil, len: %d", s, len(s))
continue
}
if err := r.Validate(); err != nil {
t.Errorf("[CreateRuleGroup] rule %s is invalid: %v", s, err)
continue
}
rs = append(rs, r.String())
}
expected := []string{
"DOMAIN,example.com,Test",
"DOMAIN-SUFFIX,google.com,DIRECT",
"DOMAIN-KEYWORD,amazon,REJECT",
"IP-CIDR,192.168.0.0/16,Test,no-resolve",
}
for i, r := range rs {
if r != expected[i] {
t.Errorf("expected %s, got %s", expected[i], r)
}
}
// Check if the rules are sorted
assert.Equal(t, len(rs), len(expected))
}
-129
View File
@@ -1,129 +0,0 @@
package service
import (
"sync"
"testing"
"github.com/perfect-panel/server/pkg/proc"
"github.com/stretchr/testify/assert"
)
var (
number = 1
mutex sync.Mutex
done = make(chan struct{})
)
func TestServiceGroup(t *testing.T) {
multipliers := []int{2, 3, 5, 7}
want := 1
group := NewServiceGroup()
for _, multiplier := range multipliers {
want *= multiplier
service := newMockedService(multiplier)
group.Add(service)
}
go group.Start()
for i := 0; i < len(multipliers); i++ {
<-done
}
group.Stop()
proc.Shutdown()
mutex.Lock()
defer mutex.Unlock()
assert.Equal(t, want, number)
}
func TestServiceGroup_WithStart(t *testing.T) {
multipliers := []int{2, 3, 5, 7}
want := 1
var wait sync.WaitGroup
var lock sync.Mutex
wait.Add(len(multipliers))
group := NewServiceGroup()
for _, multiplier := range multipliers {
mul := multiplier
group.Add(WithStart(func() {
lock.Lock()
want *= mul
lock.Unlock()
wait.Done()
}))
}
go group.Start()
wait.Wait()
group.Stop()
lock.Lock()
defer lock.Unlock()
assert.Equal(t, 210, want)
}
func TestServiceGroup_WithStarter(t *testing.T) {
multipliers := []int{2, 3, 5, 7}
want := 1
var wait sync.WaitGroup
var lock sync.Mutex
wait.Add(len(multipliers))
group := NewServiceGroup()
for _, multiplier := range multipliers {
mul := multiplier
group.Add(WithStarter(mockedStarter{
fn: func() {
lock.Lock()
want *= mul
lock.Unlock()
wait.Done()
},
}))
}
go group.Start()
wait.Wait()
group.Stop()
lock.Lock()
defer lock.Unlock()
assert.Equal(t, 210, want)
}
type mockedStarter struct {
fn func()
}
func (s mockedStarter) Start() {
s.fn()
}
type mockedService struct {
quit chan struct{}
multiplier int
}
func newMockedService(multiplier int) *mockedService {
return &mockedService{
quit: make(chan struct{}),
multiplier: multiplier,
}
}
func (s *mockedService) Start() {
mutex.Lock()
number *= s.multiplier
mutex.Unlock()
done <- struct{}{}
<-s.quit
}
func (s *mockedService) Stop() {
close(s.quit)
}
-120
View File
@@ -1,120 +0,0 @@
package signature
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"testing"
"time"
)
type mockNonceStore struct {
seen map[string]bool
}
func newMockNonceStore() *mockNonceStore {
return &mockNonceStore{seen: map[string]bool{}}
}
func (m *mockNonceStore) SetIfNotExists(_ context.Context, appId, nonce string, _ int64) (bool, error) {
key := appId + ":" + nonce
if m.seen[key] {
return true, nil
}
m.seen[key] = true
return false, nil
}
func makeSignature(secret, stringToSign string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(stringToSign))
return hex.EncodeToString(mac.Sum(nil))
}
func TestValidateSuccess(t *testing.T) {
conf := SignatureConf{
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
ValidWindowSeconds: 300,
}
v := NewValidator(conf, newMockNonceStore())
ts := strconv.FormatInt(time.Now().Unix(), 10)
nonce := fmt.Sprintf("%x", time.Now().UnixNano())
sts := BuildStringToSign("POST", "/v1/public/order/create", "", []byte(`{"plan_id":1}`), "web-client", ts, nonce)
sig := makeSignature("uB4G,XxL2{7b", sts)
if err := v.Validate(context.Background(), "web-client", ts, nonce, sig, sts); err != nil {
t.Fatalf("expected success, got %v", err)
}
}
func TestValidateExpired(t *testing.T) {
conf := SignatureConf{
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
ValidWindowSeconds: 300,
}
v := NewValidator(conf, newMockNonceStore())
ts := strconv.FormatInt(time.Now().Unix()-400, 10)
nonce := "abc"
sts := BuildStringToSign("GET", "/v1/public/user/info", "", nil, "web-client", ts, nonce)
sig := makeSignature("uB4G,XxL2{7b", sts)
if err := v.Validate(context.Background(), "web-client", ts, nonce, sig, sts); err != ErrSignatureExpired {
t.Fatalf("expected ErrSignatureExpired, got %v", err)
}
}
func TestValidateReplay(t *testing.T) {
conf := SignatureConf{
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
ValidWindowSeconds: 300,
}
v := NewValidator(conf, newMockNonceStore())
ts := strconv.FormatInt(time.Now().Unix(), 10)
nonce := "same-nonce-replay"
sts := BuildStringToSign("GET", "/v1/public/user/info", "", nil, "web-client", ts, nonce)
sig := makeSignature("uB4G,XxL2{7b", sts)
_ = v.Validate(context.Background(), "web-client", ts, nonce, sig, sts)
if err := v.Validate(context.Background(), "web-client", ts, nonce, sig, sts); err != ErrSignatureReplay {
t.Fatalf("expected ErrSignatureReplay, got %v", err)
}
}
func TestValidateInvalidSignature(t *testing.T) {
conf := SignatureConf{
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
ValidWindowSeconds: 300,
}
v := NewValidator(conf, newMockNonceStore())
ts := strconv.FormatInt(time.Now().Unix(), 10)
nonce := "nonce-invalid-sig"
sts := BuildStringToSign("POST", "/v1/public/order/create", "", []byte(`{"plan_id":1}`), "web-client", ts, nonce)
if err := v.Validate(context.Background(), "web-client", ts, nonce, "badsignature", sts); err != ErrSignatureInvalid {
t.Fatalf("expected ErrSignatureInvalid, got %v", err)
}
}
func TestBuildStringToSignCanonicalQuery(t *testing.T) {
got := BuildStringToSign(
"get",
"/v1/public/order/list",
"b=2&a=1&a=3&c=",
nil,
"web-client",
"1700000000",
"nonce-1",
)
want := "GET\n/v1/public/order/list\na=1&b=2&c=\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nweb-client\n1700000000\nnonce-1"
if got != want {
t.Fatalf("unexpected stringToSign\nwant: %s\ngot: %s", want, got)
}
}
-30
View File
@@ -1,30 +0,0 @@
package abosend
import "testing"
func TestNewClient(t *testing.T) {
t.Skipf("Skip TestNewClient test")
client := createClient()
err := client.SendCode("1", "", "223322")
if err != nil {
t.Errorf("TestNewClient() error = %v", err.Error())
return
}
t.Logf("TestNewClient success")
}
func TestClient_GetSendCodeContent(t *testing.T) {
t.Skipf("Skip TestClient_GetSendCodeContent test")
client := createClient()
content := client.GetSendCodeContent("223322")
t.Logf("TestClient_GetSendCodeContent() = %v", content)
}
func createClient() *Client {
return NewClient(Config{
ApiDomain: "https://smsapi.abosend.com",
Access: "",
Secret: "",
Template: "您的验证码是:{{.code}}。请不要把验证码泄露给其他人。",
})
}
-16
View File
@@ -1,16 +0,0 @@
package smsbao
import "testing"
func TestNewClient(t *testing.T) {
t.Skipf("Skip TestNewClient test")
client := NewClient(Config{
Template: "【XXX】您的验证码是:{{.code}},有效期 {{.expiration}}。请不要把验证码泄露给其他人。",
})
err := client.SendCode("1", "", "223322")
if err != nil {
t.Errorf("TestNewClient() error = %v", err.Error())
return
}
t.Logf("TestNewClient success")
}
-16
View File
@@ -1,16 +0,0 @@
package twilio
import "testing"
func TestClient_SendCode(t *testing.T) {
t.Skipf("Skip TestClient_SendCode test")
client := NewClient(Config{
Access: "", Secret: "", PhoneNumber: "", Template: "",
})
err := client.SendCode("", "", "123456")
if err != nil {
t.Errorf("SendCode() error = %v", err.Error())
return
}
t.Logf("SendCode() success")
}
-59
View File
@@ -1,59 +0,0 @@
package snowflake
import (
"testing"
)
func TestGetLocalIp(t *testing.T) {
tests := []struct {
name string
// want byte
wantErr bool
wantFunc func(byte) (bool, string)
}{
// TODO: Add test cases.
{
name: "",
wantErr: false,
wantFunc: func(got byte) (bool, string) { return got > 0, "got > 0" },
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetLocalIp()
if (err != nil) != tt.wantErr {
t.Errorf("GetLocalIp() error = %v, wantErr %v", err, tt.wantErr)
return
}
if r, s := tt.wantFunc(got); !r {
t.Errorf("GetLocalIp() = %v, want %v", got, s)
}
})
}
}
func TestGetID(t *testing.T) {
tests := []struct {
name string
// want int64
wantErr bool
wantFunc func(int64) (bool, string)
}{
// TODO: Add test cases.
{
name: "",
wantErr: false,
wantFunc: func(got int64) (bool, string) {
return got > 0, "got > 0"
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetID()
if r, s := tt.wantFunc(got); !r {
t.Errorf("GetID() = %v, want %v", got, s)
}
})
}
}
-27
View File
@@ -1,27 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAtomicBool(t *testing.T) {
val := ForAtomicBool(true)
assert.True(t, val.True())
val.Set(false)
assert.False(t, val.True())
val.Set(true)
assert.True(t, val.True())
val.Set(false)
assert.False(t, val.True())
ok := val.CompareAndSwap(false, true)
assert.True(t, ok)
assert.True(t, val.True())
ok = val.CompareAndSwap(true, false)
assert.True(t, ok)
assert.False(t, val.True())
ok = val.CompareAndSwap(true, false)
assert.False(t, ok)
assert.False(t, val.True())
}
-19
View File
@@ -1,19 +0,0 @@
package syncx
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestAtomicDuration(t *testing.T) {
d := ForAtomicDuration(time.Duration(100))
assert.Equal(t, time.Duration(100), d.Load())
d.Set(time.Duration(200))
assert.Equal(t, time.Duration(200), d.Load())
assert.True(t, d.CompareAndSwap(time.Duration(200), time.Duration(300)))
assert.Equal(t, time.Duration(300), d.Load())
assert.False(t, d.CompareAndSwap(time.Duration(200), time.Duration(400)))
assert.Equal(t, time.Duration(300), d.Load())
}
-24
View File
@@ -1,24 +0,0 @@
package syncx
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAtomicFloat64(t *testing.T) {
f := ForAtomicFloat64(100)
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
for i := 0; i < 100; i++ {
f.Add(1)
}
wg.Done()
}()
}
wg.Wait()
assert.Equal(t, float64(600), f.Load())
}
-56
View File
@@ -1,56 +0,0 @@
package syncx
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBarrier_Guard(t *testing.T) {
const total = 10000
var barrier Barrier
var count int
var wg sync.WaitGroup
wg.Add(total)
for i := 0; i < total; i++ {
go barrier.Guard(func() {
count++
wg.Done()
})
}
wg.Wait()
assert.Equal(t, total, count)
}
func TestBarrierPtr_Guard(t *testing.T) {
const total = 10000
barrier := new(Barrier)
var count int
wg := new(sync.WaitGroup)
wg.Add(total)
for i := 0; i < total; i++ {
go barrier.Guard(func() {
count++
wg.Done()
})
}
wg.Wait()
assert.Equal(t, total, count)
}
func TestGuard(t *testing.T) {
const total = 10000
var count int
var lock sync.Mutex
wg := new(sync.WaitGroup)
wg.Add(total)
for i := 0; i < total; i++ {
go Guard(&lock, func() {
count++
wg.Done()
})
}
wg.Wait()
assert.Equal(t, total, count)
}
-69
View File
@@ -1,69 +0,0 @@
package syncx
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTimeoutCondWait(t *testing.T) {
var wait sync.WaitGroup
cond := NewCond()
wait.Add(2)
go func() {
cond.Wait()
wait.Done()
}()
time.Sleep(time.Duration(50) * time.Millisecond)
go func() {
cond.Signal()
wait.Done()
}()
wait.Wait()
}
func TestTimeoutCondWaitTimeout(t *testing.T) {
var wait sync.WaitGroup
cond := NewCond()
wait.Add(1)
go func() {
cond.WaitWithTimeout(time.Duration(500) * time.Millisecond)
wait.Done()
}()
wait.Wait()
}
func TestTimeoutCondWaitTimeoutRemain(t *testing.T) {
var wait sync.WaitGroup
cond := NewCond()
wait.Add(2)
ch := make(chan time.Duration, 1)
defer close(ch)
timeout := time.Duration(2000) * time.Millisecond
go func() {
remainTimeout, _ := cond.WaitWithTimeout(timeout)
ch <- remainTimeout
wait.Done()
}()
sleep(200)
go func() {
cond.Signal()
wait.Done()
}()
wait.Wait()
remainTimeout := <-ch
assert.True(t, remainTimeout < timeout, "expect remainTimeout %v < %v", remainTimeout, timeout)
assert.True(t, remainTimeout >= time.Duration(200)*time.Millisecond,
"expect remainTimeout %v >= 200 millisecond", remainTimeout)
}
func TestSignalNoWait(t *testing.T) {
cond := NewCond()
cond.Signal()
}
func sleep(millisecond int) {
time.Sleep(time.Duration(millisecond) * time.Millisecond)
}
-31
View File
@@ -1,31 +0,0 @@
package syncx
import (
"sync"
"testing"
)
func TestDoneChanClose(t *testing.T) {
doneChan := NewDoneChan()
for i := 0; i < 5; i++ {
doneChan.Close()
}
}
func TestDoneChanDone(t *testing.T) {
var waitGroup sync.WaitGroup
doneChan := NewDoneChan()
waitGroup.Add(1)
go func() {
<-doneChan.Done()
waitGroup.Done()
}()
for i := 0; i < 5; i++ {
doneChan.Close()
}
waitGroup.Wait()
}
-78
View File
@@ -1,78 +0,0 @@
package syncx
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestImmutableResource(t *testing.T) {
var count int
r := NewImmutableResource(func() (any, error) {
count++
return "hello", nil
})
res, err := r.Get()
assert.Equal(t, "hello", res)
assert.Equal(t, 1, count)
assert.Nil(t, err)
// again
res, err = r.Get()
assert.Equal(t, "hello", res)
assert.Equal(t, 1, count)
assert.Nil(t, err)
}
func TestImmutableResourceError(t *testing.T) {
var count int
r := NewImmutableResource(func() (any, error) {
count++
return nil, errors.New("any")
})
res, err := r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 1, count)
// again
res, err = r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 1, count)
r.refreshInterval = 0
time.Sleep(time.Millisecond)
res, err = r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 2, count)
}
func TestImmutableResourceErrorRefreshAlways(t *testing.T) {
var count int
r := NewImmutableResource(func() (any, error) {
count++
return nil, errors.New("any")
}, WithRefreshIntervalOnFailure(0))
res, err := r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 1, count)
// again
res, err = r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 2, count)
}
-17
View File
@@ -1,17 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLimit(t *testing.T) {
limit := NewLimit(2)
limit.Borrow()
assert.True(t, limit.TryBorrow())
assert.False(t, limit.TryBorrow())
assert.Nil(t, limit.Return())
assert.Nil(t, limit.Return())
assert.Equal(t, ErrLimitReturn, limit.Return())
}
-82
View File
@@ -1,82 +0,0 @@
package syncx
import (
"errors"
"fmt"
"sync"
"testing"
"time"
)
func TestLockedCallDo(t *testing.T) {
g := NewLockedCalls()
v, err := g.Do("key", func() (any, error) {
return "bar", nil
})
if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
t.Errorf("Do = %v; want %v", got, want)
}
if err != nil {
t.Errorf("Do error = %v", err)
}
}
func TestLockedCallDoErr(t *testing.T) {
g := NewLockedCalls()
someErr := errors.New("some error")
v, err := g.Do("key", func() (any, error) {
return nil, someErr
})
if !errors.Is(err, someErr) {
t.Errorf("Do error = %v; want someErr", err)
}
if v != nil {
t.Errorf("unexpected non-nil value %#v", v)
}
}
func TestLockedCallDoDupSuppress(t *testing.T) {
g := NewLockedCalls()
c := make(chan string)
var calls int
fn := func() (any, error) {
calls++
ret := calls
<-c
calls--
return ret, nil
}
const n = 10
var results []int
var lock sync.Mutex
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
v, err := g.Do("key", fn)
if err != nil {
t.Errorf("Do error: %v", err)
}
lock.Lock()
results = append(results, v.(int))
lock.Unlock()
wg.Done()
}()
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
for i := 0; i < n; i++ {
c <- "bar"
}
wg.Wait()
lock.Lock()
defer lock.Unlock()
for _, item := range results {
if item != 1 {
t.Errorf("number of calls = %d; want 1", item)
}
}
}
-22
View File
@@ -1,22 +0,0 @@
package syncx
import (
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestManagedResource(t *testing.T) {
var count int32
resource := NewManagedResource(func() any {
return atomic.AddInt32(&count, 1)
}, func(a, b any) bool {
return a == b
})
assert.Equal(t, resource.Take(), resource.Take())
old := resource.Take()
resource.MarkBroken(old)
assert.NotEqual(t, old, resource.Take())
}
-33
View File
@@ -1,33 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOnce(t *testing.T) {
var v int
add := Once(func() {
v++
})
for i := 0; i < 5; i++ {
add()
}
assert.Equal(t, 1, v)
}
func BenchmarkOnce(b *testing.B) {
var v int
add := Once(func() {
v++
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
add()
}
assert.Equal(b, 1, v)
}
-17
View File
@@ -1,17 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOnceGuard(t *testing.T) {
var guard OnceGuard
assert.False(t, guard.Taken())
assert.True(t, guard.Take())
assert.True(t, guard.Taken())
assert.False(t, guard.Take())
assert.True(t, guard.Taken())
}
-115
View File
@@ -1,115 +0,0 @@
package syncx
import (
"sync"
"sync/atomic"
"testing"
"time"
"github.com/perfect-panel/server/pkg/lang"
"github.com/stretchr/testify/assert"
)
const limit = 10
func TestPoolGet(t *testing.T) {
stack := NewPool(limit, create, destroy)
ch := make(chan lang.PlaceholderType)
for i := 0; i < limit; i++ {
var fail AtomicBool
go func() {
v := stack.Get()
if v.(int) != 1 {
fail.Set(true)
}
ch <- lang.Placeholder
}()
select {
case <-ch:
case <-time.After(time.Second):
t.Fail()
}
if fail.True() {
t.Fatal("unmatch value")
}
}
}
func TestPoolPopTooMany(t *testing.T) {
stack := NewPool(limit, create, destroy)
ch := make(chan lang.PlaceholderType, 1)
for i := 0; i < limit; i++ {
var wait sync.WaitGroup
wait.Add(1)
go func() {
stack.Get()
ch <- lang.Placeholder
wait.Done()
}()
wait.Wait()
select {
case <-ch:
default:
t.Fail()
}
}
var waitGroup, pushWait sync.WaitGroup
waitGroup.Add(1)
pushWait.Add(1)
go func() {
pushWait.Done()
stack.Get()
waitGroup.Done()
}()
pushWait.Wait()
stack.Put(1)
waitGroup.Wait()
}
func TestPoolPopFirst(t *testing.T) {
var value int32
stack := NewPool(limit, func() any {
return atomic.AddInt32(&value, 1)
}, destroy)
for i := 0; i < 100; i++ {
v := stack.Get().(int32)
assert.Equal(t, 1, int(v))
stack.Put(v)
}
}
func TestPoolWithMaxAge(t *testing.T) {
var value int32
stack := NewPool(limit, func() any {
return atomic.AddInt32(&value, 1)
}, destroy, WithMaxAge(time.Millisecond))
v1 := stack.Get().(int32)
// put nil should not matter
stack.Put(nil)
stack.Put(v1)
time.Sleep(time.Millisecond * 10)
v2 := stack.Get().(int32)
assert.NotEqual(t, v1, v2)
}
func TestNewPoolPanics(t *testing.T) {
assert.Panics(t, func() {
NewPool(0, create, destroy)
})
}
func create() any {
return 1
}
func destroy(_ any) {
}
-27
View File
@@ -1,27 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRefCleaner(t *testing.T) {
var count int
clean := func() {
count += 1
}
cleaner := NewRefResource(clean)
err := cleaner.Use()
assert.Nil(t, err)
err = cleaner.Use()
assert.Nil(t, err)
cleaner.Clean()
cleaner.Clean()
assert.Equal(t, 1, count)
cleaner.Clean()
cleaner.Clean()
assert.Equal(t, 1, count)
assert.Equal(t, ErrUseOfCleaned, cleaner.Use())
}
-99
View File
@@ -1,99 +0,0 @@
package syncx
import (
"errors"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
type dummyResource struct {
age int
}
func (dr *dummyResource) Close() error {
return errors.New("close")
}
func TestResourceManager_GetResource(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
var age int
for i := 0; i < 10; i++ {
val, err := manager.GetResource("key", func() (io.Closer, error) {
age++
return &dummyResource{
age: age,
}, nil
})
assert.Nil(t, err)
assert.Equal(t, 1, val.(*dummyResource).age)
}
}
func TestResourceManager_GetResourceError(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
for i := 0; i < 10; i++ {
_, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
}
}
func TestResourceManager_Close(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
for i := 0; i < 10; i++ {
_, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
}
if assert.NoError(t, manager.Close()) {
assert.Equal(t, 0, len(manager.resources))
}
}
func TestResourceManager_UseAfterClose(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
_, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
if assert.NoError(t, manager.Close()) {
_, err = manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
assert.Panics(t, func() {
_, err = manager.GetResource("key", func() (io.Closer, error) {
return &dummyResource{age: 123}, nil
})
})
}
}
func TestResourceManager_Inject(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
manager.Inject("key", &dummyResource{
age: 10,
})
val, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, nil
})
assert.Nil(t, err)
assert.Equal(t, 10, val.(*dummyResource).age)
}
-141
View File
@@ -1,141 +0,0 @@
package syncx
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestExclusiveCallDo(t *testing.T) {
g := NewSingleFlight()
v, err := g.Do("key", func() (any, error) {
return "bar", nil
})
if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
t.Errorf("Do = %v; want %v", got, want)
}
if err != nil {
t.Errorf("Do error = %v", err)
}
}
func TestExclusiveCallDoErr(t *testing.T) {
g := NewSingleFlight()
someErr := errors.New("some error")
v, err := g.Do("key", func() (any, error) {
return nil, someErr
})
if !errors.Is(err, someErr) {
t.Errorf("Do error = %v; want someErr", err)
}
if v != nil {
t.Errorf("unexpected non-nil value %#v", v)
}
}
func TestExclusiveCallDoDupSuppress(t *testing.T) {
g := NewSingleFlight()
c := make(chan string)
var calls int32
fn := func() (any, error) {
atomic.AddInt32(&calls, 1)
return <-c, nil
}
const n = 10
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
v, err := g.Do("key", fn)
if err != nil {
t.Errorf("Do error: %v", err)
}
if v.(string) != "bar" {
t.Errorf("got %q; want %q", v, "bar")
}
wg.Done()
}()
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
c <- "bar"
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 1 {
t.Errorf("number of calls = %d; want 1", got)
}
}
func TestExclusiveCallDoDiffDupSuppress(t *testing.T) {
g := NewSingleFlight()
broadcast := make(chan struct{})
var calls int32
tests := []string{"e", "a", "e", "a", "b", "c", "b", "a", "c", "d", "b", "c", "d"}
var wg sync.WaitGroup
for _, key := range tests {
wg.Add(1)
go func(k string) {
<-broadcast // get all goroutines ready
_, err := g.Do(k, func() (any, error) {
atomic.AddInt32(&calls, 1)
time.Sleep(10 * time.Millisecond)
return nil, nil
})
if err != nil {
t.Errorf("Do error: %v", err)
}
wg.Done()
}(key)
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
close(broadcast)
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 5 {
// five letters
t.Errorf("number of calls = %d; want 5", got)
}
}
func TestExclusiveCallDoExDupSuppress(t *testing.T) {
g := NewSingleFlight()
c := make(chan string)
var calls int32
fn := func() (any, error) {
atomic.AddInt32(&calls, 1)
return <-c, nil
}
const n = 10
var wg sync.WaitGroup
var freshes int32
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
v, fresh, err := g.DoEx("key", fn)
if err != nil {
t.Errorf("Do error: %v", err)
}
if fresh {
atomic.AddInt32(&freshes, 1)
}
if v.(string) != "bar" {
t.Errorf("got %q; want %q", v, "bar")
}
wg.Done()
}()
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
c <- "bar"
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 1 {
t.Errorf("number of calls = %d; want 1", got)
}
if got := atomic.LoadInt32(&freshes); got != 1 {
t.Errorf("freshes = %d; want 1", got)
}
}
-70
View File
@@ -1,70 +0,0 @@
package syncx
import (
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/perfect-panel/server/pkg/lang"
"github.com/stretchr/testify/assert"
)
func TestTryLock(t *testing.T) {
var lock SpinLock
assert.True(t, lock.TryLock())
assert.False(t, lock.TryLock())
lock.Unlock()
assert.True(t, lock.TryLock())
}
func TestSpinLock(t *testing.T) {
var lock SpinLock
lock.Lock()
assert.False(t, lock.TryLock())
lock.Unlock()
assert.True(t, lock.TryLock())
}
func TestSpinLockRace(t *testing.T) {
var lock SpinLock
lock.Lock()
var wait sync.WaitGroup
wait.Add(1)
go func() {
wait.Done()
}()
time.Sleep(time.Millisecond * 100)
lock.Unlock()
wait.Wait()
assert.True(t, lock.TryLock())
}
func TestSpinLock_TryLock(t *testing.T) {
var lock SpinLock
var count int32
var wait sync.WaitGroup
wait.Add(2)
sig := make(chan lang.PlaceholderType)
go func() {
lock.TryLock()
sig <- lang.Placeholder
atomic.AddInt32(&count, 1)
runtime.Gosched()
lock.Unlock()
wait.Done()
}()
go func() {
<-sig
lock.Lock()
atomic.AddInt32(&count, 1)
lock.Unlock()
wait.Done()
}()
wait.Wait()
assert.Equal(t, int32(2), atomic.LoadInt32(&count))
}
-52
View File
@@ -1,52 +0,0 @@
package syncx
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTimeoutLimit(t *testing.T) {
tests := []struct {
name string
interval time.Duration
}{
{
name: "no wait",
},
{
name: "wait",
interval: time.Millisecond * 100,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
limit := NewTimeoutLimit(2)
assert.Nil(t, limit.Borrow(time.Millisecond*200))
assert.Nil(t, limit.Borrow(time.Millisecond*200))
var wait1, wait2, wait3 sync.WaitGroup
wait1.Add(1)
wait2.Add(1)
wait3.Add(1)
go func() {
wait1.Wait()
wait2.Done()
time.Sleep(test.interval)
assert.Nil(t, limit.Return())
wait3.Done()
}()
wait1.Done()
wait2.Wait()
assert.Nil(t, limit.Borrow(time.Second))
wait3.Wait()
assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100))
assert.Nil(t, limit.Return())
assert.Nil(t, limit.Return())
assert.Equal(t, ErrLimitReturn, limit.Return())
})
}
}
-17
View File
@@ -1,17 +0,0 @@
package templatex
import "testing"
func TestRenderToString(t *testing.T) {
tmpl := "hello {{.Name}}"
data := map[string]interface{}{
"Name": "world",
}
got, err := RenderToString(tmpl, data)
if err != nil {
t.Fatalf("RenderToString() error = %v", err)
return
}
want := "hello world"
t.Logf("got: %v, want: %v", got, want)
}
-45
View File
@@ -1,45 +0,0 @@
package threading
import (
"io"
"log"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRoutineGroupRun(t *testing.T) {
var count int32
group := NewRoutineGroup()
for i := 0; i < 3; i++ {
group.Run(func() {
atomic.AddInt32(&count, 1)
})
}
group.Wait()
assert.Equal(t, int32(3), count)
}
func TestRoutingGroupRunSafe(t *testing.T) {
log.SetOutput(io.Discard)
var count int32
group := NewRoutineGroup()
var once sync.Once
for i := 0; i < 3; i++ {
group.RunSafe(func() {
once.Do(func() {
panic("")
})
atomic.AddInt32(&count, 1)
})
}
group.Wait()
assert.Equal(t, int32(2), count)
}
-11
View File
@@ -1,11 +0,0 @@
package threading
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRoutineId(t *testing.T) {
assert.True(t, RoutineId() > 0)
}
-32
View File
@@ -1,32 +0,0 @@
package timex
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestRelativeTime(t *testing.T) {
time.Sleep(time.Millisecond)
now := Now()
assert.True(t, now > 0)
time.Sleep(time.Millisecond)
assert.True(t, Since(now) > 0)
}
func BenchmarkTimeSince(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = time.Since(time.Now())
}
}
func BenchmarkTimexSince(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = Since(Now())
}
}
-14
View File
@@ -1,14 +0,0 @@
package timex
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestReprOfDuration(t *testing.T) {
assert.Equal(t, "1000.0ms", ReprOfDuration(time.Second))
assert.Equal(t, "1111.6ms", ReprOfDuration(
time.Second+time.Millisecond*111+time.Microsecond*555))
}
-50
View File
@@ -1,50 +0,0 @@
package timex
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestRealTickerDoTick(t *testing.T) {
ticker := NewTicker(time.Millisecond * 10)
defer ticker.Stop()
var count int
for range ticker.Chan() {
count++
if count > 5 {
break
}
}
}
func TestFakeTicker(t *testing.T) {
const total = 5
ticker := NewFakeTicker()
defer ticker.Stop()
var count int32
go func() {
for range ticker.Chan() {
if atomic.AddInt32(&count, 1) == total {
ticker.Done()
}
}
}()
for i := 0; i < 5; i++ {
ticker.Tick()
}
assert.Nil(t, ticker.Wait(time.Second))
assert.Equal(t, int32(total), atomic.LoadInt32(&count))
}
func TestFakeTickerTimeout(t *testing.T) {
ticker := NewFakeTicker()
defer ticker.Stop()
assert.NotNil(t, ticker.Wait(time.Millisecond))
}
-14
View File
@@ -1,14 +0,0 @@
package tool
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsValidImageSize(t *testing.T) {
testBase64 := "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
maxSize := int64(10)
result := IsValidImageSize(testBase64, maxSize)
assert.Equal(t, result, true)
}
-11
View File
@@ -1,11 +0,0 @@
package tool
import (
"testing"
)
func TestGenerateCipher(t *testing.T) {
pwd := GenerateCipher("", 16)
t.Logf("pwd: %s", pwd)
t.Logf("pwd length: %d", len(pwd))
}
-15
View File
@@ -1,15 +0,0 @@
package tool
import (
"testing"
)
func TestEncodePassWord(t *testing.T) {
t.Logf("EncodePassWord: %v", EncodePassWord("password"))
}
func TestMultiPasswordVerify(t *testing.T) {
pwd := "$2y$10$WFO17pdtohfeBILjEChoGeVxpDG.u9kVCKhjDAeEeNmCjIlj3tDRy"
status := MultiPasswordVerify("bcrypt", "", "admin1", pwd)
t.Logf("MultiPasswordVerify: %v", status)
}
-10
View File
@@ -1,10 +0,0 @@
package tool
import "testing"
func TestFormatStringToFloat(t *testing.T) {
var value = 1.23
if FormatStringToFloat("1.23") != value {
t.Errorf("Expected %f, but got %f", value, FormatStringToFloat("1.23"))
}
}
-24
View File
@@ -1,24 +0,0 @@
package tool
import "testing"
func TestParseRedisURI(t *testing.T) {
uri := "redis://localhost:6379"
addr, password, database, err := ParseRedisURI(uri)
if err != nil {
t.Fatal(err)
}
t.Log(addr, password, database)
}
func TestRedisPing(t *testing.T) {
uri := "redis://localhost:6379"
addr, password, database, err := ParseRedisURI(uri)
if err != nil {
t.Fatal(err)
}
err = RedisPing(addr, password, database)
if err != nil {
t.Fatal(err)
}
}
-27
View File
@@ -1,27 +0,0 @@
package tool
import (
"testing"
)
func TestFixedUniqueString(t *testing.T) {
a := "example"
b := "example1"
c := "example"
strA1, err := FixedUniqueString(a, 8, "")
strB1, err := FixedUniqueString(b, 8, "")
strC1, err := FixedUniqueString(c, 8, "")
if err != nil {
t.Logf("Error: %v", err.Error())
return
}
if strA1 != strC1 {
t.Errorf("Expected strA1 and strC1 to be equal, got %s and %s", strA1, strC1)
}
if strA1 == strB1 {
t.Errorf("Expected strA1 and strB1 to be different, got %s and %s", strA1, strB1)
}
t.Logf("strA1 and strB1 are not equal, strA1: %s, strB1: %s", strA1, strB1)
t.Logf("strA1 and strC1 are equal,strA1: %s, strC1: %s", strA1, strC1)
}
-19
View File
@@ -1,19 +0,0 @@
package tool
import (
"testing"
"time"
)
func TestAddTime(t *testing.T) {
basic := time.Now()
expAt := AddTime("Month", 1, basic)
t.Logf("AddTime() success, expected year %d, got year %d, full: %v", basic.Year()+1, expAt.Year(), expAt.Format("2006-01-02 15:04:05"))
}
func TestGetYearDays(t *testing.T) {
days := GetYearDays(time.Now(), 2, 1)
t.Logf("GetYearDays() success, expected 365, got %d", days)
}
-12
View File
@@ -1,12 +0,0 @@
package tool
import (
"testing"
"github.com/perfect-panel/server/pkg/constant"
)
func TestExtractVersionNumber(t *testing.T) {
versionNumber := ExtractVersionNumber(constant.Version)
t.Log(versionNumber)
}
-111
View File
@@ -1,111 +0,0 @@
package trace
import (
"testing"
"github.com/perfect-panel/server/pkg/logger"
"github.com/stretchr/testify/assert"
)
func TestStartAgent(t *testing.T) {
logger.Disable()
const (
endpoint1 = "localhost:1234"
endpoint2 = "remotehost:1234"
endpoint3 = "localhost:1235"
endpoint4 = "localhost:1236"
endpoint5 = "udp://localhost:6831"
endpoint6 = "localhost:1237"
endpoint71 = "/tmp/trace.log"
endpoint72 = "/not-exist-fs/trace.log"
)
c1 := Config{
Name: "foo",
}
c2 := Config{
Name: "bar",
Endpoint: endpoint1,
Batcher: kindJaeger,
}
c3 := Config{
Name: "any",
Endpoint: endpoint2,
Batcher: kindZipkin,
}
c4 := Config{
Name: "bla",
Endpoint: endpoint3,
Batcher: "otlp",
}
c5 := Config{
Name: "otlpgrpc",
Endpoint: endpoint3,
Batcher: kindOtlpGrpc,
OtlpHeaders: map[string]string{
"uptrace-dsn": "http://project2_secret_token@localhost:14317/2",
},
}
c6 := Config{
Name: "otlphttp",
Endpoint: endpoint4,
Batcher: kindOtlpHttp,
OtlpHeaders: map[string]string{
"uptrace-dsn": "http://project2_secret_token@localhost:14318/2",
},
OtlpHttpPath: "/v1/traces",
}
c7 := Config{
Name: "UDP",
Endpoint: endpoint5,
Batcher: kindJaeger,
}
c8 := Config{
Disabled: true,
Endpoint: endpoint6,
Batcher: kindJaeger,
}
c9 := Config{
Name: "file",
Endpoint: endpoint71,
Batcher: kindFile,
}
c10 := Config{
Name: "file",
Endpoint: endpoint72,
Batcher: kindFile,
}
StartAgent(c1)
StartAgent(c1)
StartAgent(c2)
StartAgent(c3)
StartAgent(c4)
StartAgent(c5)
StartAgent(c6)
StartAgent(c7)
StartAgent(c8)
StartAgent(c9)
StartAgent(c10)
defer StopAgent()
lock.Lock()
defer lock.Unlock()
// because remotehost cannot be resolved
assert.Equal(t, 6, len(agents))
_, ok := agents[""]
assert.True(t, ok)
_, ok = agents[endpoint1]
assert.True(t, ok)
_, ok = agents[endpoint2]
assert.False(t, ok)
_, ok = agents[endpoint5]
assert.True(t, ok)
_, ok = agents[endpoint6]
assert.False(t, ok)
_, ok = agents[endpoint71]
assert.True(t, ok)
_, ok = agents[endpoint72]
assert.False(t, ok)
}

Some files were not shown because too many files have changed in this diff Show More