package transcode import ( "errors" "strings" "testing" ) // TestSentinelInventoryMatchesDesign 把 design/25 §2.4 的**八个**哨兵钉在测试里: // 少一个、多一个、或者文案离开 `transcode: ` 前缀都会红。它是「清单型断言」—— // 与只测边界逻辑互补(design/25 §6.3 的同一手法)。 func TestSentinelInventoryMatchesDesign(t *testing.T) { designSentinels := []struct { name string err error }{ {"ErrUnsupportedFormat", ErrUnsupportedFormat}, {"ErrInputTooLarge", ErrInputTooLarge}, {"ErrOutputTooLarge", ErrOutputTooLarge}, {"ErrDecodeFailed", ErrDecodeFailed}, {"ErrEncodeFailed", ErrEncodeFailed}, {"ErrTimeout", ErrTimeout}, {"ErrBusy", ErrBusy}, {"ErrNoBackend", ErrNoBackend}, } if len(designSentinels) != 8 { t.Fatalf("design/25 §2.4 是八个哨兵,这里列了 %d 个", len(designSentinels)) } const prefix = "transcode: " for _, s := range designSentinels { if s.err == nil { t.Fatalf("%s is nil", s.name) } if !strings.HasPrefix(s.err.Error(), prefix) { t.Errorf("%s = %q, want the %q prefix", s.name, s.err.Error(), prefix) } if !isSentinelError(s.err) { t.Errorf("%s is missing from knownErrors", s.name) } } // 两两不同(防止复制粘贴出的同一个值被当成两个哨兵)。 for i := range designSentinels { for j := i + 1; j < len(designSentinels); j++ { if errors.Is(designSentinels[i].err, designSentinels[j].err) { t.Errorf("%s and %s are the same error", designSentinels[i].name, designSentinels[j].name) } } } // 包内自有的构造期/入参错误也必须可见(它们不是 §2.4 的八个)。 for _, s := range []error{ErrDuplicate, ErrInvalidCodec, ErrBadArgs} { if !isSentinelError(s) { t.Errorf("%q is missing from knownErrors", s) } } } // TestTimeoutErrorKeepsBothCauses 断言超时错误同时能被判为 ErrTimeout、也保留 // 原始 ctx 原因(%w 链):调用方既能走哨兵分支,也能看到「为什么」。 func TestTimeoutErrorKeepsBothCauses(t *testing.T) { err := timeoutError(errTimeoutCause) if !errors.Is(err, ErrTimeout) || !errors.Is(err, errTimeoutCause) { t.Fatalf("err = %v, want both ErrTimeout and the cause", err) } } // errTimeoutCause 是超时链里的原始原因桩。 var errTimeoutCause = errors.New("stub deadline")