package reflux import ( "fmt" "testing" ) // 标量直读快路径必须与参照实现 refx 逐格等价 —— 包括溢出截断、负数转无符号 panic // 这些边角行为。这里对"字段类型 × 访问器方法"做全矩阵对拍。 type myStr string type myInt int type myBool bool type myFloat float64 type convBox struct { Str string NamedS myStr I int NamedI myInt I8 int8 I16 int16 I32 int32 I64 int64 U uint U8 uint8 U16 uint16 U32 uint32 U64 uint64 F32 float32 F64 float64 B bool NamedB myBool NamedF myFloat Neg int // 负数: 转无符号应该 panic Over int // 300: 读成 Int8 应该截断成 44 FracF float64 // 3.9: 读成整数应该截断成 3 NumStr string // "123": 字符串转数字 BadStr string // "abc": 转数字应该 panic PtrStr *string AnyVal any } func newConvBox() *convBox { s := "viaptr" return &convBox{ Str: "hello", NamedS: "named", I: 42, NamedI: 7, I8: -8, I16: -16, I32: -32, I64: -64, U: 1, U8: 2, U16: 3, U32: 4, U64: 5, F32: 1.5, F64: 2.5, B: true, NamedB: true, NamedF: 3.5, Neg: -1, Over: 300, FracF: 3.9, NumStr: "123", BadStr: "abc", PtrStr: &s, AnyVal: 99, } } // accessors 是所有会做类型转换的访问器方法 var accessors = []struct { name string call func(R) any }{ {"String", func(r R) any { return r.String() }}, {"Bool", func(r R) any { return r.Bool() }}, {"Int", func(r R) any { return r.Int() }}, {"Int8", func(r R) any { return r.Int8() }}, {"Int16", func(r R) any { return r.Int16() }}, {"Int32", func(r R) any { return r.Int32() }}, {"Int64", func(r R) any { return r.Int64() }}, {"Uint", func(r R) any { return r.Uint() }}, {"Uint8", func(r R) any { return r.Uint8() }}, {"Uint16", func(r R) any { return r.Uint16() }}, {"Uint32", func(r R) any { return r.Uint32() }}, {"Uint64", func(r R) any { return r.Uint64() }}, {"Float32", func(r R) any { return r.Float32() }}, {"Float64", func(r R) any { return r.Float64() }}, {"Any", func(r R) any { return r.Any() }}, } var convFields = []string{ "Str", "NamedS", "I", "NamedI", "I8", "I16", "I32", "I64", "U", "U8", "U16", "U32", "U64", "F32", "F64", "B", "NamedB", "NamedF", "Neg", "Over", "FracF", "NumStr", "BadStr", "PtrStr", "AnyVal", } // callSafe 调用访问器, 把 panic 也当成一种"结果"记录下来。 func callSafe(r R, f func(R) any) (result any, panicked bool) { defer func() { if rec := recover(); rec != nil { panicked = true } }() return f(r), false } func TestScalarFastPathMatchesReflux(t *testing.T) { box := newConvBox() rx := newLegacy(box) fx := New(box) for _, field := range convFields { for _, acc := range accessors { t.Run(field+"/"+acc.name, func(t *testing.T) { wantVal, wantPanic := callSafe(rx.Get(field), acc.call) gotVal, gotPanic := callSafe(fx.Get(field), acc.call) if wantPanic != gotPanic { t.Fatalf("panic 行为不一致: reflux panic=%v, fastx panic=%v (值 reflux=%#v fastx=%#v)", wantPanic, gotPanic, wantVal, gotVal) } if wantPanic { return // 两边都 panic, 一致 } if fmt.Sprintf("%v", wantVal) != fmt.Sprintf("%v", gotVal) { t.Fatalf("结果不一致: reflux=%#v, fastx=%#v", wantVal, gotVal) } }) } } } // 单独把几个最容易出错的边角行为钉死 func TestScalarEdgeCases(t *testing.T) { box := newConvBox() fx := New(box) rx := newLegacy(box) // 整数溢出静默截断: 300 -> int8 = 44 if got, want := fx.Get("Over").Int8(), rx.Get("Over").Int8(); got != want || got != 44 { t.Fatalf("溢出截断: fastx=%d reflux=%d, 期望 44", got, want) } // 负数转无符号: 两边都必须 panic if _, p1 := callSafe(fx.Get("Neg"), func(r R) any { return r.Uint8() }); !p1 { t.Fatal("负数转 Uint8 应该 panic") } // 浮点截断: 3.9 -> int = 3 if got, want := fx.Get("FracF").Int(), rx.Get("FracF").Int(); got != want || got != 3 { t.Fatalf("浮点截断: fastx=%d reflux=%d, 期望 3", got, want) } // 字符串转数字仍然可用(走 cast 回退) if got := fx.Get("NumStr").Int(); got != 123 { t.Fatalf("字符串转数字: %d", got) } // 非数字字符串转数字: 两边都 panic if _, p := callSafe(fx.Get("BadStr"), func(r R) any { return r.Int() }); !p { t.Fatal("\"abc\" 转 Int 应该 panic") } // 具名类型走快路径, 结果与 reflux 一致 if got, want := fx.Get("NamedS").String(), rx.Get("NamedS").String(); got != want { t.Fatalf("具名 string: fastx=%q reflux=%q", got, want) } // 指针字段自动解引用后仍能直读 if got := fx.Get("PtrStr").String(); got != "viaptr" { t.Fatalf("指针字段: %q", got) } }