项目文件夹

文件
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

555 行
17 KiB
Go

package indexer
import (
"errors"
"os"
"runtime"
"runtime/debug"
"testing"
)
func TestIndexMemoryBudget(t *testing.T) {
const (
gib = uint64(1) << 30
mib = uint64(1) << 20
)
tests := []struct {
name string
hostRAM uint64
cgroupLimit uint64
want int64
}{
{
name: "host ram only, no cgroup",
hostRAM: 32 * gib,
want: int64(16 * gib),
},
{
name: "cgroup below host wins",
hostRAM: 32 * gib,
cgroupLimit: 4 * gib,
want: int64(2 * gib),
},
{
name: "cgroup above host falls back to host",
hostRAM: 8 * gib,
cgroupLimit: 64 * gib,
want: int64(4 * gib),
},
{
name: "cgroup present, host unknown",
hostRAM: 0,
cgroupLimit: 6 * gib,
want: int64(3 * gib),
},
{
name: "host unknown and no cgroup yields no budget",
hostRAM: 0,
want: 0,
},
{
name: "zero everything yields no budget",
hostRAM: 0,
want: 0,
},
{
name: "absurd cgroup sentinel ignored, host used",
hostRAM: 16 * gib,
cgroupLimit: 1<<63 - 1, // cgroup v1 uncapped sentinel-class value
want: int64(8 * gib),
},
{
name: "absurd cgroup and unknown host yields no budget",
hostRAM: 0,
cgroupLimit: 1<<63 - 1,
want: 0,
},
{
name: "tiny host below floor yields no budget",
hostRAM: 512 * mib, // half == 256 MiB, below the 512 MiB floor
want: 0,
},
{
name: "tiny cgroup below floor yields no budget",
hostRAM: 32 * gib,
cgroupLimit: 256 * mib,
want: 0,
},
{
name: "exactly at floor is kept",
hostRAM: 1 * gib, // half == 512 MiB == floor
want: int64(512 * mib),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := indexMemoryBudget(tt.hostRAM, tt.cgroupLimit)
if got != tt.want {
t.Fatalf("indexMemoryBudget(%d, %d) = %d, want %d",
tt.hostRAM, tt.cgroupLimit, got, tt.want)
}
})
}
}
func TestParseCgroupMemoryLimit(t *testing.T) {
const path = "/sys/fs/cgroup/memory.max"
reader := func(body string, err error) func(string) ([]byte, error) {
return func(string) ([]byte, error) {
if err != nil {
return nil, err
}
return []byte(body), nil
}
}
tests := []struct {
name string
read func(string) ([]byte, error)
want uint64
wantOK bool
}{
{name: "valid numeric", read: reader("4294967296\n", nil), want: 4294967296, wantOK: true},
{name: "literal max", read: reader("max\n", nil), wantOK: false},
{name: "empty body", read: reader("", nil), wantOK: false},
{name: "missing file", read: reader("", errors.New("no such file")), wantOK: false},
{name: "zero value", read: reader("0", nil), wantOK: false},
{name: "non-numeric", read: reader("garbage", nil), wantOK: false},
{name: "absurd sentinel", read: reader("9223372036854771712", nil), wantOK: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := parseCgroupMemoryLimit(tt.read, path)
if ok != tt.wantOK || (ok && got != tt.want) {
t.Fatalf("parseCgroupMemoryLimit() = (%d, %v), want (%d, %v)",
got, ok, tt.want, tt.wantOK)
}
})
}
}
func TestCgroupMemoryLimitFrom(t *testing.T) {
// v2 present and capped: used directly, v1 never consulted.
got := cgroupMemoryLimitFrom(func(p string) ([]byte, error) {
if p == "/sys/fs/cgroup/memory.max" {
return []byte("2147483648"), nil
}
t.Fatalf("v1 path should not be read when v2 is capped, got read of %q", p)
return nil, nil
})
if got != 2147483648 {
t.Fatalf("v2 capped: got %d, want 2147483648", got)
}
// v2 reports "max" (uncapped): fall through to v1.
got = cgroupMemoryLimitFrom(func(p string) ([]byte, error) {
switch p {
case "/sys/fs/cgroup/memory.max":
return []byte("max"), nil
case "/sys/fs/cgroup/memory/memory.limit_in_bytes":
return []byte("1073741824"), nil
}
return nil, errors.New("unexpected path")
})
if got != 1073741824 {
t.Fatalf("v2 uncapped -> v1: got %d, want 1073741824", got)
}
// Neither hierarchy present: no limit.
got = cgroupMemoryLimitFrom(func(string) ([]byte, error) {
return nil, errors.New("no such file")
})
if got != 0 {
t.Fatalf("no cgroup: got %d, want 0", got)
}
}
func TestGCTuneEnabled(t *testing.T) {
tests := []struct {
val string
set bool
want bool
}{
{set: false, want: true}, // unset -> default on
{val: "1", set: true, want: true},
{val: "0", set: true, want: false},
{val: "false", set: true, want: false},
{val: "FALSE", set: true, want: false},
{val: "true", set: true, want: true},
{val: "anything", set: true, want: true},
}
for _, tt := range tests {
name := "unset"
if tt.set {
name = tt.val
}
t.Run(name, func(t *testing.T) {
if tt.set {
t.Setenv("GORTEX_INDEX_GC_TUNE", tt.val)
} else {
os.Unsetenv("GORTEX_INDEX_GC_TUNE")
}
if got := gcTuneEnabled(); got != tt.want {
t.Fatalf("gcTuneEnabled() with %q = %v, want %v", tt.val, got, tt.want)
}
})
}
}
func TestIndexGCPercent(t *testing.T) {
tests := []struct {
val string
set bool
want int
}{
{set: false, want: defaultIndexGCPercent},
{val: "150", set: true, want: 150},
{val: "0", set: true, want: defaultIndexGCPercent}, // non-positive rejected
{val: "-1", set: true, want: defaultIndexGCPercent}, // negative rejected
{val: "abc", set: true, want: defaultIndexGCPercent}, // non-numeric rejected
}
for _, tt := range tests {
t.Run(tt.val, func(t *testing.T) {
if tt.set {
t.Setenv("GORTEX_INDEX_GC_PERCENT", tt.val)
} else {
os.Unsetenv("GORTEX_INDEX_GC_PERCENT")
}
if got := indexGCPercent(); got != tt.want {
t.Fatalf("indexGCPercent() with %q = %d, want %d", tt.val, got, tt.want)
}
})
}
}
// TestApplyIndexGCTuningRestores asserts the tuning round-trips: after the
// returned closure runs, the GC percent and memory limit are exactly what they
// were before applyIndexGCTuning was called.
func TestApplyIndexGCTuningRestores(t *testing.T) {
// Pin a known prior GC percent for the duration of the test, then restore
// it at the end regardless of the assertions below.
const priorPct = 137
originalPct := debug.SetGCPercent(priorPct)
defer debug.SetGCPercent(originalPct)
originalLimit := debug.SetMemoryLimit(-1)
defer debug.SetMemoryLimit(originalLimit)
t.Setenv("GORTEX_INDEX_GC_TUNE", "1")
t.Setenv("GORTEX_INDEX_GC_PERCENT", "275")
restore := applyIndexGCTuning(nil)
// While tuned, the GC percent must be the configured value, not the prior.
if cur := debug.SetGCPercent(275); cur != 275 {
// SetGCPercent(275) returns the value that was in effect — should be
// 275 because the tuning installed it. (We immediately put it back.)
debug.SetGCPercent(cur)
t.Fatalf("expected GC percent 275 while tuned, got %d", cur)
}
debug.SetGCPercent(275)
restore()
// After restore, the GC percent is back to the pinned prior.
if cur := debug.SetGCPercent(priorPct); cur != priorPct {
debug.SetGCPercent(cur)
t.Fatalf("expected GC percent restored to %d, got %d", priorPct, cur)
}
debug.SetGCPercent(priorPct)
// And the memory limit is back to its captured prior.
if cur := debug.SetMemoryLimit(-1); cur != originalLimit {
t.Fatalf("expected memory limit restored to %d, got %d", originalLimit, cur)
}
}
// TestApplyIndexGCTuningDisabled asserts that with tuning disabled the runtime
// knobs are left untouched and the returned closure is a harmless no-op.
func TestApplyIndexGCTuningDisabled(t *testing.T) {
const priorPct = 91
originalPct := debug.SetGCPercent(priorPct)
defer debug.SetGCPercent(originalPct)
t.Setenv("GORTEX_INDEX_GC_TUNE", "0")
restore := applyIndexGCTuning(nil)
if cur := debug.SetGCPercent(priorPct); cur != priorPct {
debug.SetGCPercent(cur)
t.Fatalf("tuning disabled should not change GC percent; got %d, want %d", cur, priorPct)
}
restore() // must not panic or alter state
if cur := debug.SetGCPercent(priorPct); cur != priorPct {
debug.SetGCPercent(cur)
t.Fatalf("no-op restore changed GC percent; got %d, want %d", cur, priorPct)
}
}
func TestCPUQuotaCores(t *testing.T) {
tests := []struct {
name string
quota int64
period int64
want int
wantOK bool
}{
{name: "one and a half cores rounds up", quota: 150000, period: 100000, want: 2, wantOK: true},
{name: "exactly two cores", quota: 200000, period: 100000, want: 2, wantOK: true},
{name: "exactly one core", quota: 100000, period: 100000, want: 1, wantOK: true},
{name: "half a core floored to one", quota: 50000, period: 100000, want: 1, wantOK: true},
{name: "tiny fraction floored to one", quota: 1, period: 100000, want: 1, wantOK: true},
{name: "zero quota rejected", quota: 0, period: 100000, wantOK: false},
{name: "negative quota rejected", quota: -1, period: 100000, wantOK: false},
{name: "zero period guarded", quota: 100000, period: 0, wantOK: false},
{name: "negative period guarded", quota: 100000, period: -100, wantOK: false},
{name: "absurd quota rejected", quota: 1 << 40, period: 1, wantOK: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := cpuQuotaCores(tt.quota, tt.period)
if ok != tt.wantOK || (ok && got != tt.want) {
t.Fatalf("cpuQuotaCores(%d, %d) = (%d, %v), want (%d, %v)",
tt.quota, tt.period, got, ok, tt.want, tt.wantOK)
}
})
}
}
func TestParseCgroupCPUMaxV2(t *testing.T) {
const path = "/sys/fs/cgroup/cpu.max"
reader := func(body string, err error) func(string) ([]byte, error) {
return func(string) ([]byte, error) {
if err != nil {
return nil, err
}
return []byte(body), nil
}
}
tests := []struct {
name string
read func(string) ([]byte, error)
want int
wantOK bool
}{
{name: "one and a half cores", read: reader("150000 100000\n", nil), want: 2, wantOK: true},
{name: "two cores", read: reader("200000 100000", nil), want: 2, wantOK: true},
{name: "literal max is unlimited", read: reader("max 100000\n", nil), wantOK: false},
{name: "missing file", read: reader("", errors.New("no such file")), wantOK: false},
{name: "empty body", read: reader("", nil), wantOK: false},
{name: "single field malformed", read: reader("150000", nil), wantOK: false},
{name: "non-numeric quota", read: reader("abc 100000", nil), wantOK: false},
{name: "non-numeric period", read: reader("150000 xyz", nil), wantOK: false},
{name: "zero period guarded", read: reader("150000 0", nil), wantOK: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := parseCgroupCPUMaxV2(tt.read, path)
if ok != tt.wantOK || (ok && got != tt.want) {
t.Fatalf("parseCgroupCPUMaxV2() = (%d, %v), want (%d, %v)",
got, ok, tt.want, tt.wantOK)
}
})
}
}
func TestParseCgroupCPUQuotaV1(t *testing.T) {
const (
quotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
periodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
)
// reader serves quota/period bodies keyed by path; a "" body with a non-nil
// err simulates a missing file.
reader := func(quota, period string, quotaErr, periodErr error) func(string) ([]byte, error) {
return func(p string) ([]byte, error) {
switch p {
case quotaPath:
if quotaErr != nil {
return nil, quotaErr
}
return []byte(quota), nil
case periodPath:
if periodErr != nil {
return nil, periodErr
}
return []byte(period), nil
}
return nil, errors.New("unexpected path")
}
}
tests := []struct {
name string
read func(string) ([]byte, error)
want int
wantOK bool
}{
{name: "one and a half cores", read: reader("150000", "100000", nil, nil), want: 2, wantOK: true},
{name: "two cores", read: reader("200000", "100000", nil, nil), want: 2, wantOK: true},
{name: "unlimited quota -1", read: reader("-1", "100000", nil, nil), wantOK: false},
{name: "zero quota rejected", read: reader("0", "100000", nil, nil), wantOK: false},
{name: "missing quota file", read: reader("", "100000", errors.New("nope"), nil), wantOK: false},
{name: "missing period file", read: reader("150000", "", nil, errors.New("nope")), wantOK: false},
{name: "non-numeric quota", read: reader("abc", "100000", nil, nil), wantOK: false},
{name: "non-numeric period", read: reader("150000", "xyz", nil, nil), wantOK: false},
{name: "zero period guarded", read: reader("150000", "0", nil, nil), wantOK: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := parseCgroupCPUQuotaV1(tt.read, quotaPath, periodPath)
if ok != tt.wantOK || (ok && got != tt.want) {
t.Fatalf("parseCgroupCPUQuotaV1() = (%d, %v), want (%d, %v)",
got, ok, tt.want, tt.wantOK)
}
})
}
}
func TestCgroupCPUQuotaFrom(t *testing.T) {
const (
v2Path = "/sys/fs/cgroup/cpu.max"
v1Quota = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
v1Period = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
)
// v2 present and capped: used directly, v1 never consulted.
got := cgroupCPUQuotaFrom(func(p string) ([]byte, error) {
if p == v2Path {
return []byte("200000 100000"), nil
}
t.Fatalf("v1 paths should not be read when v2 is capped, got read of %q", p)
return nil, nil
})
if got != 2 {
t.Fatalf("v2 capped: got %d cores, want 2", got)
}
// v2 reports "max" (unlimited): fall through to v1.
got = cgroupCPUQuotaFrom(func(p string) ([]byte, error) {
switch p {
case v2Path:
return []byte("max 100000"), nil
case v1Quota:
return []byte("150000"), nil
case v1Period:
return []byte("100000"), nil
}
return nil, errors.New("unexpected path")
})
if got != 2 {
t.Fatalf("v2 unlimited -> v1: got %d cores, want 2", got)
}
// Neither hierarchy present: no quota, no clamp.
got = cgroupCPUQuotaFrom(func(string) ([]byte, error) {
return nil, errors.New("no such file")
})
if got != 0 {
t.Fatalf("no cgroup: got %d, want 0", got)
}
}
func TestClampWorkersToCPUQuota(t *testing.T) {
tests := []struct {
name string
configured int
quotaCores int
want int
}{
{name: "quota below numcpu clamps down", configured: 8, quotaCores: 2, want: 2},
{name: "no quota leaves numcpu untouched", configured: 8, quotaCores: 0, want: 8},
{name: "quota equal to numcpu unchanged", configured: 8, quotaCores: 8, want: 8},
{name: "quota above numcpu never raises", configured: 8, quotaCores: 16, want: 8},
{name: "floor of one when configured is zero", configured: 0, quotaCores: 0, want: 1},
{name: "floor of one when configured is negative", configured: -4, quotaCores: 0, want: 1},
{name: "quota of one clamps to one", configured: 8, quotaCores: 1, want: 1},
{name: "single core host single core quota", configured: 1, quotaCores: 1, want: 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := clampWorkersToCPUQuota(tt.configured, tt.quotaCores); got != tt.want {
t.Fatalf("clampWorkersToCPUQuota(%d, %d) = %d, want %d",
tt.configured, tt.quotaCores, got, tt.want)
}
})
}
}
func TestCPUClampEnabled(t *testing.T) {
tests := []struct {
val string
set bool
want bool
}{
{set: false, want: true}, // unset -> default on
{val: "1", set: true, want: true},
{val: "0", set: true, want: false},
{val: "false", set: true, want: false},
{val: "FALSE", set: true, want: false},
{val: "true", set: true, want: true},
{val: "anything", set: true, want: true},
}
for _, tt := range tests {
name := "unset"
if tt.set {
name = tt.val
}
t.Run(name, func(t *testing.T) {
if tt.set {
t.Setenv("GORTEX_INDEX_CPU_CLAMP", tt.val)
} else {
os.Unsetenv("GORTEX_INDEX_CPU_CLAMP")
}
if got := cpuClampEnabled(); got != tt.want {
t.Fatalf("cpuClampEnabled() with %q = %v, want %v", tt.val, got, tt.want)
}
})
}
}
func TestMemReleaseEnabled(t *testing.T) {
tests := []struct {
val string
want bool
}{
{"", true}, // unset => on by default
{"1", true},
{"true", true},
{"anything", true},
{"0", false}, // kill-switch
{"false", false},
{"FALSE", false},
}
for _, tt := range tests {
t.Run(tt.val, func(t *testing.T) {
t.Setenv("GORTEX_DAEMON_MEMRELEASE", tt.val)
if got := memReleaseEnabled(); got != tt.want {
t.Fatalf("memReleaseEnabled() with %q = %v, want %v", tt.val, got, tt.want)
}
})
}
}
func TestFreeOSMemoryAfterColdIndex(t *testing.T) {
// Kill-switch: a no-op that must not panic (logger is nil-safe).
t.Setenv("GORTEX_DAEMON_MEMRELEASE", "0")
freeOSMemoryAfterColdIndex(nil)
// Enabled: forces a scavenge; HeapReleased must not go backwards.
t.Setenv("GORTEX_DAEMON_MEMRELEASE", "1")
sink := make([]byte, 8<<20)
for i := range sink {
sink[i] = byte(i)
}
// Force the writes to land (so the allocation is not elided), then let the
// reference drop here: sink is dead past this point, so the scavenge below
// has heap to return.
runtime.KeepAlive(sink)
var before, after runtime.MemStats
runtime.ReadMemStats(&before)
freeOSMemoryAfterColdIndex(nil)
runtime.ReadMemStats(&after)
if after.HeapReleased < before.HeapReleased {
t.Fatalf("HeapReleased went backwards: before=%d after=%d",
before.HeapReleased, after.HeapReleased)
}
}