package main import ( "fmt" "path/filepath" "strings" "testing" ) // 按最坏情况(每条弹幕一个不同用户、每个都带头像)渲染一块,返回这一块的命令行长度。 // 长度从 renderChunk 自己的自检错误里读出来;没触发自检就返回 (0, false)。 // // 线上的那次事故就是这里没守住:单块 200 条时实测 48288 字符,远超 Windows 的 32767, // 而 exec 只回一句 "The filename or extension is too long",看不出跟弹幕数量有关。 func chunkCommandLenProbe(t *testing.T, n int) (length int, over bool) { t.Helper() dir := t.TempDir() avatars := make(map[int64]string, n) records := make([]danmuRecord, 0, n) for i := 0; i < n; i++ { uid := int64(1000000 + i) // 头像路径按真实缓存目录的形态造(uid_12位摘要.png),故意做得和线上一样长 avatars[uid] = filepath.Join(dir, avatarCacheDir, fmt.Sprintf("%d_%012x.png", uid, i*0x9e3779b1)) records = append(records, danmuRecord{ Time: float64(i) * 0.05, UID: uid, Name: "观众甲", Content: "这条弹幕稍微长一点测试宽度", Lane: i % 20, Width: 480, }) } ctx := &burnContext{ ffmpegFile: filepath.Join(dir, "没有这个-ffmpeg.exe"), // 自检在 exec 之前,跑不到它 outFile: filepath.Join(dir, "out.mp4"), meta: danmuMeta{PlayResX: 1920, PlayResY: 1080, FontSize: 42}, avatars: avatars, dir: dir, } err := ctx.renderChunk(danmuChunk{start: 0, end: 60, records: records}, "chunk.ass", ctx.outFile, false, nil) if err == nil { t.Fatal("用不存在的 ffmpeg 路径不该成功") } if !strings.Contains(err.Error(), "命令过长") { return 0, false } if _, scanErr := fmt.Sscanf(err.Error(), "ffmpeg 命令过长(%d 字符", &length); scanErr != nil { t.Fatalf("没能从错误里读出长度:%v / %s", scanErr, err) } return length, true } // 按硬上限切出来的块,在最坏情况下也不能顶破命令行上限 func TestChunkCommandLengthUnderLimit(t *testing.T) { if length, over := chunkCommandLenProbe(t, maxDanmuPerChunkHard); over { t.Fatalf("按硬上限 %d 条渲染,命令长达 %d 字符,已经超过安全值 %d", maxDanmuPerChunkHard, length, maxCommandLineLen) } } // 块大到一定程度必须被自检拦住,并报出「多少条弹幕、多少个头像」这种能定位的信息 func TestRenderChunkReportsTooLongCommand(t *testing.T) { length, over := chunkCommandLenProbe(t, 300) if !over { t.Fatal("300 条的最坏情况应当被自检拦住,否则交给 exec 只会报一句看不出原因的错") } if length <= maxCommandLineLen { t.Fatalf("自检报出的长度 %d 应当大于阈值 %d", length, maxCommandLineLen) } } // 硬上限本身要留出足够的余量:按实测每条约 241 字符,上限条数不能逼近 32767 func TestChunkHardLimitLeavesHeadroom(t *testing.T) { const perRecordWorstCase = 241 // 2026-09-26 真机实测(每条一个不同用户) if est := maxDanmuPerChunkHard*perRecordWorstCase + 200; est > maxCommandLineLen { t.Fatalf("硬上限 %d 条按最坏情况估算要 %d 字符,超过了安全值 %d", maxDanmuPerChunkHard, est, maxCommandLineLen) } }