package main import ( "encoding/json" "os" "path/filepath" "testing" ) // 「合并失败」推送是新增的开关,老设置文件里没有这个字段 —— 升级后不能静默关掉, // 也不能把用户显式写的 false 顶回 true。同一个坑「推送渠道」已经踩过一次 func TestLoadConfigPresetsNotifyMergeFail(t *testing.T) { dir := t.TempDir() savedConfig := config savedLocation := configFileLocation oldDir := configDir if configDir == nil { configDir = new(string) } defer func() { config = savedConfig configFileLocation = savedLocation configDir = oldDir }() *configDir = dir configFileLocation = filepath.Join(dir, configFile) // 老文件:连 notifyEnabled 都没有,两个开关都该是默认开 content, _ := json.Marshal(map[string]any{"uids": []any{}, "mergeFps": 60}) if err := os.WriteFile(configFileLocation, content, 0644); err != nil { t.Fatal(err) } config.NotifyMergeFail = false // 先弄脏,确认是 Unmarshal 之前被顶回 true 的 loadConfig() if !config.NotifyMergeFail { t.Error("老设置文件里没有 notifyMergeFail 时应当按开启处理") } if config.MergeFps != 60 { t.Errorf("mergeFps 没读进来:%d", config.MergeFps) } // 文件里显式写了 false,必须尊重 content, _ = json.Marshal(map[string]any{"notifyMergeFail": false, "mergeFps": 0}) if err := os.WriteFile(configFileLocation, content, 0644); err != nil { t.Fatal(err) } config.NotifyMergeFail = true loadConfig() if config.NotifyMergeFail { t.Error("文件里显式写的 notifyMergeFail=false 被顶回了 true") } if config.MergeFps != 0 { t.Errorf("mergeFps 应当是 0,实际 %d", config.MergeFps) } } // 命令行合成模式(-danmuburn)不走 initialize(),exeDir 必须由 loadConfigForDanmuburn 自己定: // 漏掉的话头像缓存就变成「当前工作目录/avatar_cache」—— 换个目录跑要重新下一遍头像, // 更糟的是旧版把头像一个相对路径写进滤镜,而 ffmpeg 的工作目录是弹幕目录, // 于是找不到文件、整块渲染失败(报错只显示「打不开 avatar_cache\xxx.png」) func TestLoadConfigForDanmuburnSetsExeDir(t *testing.T) { savedConfig := config savedExe, savedLocation := exeDir, configFileLocation oldDir, oldRecordDir := configDir, recordDir if configDir == nil { configDir = new(string) } if recordDir == nil { recordDir = new(string) } defer func() { config = savedConfig exeDir, configFileLocation = savedExe, savedLocation configDir, recordDir = oldDir, oldRecordDir }() exeDir = "" *configDir = "" loadConfigForDanmuburn() if exeDir == "" { t.Fatal("loadConfigForDanmuburn 应当把 exeDir 定下来,否则头像缓存会落到当前工作目录") } if !filepath.IsAbs(avatarCachePath()) { t.Fatalf("头像缓存路径应当是绝对路径,实际 %q", avatarCachePath()) } if filepath.Dir(configFileLocation) != exeDir { t.Errorf("没指定 -config 时设置文件应当在程序目录,实际 %q(exeDir=%q)", configFileLocation, exeDir) } } // 改设置会重新读取配置(web UI 保存、或直接改 config.json), // 这一步绝不能删掉正在进行的合并的临时文件 —— 否则合并会当场失败。 func TestReloadConfigKeepsMergeTempFiles(t *testing.T) { dir := t.TempDir() savedConfig := config savedLocation := configFileLocation oldDir := configDir if configDir == nil { configDir = new(string) } defer func() { config = savedConfig configFileLocation = savedLocation configDir = oldDir }() *configDir = dir configFileLocation = filepath.Join(dir, configFile) recDir := filepath.Join(dir, defaultRecordSubdir) if err := os.MkdirAll(recDir, 0755); err != nil { t.Fatal(err) } // 假装一个合并正在跑:录播目录里有分块字幕和先本地合成的成片 inUse := []string{ filepath.Join(recDir, chunkTempPrefix+"single.ass"), filepath.Join(recDir, chunkTempPrefix+"merging_x.mp4"), } for _, p := range inUse { if err := os.WriteFile(p, []byte("x"), 0644); err != nil { t.Fatal(err) } } content, _ := json.Marshal(map[string]string{"directory": filepath.ToSlash(dir)}) if err := os.WriteFile(configFileLocation, content, 0644); err != nil { t.Fatal(err) } // 重新读取配置的真实路径:loadNewConfig() 就是 loadConfig() + checkConfig() loadConfig() checkConfig() for _, p := range inUse { if _, err := os.Stat(p); err != nil { t.Errorf("重新读取配置把正在用的临时文件删了:%s", filepath.Base(p)) } } // 启动时的兜底清理还得照旧能清掉上次残留 cleanLeftoverTempFiles() for _, p := range inUse { if _, err := os.Stat(p); err == nil { t.Errorf("启动时的清理没清掉残留:%s", filepath.Base(p)) } } }