diff --git a/.gitignore b/.gitignore index 2d19289..7b482c0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,8 @@ msgp/cover.out .idea/ .vscode/ cover.out +.DS_Store + +# locally built msgp binaries +msgp-bin +msgp/msgp diff --git a/Makefile b/Makefile index a77d20d..f601ee0 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ $(MGEN): ./msgp/defs_test.go test: all go test -covermode=atomic -coverprofile=cover.out ./... + cd tests && go test ./... bench: all go test -bench ./... diff --git a/gen/elem.go b/gen/elem.go index 707b56b..0344890 100644 --- a/gen/elem.go +++ b/gen/elem.go @@ -124,6 +124,7 @@ var primitives = map[string]Primitive{ "int64": Int64, "bool": Bool, "interface{}": Intf, + "any": Intf, // builtin alias for interface{} (Go 1.18+) "time.Time": Time, "msgp.Extension": Ext, "error": Error, diff --git a/gen/testgen.go b/gen/testgen.go index f046702..088ca5b 100644 --- a/gen/testgen.go +++ b/gen/testgen.go @@ -1,6 +1,7 @@ package gen import ( + "go/ast" "io" "text/template" ) @@ -25,12 +26,25 @@ type mtestGen struct { w io.Writer } +// mtestData is the data handed to the marshal-test template. +type mtestData struct { + TypeName string + // HasRequired is true when decoding the zero value of this type would trip + // a generated `required` field check and return an error. The zero-value + // round-trip assertions are relaxed in that case, since a zero value is by + // definition not a valid encoding of a type with required fields. + HasRequired bool +} + func (m *mtestGen) Execute(p Elem) ([]string, error) { p = m.applyall(p) if p != nil && !IsDangling(p) { switch p.(type) { case *Struct, *Array, *Slice, *Map: - return nil, marshalTestTempl.Execute(m.w, p) + return nil, marshalTestTempl.Execute(m.w, mtestData{ + TypeName: p.TypeName(), + HasRequired: zeroValueHasRequired(p), + }) } } return nil, nil @@ -38,19 +52,42 @@ func (m *mtestGen) Execute(p Elem) ([]string, error) { func (m *mtestGen) Method() Method { return marshaltest } +// zeroValueHasRequired reports whether decoding the zero value of e would fail +// a generated `required` check. Only exported fields get checks. A zero slice +// or map is empty, so its elements (and their checks) are never decoded; a zero +// array decodes its zero elements, so its element type still matters. +func zeroValueHasRequired(e Elem) bool { + switch e := e.(type) { + case *Struct: + for i := range e.Fields { + if ast.IsExported(e.Fields[i].FieldName) && e.Fields[i].HasTagPart("required") { + return true + } + } + case *Array: + return zeroValueHasRequired(e.Els) + } + return false +} + func init() { template.Must(marshalTestTempl.Parse(`func TestMarshalUnmarshal{{.TypeName}}(t *testing.T) { partitiontest.PartitionTest(t) v := {{.TypeName}}{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) - if err != nil { +{{if .HasRequired}} // The zero value omits its required field(s), so UnmarshalMsg is expected to + // reject it; only MarshalMsg and Skip are exercised against a zero value. + if err == nil { + t.Errorf("expected a missing-required-field error decoding a zero {{.TypeName}}") + } +{{else}} if err != nil { t.Fatal(err) } if len(left) > 0 { t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) } - +{{end}} left, err = msgp.Skip(bts) if err != nil { t.Fatal(err) @@ -92,11 +129,14 @@ func BenchmarkUnmarshal{{.TypeName}}(b *testing.B) { b.SetBytes(int64(len(bts))) b.ResetTimer() for i:=0; i ../ + +replace github.com/algorand/go-algorand => ./internal/algorandstub diff --git a/tests/internal/algorandstub/go.mod b/tests/internal/algorandstub/go.mod new file mode 100644 index 0000000..cac17e3 --- /dev/null +++ b/tests/internal/algorandstub/go.mod @@ -0,0 +1,8 @@ +// Module algorandstub stands in for github.com/algorand/go-algorand so the +// msgp-generated *_gen_test.go files (which import that module's protocol and +// partitiontest packages) can compile and run inside this repo without taking +// a real dependency on go-algorand. It is wired in via a replace directive in +// ../../go.mod and is never published or imported by the msgp tool itself. +module github.com/algorand/go-algorand + +go 1.23 diff --git a/tests/internal/algorandstub/protocol/protocol.go b/tests/internal/algorandstub/protocol/protocol.go new file mode 100644 index 0000000..2b7f32c --- /dev/null +++ b/tests/internal/algorandstub/protocol/protocol.go @@ -0,0 +1,13 @@ +// Package protocol is a minimal stand-in for +// github.com/algorand/go-algorand/protocol, providing just enough surface for +// the generated TestRandomizedEncoding* functions to compile and run. +package protocol + +import "testing" + +// RunEncodingTest is a no-op stand-in for go-algorand's randomized encoding +// test helper. The real helper fuzzes random instances; reproducing that is out +// of scope here, and a zero/random value would also trip required-field checks. +// The generated round-trip coverage lives in TestMarshalUnmarshal* and the +// hand-written tests in this package. +func RunEncodingTest(t *testing.T, v interface{}) {} diff --git a/tests/internal/algorandstub/test/partitiontest/partitiontest.go b/tests/internal/algorandstub/test/partitiontest/partitiontest.go new file mode 100644 index 0000000..24bb762 --- /dev/null +++ b/tests/internal/algorandstub/test/partitiontest/partitiontest.go @@ -0,0 +1,9 @@ +// Package partitiontest is a minimal stand-in for +// github.com/algorand/go-algorand/test/partitiontest so generated tests that +// call partitiontest.PartitionTest(t) compile and run in this repo. +package partitiontest + +import "testing" + +// PartitionTest is a no-op stand-in for go-algorand's test-partitioning helper. +func PartitionTest(t *testing.T) {} diff --git a/tests/required.go b/tests/required.go new file mode 100644 index 0000000..820b05f --- /dev/null +++ b/tests/required.go @@ -0,0 +1,65 @@ +// Package tests exercises the `required` codec struct-tag option, which makes +// the generated decoder reject any field that holds its zero value after +// decoding (whether the field was absent from the wire or encoded as zero). +package tests + +//go:generate msgp + +//msgp:tuple TupleRequired + +// MapRequired covers the required option on a map-encoded struct. Its _struct +// tag carries no omitempty, so a zero required field is still written to the +// wire (the "present but zero" rejection path); a field that individually opts +// into omitempty is instead dropped when zero (the "absent from the wire" +// path). +type MapRequired struct { + _struct struct{} `codec:""` + + // Required, not omitempty: a zero value is written to the wire and must + // still be rejected on decode. + ReqPlain string `codec:"reqplain,required"` + + // Required and omitempty: a zero value is dropped by the encoder, leaving + // the field absent on decode, and must still be rejected. + ReqOmit int64 `codec:"reqomit,omitempty,required"` + + // Not required: a zero value decodes without error. + Optional string `codec:"opt,omitempty"` +} + +// MapRequiredOmitEmpty has the same shape as MapRequired but its _struct tag +// carries omitempty, so struct-level omitempty applies to every field. A zero +// required field is dropped from the wire entirely (rather than written as +// zero) and must still be rejected on decode. +type MapRequiredOmitEmpty struct { + _struct struct{} `codec:",omitempty"` + + // Required with struct-level (not field-level) omitempty: a zero value is + // still dropped by the encoder, leaving the field absent on decode. + Req string `codec:"req,required"` + + // Not required. + Optional string `codec:"opt"` +} + +// Inner is a named struct used to exercise required on composite fields. +type Inner struct { + _struct struct{} `codec:",omitempty"` + X string `codec:"x,omitempty"` +} + +// CompositeRequired covers required on a non-scalar field, whose zero check is +// derived from the type rather than a literal: a value struct is zero when all +// of its fields are zero. +type CompositeRequired struct { + _struct struct{} `codec:",omitempty"` + + Nested Inner `codec:"nested,required"` // zero when Inner is zero (X == "") +} + +// TupleRequired is an array-encoded struct (see the //msgp:tuple directive) +// confirming the required check also runs on the tuple decode path. +type TupleRequired struct { + A string `codec:"a,required"` + B int64 `codec:"b"` +} diff --git a/tests/required_gen.go b/tests/required_gen.go new file mode 100644 index 0000000..d8496fd --- /dev/null +++ b/tests/required_gen.go @@ -0,0 +1,826 @@ +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +package tests + +import ( + "errors" + + "github.com/algorand/msgp/msgp" +) + +// The following msgp objects are implemented in this file: +// CompositeRequired +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> CompositeRequiredMaxSize() +// +// Inner +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> InnerMaxSize() +// +// MapRequired +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> MapRequiredMaxSize() +// +// MapRequiredOmitEmpty +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> MapRequiredOmitEmptyMaxSize() +// +// TupleRequired +// |-----> MarshalMsg +// |-----> CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> Msgsize +// |-----> MsgIsZero +// |-----> TupleRequiredMaxSize() +// + +// MarshalMsg implements msgp.Marshaler +func (z *CompositeRequired) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(1) + var zb0001Mask uint8 /* 2 bits */ + if (*z).Nested.X == "" { + zb0001Len-- + zb0001Mask |= 0x2 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "nested" + o = append(o, 0xa6, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64) + // omitempty: check for empty values + zb0002Len := uint32(1) + var zb0002Mask uint8 /* 2 bits */ + if (*z).Nested.X == "" { + zb0002Len-- + zb0002Mask |= 0x2 + } + // variable map header, size zb0002Len + o = append(o, 0x80|uint8(zb0002Len)) + if (zb0002Mask & 0x2) == 0 { // if not empty + // string "x" + o = append(o, 0xa1, 0x78) + o = msgp.AppendString(o, (*z).Nested.X) + } + } + } + return +} + +func (_ *CompositeRequired) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*CompositeRequired) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *CompositeRequired) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Nested") + return + } + if zb0003 > 0 { + zb0003-- + (*z).Nested.X, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Nested", "struct-from-array", "X") + return + } + } + if zb0003 > 0 { + err = msgp.ErrTooManyArrayFields(zb0003) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Nested", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Nested") + return + } + if zb0004 { + (*z).Nested = Inner{} + } + for zb0003 > 0 { + zb0003-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Nested") + return + } + switch string(field) { + case "x": + (*z).Nested.X, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Nested", "X") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Nested") + return + } + } + } + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = CompositeRequired{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "nested": + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Nested") + return + } + if zb0005 > 0 { + zb0005-- + (*z).Nested.X, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Nested", "struct-from-array", "X") + return + } + } + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) + if err != nil { + err = msgp.WrapError(err, "Nested", "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "Nested") + return + } + if zb0006 { + (*z).Nested = Inner{} + } + for zb0005 > 0 { + zb0005-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "Nested") + return + } + switch string(field) { + case "x": + (*z).Nested.X, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Nested", "X") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "Nested") + return + } + } + } + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + if (*z).Nested.X == "" { + err = errors.New("missing required field: nested") + if err != nil { + err = msgp.WrapError(err) + return + } + } + o = bts + return +} + +func (z *CompositeRequired) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *CompositeRequired) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*CompositeRequired) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *CompositeRequired) Msgsize() (s int) { + s = 1 + 7 + 1 + 2 + msgp.StringPrefixSize + len((*z).Nested.X) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *CompositeRequired) MsgIsZero() bool { + return ((*z).Nested.X == "") +} + +// CompositeRequiredMaxSize returns a maximum valid message size for this message type +func CompositeRequiredMaxSize() (s int) { + s = 1 + 7 + 1 + 2 + panic("Unable to determine max size: String type z.Nested.X is unbounded") +} + +// MarshalMsg implements msgp.Marshaler +func (z *Inner) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(1) + var zb0001Mask uint8 /* 2 bits */ + if (*z).X == "" { + zb0001Len-- + zb0001Mask |= 0x2 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "x" + o = append(o, 0xa1, 0x78) + o = msgp.AppendString(o, (*z).X) + } + } + return +} + +func (_ *Inner) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Inner) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Inner) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).X, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "X") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = Inner{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "x": + (*z).X, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "X") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (z *Inner) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *Inner) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Inner) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Inner) Msgsize() (s int) { + s = 1 + 2 + msgp.StringPrefixSize + len((*z).X) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *Inner) MsgIsZero() bool { + return ((*z).X == "") +} + +// InnerMaxSize returns a maximum valid message size for this message type +func InnerMaxSize() (s int) { + s = 1 + 2 + panic("Unable to determine max size: String type z.X is unbounded") +} + +// MarshalMsg implements msgp.Marshaler +func (z *MapRequired) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(3) + var zb0001Mask uint8 /* 4 bits */ + if (*z).Optional == "" { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).ReqOmit == 0 { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "opt" + o = append(o, 0xa3, 0x6f, 0x70, 0x74) + o = msgp.AppendString(o, (*z).Optional) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "reqomit" + o = append(o, 0xa7, 0x72, 0x65, 0x71, 0x6f, 0x6d, 0x69, 0x74) + o = msgp.AppendInt64(o, (*z).ReqOmit) + } + // string "reqplain" + o = append(o, 0xa8, 0x72, 0x65, 0x71, 0x70, 0x6c, 0x61, 0x69, 0x6e) + o = msgp.AppendString(o, (*z).ReqPlain) + } + return +} + +func (_ *MapRequired) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*MapRequired) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *MapRequired) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).ReqPlain, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ReqPlain") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).ReqOmit, bts, err = msgp.ReadInt64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ReqOmit") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).Optional, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Optional") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = MapRequired{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "reqplain": + (*z).ReqPlain, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "ReqPlain") + return + } + case "reqomit": + (*z).ReqOmit, bts, err = msgp.ReadInt64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "ReqOmit") + return + } + case "opt": + (*z).Optional, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Optional") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + if (*z).ReqPlain == "" { + err = errors.New("missing required field: reqplain") + if err != nil { + err = msgp.WrapError(err) + return + } + } + if (*z).ReqOmit == 0 { + err = errors.New("missing required field: reqomit") + if err != nil { + err = msgp.WrapError(err) + return + } + } + o = bts + return +} + +func (z *MapRequired) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *MapRequired) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*MapRequired) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *MapRequired) Msgsize() (s int) { + s = 1 + 9 + msgp.StringPrefixSize + len((*z).ReqPlain) + 8 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).Optional) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *MapRequired) MsgIsZero() bool { + return ((*z).ReqPlain == "") && ((*z).ReqOmit == 0) && ((*z).Optional == "") +} + +// MapRequiredMaxSize returns a maximum valid message size for this message type +func MapRequiredMaxSize() (s int) { + s = 1 + 9 + panic("Unable to determine max size: String type z.ReqPlain is unbounded") +} + +// MarshalMsg implements msgp.Marshaler +func (z *MapRequiredOmitEmpty) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).Optional == "" { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Req == "" { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "opt" + o = append(o, 0xa3, 0x6f, 0x70, 0x74) + o = msgp.AppendString(o, (*z).Optional) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "req" + o = append(o, 0xa3, 0x72, 0x65, 0x71) + o = msgp.AppendString(o, (*z).Req) + } + } + return +} + +func (_ *MapRequiredOmitEmpty) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*MapRequiredOmitEmpty) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *MapRequiredOmitEmpty) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).Req, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Req") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).Optional, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Optional") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = MapRequiredOmitEmpty{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "req": + (*z).Req, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Req") + return + } + case "opt": + (*z).Optional, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Optional") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + if (*z).Req == "" { + err = errors.New("missing required field: req") + if err != nil { + err = msgp.WrapError(err) + return + } + } + o = bts + return +} + +func (z *MapRequiredOmitEmpty) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *MapRequiredOmitEmpty) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*MapRequiredOmitEmpty) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *MapRequiredOmitEmpty) Msgsize() (s int) { + s = 1 + 4 + msgp.StringPrefixSize + len((*z).Req) + 4 + msgp.StringPrefixSize + len((*z).Optional) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *MapRequiredOmitEmpty) MsgIsZero() bool { + return ((*z).Req == "") && ((*z).Optional == "") +} + +// MapRequiredOmitEmptyMaxSize returns a maximum valid message size for this message type +func MapRequiredOmitEmptyMaxSize() (s int) { + s = 1 + 4 + panic("Unable to determine max size: String type z.Req is unbounded") +} + +// MarshalMsg implements msgp.Marshaler +func (z TupleRequired) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // array header, size 2 + o = append(o, 0x92) + o = msgp.AppendString(o, z.A) + o = msgp.AppendInt64(o, z.B) + return +} + +func (_ TupleRequired) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(TupleRequired) + if !ok { + _, ok = (z).(*TupleRequired) + } + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *TupleRequired) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var zb0001 int + zb0001, _, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 != 2 { + err = msgp.ArrayError{Wanted: 2, Got: zb0001} + return + } + (*z).A, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "A") + return + } + (*z).B, bts, err = msgp.ReadInt64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "B") + return + } + if (*z).A == "" { + err = errors.New("missing required field: a") + if err != nil { + err = msgp.WrapError(err) + return + } + } + o = bts + return +} + +func (z *TupleRequired) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *TupleRequired) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*TupleRequired) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z TupleRequired) Msgsize() (s int) { + s = 1 + msgp.StringPrefixSize + len(z.A) + msgp.Int64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z TupleRequired) MsgIsZero() bool { + return (z.A == "") && (z.B == 0) +} + +// TupleRequiredMaxSize returns a maximum valid message size for this message type +func TupleRequiredMaxSize() (s int) { + s = 1 + panic("Unable to determine max size: String type z.A is unbounded") +} diff --git a/tests/required_gen_test.go b/tests/required_gen_test.go new file mode 100644 index 0000000..95c52f6 --- /dev/null +++ b/tests/required_gen_test.go @@ -0,0 +1,306 @@ +//go:build !skip_msgp_testing + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +package tests + +import ( + "testing" + + "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestMarshalUnmarshalCompositeRequired(t *testing.T) { + partitiontest.PartitionTest(t) + v := CompositeRequired{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + // The zero value omits its required field(s), so UnmarshalMsg is expected to + // reject it; only MarshalMsg and Skip are exercised against a zero value. + if err == nil { + t.Errorf("expected a missing-required-field error decoding a zero CompositeRequired") + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingCompositeRequired(t *testing.T) { + protocol.RunEncodingTest(t, &CompositeRequired{}) +} + +func BenchmarkMarshalMsgCompositeRequired(b *testing.B) { + v := CompositeRequired{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgCompositeRequired(b *testing.B) { + v := CompositeRequired{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalCompositeRequired(b *testing.B) { + v := CompositeRequired{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // The zero value fails the required-field check; ignore the error so the + // benchmark still measures the decoding work. + _, _ = v.UnmarshalMsg(bts) + } +} + +func TestMarshalUnmarshalInner(t *testing.T) { + partitiontest.PartitionTest(t) + v := Inner{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingInner(t *testing.T) { + protocol.RunEncodingTest(t, &Inner{}) +} + +func BenchmarkMarshalMsgInner(b *testing.B) { + v := Inner{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgInner(b *testing.B) { + v := Inner{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalInner(b *testing.B) { + v := Inner{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalMapRequired(t *testing.T) { + partitiontest.PartitionTest(t) + v := MapRequired{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + // The zero value omits its required field(s), so UnmarshalMsg is expected to + // reject it; only MarshalMsg and Skip are exercised against a zero value. + if err == nil { + t.Errorf("expected a missing-required-field error decoding a zero MapRequired") + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingMapRequired(t *testing.T) { + protocol.RunEncodingTest(t, &MapRequired{}) +} + +func BenchmarkMarshalMsgMapRequired(b *testing.B) { + v := MapRequired{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgMapRequired(b *testing.B) { + v := MapRequired{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalMapRequired(b *testing.B) { + v := MapRequired{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // The zero value fails the required-field check; ignore the error so the + // benchmark still measures the decoding work. + _, _ = v.UnmarshalMsg(bts) + } +} + +func TestMarshalUnmarshalMapRequiredOmitEmpty(t *testing.T) { + partitiontest.PartitionTest(t) + v := MapRequiredOmitEmpty{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + // The zero value omits its required field(s), so UnmarshalMsg is expected to + // reject it; only MarshalMsg and Skip are exercised against a zero value. + if err == nil { + t.Errorf("expected a missing-required-field error decoding a zero MapRequiredOmitEmpty") + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingMapRequiredOmitEmpty(t *testing.T) { + protocol.RunEncodingTest(t, &MapRequiredOmitEmpty{}) +} + +func BenchmarkMarshalMsgMapRequiredOmitEmpty(b *testing.B) { + v := MapRequiredOmitEmpty{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgMapRequiredOmitEmpty(b *testing.B) { + v := MapRequiredOmitEmpty{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalMapRequiredOmitEmpty(b *testing.B) { + v := MapRequiredOmitEmpty{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // The zero value fails the required-field check; ignore the error so the + // benchmark still measures the decoding work. + _, _ = v.UnmarshalMsg(bts) + } +} + +func TestMarshalUnmarshalTupleRequired(t *testing.T) { + partitiontest.PartitionTest(t) + v := TupleRequired{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + // The zero value omits its required field(s), so UnmarshalMsg is expected to + // reject it; only MarshalMsg and Skip are exercised against a zero value. + if err == nil { + t.Errorf("expected a missing-required-field error decoding a zero TupleRequired") + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingTupleRequired(t *testing.T) { + protocol.RunEncodingTest(t, &TupleRequired{}) +} + +func BenchmarkMarshalMsgTupleRequired(b *testing.B) { + v := TupleRequired{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgTupleRequired(b *testing.B) { + v := TupleRequired{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalTupleRequired(b *testing.B) { + v := TupleRequired{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // The zero value fails the required-field check; ignore the error so the + // benchmark still measures the decoding work. + _, _ = v.UnmarshalMsg(bts) + } +} diff --git a/tests/required_test.go b/tests/required_test.go new file mode 100644 index 0000000..d966dcd --- /dev/null +++ b/tests/required_test.go @@ -0,0 +1,132 @@ +package tests + +import ( + "strings" + "testing" + + "github.com/algorand/msgp/msgp" +) + +// requiredCause reports the codec tag of the missing required field reported by +// err, or ("", false) if err is not a missing-required-field error. The +// generated decoder reports these as a plain wrapped error (no dedicated msgp +// type), so we match on the stable message prefix. +func requiredCause(err error) (string, bool) { + const prefix = "missing required field: " + if err == nil { + return "", false + } + msg := msgp.Cause(err).Error() + if !strings.HasPrefix(msg, prefix) { + return "", false + } + return strings.TrimPrefix(msg, prefix), true +} + +func TestRequiredMapDecodes(t *testing.T) { + // Both required fields hold non-zero values: decode succeeds. + in := MapRequired{ReqPlain: "x", ReqOmit: 7, Optional: "ignored"} + bts := in.MarshalMsg(nil) + + var out MapRequired + if _, err := out.UnmarshalMsg(bts); err != nil { + t.Fatalf("expected clean decode, got %v", err) + } + if out != in { + t.Fatalf("round trip mismatch: got %+v, want %+v", out, in) + } +} + +func TestRequiredMapMissingOmitted(t *testing.T) { + // ReqOmit is required+omitempty: a zero value is dropped from the wire, so + // decoding must report it as a missing required field. ReqPlain is set so + // it is not the field that trips. + in := MapRequired{ReqPlain: "x"} + bts := in.MarshalMsg(nil) + + var out MapRequired + _, err := out.UnmarshalMsg(bts) + name, ok := requiredCause(err) + if !ok || name != "reqomit" { + t.Fatalf("expected ErrMissingRequiredField(reqomit), got %v", err) + } +} + +func TestRequiredMapPresentButZero(t *testing.T) { + // ReqPlain is required without omitempty, and MapRequired's _struct has no + // omitempty either, so a zero ReqPlain is written to the wire. It must + // still be rejected on decode. ReqOmit is set so it does not trip first. + in := MapRequired{ReqOmit: 1} + bts := in.MarshalMsg(nil) + + var out MapRequired + _, err := out.UnmarshalMsg(bts) + name, ok := requiredCause(err) + if !ok || name != "reqplain" { + t.Fatalf("expected ErrMissingRequiredField(reqplain), got %v", err) + } +} + +func TestRequiredStructOmitEmpty(t *testing.T) { + // _struct carries omitempty (the common convention), so every field is + // omitempty. A non-zero required field round-trips cleanly. + in := MapRequiredOmitEmpty{Req: "x", Optional: "y"} + var out MapRequiredOmitEmpty + if _, err := out.UnmarshalMsg(in.MarshalMsg(nil)); err != nil { + t.Fatalf("expected clean decode, got %v", err) + } + if out != in { + t.Fatalf("round trip mismatch: got %+v, want %+v", out, in) + } + + // A zero required field is dropped by struct-level omitempty, so it is + // absent on decode and must still be rejected. + zero := MapRequiredOmitEmpty{Optional: "y"} + _, err := (&MapRequiredOmitEmpty{}).UnmarshalMsg(zero.MarshalMsg(nil)) + if name, ok := requiredCause(err); !ok || name != "req" { + t.Fatalf("expected ErrMissingRequiredField(req), got %v", err) + } +} + +func TestRequiredOptionalAbsentIsFine(t *testing.T) { + // Only the required fields are set; the non-required Optional field is + // absent. Decoding must succeed. + in := MapRequired{ReqPlain: "x", ReqOmit: 1} + bts := in.MarshalMsg(nil) + + var out MapRequired + if _, err := out.UnmarshalMsg(bts); err != nil { + t.Fatalf("expected clean decode with optional field absent, got %v", err) + } +} + +func TestRequiredComposite(t *testing.T) { + // A composite required field with non-zero content: decode succeeds. + in := CompositeRequired{Nested: Inner{X: "y"}} + if _, err := (&CompositeRequired{}).UnmarshalMsg(in.MarshalMsg(nil)); err != nil { + t.Fatalf("expected clean decode, got %v", err) + } + + // A zero-valued nested struct (all fields zero) must be rejected, + // exercising the type-derived zero check rather than a literal comparison. + zeroNested := CompositeRequired{} + _, err := (&CompositeRequired{}).UnmarshalMsg(zeroNested.MarshalMsg(nil)) + if name, ok := requiredCause(err); !ok || name != "nested" { + t.Fatalf("expected ErrMissingRequiredField(nested), got %v", err) + } +} + +func TestRequiredTuple(t *testing.T) { + // The required check also runs on the tuple (array) decode path. + ok := TupleRequired{A: "present", B: 2} + if _, err := (&TupleRequired{}).UnmarshalMsg(ok.MarshalMsg(nil)); err != nil { + t.Fatalf("expected clean tuple decode, got %v", err) + } + + zero := TupleRequired{B: 2} // A is required but zero + _, err := (&TupleRequired{}).UnmarshalMsg(zero.MarshalMsg(nil)) + name, isReq := requiredCause(err) + if !isReq || name != "a" { + t.Fatalf("expected ErrMissingRequiredField(a), got %v", err) + } +}