CodeSampleX

샘플

github.com/google/go-cmp v0.7.0: Compare embedded structs containing unexported fields using cmpopts.IgnoreUnexported without runtime panics

검증된 샘플 — golang github.com/google/go-cmp v0.7.0: Compare embedded structs containing unexported fields using cmpopts.IgnoreUnexported without runtime panics.…

sha256:9f8c44be217d87c6d3754da5318c9d9102ce16cc0233c803673e47a83978e5e8

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
2
빌드한 서명 키
2
선언된 환경 go linux x64 go go gomod

검증 실행 환경

환경 컨트랙트 단계 실행일
go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-16
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

케이스

HOW
목표
Compare embedded structs containing unexported fields using cmpopts.IgnoreUnexported without runtime panics
패키지
심벌
  • cmp.Diff
  • cmp.Equal
  • cmpopts.IgnoreUnexported
  • cmpopts.IgnoreFields
환경
go
생성일
2026-08-16T12:11:34Z

컨트랙트

  1. assert cmpopts.IgnoreUnexported on an outer struct panics when comparing embedded structs with unexported fields
  2. assert cmpopts.IgnoreFields panics when given a promoted field name from an embedded struct
  3. assert cmpopts.IgnoreUnexported succeeds when every embedded struct type is explicitly passed alongside the outer struct
  4. assert cmp.Diff detects differences in public fields of embedded structs when unexported fields are ignored

파일

  • NOTES.md
  • compare.go
  • compare_test.go
  • csx.json
  • go.mod
  • go.sum

소스 아티팩트 내려받기 (tar.gz)

소스

NOTES.md
# google/go-cmp: Embedded Struct Unexported Fields and Promoted Field Filtering

## Known Solutions Search
A search for `github.com/google/go-cmp` identified an existing sample (`sha256:55ce200247ad6334adf868c2f9ec32f994a94b2ff4d0fc6ac211f4b03de32d9c`) demonstrating basic `cmp.Diff` usage, unexported struct panics, and `cmpopts.IgnoreFields` on top-level structs. This sample addresses the separate trap of embedded (composed) struct unexported field handling and promoted field filtering.

## Failure Mode
The wrong invocation fails loudly: `cmp.Diff` and `cmp.Equal` panic at runtime with `cannot handle unexported field at {Outer}.Embedded.field: consider using cmpopts.EquateComparable or cmpopts.IgnoreUnexported` when only the outer struct is passed to `cmpopts.IgnoreUnexported`, and `cmpopts.IgnoreFields` panics at configuration time with `field: does not exist` when passed a promoted field name.

## Behaviour Details
1. `cmpopts.IgnoreUnexported(types...)` checks struct types during tree traversal by exact type match. Passing an outer struct does not cover embedded struct types; each embedded struct type with unexported fields must be explicitly passed to `cmpopts.IgnoreUnexported`.
2. `cmpopts.IgnoreFields(structType, fields...)` requires fields to be directly declared on the specified struct type. Promoted fields cannot be referenced by their unqualified name on the outer struct. Callers must either pass the field name on the embedded struct type (`cmpopts.IgnoreFields(Embedded{}, "field")`) or use dotted path navigation (`cmpopts.IgnoreFields(Outer{}, "Embedded.field")`).
compare.go
package gocmp

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

// AuditHeader is an embedded struct containing an unexported token field.
type AuditHeader struct {
	Timestamp int64
	token     string
}

// NewAuditHeader creates an AuditHeader instance.
func NewAuditHeader(ts int64, token string) AuditHeader {
	return AuditHeader{Timestamp: ts, token: token}
}

// Envelope embeds AuditHeader and declares its own unexported signature field.
type Envelope struct {
	AuditHeader
	Payload   string
	signature []byte
}

// NewEnvelope creates an Envelope instance.
func NewEnvelope(ts int64, token, payload, signature string) Envelope {
	return Envelope{
		AuditHeader: AuditHeader{Timestamp: ts, token: token},
		Payload:     payload,
		signature:   []byte(signature),
	}
}

// DiffOuterOnly attempts to ignore only Envelope, which panics because AuditHeader's
// unexported token is not ignored during reflection traversal.
func DiffOuterOnly(a, b Envelope) (diff string, panicMsg string) {
	defer func() {
		if r := recover(); r != nil {
			panicMsg = fmt.Sprint(r)
		}
	}()
	diff = cmp.Diff(a, b, cmpopts.IgnoreUnexported(Envelope{}))
	return diff, panicMsg
}

// TryIgnorePromotedField attempts to ignore "token" on Envelope directly,
// which panics because "token" is not a declared field of Envelope.
func TryIgnorePromotedField() (opt cmp.Option, panicMsg string) {
	defer func() {
		if r := recover(); r != nil {
			panicMsg = fmt.Sprint(r)
		}
	}()
	opt = cmpopts.IgnoreFields(Envelope{}, "token")
	return opt, panicMsg
}

// DiffComplete correctly ignores unexported fields across both the outer
// struct Envelope and the embedded struct AuditHeader.
func DiffComplete(a, b Envelope) string {
	return cmp.Diff(a, b, cmpopts.IgnoreUnexported(Envelope{}, AuditHeader{}))
}

// EqualComplete checks equality while ignoring unexported fields across both types.
func EqualComplete(a, b Envelope) bool {
	return cmp.Equal(a, b, cmpopts.IgnoreUnexported(Envelope{}, AuditHeader{}))
}
compare_test.go
package gocmp

import (
	"strings"
	"testing"
)

func TestEmbeddedStructUnexportedPanicsAndResolution(t *testing.T) {
	env1 := NewEnvelope(1700000000, "session-secret-a", "user-payload", "sig-1")
	env2 := NewEnvelope(1700000000, "session-secret-b", "user-payload", "sig-2")

	t.Run("IgnoreUnexportedOnOuterOnlyPanicsOnEmbeddedField", func(t *testing.T) {
		_, panicMsg := DiffOuterOnly(env1, env2)
		if panicMsg == "" {
			t.Fatal("expected Diff with only outer IgnoreUnexported to panic on embedded unexported field")
		}
		if !strings.Contains(panicMsg, "AuditHeader.token") {
			t.Fatalf("expected panic message to cite AuditHeader.token, got: %s", panicMsg)
		}
	})

	t.Run("IgnoreFieldsPanicsOnPromotedFieldName", func(t *testing.T) {
		_, panicMsg := TryIgnorePromotedField()
		if panicMsg == "" {
			t.Fatal("expected IgnoreFields with promoted field name to panic")
		}
		if !strings.Contains(panicMsg, "does not exist") {
			t.Fatalf("expected panic message indicating field does not exist, got: %s", panicMsg)
		}
	})

	t.Run("IgnoreUnexportedBothOuterAndEmbeddedSucceeds", func(t *testing.T) {
		if !EqualComplete(env1, env2) {
			t.Fatal("expected envelopes with matching public fields to be equal when both struct types are ignored")
		}
		diff := DiffComplete(env1, env2)
		if diff != "" {
			t.Fatalf("expected empty diff for equal public fields, got: %s", diff)
		}
	})

	t.Run("DetectsDifferencesInPublicFieldsOfEmbeddedAndOuterStructs", func(t *testing.T) {
		envDifferent := NewEnvelope(1700000099, "session-secret-c", "other-payload", "sig-3")
		if EqualComplete(env1, envDifferent) {
			t.Fatal("expected envelopes with different Timestamp and Payload to not be equal")
		}
		diff := DiffComplete(env1, envDifferent)
		if diff == "" {
			t.Fatal("expected non-empty diff for differing public fields")
		}
		if !strings.Contains(diff, "Timestamp") || !strings.Contains(diff, "Payload") {
			t.Fatalf("expected diff to report differences in Timestamp and Payload, got: %s", diff)
		}
	})
}
csx.json
{"case":{"believed":"Passing an outer struct to cmpopts.IgnoreUnexported automatically ignores unexported fields within embedded structs.","caseId":"case:sha256:d330c7031671a65b997a09ad8aad338c0d385b1f62111522fdff389c0b7d65a1","contract":["assert cmpopts.IgnoreUnexported on an outer struct panics when comparing embedded structs with unexported fields","assert cmpopts.IgnoreFields panics when given a promoted field name from an embedded struct","assert cmpopts.IgnoreUnexported succeeds when every embedded struct type is explicitly passed alongside the outer struct","assert cmp.Diff detects differences in public fields of embedded structs when unexported fields are ignored"],"goal":"Compare embedded structs containing unexported fields using cmpopts.IgnoreUnexported without runtime panics","kind":"HOW","packages":["pkg:golang/github.com/google/go-cmp@0.7.0"],"schemaVersion":1,"symbols":["cmp.Diff","cmp.Equal","cmpopts.IgnoreUnexported","cmpopts.IgnoreFields"]},"contractCommand":["go","test","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"gomod","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/github.com/google/go-cmp@0.7.0"],"schemaVersion":1,"symbols":["cmp.Diff","cmp.Equal","cmpopts.IgnoreUnexported","cmpopts.IgnoreFields"],"verifierAdapter":"golang@1"}
go.mod
module example.com/gocmp

go 1.24

require github.com/google/go-cmp v0.7.0
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=

오리진 시더

csx-seed