forked from TrueCloudLab/neoneo-go
e1607e23c2
vm: implement immutable stackitems
583 lines
13 KiB
Go
583 lines
13 KiB
Go
package stackitem
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var makeStackItemTestCases = []struct {
|
|
input interface{}
|
|
result Item
|
|
}{
|
|
{
|
|
input: int64(3),
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: int16(3),
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: 3,
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: uint8(3),
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: uint16(3),
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: uint32(3),
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: uint64(3),
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: big.NewInt(3),
|
|
result: (*BigInteger)(big.NewInt(3)),
|
|
},
|
|
{
|
|
input: []byte{1, 2, 3, 4},
|
|
result: NewByteArray([]byte{1, 2, 3, 4}),
|
|
},
|
|
{
|
|
input: []byte{},
|
|
result: NewByteArray([]byte{}),
|
|
},
|
|
{
|
|
input: "bla",
|
|
result: NewByteArray([]byte("bla")),
|
|
},
|
|
{
|
|
input: "",
|
|
result: NewByteArray([]byte{}),
|
|
},
|
|
{
|
|
input: true,
|
|
result: Bool(true),
|
|
},
|
|
{
|
|
input: false,
|
|
result: Bool(false),
|
|
},
|
|
{
|
|
input: []Item{(*BigInteger)(big.NewInt(3)), NewByteArray([]byte{1, 2, 3})},
|
|
result: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), NewByteArray([]byte{1, 2, 3})}},
|
|
},
|
|
{
|
|
input: []int{1, 2, 3},
|
|
result: &Array{value: []Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}},
|
|
},
|
|
}
|
|
|
|
var makeStackItemErrorCases = []struct {
|
|
input interface{}
|
|
}{
|
|
{
|
|
input: nil,
|
|
},
|
|
}
|
|
|
|
func TestMakeStackItem(t *testing.T) {
|
|
for _, testCase := range makeStackItemTestCases {
|
|
assert.Equal(t, testCase.result, Make(testCase.input))
|
|
}
|
|
for _, errorCase := range makeStackItemErrorCases {
|
|
assert.Panics(t, func() { Make(errorCase.input) })
|
|
}
|
|
}
|
|
|
|
var stringerTestCases = []struct {
|
|
input Item
|
|
result string
|
|
}{
|
|
{
|
|
input: NewStruct([]Item{}),
|
|
result: "Struct",
|
|
},
|
|
{
|
|
input: NewBigInteger(big.NewInt(3)),
|
|
result: "BigInteger",
|
|
},
|
|
{
|
|
input: NewBool(true),
|
|
result: "Boolean",
|
|
},
|
|
{
|
|
input: NewByteArray([]byte{}),
|
|
result: "ByteString",
|
|
},
|
|
{
|
|
input: NewArray([]Item{}),
|
|
result: "Array",
|
|
},
|
|
{
|
|
input: NewMap(),
|
|
result: "Map",
|
|
},
|
|
{
|
|
input: NewInterop(nil),
|
|
result: "InteropInterface",
|
|
},
|
|
{
|
|
input: NewPointer(0, nil),
|
|
result: "Pointer",
|
|
},
|
|
}
|
|
|
|
func TestStringer(t *testing.T) {
|
|
for _, testCase := range stringerTestCases {
|
|
assert.Equal(t, testCase.result, testCase.input.String())
|
|
}
|
|
}
|
|
|
|
var equalsTestCases = map[string][]struct {
|
|
item1 Item
|
|
item2 Item
|
|
result bool
|
|
panics bool
|
|
}{
|
|
"struct": {
|
|
{
|
|
item1: NewStruct(nil),
|
|
item2: nil,
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewStruct(nil),
|
|
item2: NewBigInteger(big.NewInt(1)),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewStruct(nil),
|
|
item2: NewStruct([]Item{NewBigInteger(big.NewInt(1))}),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewStruct([]Item{NewBigInteger(big.NewInt(1))}),
|
|
item2: NewStruct([]Item{NewBigInteger(big.NewInt(2))}),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewStruct([]Item{NewBigInteger(big.NewInt(1))}),
|
|
item2: NewStruct([]Item{NewBigInteger(big.NewInt(1))}),
|
|
result: true,
|
|
},
|
|
{
|
|
item1: NewStruct([]Item{NewBigInteger(big.NewInt(1)), NewStruct([]Item{})}),
|
|
item2: NewStruct([]Item{NewBigInteger(big.NewInt(1)), NewStruct([]Item{})}),
|
|
result: true,
|
|
},
|
|
},
|
|
"bigint": {
|
|
{
|
|
item1: NewBigInteger(big.NewInt(2)),
|
|
item2: nil,
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewBigInteger(big.NewInt(2)),
|
|
item2: NewBigInteger(big.NewInt(2)),
|
|
result: true,
|
|
},
|
|
{
|
|
item1: NewBigInteger(big.NewInt(2)),
|
|
item2: NewBool(false),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewBigInteger(big.NewInt(0)),
|
|
item2: NewBool(false),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewBigInteger(big.NewInt(2)),
|
|
item2: Make(int32(2)),
|
|
result: true,
|
|
},
|
|
},
|
|
"bool": {
|
|
{
|
|
item1: NewBool(true),
|
|
item2: nil,
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewBool(true),
|
|
item2: NewBool(true),
|
|
result: true,
|
|
},
|
|
{
|
|
item1: NewBool(true),
|
|
item2: NewBigInteger(big.NewInt(1)),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewBool(true),
|
|
item2: NewBool(false),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewBool(true),
|
|
item2: Make(true),
|
|
result: true,
|
|
},
|
|
},
|
|
"bytearray": {
|
|
{
|
|
item1: NewByteArray(nil),
|
|
item2: nil,
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewByteArray([]byte{1, 2, 3}),
|
|
item2: NewByteArray([]byte{1, 2, 3}),
|
|
result: true,
|
|
},
|
|
{
|
|
item1: NewByteArray([]byte{1}),
|
|
item2: NewBigInteger(big.NewInt(1)),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewByteArray([]byte{1, 2, 3}),
|
|
item2: NewByteArray([]byte{1, 2, 4}),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewByteArray([]byte{1, 2, 3}),
|
|
item2: Make([]byte{1, 2, 3}),
|
|
result: true,
|
|
},
|
|
{
|
|
item1: NewByteArray(make([]byte, MaxByteArrayComparableSize+1)),
|
|
item2: NewByteArray([]byte{1, 2, 3}),
|
|
panics: true,
|
|
},
|
|
{
|
|
item1: NewByteArray([]byte{1, 2, 3}),
|
|
item2: NewByteArray(make([]byte, MaxByteArrayComparableSize+1)),
|
|
panics: true,
|
|
},
|
|
{
|
|
item1: NewByteArray(make([]byte, MaxByteArrayComparableSize+1)),
|
|
item2: NewByteArray(make([]byte, MaxByteArrayComparableSize+1)),
|
|
panics: true,
|
|
},
|
|
},
|
|
"array": {
|
|
{
|
|
item1: NewArray(nil),
|
|
item2: nil,
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}),
|
|
item2: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewArray([]Item{(*BigInteger)(big.NewInt(1))}),
|
|
item2: NewBigInteger(big.NewInt(1)),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}),
|
|
item2: NewArray([]Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(4))}),
|
|
result: false,
|
|
},
|
|
},
|
|
"map": {
|
|
{
|
|
item1: NewMap(),
|
|
item2: nil,
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewMap(),
|
|
item2: NewMap(),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{2})}}},
|
|
item2: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{2})}}},
|
|
result: false,
|
|
},
|
|
{
|
|
item1: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{2})}}},
|
|
item2: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{3})}}},
|
|
result: false,
|
|
},
|
|
},
|
|
"interop": {
|
|
{
|
|
item1: NewInterop(nil),
|
|
item2: nil,
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewInterop(nil),
|
|
item2: NewInterop(nil),
|
|
result: true,
|
|
},
|
|
{
|
|
item1: NewInterop(2),
|
|
item2: NewInterop(3),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewInterop(3),
|
|
item2: NewInterop(3),
|
|
result: true,
|
|
},
|
|
},
|
|
"pointer": {
|
|
{
|
|
item1: NewPointer(0, []byte{}),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewPointer(1, []byte{1}),
|
|
item2: NewPointer(1, []byte{1}),
|
|
result: true,
|
|
},
|
|
{
|
|
item1: NewPointer(1, []byte{1}),
|
|
item2: NewPointer(2, []byte{1}),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewPointer(1, []byte{1}),
|
|
item2: NewPointer(1, []byte{2}),
|
|
result: false,
|
|
},
|
|
{
|
|
item1: NewPointer(0, []byte{}),
|
|
item2: NewBigInteger(big.NewInt(0)),
|
|
result: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestEquals(t *testing.T) {
|
|
for name, testBatch := range equalsTestCases {
|
|
for _, testCase := range testBatch {
|
|
t.Run(name, func(t *testing.T) {
|
|
if testCase.panics {
|
|
assert.Panics(t, func() {
|
|
testCase.item1.Equals(testCase.item2)
|
|
})
|
|
} else {
|
|
assert.Equal(t, testCase.result, testCase.item1.Equals(testCase.item2))
|
|
// Reference equals
|
|
assert.Equal(t, true, testCase.item1.Equals(testCase.item1))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEqualsDeepStructure(t *testing.T) {
|
|
const perStruct = 4
|
|
var items = []Item{}
|
|
var num int
|
|
for i := 0; i < perStruct; i++ {
|
|
items = append(items, Make(0))
|
|
num++
|
|
}
|
|
var layerUp = func(sa *Struct, num int) (*Struct, int) {
|
|
items := []Item{}
|
|
for i := 0; i < perStruct; i++ {
|
|
clon, err := sa.Clone()
|
|
require.NoError(t, err)
|
|
items = append(items, clon)
|
|
}
|
|
num *= perStruct
|
|
num++
|
|
return NewStruct(items), num
|
|
}
|
|
var sa = NewStruct(items)
|
|
for i := 0; i < 4; i++ {
|
|
sa, num = layerUp(sa, num)
|
|
}
|
|
require.Less(t, num, MaxComparableNumOfItems)
|
|
sb, err := sa.Clone()
|
|
require.NoError(t, err)
|
|
require.True(t, sa.Equals(sb))
|
|
sa, num = layerUp(sa, num)
|
|
sb, num = layerUp(sb, num)
|
|
|
|
require.Less(t, MaxComparableNumOfItems, num)
|
|
require.Panics(t, func() { sa.Equals(sb) })
|
|
}
|
|
|
|
var marshalJSONTestCases = []struct {
|
|
input Item
|
|
result []byte
|
|
}{
|
|
{
|
|
input: NewBigInteger(big.NewInt(2)),
|
|
result: []byte(`2`),
|
|
},
|
|
{
|
|
input: NewBool(true),
|
|
result: []byte(`true`),
|
|
},
|
|
{
|
|
input: NewByteArray([]byte{1, 2, 3}),
|
|
result: []byte(`"010203"`),
|
|
},
|
|
{
|
|
input: NewBuffer([]byte{1, 2, 3}),
|
|
result: []byte(`"010203"`),
|
|
},
|
|
{
|
|
input: &Array{value: []Item{(*BigInteger)(big.NewInt(3)), NewByteArray([]byte{1, 2, 3})}},
|
|
result: []byte(`[3,"010203"]`),
|
|
},
|
|
{
|
|
input: &Interop{value: 3},
|
|
result: []byte(`3`),
|
|
},
|
|
}
|
|
|
|
func TestMarshalJSON(t *testing.T) {
|
|
var (
|
|
actual []byte
|
|
err error
|
|
)
|
|
for _, testCase := range marshalJSONTestCases {
|
|
switch testCase.input.(type) {
|
|
case *BigInteger:
|
|
actual, err = testCase.input.(*BigInteger).MarshalJSON()
|
|
case Bool:
|
|
actual, err = testCase.input.(Bool).MarshalJSON()
|
|
case *ByteArray:
|
|
actual, err = testCase.input.(*ByteArray).MarshalJSON()
|
|
case *Array:
|
|
actual, err = testCase.input.(*Array).MarshalJSON()
|
|
case *Interop:
|
|
actual, err = testCase.input.(*Interop).MarshalJSON()
|
|
default:
|
|
continue
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testCase.result, actual)
|
|
}
|
|
}
|
|
|
|
func TestNewVeryBigInteger(t *testing.T) {
|
|
check := func(ok bool, v *big.Int) {
|
|
bs := bigint.ToBytes(v)
|
|
if ok {
|
|
assert.True(t, len(bs)*8 <= MaxBigIntegerSizeBits)
|
|
} else {
|
|
assert.True(t, len(bs)*8 > MaxBigIntegerSizeBits)
|
|
assert.Panics(t, func() { NewBigInteger(v) })
|
|
}
|
|
}
|
|
|
|
maxBitSet := big.NewInt(1)
|
|
maxBitSet.Lsh(maxBitSet, MaxBigIntegerSizeBits-1)
|
|
|
|
check(false, maxBitSet)
|
|
check(true, new(big.Int).Neg(maxBitSet))
|
|
|
|
minus1 := new(big.Int).Sub(maxBitSet, big.NewInt(1))
|
|
check(true, minus1)
|
|
check(true, new(big.Int).Neg(minus1))
|
|
|
|
plus1 := new(big.Int).Add(maxBitSet, big.NewInt(1))
|
|
check(false, plus1)
|
|
check(false, new(big.Int).Neg(plus1))
|
|
|
|
check(false, new(big.Int).Mul(maxBitSet, big.NewInt(2)))
|
|
}
|
|
|
|
func TestStructClone(t *testing.T) {
|
|
st0 := Struct{}
|
|
st := Struct{value: []Item{&st0}}
|
|
for i := 0; i < MaxClonableNumOfItems-1; i++ {
|
|
nst, err := st.Clone()
|
|
require.NoError(t, err)
|
|
st = Struct{value: []Item{nst}}
|
|
}
|
|
_, err := st.Clone()
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestDeepCopy(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
item Item
|
|
}{
|
|
{"Integer", NewBigInteger(big.NewInt(1))},
|
|
{"ByteArray", NewByteArray([]byte{1, 2, 3})},
|
|
{"Buffer", NewBuffer([]byte{1, 2, 3})},
|
|
{"Bool", NewBool(true)},
|
|
{"Pointer", NewPointer(1, []byte{1, 2, 3})},
|
|
{"Interop", NewInterop(&[]byte{1, 2})},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actual := DeepCopy(tc.item, false)
|
|
if immut, ok := tc.item.(Immutable); ok {
|
|
immut.MarkAsReadOnly() // tiny hack for test to be able to compare object references.
|
|
}
|
|
require.Equal(t, tc.item, actual)
|
|
if tc.item.Type() != BooleanT {
|
|
require.False(t, actual == tc.item)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("Null", func(t *testing.T) {
|
|
require.Equal(t, Null{}, DeepCopy(Null{}, false))
|
|
})
|
|
|
|
t.Run("Array", func(t *testing.T) {
|
|
arr := NewArray(make([]Item, 2))
|
|
arr.value[0] = NewBool(true)
|
|
arr.value[1] = arr
|
|
|
|
actual := DeepCopy(arr, false)
|
|
arr.isReadOnly = true // tiny hack for test to be able to compare object references.
|
|
require.Equal(t, arr, actual)
|
|
require.False(t, arr == actual)
|
|
require.True(t, actual == actual.(*Array).value[1])
|
|
})
|
|
|
|
t.Run("Struct", func(t *testing.T) {
|
|
arr := NewStruct(make([]Item, 2))
|
|
arr.value[0] = NewBool(true)
|
|
arr.value[1] = arr
|
|
|
|
actual := DeepCopy(arr, false)
|
|
arr.isReadOnly = true // tiny hack for test to be able to compare object references.
|
|
require.Equal(t, arr, actual)
|
|
require.False(t, arr == actual)
|
|
require.True(t, actual == actual.(*Struct).value[1])
|
|
})
|
|
|
|
t.Run("Map", func(t *testing.T) {
|
|
m := NewMapWithValue(make([]MapElement, 2))
|
|
m.value[0] = MapElement{Key: NewBool(true), Value: m}
|
|
m.value[1] = MapElement{Key: NewBigInteger(big.NewInt(1)), Value: NewByteArray([]byte{1, 2, 3})}
|
|
|
|
actual := DeepCopy(m, false)
|
|
m.isReadOnly = true // tiny hack for test to be able to compare object references.
|
|
require.Equal(t, m, actual)
|
|
require.False(t, m == actual)
|
|
require.True(t, actual == actual.(*Map).value[0].Value)
|
|
})
|
|
}
|