Skip to content

Commit

Permalink
refactor: mv rapidproto from sdk -> cosmos-proto (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
kocubinski authored Feb 7, 2023
1 parent 7348c40 commit e7b0579
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/google/go-cmp v0.5.9
github.com/stretchr/testify v1.8.1
google.golang.org/protobuf v1.28.1
gotest.tools/v3 v3.4.0
pgregory.net/rapid v0.5.5
)

Expand Down
24 changes: 24 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,29 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
Expand All @@ -23,5 +45,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA=
pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
221 changes: 221 additions & 0 deletions rapidproto/rapidproto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package rapidproto

import (
"fmt"

"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"gotest.tools/v3/assert"
"pgregory.net/rapid"
)

func MessageGenerator[T proto.Message](x T, options GeneratorOptions) *rapid.Generator[T] {
msgType := x.ProtoReflect().Type()
return rapid.Custom(func(t *rapid.T) T {
msg := msgType.New()

options.setFields(t, msg, 0)

return msg.Interface().(T)
})
}

type GeneratorOptions struct {
AnyTypeURLs []string
Resolver protoregistry.MessageTypeResolver
}

const depthLimit = 10

func (opts GeneratorOptions) setFields(t *rapid.T, msg protoreflect.Message, depth int) bool {
// to avoid stack overflow we limit the depth of nested messages
if depth > depthLimit {
return false
}

descriptor := msg.Descriptor()
fullName := descriptor.FullName()
switch fullName {
case timestampFullName:
opts.genTimestamp(t, msg)
return true
case durationFullName:
opts.genDuration(t, msg)
return true
case anyFullName:
return opts.genAny(t, msg, depth)
case fieldMaskFullName:
opts.genFieldMask(t, msg)
return true
default:
fields := descriptor.Fields()
n := fields.Len()
for i := 0; i < n; i++ {
field := fields.Get(i)
if !rapid.Bool().Draw(t, fmt.Sprintf("gen-%s", field.Name())) {
continue
}

opts.setFieldValue(t, msg, field, depth)
}
return true
}
}

const (
timestampFullName = "google.protobuf.Timestamp"
durationFullName = "google.protobuf.Duration"
anyFullName = "google.protobuf.Any"
fieldMaskFullName = "google.protobuf.FieldMask"
)

func (opts GeneratorOptions) setFieldValue(t *rapid.T, msg protoreflect.Message, field protoreflect.FieldDescriptor, depth int) {
name := string(field.Name())
kind := field.Kind()

switch {
case field.IsList():
list := msg.Mutable(field).List()
n := rapid.IntRange(0, 10).Draw(t, fmt.Sprintf("%sN", name))
for i := 0; i < n; i++ {
if kind == protoreflect.MessageKind || kind == protoreflect.GroupKind {
if !opts.setFields(t, list.AppendMutable().Message(), depth+1) {
list.Truncate(i)
}
} else {
list.Append(opts.genScalarFieldValue(t, field, fmt.Sprintf("%s%d", name, i)))
}
}
case field.IsMap():
m := msg.Mutable(field).Map()
n := rapid.IntRange(0, 10).Draw(t, fmt.Sprintf("%sN", name))
for i := 0; i < n; i++ {
keyField := field.MapKey()
valueField := field.MapValue()
valueKind := valueField.Kind()
key := opts.genScalarFieldValue(t, keyField, fmt.Sprintf("%s%d-key", name, i))
if valueKind == protoreflect.MessageKind || valueKind == protoreflect.GroupKind {
if !opts.setFields(t, m.Mutable(key.MapKey()).Message(), depth+1) {
m.Clear(key.MapKey())
}
} else {
value := opts.genScalarFieldValue(t, valueField, fmt.Sprintf("%s%d-key", name, i))
m.Set(key.MapKey(), value)
}
}
default:
if kind == protoreflect.MessageKind || kind == protoreflect.GroupKind {
if !opts.setFields(t, msg.Mutable(field).Message(), depth+1) {
msg.Clear(field)
}
} else {
msg.Set(field, opts.genScalarFieldValue(t, field, name))
}
}
}

func (opts GeneratorOptions) genScalarFieldValue(t *rapid.T, field protoreflect.FieldDescriptor, name string) protoreflect.Value {
switch field.Kind() {
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
return protoreflect.ValueOfInt32(rapid.Int32().Draw(t, name))
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
return protoreflect.ValueOfUint32(rapid.Uint32().Draw(t, name))
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
return protoreflect.ValueOfInt64(rapid.Int64().Draw(t, name))
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
return protoreflect.ValueOfUint64(rapid.Uint64().Draw(t, name))
case protoreflect.BoolKind:
return protoreflect.ValueOfBool(rapid.Bool().Draw(t, name))
case protoreflect.BytesKind:
return protoreflect.ValueOfBytes(rapid.SliceOf(rapid.Byte()).Draw(t, name))
case protoreflect.FloatKind:
return protoreflect.ValueOfFloat32(rapid.Float32().Draw(t, name))
case protoreflect.DoubleKind:
return protoreflect.ValueOfFloat64(rapid.Float64().Draw(t, name))
case protoreflect.EnumKind:
enumValues := field.Enum().Values()
val := rapid.Int32Range(0, int32(enumValues.Len()-1)).Draw(t, name)
return protoreflect.ValueOfEnum(protoreflect.EnumNumber(val))
case protoreflect.StringKind:
return protoreflect.ValueOfString(rapid.String().Draw(t, name))
default:
t.Fatalf("unexpected %v", field)
return protoreflect.Value{}
}
}

const (
secondsName = "seconds"
nanosName = "nanos"
)

func (opts GeneratorOptions) genTimestamp(t *rapid.T, msg protoreflect.Message) {
seconds := rapid.Int64Range(-9999999999, 9999999999).Draw(t, "seconds")
nanos := rapid.Int32Range(0, 999999999).Draw(t, "nanos")
setSecondsNanosFields(t, msg, seconds, nanos)
}

func (opts GeneratorOptions) genDuration(t *rapid.T, msg protoreflect.Message) {
seconds := rapid.Int64Range(0, 315576000000).Draw(t, "seconds")
nanos := rapid.Int32Range(0, 999999999).Draw(t, "nanos")
setSecondsNanosFields(t, msg, seconds, nanos)
}

func setSecondsNanosFields(t *rapid.T, message protoreflect.Message, seconds int64, nanos int32) {
fields := message.Descriptor().Fields()

secondsField := fields.ByName(secondsName)
assert.Assert(t, secondsField != nil)
message.Set(secondsField, protoreflect.ValueOfInt64(seconds))

nanosField := fields.ByName(nanosName)
assert.Assert(t, nanosField != nil)
message.Set(nanosField, protoreflect.ValueOfInt32(nanos))
}

const (
typeURLName = "type_url"
valueName = "value"
)

func (opts GeneratorOptions) genAny(t *rapid.T, msg protoreflect.Message, depth int) bool {
if len(opts.AnyTypeURLs) == 0 {
return false
}

fields := msg.Descriptor().Fields()

typeURL := rapid.SampledFrom(opts.AnyTypeURLs).Draw(t, "type_url")
typ, err := opts.Resolver.FindMessageByURL(typeURL)
assert.NilError(t, err)

typeURLField := fields.ByName(typeURLName)
assert.Assert(t, typeURLField != nil)
msg.Set(typeURLField, protoreflect.ValueOfString(typeURL))

valueMsg := typ.New()
opts.setFields(t, valueMsg, depth+1)
valueBz, err := proto.Marshal(valueMsg.Interface())
assert.NilError(t, err)

valueField := fields.ByName(valueName)
assert.Assert(t, valueField != nil)
msg.Set(valueField, protoreflect.ValueOfBytes(valueBz))

return true
}

const (
pathsName = "paths"
)

func (opts GeneratorOptions) genFieldMask(t *rapid.T, msg protoreflect.Message) {
paths := rapid.SliceOfN(rapid.StringMatching("[a-z]+([.][a-z]+){0,2}"), 1, 5).Draw(t, "paths")
pathsField := msg.Descriptor().Fields().ByName(pathsName)
assert.Assert(t, pathsField != nil)
pathsList := msg.NewField(pathsField).List()
for _, path := range paths {
pathsList.Append(protoreflect.ValueOfString(path))
}
}
32 changes: 32 additions & 0 deletions rapidproto/rapidproto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package rapidproto_test

import (
"fmt"
"testing"

"github.com/cosmos/cosmos-proto/rapidproto"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
"pgregory.net/rapid"

"github.com/cosmos/cosmos-proto/testpb"
)

// TestRegression checks that the generator still produces the same output
// for the same random seeds, assuming that this data has been hand expected
// to generally look good.
func TestRegression(t *testing.T) {
gen := rapidproto.MessageGenerator(&testpb.A{}, rapidproto.GeneratorOptions{})
for i := 0; i < 5; i++ {
testRegressionSeed(t, i, gen)
}
}

func testRegressionSeed[X proto.Message](t *testing.T, seed int, generator *rapid.Generator[X]) {
x := generator.Example(seed)
bz, err := protojson.Marshal(x)
assert.NilError(t, err)
golden.Assert(t, string(bz), fmt.Sprintf("seed%d.json", seed))
}
1 change: 1 addition & 0 deletions rapidproto/testdata/seed0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"enum":"Two", "someBoolean":true, "INT32":6, "SINT32":-53, "INT64":"-261", "SFIXED32":3, "FIXED32":65302, "FIXED64":"45044", "STRING":"󳲠~Âaႃ#", "MESSAGE":{"x":"ʰ="}, "MAP":{"":{"x":""}, "%󠇯º$&.":{"x":"-"}, "=A":{}, "AA|𞀠":{"x":"a\u0000"}}, "LIST":[{}], "ONEOFSTRING":"", "imported":{}}
1 change: 1 addition & 0 deletions rapidproto/testdata/seed1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"UINT32":177, "INT64":"-139958413", "SFIXED32":41418, "FIXED32":25381940, "FLOAT":-8.336453e+31, "SFIXED64":"-2503553836720", "DOUBLE":-0.03171187036377887, "STRING":"?˄~ע", "MESSAGE":{"x":"dDž#"}, "MAP":{"Ⱥa<":{"x":"+["}, "֑Ⱥ|@!`":{}}, "ONEOFSTRING":"\u0012\t?A", "imported":{}, "type":"A�=*ى~~‮Ⱥ*ᾈാȺAᶊ?"}
1 change: 1 addition & 0 deletions rapidproto/testdata/seed2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"INT32":-48, "UINT32":246, "INT64":"-21558176502", "SING64":"5030347", "UINT64":"28", "FIXED32":92, "DOUBLE":2.3547259926790202e-142, "STRING":"", "LIST":[{}, {}, {}, {}, {"x":" ᾚ DzA{˭҄\nA ^$?ᾦ,:<\"?_\u0014;|"}], "ONEOFSTRING":"𝟠Ÿ", "LISTENUM":["Two", "One", "One"]}
1 change: 1 addition & 0 deletions rapidproto/testdata/seed3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"INT32":22525032, "SINT32":897, "INT64":"-301128487533312", "SFIXED64":"-71", "FIXED64":"14", "DOUBLE":-2.983041182946181, "STRING":"-A^'", "MESSAGE":{"x":"#ऻ;́\r‮⋁"}, "LIST":[{}, {}, {}, {}, {}], "ONEOFSTRING":"", "imported":{}, "type":"\u0000^৴~౽  NjAৈ􁇸⃠𝖜ೄ"}
1 change: 1 addition & 0 deletions rapidproto/testdata/seed4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"SINT32":1, "INT64":"-9223372036854775808", "SING64":"1", "FLOAT":-0.00013906474, "SFIXED64":"71414010", "STRING":"ף̂", "MESSAGE":{"x":""}, "LIST":[{}], "ONEOFSTRING":"#¯∑Ⱥ�", "LISTENUM":["One", "One", "Two", "Two", "One", "One", "One", "Two"], "imported":{}, "type":"\u001b<ʰ+`𑱐@\u001b*Dž‮\u0000#₻\u0000"}

0 comments on commit e7b0579

Please sign in to comment.