package reflux // 新实现(rfx)特有的行为验证: 未导出字段不可达、map 元素副本语义、 // 地址别名、自引用类型的描述符构建、并发安全。 import ( "encoding/json" "reflect" "strings" "sync" "testing" ) type uxInner struct { City string Zip int Active bool Ratio float64 } type uxMiddle struct { Inner uxInner InnerPtr *uxInner Any any } type uxOuter struct { Name string Mid uxMiddle Tags []string Nums [3]int Meta map[string]string Objs map[string]uxInner secret string // 未导出字段, 必须无法读写 Structs []uxInner } func newUxOuter() *uxOuter { return &uxOuter{ Name: "root", Mid: uxMiddle{ Inner: uxInner{City: "Beijing", Zip: 100000, Active: true, Ratio: 1.5}, InnerPtr: &uxInner{City: "Shanghai", Zip: 200000}, Any: uxInner{City: "Shenzhen", Zip: 518000}, }, Tags: []string{"a", "b", "c"}, Nums: [3]int{10, 20, 30}, Meta: map[string]string{"k1": "v1", "k2": "v2"}, Objs: map[string]uxInner{"o1": {City: "Chengdu", Zip: 610000}}, secret: "should-not-be-reachable", Structs: []uxInner{{City: "Wuhan"}, {City: "Xian"}}, } } // --------------------------------------------------------------------------- // Get: 各种路径形态 // --------------------------------------------------------------------------- func TestGetPaths(t *testing.T) { o := newUxOuter() r := New(o) cases := []struct { name string path []string want string }{ {"单层字段", []string{"Name"}, "root"}, {"多层嵌套", []string{"Mid", "Inner", "City"}, "Beijing"}, {"点号路径", []string{"Mid.Inner.City"}, "Beijing"}, {"点号混合", []string{"Mid.Inner", "City"}, "Beijing"}, {"指针自动解引用", []string{"Mid", "InnerPtr", "City"}, "Shanghai"}, {"interface 自动解引用", []string{"Mid", "Any", "City"}, "Shenzhen"}, {"slice 下标", []string{"Tags", "1"}, "b"}, {"array 下标", []string{"Nums", "2"}, "30"}, {"map 键", []string{"Meta", "k1"}, "v1"}, {"map 里的 struct", []string{"Objs", "o1", "City"}, "Chengdu"}, {"slice 里的 struct", []string{"Structs", "1", "City"}, "Xian"}, {"首字母小写兼容", []string{"mid", "inner", "city"}, "Beijing"}, {"前后多余点号", []string{".Mid..Inner.City."}, "Beijing"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := r.Get(c.path...).String() if got != c.want { t.Fatalf("Get(%q) = %q, 期望 %q", c.path, got, c.want) } // 与 reflux 现有实现逐条对齐 if want := newLegacy(o).Get(c.path...).String(); want != got { t.Fatalf("Get(%q): fastx=%q reflux=%q, 两者行为不一致", c.path, got, want) } }) } } func TestGetMissing(t *testing.T) { o := newUxOuter() r := New(o) missing := [][]string{ {"NoSuchField"}, {"Mid", "Inner", "NoSuchField"}, {"Tags", "99"}, {"Tags", "notanumber"}, {"Nums", "5"}, {"Meta", "nokey"}, {"Name", "City"}, // 在标量上继续取路径 } for _, p := range missing { got := r.Get(p...) if got.Exists() { t.Fatalf("Get(%q) 不应该存在, 却返回了 %v", p, got.Any()) } if got.String() != "" { t.Fatalf("Get(%q).String() 应该是空串, 得到 %q", p, got.String()) } } } // 未导出字段既不能读也不能写 —— 这是 unsafe 路径上最关键的安全断言。 func TestUnexportedFieldIsUnreachable(t *testing.T) { o := newUxOuter() r := New(o) if r.Get("secret").Exists() { t.Fatal("未导出字段 secret 不应该可读") } err := uxMustPanic(t, func() { r.Set("secret", "hacked") }) if err == nil { t.Fatal("向未导出字段写入应该 panic") } if o.secret != "should-not-be-reachable" { t.Fatalf("未导出字段被改写了: %q", o.secret) } } // --------------------------------------------------------------------------- // Set // --------------------------------------------------------------------------- func TestUnsafeSet(t *testing.T) { o := newUxOuter() r := New(o) r.Set("Name", "changed") if o.Name != "changed" { t.Fatalf("Set(Name) 失败: %q", o.Name) } r.Set("Mid.Inner.City", "Hangzhou") if o.Mid.Inner.City != "Hangzhou" { t.Fatalf("Set 嵌套字段失败: %q", o.Mid.Inner.City) } r.Set("Mid.Inner.Zip", 330000) if o.Mid.Inner.Zip != 330000 { t.Fatalf("Set int 失败: %d", o.Mid.Inner.Zip) } r.Set("Mid.Inner.Active", false) if o.Mid.Inner.Active { t.Fatal("Set bool 失败") } r.Set("Mid.Inner.Ratio", 2.5) if o.Mid.Inner.Ratio != 2.5 { t.Fatalf("Set float64 失败: %v", o.Mid.Inner.Ratio) } // 通过指针字段设置 r.Set("Mid.InnerPtr.City", "Nanjing") if o.Mid.InnerPtr.City != "Nanjing" { t.Fatalf("经指针 Set 失败: %q", o.Mid.InnerPtr.City) } // slice 元素 r.Set("Tags.0", "z") if o.Tags[0] != "z" { t.Fatalf("Set slice 元素失败: %v", o.Tags) } // array 元素 r.Set("Nums.1", 99) if o.Nums[1] != 99 { t.Fatalf("Set array 元素失败: %v", o.Nums) } // map 键(新增 + 覆盖) r.Set("Meta.k1", "v1-new") r.Set("Meta.k3", "v3") if o.Meta["k1"] != "v1-new" || o.Meta["k3"] != "v3" { t.Fatalf("Set map 键失败: %v", o.Meta) } // slice 里的 struct 字段 r.Set("Structs.0.City", "Changsha") if o.Structs[0].City != "Changsha" { t.Fatalf("Set slice 内 struct 字段失败: %v", o.Structs) } } // 类型不完全一致时应该走 cast 回退, 与 reflux 行为一致 func TestUnsafeSetTypeConversion(t *testing.T) { o := newUxOuter() r := New(o) r.Set("Mid.Inner.Zip", "123456") // string -> int if o.Mid.Inner.Zip != 123456 { t.Fatalf("string->int 转换失败: %d", o.Mid.Inner.Zip) } r.Set("Name", 42) // int -> string if o.Name != "42" { t.Fatalf("int->string 转换失败: %q", o.Name) } r.Set("Mid.Inner.Ratio", 3) // int -> float64 if o.Mid.Inner.Ratio != 3 { t.Fatalf("int->float64 转换失败: %v", o.Mid.Inner.Ratio) } } func TestUnsafeSetErrors(t *testing.T) { o := newUxOuter() r := New(o) // 路径不存在 if err := uxMustPanic(t, func() { r.Set("NoSuchField", 1) }); err == nil { t.Fatal("向不存在的字段写入应该 panic") } // 父路径不存在 if err := uxMustPanic(t, func() { r.Set("NoSuch.Deep.Path", 1) }); err == nil { t.Fatal("父路径不存在时应该 panic") } // 空路径 if err := uxMustPanic(t, func() { r.Set("", 1) }); err == nil { t.Fatal("空路径应该 panic") } // slice 越界 if err := uxMustPanic(t, func() { r.Set("Tags.99", "x") }); err == nil { t.Fatal("slice 越界应该 panic") } // 无法转换的类型 if err := uxMustPanic(t, func() { r.Set("Mid.Inner.Zip", struct{}{}) }); err == nil { t.Fatal("无法转换的类型应该 panic") } } // 从 map 里取出来的 struct 是副本, 对它的写入不应该影响原 map —— // 这与 reflect 里 "map 元素不可寻址" 的语义一致。 func TestMapValueIsCopy(t *testing.T) { o := newUxOuter() r := New(o) got := r.Get("Objs", "o1") if !got.Exists() { t.Fatal("Objs.o1 应该存在") } err := uxMustPanic(t, func() { got.Set("City", "Modified") }) if err == nil { t.Log("对 map 元素副本的写入未 panic(落在副本上)") } if o.Objs["o1"].City != "Chengdu" { t.Fatalf("原 map 里的值被改写了: %q", o.Objs["o1"].City) } } // 传值(非指针)时不应该影响调用方的原始数据 func TestNewByValueDoesNotMutateOriginal(t *testing.T) { o := *newUxOuter() r := New(o) r.Set("Name", "changed") if o.Name != "root" { t.Fatalf("传值构造时原始数据被改写了: %q", o.Name) } if r.Get("Name").String() != "changed" { t.Fatal("副本上的写入没生效") } } // --------------------------------------------------------------------------- // Accessor / JSON / 其它 R 方法 // --------------------------------------------------------------------------- func TestAccessors(t *testing.T) { o := newUxOuter() r := New(o) if got := r.Get("Mid.Inner.City").String(); got != "Beijing" { t.Fatalf("String() = %q", got) } if got := r.Get("Mid.Inner.Zip").Int(); got != 100000 { t.Fatalf("Int() = %d", got) } if got := r.Get("Mid.Inner.Zip").Int64(); got != 100000 { t.Fatalf("Int64() = %d", got) } if got := r.Get("Mid.Inner.Active").Bool(); !got { t.Fatal("Bool() = false") } if got := r.Get("Mid.Inner.Ratio").Float64(); got != 1.5 { t.Fatalf("Float64() = %v", got) } if got := r.Get("Mid.Inner.Ratio").Float32(); got != 1.5 { t.Fatalf("Float32() = %v", got) } if got := r.Get("Mid.Inner.Zip").String(); got != "100000" { t.Fatalf("int 转 String() = %q", got) } // Raw / Ptr raw := r.Get("Mid.Inner.City").Raw() if !raw.IsValid() || raw.Kind() != reflect.String || raw.String() != "Beijing" { t.Fatalf("Raw() = %v", raw) } ptr, ok := r.Get("Mid.Inner.City").Ptr().(*string) if !ok || *ptr != "Beijing" { t.Fatalf("Ptr() = %#v", r.Get("Mid.Inner.City").Ptr()) } // Ptr 拿到的必须是原始数据的地址, 改它能反映到原对象上 *ptr = "ViaPtr" if o.Mid.Inner.City != "ViaPtr" { t.Fatal("Ptr() 返回的不是原始数据的地址") } // Any if got := r.Get("Mid.Inner.Zip").Any(); got != 100000 { t.Fatalf("Any() = %#v", got) } // StringSlice / Slice if got := r.Get("Tags").StringSlice(); !reflect.DeepEqual(got, []string{"a", "b", "c"}) { t.Fatalf("StringSlice() = %v", got) } if got := r.Get("Meta").StringMapString(); got["k1"] != "v1" { t.Fatalf("StringMapString() = %v", got) } } func TestExistsKeysArray(t *testing.T) { o := newUxOuter() r := New(o) if !r.Exists("Mid", "Inner", "City") { t.Fatal("Exists 应该为 true") } if r.Exists("Mid", "Nope") { t.Fatal("Exists 应该为 false") } keys := r.Keys() if len(keys) == 0 || keys[0] != "Name" { t.Fatalf("Keys() = %v", keys) } arr := r.Get("Tags").Array() if len(arr) != 3 || arr[2].String() != "c" { t.Fatalf("Array() = %v", arr) } // Array 返回的元素应该指向原始底层数组, 对它的写入能反映到原对象上 objs := r.Get("Structs").Array() if len(objs) != 2 { t.Fatalf("Structs.Array() 长度 = %d", len(objs)) } objs[0].Set("City", "Guiyang") if o.Structs[0].City != "Guiyang" { t.Fatalf("Array() 元素没有指向原始底层数组: %+v", o.Structs) } } func TestLookup(t *testing.T) { o := newUxOuter() r := New(o) acc, ok := r.Lookup("Mid.Inner.City") if !ok || acc.String() != "Beijing" { t.Fatalf("Lookup = %v %v", acc, ok) } if _, ok := r.Lookup("Nope"); ok { t.Fatal("Lookup 不存在的路径应该返回 false") } if got := r.MustLookup("Nope").String(); got != "" { t.Fatalf("MustLookup 不存在时应该返回零值, 得到 %q", got) } } func TestJSON(t *testing.T) { o := newUxOuter() r := New(o) b, err := json.Marshal(r.Get("Mid", "Inner")) if err != nil { t.Fatalf("Marshal 失败: %v", err) } if !strings.Contains(string(b), `"City":"Beijing"`) { t.Fatalf("Marshal 结果不对: %s", b) } target := r.Get("Mid", "Inner") if err := json.Unmarshal([]byte(`{"City":"Kunming","Zip":650000}`), target); err != nil { t.Fatalf("Unmarshal 失败: %v", err) } if o.Mid.Inner.City != "Kunming" || o.Mid.Inner.Zip != 650000 { t.Fatalf("Unmarshal 没有写回原对象: %+v", o.Mid.Inner) } } // --------------------------------------------------------------------------- // TypeDescriptor 本身 // --------------------------------------------------------------------------- type uxSelfRef struct { Name string Next *uxSelfRef Kids []uxSelfRef } // 自引用类型不能让描述符构建无限递归 func TestSelfReferentialType(t *testing.T) { done := make(chan struct{}) go func() { defer close(done) td := rfxDescriptorOf(reflect.TypeOf(uxSelfRef{})) if len(td.fields) != 3 { t.Errorf("字段数 = %d", len(td.fields)) } }() <-done root := &uxSelfRef{Name: "a", Next: &uxSelfRef{Name: "b", Next: &uxSelfRef{Name: "c"}}} r := New(root) if got := r.Get("Next.Next.Name").String(); got != "c" { t.Fatalf("自引用类型路径遍历失败: %q", got) } } // --------------------------------------------------------------------------- // 并发 // --------------------------------------------------------------------------- type uxRaceA struct { X string B uxRaceB } type uxRaceB struct { Y string C uxRaceC } type uxRaceC struct{ Z string } // 多个 goroutine 同时构建描述符 + 读写各自独立的实例 func TestConcurrentAccess(t *testing.T) { const n = 32 var wg sync.WaitGroup wg.Add(n) for i := 0; i < n; i++ { go func(i int) { defer wg.Done() // 每个 goroutine 操作自己的实例, 但共享同一份全局描述符缓存 a := &uxRaceA{X: "x", B: uxRaceB{Y: "y", C: uxRaceC{Z: "z"}}} r := New(a) for j := 0; j < 200; j++ { if got := r.Get("B.C.Z").String(); got != "z" && got != "changed" { t.Errorf("并发读到了意外的值: %q", got) return } r.Set("B.C.Z", "changed") r.Set("B.C.Z", "z") } // 也并发构建一些新类型的描述符 switch i % 4 { case 0: rfxDescriptorOf(reflect.TypeOf(uxOuter{})) case 1: rfxDescriptorOf(reflect.TypeOf(uxSelfRef{})) case 2: rfxDescriptorOf(reflect.TypeOf(uxMiddle{})) case 3: rfxDescriptorOf(reflect.TypeOf(uxOuter{})) } }(i) } wg.Wait() } // 同一个实例被多个 goroutine 并发只读, 不应该有竞争 func TestConcurrentReadSameInstance(t *testing.T) { a := &uxRaceA{X: "x", B: uxRaceB{Y: "y", C: uxRaceC{Z: "z"}}} r := New(a) var wg sync.WaitGroup for i := 0; i < 16; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 500; j++ { if got := r.Get("B", "C", "Z").String(); got != "z" { t.Errorf("并发只读得到 %q", got) return } } }() } wg.Wait() } // --------------------------------------------------------------------------- func uxMustPanic(t *testing.T, f func()) (recovered any) { t.Helper() defer func() { recovered = recover() }() f() return nil }