Skip to content

Commit

Permalink
Fix flaky test in randomized ABI encoding test (#3346)
Browse files Browse the repository at this point in the history
* update abi encoding test random testcase generator, scale down parameters to avoid flaky test

* parameterized test script

* add notes to explain why flaky test is eliminated

* show more information from self-roundtrip testing

* fully utilize require, remove fmt
  • Loading branch information
ahangsu authored Dec 23, 2021
1 parent 4cb4241 commit 7da1026
Showing 1 changed file with 96 additions and 42 deletions.
138 changes: 96 additions & 42 deletions data/abi/abi_encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,58 @@ import (
"github.com/stretchr/testify/require"
)

const (
UintStepLength = 8
UintBegin = 8
UintEnd = 512
UintRandomTestPoints = 1000
UintTestCaseCount = 200
UfixedPrecision = 160
UfixedRandomTestPoints = 20
TupleMaxLength = 10
ByteTestCaseCount = 1 << 8
BoolTestCaseCount = 2
AddressTestCaseCount = 300
StringTestCaseCount = 10
StringTestCaseSpecLenCount = 5
TakeNum = 10
TupleTestCaseCount = 100
)

/*
The set of parameters ensure that the error of byte length >= 2^16 is eliminated.
i. Consider uint512[] with length 10, the ABI encoding length is: 64 x 10 + 2
(2 is introduced from dynamic array length encoding)
The motivation here is that, forall ABI type that is non-array/non-tuple like,
uint512 gives the longest byte length in ABI encoding
(utf-8 string's byte length is at most 42, address byte length is at most 32)
ii. Consider a tuple of length 10, with all elements uint512[] of length 10.
The ABI encoding length is: 10 x 2 + 10 x 642 == 6440
(2 is for tuple index to keep track of dynamic type encoding)
iii. Consider a tuple of length 10, with all elements of tuples mentioned in (ii).
The ABI encoding length is: 10 x 2 + 10 x 6440 == 64420
This is the end of the generation of nested-tuple test case,
no more layers of random tuples will be produced.
This gives an upper bound for the produced ABI encoding byte length in this test script,
and noticing that length 64420 mentioned in (iii) is less than 2^16 == 65536.
Assuming that ABI implementation is correct, then the flaky test should not happen again.
*/

func TestEncodeValid(t *testing.T) {
partitiontest.PartitionTest(t)

// encoding test for uint type, iterating through all uint sizes
// randomly pick 1000 valid uint values and check if encoded value match with expected
for intSize := 8; intSize <= 512; intSize += 8 {
for intSize := UintBegin; intSize <= UintEnd; intSize += UintStepLength {
upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize))
uintType, err := makeUintType(intSize)
require.NoError(t, err, "make uint type fail")

for i := 0; i < 1000; i++ {
for i := 0; i < UintRandomTestPoints; i++ {
randomInt, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")

Expand All @@ -64,17 +105,17 @@ func TestEncodeValid(t *testing.T) {
// encoding test for ufixed, iterating through all the valid ufixed bitSize and precision
// randomly generate 10 big int values for ufixed numerator and check if encoded value match with expected
// also check if ufixed can fit max numerator (2^bitSize - 1) under specific byte bitSize
for size := 8; size <= 512; size += 8 {
for size := UintBegin; size <= UintEnd; size += UintStepLength {
upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size))
largest := big.NewInt(0).Add(
upperLimit,
big.NewInt(1).Neg(big.NewInt(1)),
)
for precision := 1; precision <= 160; precision++ {
for precision := 1; precision <= UfixedPrecision; precision++ {
typeUfixed, err := makeUfixedType(size, precision)
require.NoError(t, err, "make ufixed type fail")

for i := 0; i < 10; i++ {
for i := 0; i < UfixedRandomTestPoints; i++ {
randomInt, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")

Expand All @@ -96,13 +137,13 @@ func TestEncodeValid(t *testing.T) {

// encoding test for address, since address is 32 byte, it can be considered as 256 bit uint
// randomly generate 1000 uint256 and make address values, check if encoded value match with expected
upperLimit := big.NewInt(0).Lsh(big.NewInt(1), 256)
for i := 0; i < 1000; i++ {
upperLimit := big.NewInt(0).Lsh(big.NewInt(1), addressByteSize<<3)
for i := 0; i < UintRandomTestPoints; i++ {
randomAddrInt, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")

rand256Bytes := randomAddrInt.Bytes()
addrBytesExpected := make([]byte, 32-len(rand256Bytes))
addrBytesExpected := make([]byte, addressByteSize-len(rand256Bytes))
addrBytesExpected = append(addrBytesExpected, rand256Bytes...)

addrBytesActual, err := addressType.Encode(addrBytesExpected)
Expand All @@ -111,7 +152,7 @@ func TestEncodeValid(t *testing.T) {
}

// encoding test for bool values
for i := 0; i < 2; i++ {
for i := 0; i < BoolTestCaseCount; i++ {
boolEncode, err := boolType.Encode(i == 1)
require.NoError(t, err, "bool encode fail")
expected := []byte{0x00}
Expand All @@ -122,7 +163,7 @@ func TestEncodeValid(t *testing.T) {
}

// encoding test for byte values
for i := 0; i < (1 << 8); i++ {
for i := 0; i < ByteTestCaseCount; i++ {
byteEncode, err := byteType.Encode(byte(i))
require.NoError(t, err, "byte encode fail")
expected := []byte{byte(i)}
Expand All @@ -133,8 +174,8 @@ func TestEncodeValid(t *testing.T) {
// we use `gobberish` to generate random utf-8 symbols
// randomly generate utf-8 str from length 1 to 100, each length draw 10 random strs
// check if encoded ABI str match with expected value
for length := 1; length <= 100; length++ {
for i := 0; i < 10; i++ {
for length := 1; length <= StringTestCaseCount; length++ {
for i := 0; i < StringTestCaseSpecLenCount; i++ {
// generate utf8 strings from `gobberish` at some length
utf8Str := gobberish.GenerateString(length)
// since string is just type alias of `byte[]`, we need to store number of bytes in encoding
Expand Down Expand Up @@ -828,35 +869,48 @@ type testUnit struct {
func categorySelfRoundTripTest(t *testing.T, category []testUnit) {
for _, testObj := range category {
abiType, err := TypeOf(testObj.serializedType)
require.NoError(t, err, "failure to deserialize type")
require.NoError(t, err, "failure to deserialize type: "+testObj.serializedType)
encodedValue, err := abiType.Encode(testObj.value)
require.NoError(t, err, "failure to encode value")
require.NoError(t, err,
"failure to encode value %#v over type %s", testObj.value, testObj.serializedType,
)
actual, err := abiType.Decode(encodedValue)
require.NoError(t, err, "failure to decode value")
require.Equal(t, testObj.value, actual, "decoded value not equal to expected")
require.NoError(t, err,
"failure to decode value %#v for type %s", encodedValue, testObj.serializedType,
)
require.Equal(t, testObj.value, actual,
"decoded value %#v not equal to expected value %#v", actual, testObj.value,
)
jsonEncodedValue, err := abiType.MarshalToJSON(testObj.value)
require.NoError(t, err, "failure to encode value to JSON type")
require.NoError(t, err,
"failure to encode value %#v to JSON type", testObj.value,
)
jsonActual, err := abiType.UnmarshalFromJSON(jsonEncodedValue)
require.NoError(t, err, "failure to decode JSON value back")
require.Equal(t, testObj.value, jsonActual, "decode JSON value not equal to expected")
require.NoError(t, err,
"failure to decode JSON value %s back for type %s",
string(jsonEncodedValue), testObj.serializedType,
)
require.Equal(t, testObj.value, jsonActual,
"decode JSON value %s not equal to expected %s", jsonActual, testObj.value,
)
}
}

func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
(*pool)[Uint] = make([]testUnit, 200*64)
(*pool)[Ufixed] = make([]testUnit, 160*64)
(*pool)[Uint] = make([]testUnit, UintTestCaseCount*UintEnd/UintStepLength)
(*pool)[Ufixed] = make([]testUnit, UfixedPrecision*UintEnd/UintStepLength)

uintIndex := 0
ufixedIndex := 0

for bitSize := 8; bitSize <= 512; bitSize += 8 {
for bitSize := UintBegin; bitSize <= UintEnd; bitSize += UintStepLength {
max := new(big.Int).Lsh(big.NewInt(1), uint(bitSize))

uintT, err := makeUintType(bitSize)
require.NoError(t, err, "make uint type failure")
uintTstr := uintT.String()

for j := 0; j < 200; j++ {
for j := 0; j < UintTestCaseCount; j++ {
randVal, err := rand.Int(rand.Reader, max)
require.NoError(t, err, "generate random uint, should be no error")

Expand All @@ -867,7 +921,7 @@ func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
uintIndex++
}

for precision := 1; precision <= 160; precision++ {
for precision := 1; precision <= UfixedPrecision; precision++ {
randVal, err := rand.Int(rand.Reader, max)
require.NoError(t, err, "generate random ufixed, should be no error")

Expand All @@ -884,33 +938,33 @@ func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
categorySelfRoundTripTest(t, (*pool)[Uint])
categorySelfRoundTripTest(t, (*pool)[Ufixed])

(*pool)[Byte] = make([]testUnit, 1<<8)
for i := 0; i < (1 << 8); i++ {
(*pool)[Byte] = make([]testUnit, ByteTestCaseCount)
for i := 0; i < ByteTestCaseCount; i++ {
(*pool)[Byte][i] = testUnit{serializedType: byteType.String(), value: byte(i)}
}
categorySelfRoundTripTest(t, (*pool)[Byte])

(*pool)[Bool] = make([]testUnit, 2)
(*pool)[Bool] = make([]testUnit, BoolTestCaseCount)
(*pool)[Bool][0] = testUnit{serializedType: boolType.String(), value: false}
(*pool)[Bool][1] = testUnit{serializedType: boolType.String(), value: true}
categorySelfRoundTripTest(t, (*pool)[Bool])

maxAddress := new(big.Int).Lsh(big.NewInt(1), 256)
(*pool)[Address] = make([]testUnit, 300)
for i := 0; i < 300; i++ {
maxAddress := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3)
(*pool)[Address] = make([]testUnit, AddressTestCaseCount)
for i := 0; i < AddressTestCaseCount; i++ {
randAddrVal, err := rand.Int(rand.Reader, maxAddress)
require.NoError(t, err, "generate random value for address, should be no error")
addrBytes := randAddrVal.Bytes()
remainBytes := make([]byte, 32-len(addrBytes))
remainBytes := make([]byte, addressByteSize-len(addrBytes))
addrBytes = append(remainBytes, addrBytes...)
(*pool)[Address][i] = testUnit{serializedType: addressType.String(), value: addrBytes}
}
categorySelfRoundTripTest(t, (*pool)[Address])

(*pool)[String] = make([]testUnit, 400)
(*pool)[String] = make([]testUnit, StringTestCaseCount*StringTestCaseSpecLenCount)
stringIndex := 0
for length := 1; length <= 100; length++ {
for i := 0; i < 4; i++ {
for length := 1; length <= StringTestCaseCount; length++ {
for i := 0; i < StringTestCaseSpecLenCount; i++ {
(*pool)[String][stringIndex] = testUnit{
serializedType: stringType.String(),
value: gobberish.GenerateString(length),
Expand Down Expand Up @@ -945,21 +999,21 @@ func takeSomeFromCategoryAndGenerateArray(
}

func addArrayRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
for intIndex := 0; intIndex < len((*pool)[Uint]); intIndex += 200 {
takeSomeFromCategoryAndGenerateArray(t, Uint, intIndex, 20, pool)
for intIndex := 0; intIndex < len((*pool)[Uint]); intIndex += UintTestCaseCount {
takeSomeFromCategoryAndGenerateArray(t, Uint, intIndex, TakeNum, pool)
}
takeSomeFromCategoryAndGenerateArray(t, Byte, 0, 20, pool)
takeSomeFromCategoryAndGenerateArray(t, Address, 0, 20, pool)
takeSomeFromCategoryAndGenerateArray(t, String, 0, 20, pool)
takeSomeFromCategoryAndGenerateArray(t, Bool, 0, 20, pool)
takeSomeFromCategoryAndGenerateArray(t, Byte, 0, TakeNum, pool)
takeSomeFromCategoryAndGenerateArray(t, Address, 0, TakeNum, pool)
takeSomeFromCategoryAndGenerateArray(t, String, 0, TakeNum, pool)
takeSomeFromCategoryAndGenerateArray(t, Bool, 0, TakeNum, pool)

categorySelfRoundTripTest(t, (*pool)[ArrayStatic])
categorySelfRoundTripTest(t, (*pool)[ArrayDynamic])
}

func addTupleRandomValues(t *testing.T, slotRange BaseType, pool *map[BaseType][]testUnit) {
for i := 0; i < 100; i++ {
tupleLenBig, err := rand.Int(rand.Reader, big.NewInt(20))
for i := 0; i < TupleTestCaseCount; i++ {
tupleLenBig, err := rand.Int(rand.Reader, big.NewInt(TupleMaxLength))
require.NoError(t, err, "generate random tuple length should not return error")
tupleLen := tupleLenBig.Int64() + 1
testUnits := make([]testUnit, tupleLen)
Expand Down

0 comments on commit 7da1026

Please sign in to comment.