1453cfc69b
gates / gates (push) Failing after 50s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package stacks
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// R-591 follow-up: deepCopyStack's contract is "a snapshot the caller may mutate", and the I18n hole
|
|
// was not the only one — DataPaths, AfterLoad (and every other reference added to Metadata after the
|
|
// copy was written) stayed shared. Instead of one assertion per field, this fills EVERY pointer, slice
|
|
// and map reachable from Stack.Meta with a non-empty value and then walks the copy beside the original:
|
|
// any reference the two share is a field a caller's write would leak through. A field added to
|
|
// Metadata tomorrow is covered the day it is added.
|
|
func TestDeepCopyStackMetaSharesNoReference(t *testing.T) {
|
|
orig := &Stack{Name: "demo"}
|
|
fillAll(reflect.ValueOf(&orig.Meta).Elem(), 0)
|
|
cp := deepCopyStack(orig)
|
|
var shared []string
|
|
findAliases(reflect.ValueOf(orig.Meta), reflect.ValueOf(cp.Meta), "Meta", &shared)
|
|
for _, p := range shared {
|
|
t.Errorf("R-591: deepCopyStack leaves %s shared with the original — a write through the copy changes the stack", p)
|
|
}
|
|
}
|
|
|
|
// fillAll gives every pointer, slice (one element) and map (one entry) under v a non-nil value.
|
|
func fillAll(v reflect.Value, depth int) {
|
|
if depth > 6 || !v.CanSet() && v.Kind() != reflect.Struct {
|
|
return
|
|
}
|
|
switch v.Kind() {
|
|
case reflect.Ptr:
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
fillAll(v.Elem(), depth+1)
|
|
case reflect.Slice:
|
|
if v.Len() == 0 {
|
|
v.Set(reflect.MakeSlice(v.Type(), 1, 1))
|
|
}
|
|
fillAll(v.Index(0), depth+1)
|
|
case reflect.Map:
|
|
if v.Len() == 0 {
|
|
m := reflect.MakeMap(v.Type())
|
|
k := reflect.New(v.Type().Key()).Elem()
|
|
if k.Kind() == reflect.String {
|
|
k.SetString("k")
|
|
}
|
|
e := reflect.New(v.Type().Elem()).Elem()
|
|
fillAll(e, depth+1)
|
|
m.SetMapIndex(k, e)
|
|
v.Set(m)
|
|
}
|
|
case reflect.Struct:
|
|
for i := 0; i < v.NumField(); i++ {
|
|
if v.Type().Field(i).IsExported() {
|
|
fillAll(v.Field(i), depth+1)
|
|
}
|
|
}
|
|
case reflect.String:
|
|
if v.String() == "" {
|
|
v.SetString("x")
|
|
}
|
|
}
|
|
}
|
|
|
|
// findAliases records every pointer/slice/map under a and b that points at the same memory.
|
|
func findAliases(a, b reflect.Value, path string, out *[]string) {
|
|
switch a.Kind() {
|
|
case reflect.Ptr:
|
|
if a.IsNil() || b.IsNil() {
|
|
return
|
|
}
|
|
if a.Pointer() == b.Pointer() {
|
|
*out = append(*out, path)
|
|
return
|
|
}
|
|
findAliases(a.Elem(), b.Elem(), path, out)
|
|
case reflect.Slice:
|
|
if a.Len() == 0 || b.Len() == 0 {
|
|
return
|
|
}
|
|
if a.Pointer() == b.Pointer() {
|
|
*out = append(*out, path)
|
|
return
|
|
}
|
|
for i := 0; i < a.Len() && i < b.Len(); i++ {
|
|
findAliases(a.Index(i), b.Index(i), path+"[]", out)
|
|
}
|
|
case reflect.Map:
|
|
if a.Len() == 0 || b.Len() == 0 {
|
|
return
|
|
}
|
|
if a.Pointer() == b.Pointer() {
|
|
*out = append(*out, path)
|
|
return
|
|
}
|
|
for _, k := range a.MapKeys() {
|
|
if bv := b.MapIndex(k); bv.IsValid() {
|
|
findAliases(a.MapIndex(k), bv, path+"[k]", out)
|
|
}
|
|
}
|
|
case reflect.Struct:
|
|
for i := 0; i < a.NumField(); i++ {
|
|
if a.Type().Field(i).IsExported() {
|
|
findAliases(a.Field(i), b.Field(i), path+"."+a.Type().Field(i).Name, out)
|
|
}
|
|
}
|
|
}
|
|
}
|