This commit is contained in:
@@ -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")
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
-28
@@ -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)
|
||||
})
|
||||
}
|
||||
Vendored
+7
@@ -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
|
||||
}
|
||||
|
||||
Vendored
+168
-51
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
Server:
|
||||
Port: 9999
|
||||
Host: 0.0.0.0
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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.")
|
||||
}
|
||||
@@ -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.")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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!")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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}}。请不要把验证码泄露给其他人。",
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package threading
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRoutineId(t *testing.T) {
|
||||
assert.True(t, RoutineId() > 0)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user