2bec257289
iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。 - Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件) - 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、 库名 xiujie_admin → iotlcm、哨兵名占位 - 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异, 不破坏「校验 MSISDN 格式」等测试断言) - 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像 digest 记录 - Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23), 移除写死的 git 凭据 - README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 已知:3 个测试包在源仓同一提交上同样失败(client/upstream、 service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
111 lines
5.1 KiB
Go
111 lines
5.1 KiB
Go
package dailyusage
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/queue/zset"
|
|
)
|
|
|
|
func TestLocalSupplyObservationFirstEventsAndStages(t *testing.T) {
|
|
started := time.Now()
|
|
o := &supplyObservation{started: started, channels: map[string]*supplyChannel{"a": {stage: "initializing", changed: started, stageDuration: map[string]time.Duration{}}}}
|
|
o.event("a", "sql_query", 0, false, false, started.Add(10*time.Millisecond))
|
|
o.event("a", "page_ready", 2, false, false, started.Add(20*time.Millisecond))
|
|
o.event("a", "redis_write", 1, true, false, started.Add(30*time.Millisecond))
|
|
o.event("a", "channel_capacity", 1, false, false, started.Add(40*time.Millisecond))
|
|
o.event("a", "sql_query", 0, false, false, started.Add(50*time.Millisecond))
|
|
o.event("a", "redis_write", 0, true, false, started.Add(60*time.Millisecond))
|
|
o.event("a", "upper_bound", 0, false, true, started.Add(70*time.Millisecond))
|
|
o.event("unknown", "ignored", 0, false, false, started)
|
|
s := o.channels["a"]
|
|
if s.firstQuery == nil || *s.firstQuery != 10 || s.firstEnqueue == nil || *s.firstEnqueue != 30 || !s.ended || s.pending != 0 || s.version != 7 || s.stageDuration["channel_capacity"] != 10*time.Millisecond || s.stageDuration["sql_query"] != 20*time.Millisecond {
|
|
t.Fatalf("observation=%+v", s)
|
|
}
|
|
}
|
|
|
|
func TestSupplyObservationAccumulatesSubMillisecondStages(t *testing.T) {
|
|
started := time.Unix(0, 0)
|
|
o := &supplyObservation{started: started, channels: map[string]*supplyChannel{"a": {stage: "redis_read", changed: started, stageDuration: map[string]time.Duration{}}}}
|
|
for i := 1; i <= 100000; i++ {
|
|
stage := "redis_read"
|
|
if i%2 == 1 {
|
|
stage = "redis_write"
|
|
}
|
|
o.event("a", stage, 0, false, i == 100000, started.Add(time.Duration(i)*500*time.Microsecond))
|
|
}
|
|
if got := o.channels["a"].stageMilliseconds(started.Add(time.Hour)); got["redis_read"] != 25000 || got["redis_write"] != 25000 {
|
|
t.Fatalf("stageMs=%v, want 25000ms per stage", got)
|
|
}
|
|
}
|
|
|
|
func TestSupplyObservationFinalFlushPreservesFraction(t *testing.T) {
|
|
started := time.Unix(0, 0)
|
|
o := &supplyObservation{started: started, channels: map[string]*supplyChannel{"a": {stage: "redis_write", changed: started, stageDuration: map[string]time.Duration{}}}}
|
|
o.event("a", "redis_read", 0, false, false, started.Add(600*time.Microsecond))
|
|
o.event("a", "redis_write", 0, false, false, started.Add(1200*time.Microsecond))
|
|
s := o.channels["a"]
|
|
if got := s.stageMilliseconds(started.Add(1800 * time.Microsecond)); got["redis_write"] != 1 || got["redis_read"] != 0 {
|
|
t.Fatalf("final stageMs=%v", got)
|
|
}
|
|
if s.stageDuration["redis_write"] != 1200*time.Microsecond || s.stageDuration["redis_read"] != 600*time.Microsecond {
|
|
t.Fatalf("fractional duration lost: %v", s.stageDuration)
|
|
}
|
|
}
|
|
|
|
func TestSupplySamplesDistinguishReadyLeaseFutureAndClosedRound(t *testing.T) {
|
|
store := testStore(t)
|
|
ctx := contextForTest(t)
|
|
p := testProducer(t, scannerFor(nil, 0), store, Config{})
|
|
if err := store.CreateRun(ctx, zset.RunMeta{RunID: "supply"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
active, _ := store.RoundActiveKey("supply", 1, "a")
|
|
lease, _ := store.RoundLeaseKey("supply", 1, "a")
|
|
_, _ = store.RedisClient().Do(ctx, "ZADD", active, time.Now().Add(-time.Minute).UnixMilli(), "ready", time.Now().Add(time.Hour).UnixMilli(), "future")
|
|
_, _ = store.RedisClient().Do(ctx, "HSET", lease, "leased", "token")
|
|
values, err := p.sampleSupply(ctx, "supply", "a")
|
|
if err != nil || !reflect.DeepEqual(values, []int64{1, 1, 1, 1, 1}) {
|
|
t.Fatalf("sample=%v err=%v", values, err)
|
|
}
|
|
meta, _ := store.RunMetaKey("supply")
|
|
for _, args := range [][]any{{"blocked:a", 1}, {"blocked:a", 0, "producerDone", 1}, {"producerDone", 0, "currentRound", 2}, {"currentRound", 1, "syncType", "replay"}} {
|
|
_, _ = store.RedisClient().Do(ctx, "HSET", append([]any{meta}, args...)...)
|
|
values, err = p.sampleSupply(ctx, "supply", "a")
|
|
if err != nil || values[0] != 1 || values[1] != 0 {
|
|
t.Fatalf("ineligible sample=%v err=%v", values, err)
|
|
}
|
|
}
|
|
values, err = p.sampleSupply(ctx, "missing", "a")
|
|
if err != nil || values[0] != 0 || values[1] != 0 {
|
|
t.Fatalf("missing sample=%v err=%v", values, err)
|
|
}
|
|
}
|
|
|
|
func TestSupplySamplerTracksWaitingChannelsAndStops(t *testing.T) {
|
|
store := testStore(t)
|
|
ctx := contextForTest(t)
|
|
p := testProducer(t, scannerFor(nil, 0), store, Config{})
|
|
if err := store.CreateRun(ctx, zset.RunMeta{RunID: "sampling"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
finish := p.startObservation(ctx, "sampling", []string{"a", "b"}, time.Now())
|
|
defer finish()
|
|
o := p.observation
|
|
p.observe("a", "channel_capacity", 1, false, false)
|
|
p.observe("b", "sql_slot", 0, false, false)
|
|
waitUntil(t, ctx, func() bool {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
return o.channels["a"].validSamples > 0 && o.channels["b"].validSamples > 0
|
|
})
|
|
p.observe("a", "cancelled", 0, false, true)
|
|
p.observe("b", "cancelled", 0, false, true)
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
if o.channels["a"].stageDuration["channel_capacity"] < 900*time.Millisecond || o.channels["b"].stageDuration["sql_slot"] < 900*time.Millisecond {
|
|
t.Fatalf("missing wait attribution: a=%+v b=%+v", o.channels["a"], o.channels["b"])
|
|
}
|
|
}
|