CodeSampleX

Beispiel

google.golang.org/protobuf v1.36.12: Merge, clone, equality, and reset semantics for Protocol Buffers messages across presence types, repeated slices, maps, and floating-point values

Verifiziertes Beispiel für golang google.golang.org/protobuf v1.36.12: Merge, clone, equality, and reset semantics for Protocol Buffers messages across…

sha256:45bb69bf251a6f037374e5373c90a8b7ada4e709ed3c0558a13a7f718836d1ed

Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen. Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen. MIT-0

Ausführungsbelege

Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.

Beleggrundlage
Signierter Vertrag bestanden
Verifizierungsbelege
2
Signaturschlüssel, die es gebaut haben
2
Deklarierte Umgebung go linux x64 go go go

Umgebungen der Verifizierungsläufe

Umgebung Contract Stufen Lauf
go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-17
go 1.26 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-18

Fall

HOW
Ziel
Merge, clone, equality, and reset semantics for Protocol Buffers messages across presence types, repeated slices, maps, and floating-point values
Pakete
Symbole
  • google.golang.org/protobuf/proto.Merge
  • google.golang.org/protobuf/proto.Equal
  • google.golang.org/protobuf/proto.Clone
  • google.golang.org/protobuf/proto.Reset
  • google.golang.org/protobuf/reflect/protoreflect.Message.Has
Umgebung
go
Erstellt
2026-08-17T19:33:38Z

Contract

  1. proto.Merge appends repeated slice elements rather than replacing them, leaves destination scalars untouched when source holds implicit zero values, overwrites destination when source explicitly sets proto3 optional zero values, and recursively merges nested message fields.
  2. proto.Merge replaces matching map entry values with the source message rather than recursively merging nested submessages within the map.
  3. proto.Clone creates an isolated deep copy where mutating cloned slices, maps, or nested messages does not affect the original message, and proto.Clone(nil) returns nil.
  4. proto.Equal correctly evaluates semantic message equality including IEEE 754 NaN == NaN for float/double fields, whereas reflect.DeepEqual fails on NaN and internal message state.
  5. protoreflect.Message.Has distinguishes between implicit-presence scalars (reporting false for default zero) and proto3 optional scalars (reporting true even when explicitly set to zero).
  6. proto.Reset clears all message fields in-place back to their default unpopulated state without reallocating the message struct.

Dateien

  • NOTES.md
  • csx.json
  • go.mod
  • go.sum
  • proto_test.go

Quellartefakt herunterladen (tar.gz)

Quelltext

NOTES.md
# Protocol Buffers Core Operations and Presence Behavior

A known-solution search for `google.golang.org/protobuf` identified existing samples for JSON formatting and parsing (`protojson.MarshalOptions` / `UnmarshalOptions` in sample sha256:b4eaaa06b9ceab6e52f7e4023b4e801213a60c5d5835efcfda2490e38d021c6c) and duplicate registration conflict configuration (`GOLANG_PROTOBUF_REGISTRATION_CONFLICT` in sample sha256:483581752059a6c7b423f8e927bd877e782e82da6eb0bffd094f66af376e53b7). A specific query for `proto.Merge`, `proto.Clone`, `proto.Equal`, `proto.Reset`, and presence handling returned no safe match.

This sample tests and verifies core message manipulation behaviors in `google.golang.org/protobuf` pinned to release `v1.36.12`.

## Observed Behavior

1. **Repeated Field Merging (`proto.Merge`)**:
   `proto.Merge(dst, src)` appends all elements from `src` repeated fields into `dst`. It does not replace, overwrite, or deduplicate existing list elements. Naive callers expecting `Merge` to replace slices will silently retain stale elements alongside newly appended entries.

2. **Scalar Field Presence in `proto.Merge`**:
   - For proto3 scalar fields with implicit presence (standard integers, strings, booleans, enums without `optional`), an unpopulated/zero-valued field (`0`, `""`, `false`) in `src` does not overwrite an existing non-zero value in `dst`.
   - For proto3 scalar fields with explicit presence (`proto3_optional`), an explicitly set zero value in `src` (`retries = 0`) has presence and correctly overwrites the corresponding field in `dst`. Unset optional fields in `src` leave `dst` unchanged.

3. **Singular Message vs. Map Merging**:
   - Singular nested message fields are recursively merged: fields set in `src` update `dst`'s child message, while fields unset in `src` remain intact in `dst`.
   - For protobuf map fields (`map<string, Message>`), matching keys in `src` replace the entire entry value in `dst`, rather than recursively merging child messages within the map.

4. **Deep Copy Isolation (`proto.Clone`)**:
   `proto.Clone` produces an independent deep copy of the message structure. Appending to repeated lists, mutating child submessages, or altering map entries on the cloned message does not mutate the source message. `proto.Clone(nil)` returns `nil`.

5. **Semantic Equality vs. `reflect.DeepEqual` (`proto.Equal`)**:
   `proto.Equal` performs descriptor-aware semantic comparison of protobuf messages. In accordance with the Protocol Buffers specification, float/double fields containing IEEE 754 `NaN` values are evaluated as equal (`NaN == NaN`). In contrast, Go's standard `reflect.DeepEqual` evaluates `NaN` comparisons as unequal and fails on unexported internal runtime metadata.

6. **Reflection Presence Inspection (`protoreflect.Message.Has`)**:
   `protoreflect.Message.Has` distinguishes between fields with and without explicit presence:
   - For implicit-presence scalars, `Has` returns `true` only when the field holds a non-default (non-zero) value; setting the zero value leaves `Has` as `false`.
   - For `proto3_optional` scalars, `Has` returns `true` as long as the field was explicitly populated, even if set to the default zero value.
   - For repeated fields, `Has` reports `true` if and only if the list length is greater than zero.

7. **In-Place Reset (`proto.Reset`)**:
   `proto.Reset` clears all fields in-place back to their default unpopulated state without reallocating the underlying struct pointer.
csx.json
{"case":{"believed":"proto.Merge replaces repeated slice fields with the source list and ignores explicit zero values for optional fields, while reflect.DeepEqual can safely compare protobuf messages.","caseId":"case:sha256:988e3294aff95cd3dd469e7849e27717a36c15570e1e4bdce39b16a358008aeb","contract":["proto.Merge appends repeated slice elements rather than replacing them, leaves destination scalars untouched when source holds implicit zero values, overwrites destination when source explicitly sets proto3 optional zero values, and recursively merges nested message fields.","proto.Merge replaces matching map entry values with the source message rather than recursively merging nested submessages within the map.","proto.Clone creates an isolated deep copy where mutating cloned slices, maps, or nested messages does not affect the original message, and proto.Clone(nil) returns nil.","proto.Equal correctly evaluates semantic message equality including IEEE 754 NaN == NaN for float/double fields, whereas reflect.DeepEqual fails on NaN and internal message state.","protoreflect.Message.Has distinguishes between implicit-presence scalars (reporting false for default zero) and proto3 optional scalars (reporting true even when explicitly set to zero).","proto.Reset clears all message fields in-place back to their default unpopulated state without reallocating the message struct."],"goal":"Merge, clone, equality, and reset semantics for Protocol Buffers messages across presence types, repeated slices, maps, and floating-point values","kind":"HOW","packages":["pkg:golang/google.golang.org/protobuf@v1.36.12"],"schemaVersion":1,"symbols":["google.golang.org/protobuf/proto.Merge","google.golang.org/protobuf/proto.Equal","google.golang.org/protobuf/proto.Clone","google.golang.org/protobuf/proto.Reset","google.golang.org/protobuf/reflect/protoreflect.Message.Has"]},"contractCommand":["go","test","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"go","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/google.golang.org/protobuf@v1.36.12"],"schemaVersion":1,"symbols":["google.golang.org/protobuf/proto.Merge","google.golang.org/protobuf/proto.Equal","google.golang.org/protobuf/proto.Clone","google.golang.org/protobuf/proto.Reset","google.golang.org/protobuf/reflect/protoreflect.Message.Has"],"verifierAdapter":"golang@1"}
go.mod
module example.com/protocore

go 1.26.5

require google.golang.org/protobuf v1.36.12
go.sum
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
proto_test.go
package protocore_test

import (
	"math"
	"reflect"
	"testing"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protodesc"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/reflect/protoregistry"
	"google.golang.org/protobuf/types/descriptorpb"
	"google.golang.org/protobuf/types/dynamicpb"
)

func buildSchema(t *testing.T) (*descriptorpb.FileDescriptorProto, protoreflect.MessageDescriptor, protoreflect.MessageDescriptor) {
	t.Helper()

	fd := &descriptorpb.FileDescriptorProto{
		Name:    proto.String("service_config.proto"),
		Package: proto.String("example.config"),
		Syntax:  proto.String("proto3"),
		MessageType: []*descriptorpb.DescriptorProto{
			{
				Name: proto.String("Endpoint"),
				Field: []*descriptorpb.FieldDescriptorProto{
					{
						Name:     proto.String("host"),
						Number:   proto.Int32(1),
						Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
						JsonName: proto.String("host"),
					},
					{
						Name:     proto.String("port"),
						Number:   proto.Int32(2),
						Type:     descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
						JsonName: proto.String("port"),
					},
				},
			},
			{
				Name: proto.String("ServiceConfig"),
				Field: []*descriptorpb.FieldDescriptorProto{
					{
						Name:     proto.String("timeout_ms"),
						Number:   proto.Int32(1),
						Type:     descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
						JsonName: proto.String("timeoutMs"),
					},
					{
						Name:           proto.String("retries"),
						Number:         proto.Int32(2),
						Type:           descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
						JsonName:       proto.String("retries"),
						Proto3Optional: proto.Bool(true),
						OneofIndex:     proto.Int32(0),
					},
					{
						Name:     proto.String("tags"),
						Number:   proto.Int32(3),
						Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
						Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
						JsonName: proto.String("tags"),
					},
					{
						Name:     proto.String("endpoint"),
						Number:   proto.Int32(4),
						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
						TypeName: proto.String(".example.config.Endpoint"),
						JsonName: proto.String("endpoint"),
					},
					{
						Name:     proto.String("ratio"),
						Number:   proto.Int32(5),
						Type:     descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
						JsonName: proto.String("ratio"),
					},
					{
						Name:     proto.String("metadata"),
						Number:   proto.Int32(6),
						Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
						TypeName: proto.String(".example.config.ServiceConfig.MetadataEntry"),
						Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
						JsonName: proto.String("metadata"),
					},
				},
				NestedType: []*descriptorpb.DescriptorProto{
					{
						Name: proto.String("MetadataEntry"),
						Field: []*descriptorpb.FieldDescriptorProto{
							{
								Name:     proto.String("key"),
								Number:   proto.Int32(1),
								Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
								JsonName: proto.String("key"),
							},
							{
								Name:     proto.String("value"),
								Number:   proto.Int32(2),
								Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
								TypeName: proto.String(".example.config.Endpoint"),
								JsonName: proto.String("value"),
							},
						},
						Options: &descriptorpb.MessageOptions{
							MapEntry: proto.Bool(true),
						},
					},
				},
				OneofDecl: []*descriptorpb.OneofDescriptorProto{
					{
						Name: proto.String("_retries"),
					},
				},
			},
		},
	}

	fileDesc, err := protodesc.NewFile(fd, protoregistry.GlobalFiles)
	if err != nil {
		t.Fatalf("protodesc.NewFile failed: %v", err)
	}

	svcDesc := fileDesc.Messages().ByName("ServiceConfig")
	endpointDesc := fileDesc.Messages().ByName("Endpoint")
	if svcDesc == nil || endpointDesc == nil {
		t.Fatalf("failed to resolve message descriptors")
	}

	return fd, svcDesc, endpointDesc
}

func TestProtoMergeRepeatedAndScalars(t *testing.T) {
	_, svcDesc, endpointDesc := buildSchema(t)

	timeoutField := svcDesc.Fields().ByName("timeout_ms")
	retriesField := svcDesc.Fields().ByName("retries")
	tagsField := svcDesc.Fields().ByName("tags")
	endpointField := svcDesc.Fields().ByName("endpoint")

	hostField := endpointDesc.Fields().ByName("host")
	portField := endpointDesc.Fields().ByName("port")

	// Destination message
	dst := dynamicpb.NewMessage(svcDesc)
	dst.Set(timeoutField, protoreflect.ValueOfInt32(5000))
	dst.Set(retriesField, protoreflect.ValueOfInt32(3))

	dstTags := dst.Mutable(tagsField).List()
	dstTags.Append(protoreflect.ValueOfString("us-east"))
	dstTags.Append(protoreflect.ValueOfString("prod"))

	dstEndpoint := dst.Mutable(endpointField).Message()
	dstEndpoint.Set(hostField, protoreflect.ValueOfString("primary.example.com"))
	dstEndpoint.Set(portField, protoreflect.ValueOfInt32(8080))

	// Source message
	src := dynamicpb.NewMessage(svcDesc)
	// timeout_ms is left 0 (zero-value, implicit presence in proto3)
	// retries is explicitly set to 0 (proto3_optional explicit presence)
	src.Set(retriesField, protoreflect.ValueOfInt32(0))

	srcTags := src.Mutable(tagsField).List()
	srcTags.Append(protoreflect.ValueOfString("canary"))

	srcEndpoint := src.Mutable(endpointField).Message()
	// host is left unset in srcEndpoint, only port is updated
	srcEndpoint.Set(portField, protoreflect.ValueOfInt32(9090))

	// Merge src into dst
	proto.Merge(dst, src)

	// 1. Implicit presence scalar in src with 0 does NOT overwrite non-zero dst value
	if got := dst.Get(timeoutField).Int(); got != 5000 {
		t.Fatalf("expected timeout_ms to remain 5000, got %d", got)
	}

	// 2. Explicit presence (proto3_optional) scalar in src with 0 DOES overwrite dst value
	if got := dst.Get(retriesField).Int(); got != 0 {
		t.Fatalf("expected retries to be overwritten to 0, got %d", got)
	}

	// 3. Repeated fields are APPENDED, not overwritten
	resTags := dst.Get(tagsField).List()
	if resTags.Len() != 3 {
		t.Fatalf("expected tags length to be 3 (appended), got %d", resTags.Len())
	}
	expectedTags := []string{"us-east", "prod", "canary"}
	for i, exp := range expectedTags {
		if resTags.Get(i).String() != exp {
			t.Errorf("tags[%d] = %q, expected %q", i, resTags.Get(i).String(), exp)
		}
	}

	// 4. Singular submessage fields are recursively merged
	resEndpoint := dst.Get(endpointField).Message()
	if resEndpoint.Get(hostField).String() != "primary.example.com" {
		t.Fatalf("expected host to be retained as primary.example.com, got %q", resEndpoint.Get(hostField).String())
	}
	if resEndpoint.Get(portField).Int() != 9090 {
		t.Fatalf("expected port to be updated to 9090, got %d", resEndpoint.Get(portField).Int())
	}
}

func TestProtoMergeMapFieldReplacement(t *testing.T) {
	_, svcDesc, endpointDesc := buildSchema(t)

	metadataField := svcDesc.Fields().ByName("metadata")
	hostField := endpointDesc.Fields().ByName("host")
	portField := endpointDesc.Fields().ByName("port")

	// Destination message with map entry "svc1"
	dst := dynamicpb.NewMessage(svcDesc)
	dstMeta := dst.Mutable(metadataField).Map()

	ep1Dst := dynamicpb.NewMessage(endpointDesc)
	ep1Dst.Set(hostField, protoreflect.ValueOfString("meta1.example.com"))
	ep1Dst.Set(portField, protoreflect.ValueOfInt32(7000))
	dstMeta.Set(protoreflect.ValueOfString("svc1").MapKey(), protoreflect.ValueOfMessage(ep1Dst))

	// Source message with updated "svc1" (host unset) and new entry "svc2"
	src := dynamicpb.NewMessage(svcDesc)
	srcMeta := src.Mutable(metadataField).Map()

	ep1Src := dynamicpb.NewMessage(endpointDesc)
	ep1Src.Set(portField, protoreflect.ValueOfInt32(7001))
	srcMeta.Set(protoreflect.ValueOfString("svc1").MapKey(), protoreflect.ValueOfMessage(ep1Src))

	ep2Src := dynamicpb.NewMessage(endpointDesc)
	ep2Src.Set(hostField, protoreflect.ValueOfString("meta2.example.com"))
	ep2Src.Set(portField, protoreflect.ValueOfInt32(8000))
	srcMeta.Set(protoreflect.ValueOfString("svc2").MapKey(), protoreflect.ValueOfMessage(ep2Src))

	// Merge src into dst
	proto.Merge(dst, src)

	// For map fields, matching keys in src REPLACE dst map entry values rather than recursively merging them
	resMeta := dst.Get(metadataField).Map()
	if resMeta.Len() != 2 {
		t.Fatalf("expected metadata map length to be 2, got %d", resMeta.Len())
	}

	svc1Val := resMeta.Get(protoreflect.ValueOfString("svc1").MapKey()).Message()
	// In protobuf merge semantics, the entire map value is replaced by the source entry
	if svc1Val.Get(hostField).String() != "" {
		t.Fatalf("expected host to be replaced by empty string from src, got %q", svc1Val.Get(hostField).String())
	}
	if svc1Val.Get(portField).Int() != 7001 {
		t.Fatalf("expected port to be 7001, got %d", svc1Val.Get(portField).Int())
	}

	svc2Val := resMeta.Get(protoreflect.ValueOfString("svc2").MapKey()).Message()
	if svc2Val.Get(hostField).String() != "meta2.example.com" || svc2Val.Get(portField).Int() != 8000 {
		t.Fatalf("expected svc2 entry to be copied from src")
	}
}

func TestProtoCloneIsolation(t *testing.T) {
	_, svcDesc, endpointDesc := buildSchema(t)

	tagsField := svcDesc.Fields().ByName("tags")
	endpointField := svcDesc.Fields().ByName("endpoint")
	metadataField := svcDesc.Fields().ByName("metadata")
	hostField := endpointDesc.Fields().ByName("host")
	portField := endpointDesc.Fields().ByName("port")
	ratioField := svcDesc.Fields().ByName("ratio")

	orig := dynamicpb.NewMessage(svcDesc)
	orig.Set(ratioField, protoreflect.ValueOfFloat64(math.NaN()))

	origTags := orig.Mutable(tagsField).List()
	origTags.Append(protoreflect.ValueOfString("alpha"))

	origEndpoint := orig.Mutable(endpointField).Message()
	origEndpoint.Set(hostField, protoreflect.ValueOfString("cluster.example.com"))

	origMeta := orig.Mutable(metadataField).Map()
	ep := dynamicpb.NewMessage(endpointDesc)
	ep.Set(portField, protoreflect.ValueOfInt32(5000))
	origMeta.Set(protoreflect.ValueOfString("app").MapKey(), protoreflect.ValueOfMessage(ep))

	// proto.Clone creates a deep copy
	cloned := proto.Clone(orig)
	clonedMsg, ok := cloned.(*dynamicpb.Message)
	if !ok {
		t.Fatalf("cloned message is not *dynamicpb.Message")
	}

	// Mutating the clone's list, child message, and map does not mutate original
	clonedTags := clonedMsg.Mutable(tagsField).List()
	clonedTags.Append(protoreflect.ValueOfString("beta"))

	clonedEndpoint := clonedMsg.Mutable(endpointField).Message()
	clonedEndpoint.Set(hostField, protoreflect.ValueOfString("mutated.example.com"))

	clonedMeta := clonedMsg.Mutable(metadataField).Map()
	clonedMeta.Clear(protoreflect.ValueOfString("app").MapKey())

	if orig.Get(tagsField).List().Len() != 1 {
		t.Fatalf("mutating clone list modified original list")
	}
	if orig.Get(endpointField).Message().Get(hostField).String() != "cluster.example.com" {
		t.Fatalf("mutating clone child message modified original child message")
	}
	if orig.Get(metadataField).Map().Len() != 1 {
		t.Fatalf("clearing clone map entry modified original map")
	}

	// proto.Clone(nil) returns nil
	var nilMsg proto.Message
	if proto.Clone(nilMsg) != nil {
		t.Fatalf("proto.Clone(nil) must return nil")
	}
}

func TestProtoEqualSemantics(t *testing.T) {
	_, svcDesc, endpointDesc := buildSchema(t)

	tagsField := svcDesc.Fields().ByName("tags")
	endpointField := svcDesc.Fields().ByName("endpoint")
	hostField := endpointDesc.Fields().ByName("host")
	ratioField := svcDesc.Fields().ByName("ratio")

	// Message 1 with NaN float
	msg1 := dynamicpb.NewMessage(svcDesc)
	msg1.Set(ratioField, protoreflect.ValueOfFloat64(math.NaN()))
	msg1.Mutable(tagsField).List().Append(protoreflect.ValueOfString("v1"))
	msg1.Mutable(endpointField).Message().Set(hostField, protoreflect.ValueOfString("equal.example.com"))

	// Message 2 constructed independently with identical values
	msg2 := dynamicpb.NewMessage(svcDesc)
	msg2.Set(ratioField, protoreflect.ValueOfFloat64(math.NaN()))
	msg2.Mutable(tagsField).List().Append(protoreflect.ValueOfString("v1"))
	msg2.Mutable(endpointField).Message().Set(hostField, protoreflect.ValueOfString("equal.example.com"))

	// 1. proto.Equal treats NaN == NaN as equal according to protobuf specifications
	if !proto.Equal(msg1, msg2) {
		t.Fatalf("expected proto.Equal(msg1, msg2) to be true")
	}

	// 2. reflect.DeepEqual fails because Go float NaN != NaN and internal runtime metadata differs
	if reflect.DeepEqual(msg1, msg2) {
		t.Fatalf("expected reflect.DeepEqual to evaluate to false due to NaN comparison")
	}

	// 3. Nil comparisons
	if !proto.Equal(nil, nil) {
		t.Fatalf("proto.Equal(nil, nil) must be true")
	}
	if proto.Equal(msg1, nil) {
		t.Fatalf("proto.Equal(msg1, nil) must be false")
	}
	if proto.Equal(nil, msg1) {
		t.Fatalf("proto.Equal(nil, msg1) must be false")
	}
}

func TestProtoReflectHasPresence(t *testing.T) {
	_, svcDesc, endpointDesc := buildSchema(t)

	timeoutField := svcDesc.Fields().ByName("timeout_ms")
	retriesField := svcDesc.Fields().ByName("retries")
	tagsField := svcDesc.Fields().ByName("tags")
	endpointField := svcDesc.Fields().ByName("endpoint")

	msg := dynamicpb.NewMessage(svcDesc)

	// 1. Proto3 scalar without optional has implicit presence (HasPresence() == false)
	if timeoutField.HasPresence() {
		t.Fatalf("expected proto3 scalar without optional to have HasPresence() == false")
	}
	// Has() returns false when value is 0 (default) and true when non-zero
	if msg.ProtoReflect().Has(timeoutField) {
		t.Fatalf("expected unpopulated implicit-presence scalar to report Has() == false")
	}
	msg.Set(timeoutField, protoreflect.ValueOfInt32(0))
	if msg.ProtoReflect().Has(timeoutField) {
		t.Fatalf("setting default 0 on implicit-presence scalar must still report Has() == false")
	}
	msg.Set(timeoutField, protoreflect.ValueOfInt32(42))
	if !msg.ProtoReflect().Has(timeoutField) {
		t.Fatalf("setting non-zero 42 on implicit-presence scalar must report Has() == true")
	}

	// 2. Proto3 scalar with proto3_optional has explicit presence (HasPresence() == true)
	if !retriesField.HasPresence() {
		t.Fatalf("expected proto3_optional scalar to have HasPresence() == true")
	}
	if msg.ProtoReflect().Has(retriesField) {
		t.Fatalf("expected unpopulated proto3_optional scalar to report Has() == false")
	}
	msg.Set(retriesField, protoreflect.ValueOfInt32(0))
	if !msg.ProtoReflect().Has(retriesField) {
		t.Fatalf("setting explicit 0 on proto3_optional scalar must report Has() == true")
	}

	// 3. Repeated fields report Has() == true only when non-empty
	if tagsField.HasPresence() {
		t.Fatalf("expected repeated field to have HasPresence() == false")
	}
	if msg.ProtoReflect().Has(tagsField) {
		t.Fatalf("empty repeated field must report Has() == false")
	}
	msg.Mutable(tagsField).List().Append(protoreflect.ValueOfString("tag1"))
	if !msg.ProtoReflect().Has(tagsField) {
		t.Fatalf("non-empty repeated field must report Has() == true")
	}

	// 4. Message fields have explicit presence
	if !endpointField.HasPresence() {
		t.Fatalf("expected message field to have HasPresence() == true")
	}
	if msg.ProtoReflect().Has(endpointField) {
		t.Fatalf("unset message field must report Has() == false")
	}
	ep := dynamicpb.NewMessage(endpointDesc)
	msg.Set(endpointField, protoreflect.ValueOfMessage(ep))
	if !msg.ProtoReflect().Has(endpointField) {
		t.Fatalf("populated message field must report Has() == true")
	}
}

func TestProtoReset(t *testing.T) {
	_, svcDesc, endpointDesc := buildSchema(t)

	timeoutField := svcDesc.Fields().ByName("timeout_ms")
	tagsField := svcDesc.Fields().ByName("tags")
	endpointField := svcDesc.Fields().ByName("endpoint")
	hostField := endpointDesc.Fields().ByName("host")

	msg := dynamicpb.NewMessage(svcDesc)
	msg.Set(timeoutField, protoreflect.ValueOfInt32(999))
	msg.Mutable(tagsField).List().Append(protoreflect.ValueOfString("prod"))
	msg.Mutable(endpointField).Message().Set(hostField, protoreflect.ValueOfString("reset.example.com"))

	// Reset in-place
	proto.Reset(msg)

	if msg.Get(timeoutField).Int() != 0 {
		t.Fatalf("expected timeout_ms to be reset to 0, got %d", msg.Get(timeoutField).Int())
	}
	if msg.Get(tagsField).List().Len() != 0 {
		t.Fatalf("expected tags list to be cleared to length 0, got %d", msg.Get(tagsField).List().Len())
	}
	if msg.ProtoReflect().Has(endpointField) {
		t.Fatalf("expected endpoint message field to be cleared after Reset")
	}
}

Ursprungs-Seeder

csx-seed