- 日志落盘:新增 internal/gui/logfile.go,写到 <用户配置目录>/huya-go/logs/huya-go.log,单文件 5MB 后滚动保留 5 个备份;「日志」页签顶部显示路径并支持打开目录/复制路径;写盘失败自动降级为仅界面输出 - 修复重复兑换:界面选品会同时提交 pid、名称与 targetText 里的同一 pid,normalizeTargets 现按 pid/名称去重;runState 再按「账号+奖品」记账,多个 target 命中同一奖品时只兑换一次(新增 skip_duplicate 结果) - 修复 use of closed network connection:成功后的异步刷新改为 runState.refresh 等待,RunTargets 返回前不再提前关闭连接 - queryHttpDns 增加 single-flight 与失败冷却,多账号并发建连只解析一次 - 新增 internal/gui/logfile_test.go 与 sniper/huya 回归测试;README 补日志说明
139 lines
3.2 KiB
Go
139 lines
3.2 KiB
Go
package gui
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLogSinkWritesFormattedLines(t *testing.T) {
|
|
sink, err := newLogSinkAt(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sink.Close()
|
|
|
|
at := time.Date(2026, 10, 13, 20, 21, 57, 822000000, time.Local)
|
|
if err := sink.Write(LogEntry{
|
|
Time: "20:21:57.822",
|
|
at: at,
|
|
Level: "debug",
|
|
Message: "[WSS] 请求 launch.pingSignal requestID=2",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, err := os.ReadFile(sink.Path())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "2026-10-13 20:21:57.822 [debug] [WSS] 请求 launch.pingSignal requestID=2\n"
|
|
if string(data) != want {
|
|
t.Fatalf("日志内容=%q, want %q", data, want)
|
|
}
|
|
if sink.Dir() != filepath.Dir(sink.Path()) {
|
|
t.Fatalf("Dir=%q Path=%q", sink.Dir(), sink.Path())
|
|
}
|
|
}
|
|
|
|
func TestLogSinkRotatesAndDropsOldest(t *testing.T) {
|
|
dir := t.TempDir()
|
|
sink, err := newLogSinkAt(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sink.Close()
|
|
sink.maxBytes = 200
|
|
sink.backups = 2
|
|
|
|
for i := 0; i < 60; i++ {
|
|
if err := sink.Write(LogEntry{Level: "info", Message: fmt.Sprintf("第 %d 条日志,用于触发滚动", i)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if _, err := os.Stat(sink.Path() + ".1"); err != nil {
|
|
t.Fatalf("期望存在滚动文件 .1: %v", err)
|
|
}
|
|
if _, err := os.Stat(sink.Path() + ".2"); err != nil {
|
|
t.Fatalf("期望存在滚动文件 .2: %v", err)
|
|
}
|
|
if _, err := os.Stat(sink.Path() + ".3"); err == nil {
|
|
t.Fatal("backups=2 时不应出现 .3")
|
|
}
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, entry := range entries {
|
|
info, err := entry.Info()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if info.Size() > int64(sink.maxBytes)+200 {
|
|
t.Fatalf("%s 体积异常: %d", entry.Name(), info.Size())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLogSinkAppendsAcrossReopen(t *testing.T) {
|
|
dir := t.TempDir()
|
|
sink, err := newLogSinkAt(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := sink.Write(LogEntry{Level: "info", Message: "第一条"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := sink.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reopened, err := newLogSinkAt(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer reopened.Close()
|
|
if err := reopened.Write(LogEntry{Level: "info", Message: "第二条"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, err := os.ReadFile(reopened.Path())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
text := string(data)
|
|
if !strings.Contains(text, "第一条") || !strings.Contains(text, "第二条") {
|
|
t.Fatalf("追加写入失败: %q", text)
|
|
}
|
|
if strings.Index(text, "第一条") > strings.Index(text, "第二条") {
|
|
t.Fatalf("日志顺序异常: %q", text)
|
|
}
|
|
}
|
|
|
|
func TestAppWritesLogsToFile(t *testing.T) {
|
|
sink, err := newLogSinkAt(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sink.Close()
|
|
|
|
app := &App{logSink: sink}
|
|
app.addLog("info", "日志文件: %s", sink.Path())
|
|
app.addLog("debug", "[WSS] pingSignal 往返=28ms 服务端=17ms")
|
|
|
|
data, err := os.ReadFile(sink.Path())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
text := string(data)
|
|
if !strings.Contains(text, "日志文件: ") || !strings.Contains(text, "pingSignal") {
|
|
t.Fatalf("日志未落盘: %q", text)
|
|
}
|
|
if !strings.Contains(text, "[info]") || !strings.Contains(text, "[debug]") {
|
|
t.Fatalf("日志级别未写入: %q", text)
|
|
}
|
|
}
|