test: x/tx/signing/textual: fuzz CoinsValueRenderer (#16521)

Co-authored-by: Marko <marbar3778@yahoo.com>
Co-authored-by: atheeshp <59333759+atheeshp@users.noreply.github.com>
This commit is contained in:
Emmanuel T Odeke 2023-06-29 23:21:28 -07:00 committed by GitHub
parent 13a752b063
commit 1afeca75a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 3 deletions

View File

@ -22,6 +22,7 @@ require (
github.com/cosmos/gogoproto v1.4.10 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.11.0 // indirect

View File

@ -20,8 +20,9 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

View File

@ -71,7 +71,7 @@ func checkCoinsEqual(t *testing.T, l1, l2 protoreflect.List) {
for i := 0; i < l1.Len(); i++ {
coin, ok := l1.Get(i).Message().Interface().(*basev1beta1.Coin)
require.True(t, ok)
require.True(t, ok, "not a *basev1beta1.Coin: %#v", l1.Get(i).Message().Interface())
coinsMap[coin.Denom] = coin
}
@ -90,7 +90,7 @@ func checkCoinEqual(t *testing.T, coin, coin1 *basev1beta1.Coin) {
require.True(t, ok)
v1, ok := math.NewIntFromString(coin1.Amount)
require.True(t, ok)
require.True(t, v.Equal(v1))
require.True(t, v.Equal(v1), "Mismatch\n\tv: %+v\n\tv1: %+v", v, v1)
}
// coinsJSONTest is the type of test cases in the testdata file.

View File

@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"os"
"regexp"
"testing"
"github.com/google/go-cmp/cmp"
@ -12,6 +13,7 @@ import (
"google.golang.org/protobuf/testing/protocmp"
tspb "google.golang.org/protobuf/types/known/timestamppb"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/x/tx/internal/testpb"
"cosmossdk.io/x/tx/signing/textual"
)
@ -211,3 +213,87 @@ func FuzzMessageValueRendererParse(f *testing.F) {
}
})
}
// Copied from types/coin.go but pasted in here so as to avoid any imports
// of that package as has been mandated by team decisions.
var (
reCoinDenom = regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9/:._-]{2,127}`)
reCoinAmount = regexp.MustCompile(`[[:digit:]]+(?:\.[[:digit:]]+)?|\.[[:digit:]]+`)
)
func FuzzCoinsJSONTestcases(f *testing.F) {
// Generate some seeds.
seed, err := os.ReadFile("./internal/testdata/coins.json")
if err != nil {
f.Fatal(err)
}
f.Add(seed)
txt, err := textual.NewSignModeHandler(textual.SignModeOptions{CoinMetadataQuerier: mockCoinMetadataQuerier})
if err != nil {
f.Fatal(err)
}
rend, err := txt.GetFieldValueRenderer(fieldDescriptorFromName("COINS"))
if err != nil {
f.Fatal(err)
}
vrr := rend.(textual.RepeatedValueRenderer)
f.Fuzz(func(t *testing.T, input []byte) {
var testCases []coinsJSONTest
if err := json.Unmarshal(input, &testCases); err != nil {
return
}
for _, tc := range testCases {
if tc.Proto == nil {
continue
}
// Create a context.Context containing all coins metadata, to simulate
// that they are in state.
ctx := context.Background()
for _, v := range tc.Metadata {
ctx = addMetadataToContext(ctx, v)
}
listValue := NewGenericList(tc.Proto)
screens, err := vrr.FormatRepeated(ctx, protoreflect.ValueOf(listValue))
if err != nil {
cpt := tc.Proto[0]
likeEmpty := err.Error() == "cannot format empty string" || err.Error() == "decimal string cannot be empty"
if likeEmpty && (!reCoinDenom.MatchString(cpt.Denom) || cpt.Amount == "") {
return
}
if !reCoinDenom.MatchString(cpt.Denom) {
return
}
if !reCoinAmount.MatchString(cpt.Amount) {
return
}
t.Fatalf("%v\n%q\n%#v => %t", err, tc.Text, cpt, cpt.Amount == "")
}
if g, w := len(screens), 1; g != w {
t.Fatalf("Screens mismatch: got=%d want=%d", g, w)
}
wantContent := tc.Text
if wantContent == "" {
wantContent = "zero"
}
if false {
if g, w := screens[0].Content, wantContent; g != w {
t.Fatalf("Content mismatch:\n\tGot: %s\n\tWant: %s", g, w)
}
}
// Round trip.
parsedValue := NewGenericList([]*basev1beta1.Coin{})
if err := vrr.ParseRepeated(ctx, screens, parsedValue); err != nil {
return
}
checkCoinsEqual(t, listValue, parsedValue)
}
})
}

View File

@ -0,0 +1,2 @@
go test fuzz v1
[]byte("[{\"proto\":[{\"Amount\":\"0\"},{\"Amount\":\"1\"}]}]")