Scope() 原来是 tv.slow().Scope(), 返回的是 *refx —— 基于 reflect 的慢实现。
功能完全正常, 只是它之后的每一次 Get/Set 都退回逐次反射, 这次重写的收益
在 Scope 之后全部消失。
Scope("A") 之后再 Get 144.6ns / 7 allocs -> 48.7ns / 2 allocs 2.97x
对照: 不经 Scope 直接 Get 59.0ns / 2 allocs
改为自己走 walk 定位、克隆、再包成 *rfx。克隆复用 cloneForValueInput,
与 New(值) 同一套逻辑: 含引用成分才递归深拷贝, 纯值类型逐字节复制。
这类问题很容易在重构里悄悄失守 —— 交回慢实现不会有任何功能异常, 只是慢 3 倍,
测试不看动态类型就发现不了。所以 TestScopeReturnsFastImpl 直接断言返回的是
*rfx, 并验证过有效: 改回 &refx{} 后该用例失败。
顺带确认了其余 slow() 调用点都不会把慢实现泄漏给调用方:
Append/Delete 返回 v 自身, Keys/StringMap*/Slice* 返回的是具体 Go 类型。
另加 Scope 的语义测试(含引用子树 / 纯值子树 / map 子树 / 无参数整体克隆,
都断言原数据未被改写)与 78 个对拍用例。
145 lines
3.7 KiB
Go
145 lines
3.7 KiB
Go
package reflux
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// Scope 必须返回快实现(*rfx), 否则它之后的所有 Get/Set 都退回逐次反射。
|
|
// 这一条很容易在重构里悄悄失守: 交回 refx 功能完全正常, 只是慢 3 倍,
|
|
// 测试不看类型就发现不了。
|
|
func TestScopeReturnsFastImpl(t *testing.T) {
|
|
type leaf struct{ V string }
|
|
type doc struct {
|
|
A leaf
|
|
Tags []string
|
|
M map[string]any
|
|
}
|
|
d := &doc{A: leaf{V: "v"}, Tags: []string{"t"}, M: map[string]any{"k": "v"}}
|
|
r := New(d)
|
|
|
|
for _, p := range [][]string{nil, {"A"}, {"Tags"}, {"M"}} {
|
|
got := r.Scope(p...)
|
|
if _, ok := got.(*rfx); !ok {
|
|
t.Fatalf("Scope(%v) 返回了 %T, 应当是 *rfx —— 慢实现会让后续操作退回逐次反射", p, got)
|
|
}
|
|
// 链下去也必须还是快实现
|
|
if inner := got.Get("V"); p != nil && len(p) == 1 && p[0] == "A" {
|
|
if _, ok := inner.(*rfx); !ok {
|
|
t.Fatalf("Scope(%v).Get() 返回了 %T", p, inner)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 语义不能变: Scope 仍然是深度克隆, 改副本不影响原数据
|
|
func TestScopeStillIsolates(t *testing.T) {
|
|
type inner struct {
|
|
Name string
|
|
Tags []string
|
|
}
|
|
type doc struct {
|
|
In inner
|
|
M map[string]string
|
|
Pure struct{ A, B string }
|
|
}
|
|
mk := func() *doc {
|
|
return &doc{
|
|
In: inner{Name: "n", Tags: []string{"t0", "t1"}},
|
|
M: map[string]string{"k": "v"},
|
|
Pure: struct{ A, B string }{"a", "b"},
|
|
}
|
|
}
|
|
|
|
t.Run("含引用的子树", func(t *testing.T) {
|
|
d := mk()
|
|
s := New(d).Scope("In")
|
|
s.Set("Name", "changed")
|
|
s.Set("Tags.0", "changed")
|
|
if d.In.Name != "n" || d.In.Tags[0] != "t0" {
|
|
t.Fatalf("原数据被改写了: %+v", d.In)
|
|
}
|
|
if s.Get("Name").String() != "changed" || s.Get("Tags.0").String() != "changed" {
|
|
t.Fatal("副本上的写入没生效")
|
|
}
|
|
})
|
|
|
|
t.Run("纯值子树(走浅拷贝分支)", func(t *testing.T) {
|
|
d := mk()
|
|
s := New(d).Scope("Pure")
|
|
s.Set("A", "changed")
|
|
if d.Pure.A != "a" {
|
|
t.Fatalf("原数据被改写了: %+v", d.Pure)
|
|
}
|
|
})
|
|
|
|
t.Run("map 子树", func(t *testing.T) {
|
|
d := mk()
|
|
s := New(d).Scope("M")
|
|
s.Set("k", "changed")
|
|
if d.M["k"] != "v" {
|
|
t.Fatalf("原 map 被改写了: %v", d.M)
|
|
}
|
|
})
|
|
|
|
t.Run("无参数克隆整个对象", func(t *testing.T) {
|
|
d := mk()
|
|
s := New(d).Scope()
|
|
s.Set("In.Name", "changed")
|
|
if d.In.Name != "n" {
|
|
t.Fatalf("原数据被改写了: %+v", d.In)
|
|
}
|
|
})
|
|
}
|
|
|
|
// 与旧实现逐格对拍
|
|
func TestScopeMatchesLegacy(t *testing.T) {
|
|
type inner struct {
|
|
Name string
|
|
Tags []string
|
|
}
|
|
type doc struct {
|
|
In inner
|
|
M map[string]any
|
|
S []inner
|
|
}
|
|
mk := func() *doc {
|
|
return &doc{
|
|
In: inner{Name: "n", Tags: []string{"t"}},
|
|
M: map[string]any{"k": map[string]any{"deep": "d"}},
|
|
S: []inner{{Name: "s0"}},
|
|
}
|
|
}
|
|
|
|
paths := [][]string{
|
|
nil, {"In"}, {"In", "Name"}, {"In", "Tags"}, {"In", "Tags", "0"},
|
|
{"M"}, {"M", "k"}, {"M", "k", "deep"},
|
|
{"S"}, {"S", "0"}, {"S", "0", "Name"},
|
|
{"nosuch"}, {"In", "nosuch"},
|
|
}
|
|
ops := []struct {
|
|
name string
|
|
f func(R) any
|
|
}{
|
|
{"String", func(r R) any { return r.String() }},
|
|
{"Exists", func(r R) any { return r.Exists() }},
|
|
{"Keys", func(r R) any { return fmt.Sprintf("%v", r.Keys()) }},
|
|
{"RawKind", func(r R) any { return r.Raw().Kind().String() }},
|
|
{"JSON", func(r R) any { b, e := json.Marshal(r); return fmt.Sprintf("%s/%v", b, e) }},
|
|
{"再取一层", func(r R) any { return r.Get("Name").String() }},
|
|
}
|
|
|
|
for _, p := range paths {
|
|
for _, op := range ops {
|
|
t.Run(fmt.Sprintf("%v/%s", p, op.name), func(t *testing.T) {
|
|
want := call(newLegacy(mk()).Scope(p...), op.f)
|
|
got := call(New(mk()).Scope(p...), op.f)
|
|
if want != got {
|
|
t.Fatalf("行为不一致\n 旧 = %s\n 新 = %s", want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|