func TestNoCopyOnAssignment(t *testing.T) {
	runSkycfgTests(t, []skycfgTest{
		{
			name: "list does not copy on assignment, *protoRepeated",
			srcFunc: `
def fun():
    pkg = proto.package("skycfg.test_proto")
    msg1 = pkg.MessageV3()
    msg2 = pkg.MessageV3()
    msg1.r_string = ["a","b"]
    a = msg1.r_string
    msg2.r_string = msg1.r_string
    a.append("c")
    return [msg1.r_string, msg2.r_string, a]
`,
			want: `[["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]]`,
		},
		{
			name: "list does not copy on assignment, *stalark.List",
			srcFunc: `
def fun():
    pkg = proto.package("skycfg.test_proto")
    a = ["a","b"]
    msg1 = pkg.MessageV3()
    msg1.r_string = a
    a.append("c")
    msg1.r_string.append("d")
    return [msg1.r_string, a]
`,
			want: `[["a", "b", "c", "d"], ["a", "b", "c", "d"]]`,
		},
		{
			name: "map does not copy on assignment, *protoMap",
			srcFunc: `
def fun():
    pkg = proto.package("skycfg.test_proto")
    msg1 = pkg.MessageV3()
    msg2 = pkg.MessageV3()
    msg1.map_string = {
        "ka": "va",
        "kb": "vb",
    }
    a = msg1.map_string
    msg2.map_string = msg1.map_string
    a["kc"] = "vc"
    return [msg1.map_string, msg2.map_string, a]
`,
			want: `[{"ka": "va", "kb": "vb", "kc": "vc"}, {"ka": "va", "kb": "vb", "kc": "vc"}, {"ka": "va", "kb": "vb", "kc": "vc"}]`,
		},
		{
			name: "map does not copy on assignment, *stalark.Dict",
			srcFunc: `
def fun():
    pkg = proto.package("skycfg.test_proto")
    msg1 = pkg.MessageV3()
    a = {
        "ka": "va",
        "kb": "vb",
    }
    msg1.map_string = a
    a["kc"] = "vc"
    msg1.map_string["kd"] = "vd"
    return [msg1.map_string, a]
`,
			want: `[{"ka": "va", "kb": "vb", "kc": "vc", "kd": "vd"}, {"ka": "va", "kb": "vb", "kc": "vc", "kd": "vd"}]`,
		},
		{
			name: "message does not copy on assignment",
			srcFunc: `
def fun():
    pkg = proto.package("skycfg.test_proto")
    msg1 = pkg.MessageV3()
    msg2 = pkg.MessageV3()
    msg1.f_submsg = pkg.MessageV3()
    msg2.f_submsg = msg1.f_submsg
    a = msg1.f_submsg
    a.f_string = "a"
    return [msg1.f_submsg.f_string, msg2.f_submsg.f_string, a.f_string]
`,
			want: `["a", "a", "a"]`,
		},
	})
}
Skycfg has had inconsistent copy on assignment behavior
Test that Skycfg does not copy lists/maps on assignment, matching Starlark/Python's behavior
package protomodule
import (
	"errors"
	"strings"
	"testing"
	"go.starlark.net/resolve"
	"go.starlark.net/starlark"
	"go.starlark.net/starlarkstruct"
	"google.golang.org/protobuf/encoding/prototext"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/reflect/protoregistry"
	"google.golang.org/protobuf/types/known/anypb"
	any "google.golang.org/protobuf/types/known/anypb"
	pb "github.com/stripe/skycfg/internal/testdata/test_proto"
)
func init() {
	resolve.AllowFloat = true
}
func newRegistry() *protoregistry.Types {
	registry := &protoregistry.Types{}
	registry.RegisterMessage((&pb.MessageV2{}).ProtoReflect().Type())
	registry.RegisterMessage((&pb.MessageV2_NestedMessage{}).ProtoReflect().Type())
	registry.RegisterMessage((&pb.MessageV3{}).ProtoReflect().Type())
	registry.RegisterMessage((&pb.MessageV3_NestedMessage{}).ProtoReflect().Type())
	registry.RegisterEnum((pb.ToplevelEnumV2)(0).Type())
	registry.RegisterEnum((pb.ToplevelEnumV3)(0).Type())
	return registry
}
func TestProtoPackage(t *testing.T) {
	globals := starlark.StringDict{
		"proto": &starlarkstruct.Module{
			Name: "proto",
			Members: starlark.StringDict{
				"package": starlarkPackageFn(newRegistry()),
			},
		},
	}
	runSkycfgTests(t, []skycfgTest{
		{
			src:  `proto.package("skycfg.test_proto")`,
			want: `<proto.Package "skycfg.test_proto">`,
		},
		{
			src:  `dir(proto.package("skycfg.test_proto"))`,
			want: `["MessageV2", "MessageV3", "ToplevelEnumV2", "ToplevelEnumV3"]`,
		},
		{
			src:  `proto.package("skycfg.test_proto").MessageV2`,
			want: `<proto.MessageType "skycfg.test_proto.MessageV2">`,
		},
		{
			src:  `proto.package("skycfg.test_proto").ToplevelEnumV2`,
			want: `<proto.EnumType "skycfg.test_proto.ToplevelEnumV2">`,
		},
		{
			src:     `proto.package("skycfg.test_proto").NoExist`,
			wantErr: errors.New(`Protobuf type "skycfg.test_proto.NoExist" not found`),
		},
	}, withGlobals(globals))
}
func TestMessageType(t *testing.T) {
	globals := starlark.StringDict{
		"pb": NewProtoPackage(newRegistry(), "skycfg.test_proto"),
	}
	runSkycfgTests(t, []skycfgTest{
		{
			src:  `pb.MessageV2`,
			want: `<proto.MessageType "skycfg.test_proto.MessageV2">`,
		},
		{
			src:  `dir(pb.MessageV2)`,
			want: `["NestedEnum", "NestedMessage"]`,
		},
		{
			src:  `pb.MessageV2.NestedMessage`,
			want: `<proto.MessageType "skycfg.test_proto.MessageV2.NestedMessage">`,
		},
		{
			src:  `pb.MessageV2.NestedEnum`,
			want: `<proto.EnumType "skycfg.test_proto.MessageV2.NestedEnum">`,
		},
		{
			src:     `pb.MessageV2.NoExist`,
			wantErr: errors.New(`Protobuf type "skycfg.test_proto.MessageV2.NoExist" not found`),
		},
	}, withGlobals(globals))
}
func TestEnumType(t *testing.T) {
	globals := starlark.StringDict{
		"pb": NewProtoPackage(newRegistry(), "skycfg.test_proto"),
	}
	runSkycfgTests(t, []skycfgTest{
		{
			src:  `pb.ToplevelEnumV2`,
			want: `<proto.EnumType "skycfg.test_proto.ToplevelEnumV2">`,
		},
		{
			src:  `dir(pb.ToplevelEnumV2)`,
			want: `["TOPLEVEL_ENUM_V2_A", "TOPLEVEL_ENUM_V2_B"]`,
		},
		{
			src:  `pb.MessageV2.NestedEnum`,
			want: `<proto.EnumType "skycfg.test_proto.MessageV2.NestedEnum">`,
		},
		{
			src:  `dir(pb.MessageV2.NestedEnum)`,
			want: `["NESTED_ENUM_A", "NESTED_ENUM_B"]`,
		},
		{
			src:     `pb.ToplevelEnumV2.NoExist`,
			wantErr: errors.New(`proto.EnumType has no .NoExist field or method`),
		},
	}, withGlobals(globals))
}
func TestListType(t *testing.T) {
	var listFieldDesc protoreflect.FieldDescriptor
	msg := (&pb.MessageV3{}).ProtoReflect().Descriptor()
	listFieldDesc = msg.Fields().ByName("r_string")
	globals := starlark.StringDict{
		"list": starlark.NewBuiltin("list", func(
			t *starlark.Thread,
			fn *starlark.Builtin,
			args starlark.Tuple,
			kwargs []starlark.Tuple,
		) (starlark.Value, error) {
			return newProtoRepeated(listFieldDesc), nil
		}),
	}
	runSkycfgTests(t, []skycfgTest{
		{
			name: "new list",
			src:  `list()`,
			want: `[]`,
		},
		{
			name: "list AttrNames",
			src:  `dir(list())`,
			want: `["append", "clear", "extend", "index", "insert", "pop", "remove"]`,
		},
		{
			name: "list.Append",
			srcFunc: `
def fun():
    l = list()
    l.append("some string")
    return l
`,
			want: `["some string"]`,
		},
		{
			name: "list.Extend",
			srcFunc: `
def fun():
    l = list()
    l.extend(["a", "b"])
    return l
`,
			want: `["a", "b"]`,
		},
		{
			name: "list.Clear",
			srcFunc: `
def fun():
    l = list()
    l.extend(["a", "b"])
    l.clear()
    return l
`,
			want: `[]`,
		},
		{
			name: "list.SetIndex",
			srcFunc: `
def fun():
    l = list()
    l.extend(["a", "b"])
    l[1] = "c"
    return l
`,
			want: `["a", "c"]`,
		},
		{
			name: "list binary add operation",
			srcFunc: `
def fun():
    l = list()
    l2 = list()
    l2.extend(["a", "b"])
    l += l2
    l += ["c", "d"]
    return l
`,
			want: `["a", "b", "c", "d"]`,
		},
		{
			name:    "list append typchecks",
			src:     `list().append(1)`,
			wantErr: errors.New(`TypeError: value 1 (type "int") can't be assigned to type "string".`),
		},
		{
			name:    "list extend typchecks",
			src:     `list().extend([1,2])`,
			wantErr: errors.New(`TypeError: value 1 (type "int") can't be assigned to type "string".`),
		},
		{
			name: "list set index typchecks",
			srcFunc: `
def fun():
    l = list()
    l.extend(["a", "b"])
    l[1] = 1
    return l
`,
			wantErr: errors.New(`TypeError: value 1 (type "int") can't be assigned to type "string".`),
		},
	}, withGlobals(globals))
}
func TestMapType(t *testing.T) {
	var mapFieldDesc protoreflect.FieldDescriptor
	msg := (&pb.MessageV3{}).ProtoReflect().Descriptor()
	mapFieldDesc = msg.Fields().ByName("map_string")
	globals := starlark.StringDict{
		"map": starlark.NewBuiltin("map", func(
			t *starlark.Thread,
			fn *starlark.Builtin,
			args starlark.Tuple,
			kwargs []starlark.Tuple,
		) (starlark.Value, error) {
			return newProtoMap(mapFieldDesc.MapKey(), mapFieldDesc.MapValue()), nil
		}),
	}
	runSkycfgTests(t, []skycfgTest{
		{
			name: "new map",
			src:  `map()`,
			want: `{}`,
		},
		{
			name: "map AttrNames",
			src:  `dir(map())`,
			want: `["clear", "get", "items", "keys", "pop", "popitem", "setdefault", "update", "values"]`,
		},
		// Map methods
		{
			name: "map.SetDefault",
			srcFunc: `
def fun():
    m = map()
    m["a"] = "A"
    m.setdefault('a', 'Z')
    m.setdefault('b', 'Z')
    return m
`,
			want: `{"a": "A", "b": "Z"}`,
		},
		{
			name: "map.SetKey",
			srcFunc: `
def fun():
    m = map()
    m["a"] = "some string"
    return m
`,
			want: `{"a": "some string"}`,
		},
		{
			name: "map.Update",
			srcFunc: `
def fun():
    m = map()
    m.update([("a", "a_string"), ("b", "b_string")])
    return m
`,
			want: `{"a": "a_string", "b": "b_string"}`,
		},
		{
			name: "map.Clear",
			srcFunc: `
def fun():
    m = map()
    m["a"] = "some string"
    m.clear()
    return m
`,
			want: `{}`,
		},
		// Map typechecking
		{
			name: "map.SetKey typechecks",
			srcFunc: `
def fun():
    m = map()
    m["a"] = 1
    return m
`,
			wantErr: errors.New(`TypeError: value 1 (type "int") can't be assigned to type "string".`),
		},
		{
			name:    "map.Update typechecks",
			src:     `map().update([("a", 1)])`,
			wantErr: errors.New(`TypeError: value 1 (type "int") can't be assigned to type "string".`),
		},
		{
			name:    "map.SetDefault typechecks",
			src:     `map().setdefault("a", 1)`,
			wantErr: errors.New(`TypeError: value 1 (type "int") can't be assigned to type "string".`),
		},
		{
			name:    "map.SetDefault typechecks key",
			src:     `map().setdefault(1, "a")`,
			wantErr: errors.New(`TypeError: value 1 (type "int") can't be assigned to type "string".`),
		},
	}, withGlobals(globals))
}
func TestRepeatedProtoFieldMutation(t *testing.T) {
	runSkycfgTests(t, []skycfgTest{
		{
			srcFunc: `
def fun():
    pkg = proto.package("skycfg.test_proto")
    msg = pkg.MessageV3()
    msg.r_submsg.append(pkg.MessageV3())
    msg.r_submsg[0].f_string = "foo"
    msg.r_submsg.extend([pkg.MessageV3()])
    msg.r_submsg[1].f_string = "bar"
    return msg`,
			want:              `<skycfg.test_proto.MessageV3 r_submsg:{f_string:"foo"} r_submsg:{f_string:"bar"}>`,
			removeRandomSpace: true,
		},
	})
}
// Skycfg has had inconsistent copy on assignment behavior
// Test that Skycfg does not copy lists/maps on assignment, matching Starlark/Python's behavior