diff --git a/neo/VM/ExecutionEngine.py b/neo/VM/ExecutionEngine.py index 80fd9ae0a..3b3fa4a1b 100644 --- a/neo/VM/ExecutionEngine.py +++ b/neo/VM/ExecutionEngine.py @@ -679,7 +679,7 @@ def ExecuteOp(self, opcode, context): if isinstance(key, CollectionMixin): # key must be an array index or dictionary key, but not a collection - return self.VM_FAULT_and_report(VMFault.UNKNOWN1, key) + return self.VM_FAULT_and_report(VMFault.KEY_IS_COLLECTION, key) collection = estack.Pop() @@ -693,7 +693,6 @@ def ExecuteOp(self, opcode, context): estack.PushT(to_pick) elif isinstance(collection, Map): - success, value = collection.TryGetValue(key) if success: @@ -713,7 +712,7 @@ def ExecuteOp(self, opcode, context): key = estack.Pop() if isinstance(key, CollectionMixin): - return self.VM_FAULT_and_report(VMFault.UNKNOWN1) + return self.VM_FAULT_and_report(VMFault.KEY_IS_COLLECTION) collection = estack.Pop() @@ -752,12 +751,6 @@ def ExecuteOp(self, opcode, context): elif opcode == NEWMAP: estack.PushT(Map()) -# keysItem = estack.Pop() -# valuesItem = estack.Pop() -# if not keysItem.IsArray or not valuesItem.IsArray: -# return self.VM_FAULT_and_report(VMFault.POP_ITEM_NOT_ARRAY, keysItem, valuesItem) -# estack.PushT(Struct([keysItem, valuesItem])) - elif opcode == APPEND: newItem = estack.Pop() diff --git a/neo/VM/VMFault.py b/neo/VM/VMFault.py index 1cfbd2e7f..12c1be98b 100644 --- a/neo/VM/VMFault.py +++ b/neo/VM/VMFault.py @@ -37,6 +37,8 @@ class VMFault(Enum): DICT_KEY_ERROR = auto() + KEY_IS_COLLECTION = auto() + THROW = auto() THROWIFNOT = auto() UNKNOWN_OPCODE = auto() diff --git a/neo/VM/tests/__init__.py b/neo/VM/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neo/VM/tests/test_execution_engine.py b/neo/VM/tests/test_execution_engine.py new file mode 100644 index 000000000..05c2941ee --- /dev/null +++ b/neo/VM/tests/test_execution_engine.py @@ -0,0 +1,38 @@ +from unittest import TestCase +from neo.VM.InteropService import * +from neo.VM.ExecutionEngine import ExecutionEngine +from neo.VM.ExecutionEngine import ExecutionContext +from neo.VM import OpCode + + +class VMTestCase(TestCase): + + engine = None + econtext = None + + def setUp(self): + + self.engine = ExecutionEngine() + self.econtext = ExecutionContext() + + def test_add_operations(self): + + self.engine.EvaluationStack.PushT(StackItem.New(2)) + self.engine.EvaluationStack.PushT(StackItem.New(3)) + + self.engine.ExecuteOp(OpCode.ADD, self.econtext) + + self.assertEqual(len(self.engine.EvaluationStack.Items), 1) + + self.assertEqual(self.engine.EvaluationStack.Items[0], StackItem.New(5)) + + def test_sub_operations(self): + + self.engine.EvaluationStack.PushT(StackItem.New(2)) + self.engine.EvaluationStack.PushT(StackItem.New(3)) + + self.engine.ExecuteOp(OpCode.SUB, self.econtext) + + self.assertEqual(len(self.engine.EvaluationStack.Items), 1) + + self.assertEqual(self.engine.EvaluationStack.Items[0], StackItem.New(-1)) diff --git a/neo/VM/tests/test_interop_map.py b/neo/VM/tests/test_interop_map.py index a202e15f2..3d2aba8e0 100644 --- a/neo/VM/tests/test_interop_map.py +++ b/neo/VM/tests/test_interop_map.py @@ -1,10 +1,28 @@ from unittest import TestCase from neo.VM.InteropService import * +from neo.VM.ExecutionEngine import ExecutionEngine +from neo.VM.ExecutionContext import ExecutionContext +from neo.VM import OpCode +from neo.VM import VMState +from mock import patch + + +class StringIn(str): + def __eq__(self, other): + return self in other class InteropTest(TestCase): - def test_map1(self): + engine = None + econtext = None + + def setUp(self): + + self.engine = ExecutionEngine() + self.econtext = ExecutionContext() + + def test_interop_map1(self): map = Map() @@ -14,24 +32,21 @@ def test_map1(self): map.SetItem(Integer(BigInteger(3)), ByteArray(b'abc')) self.assertEqual(map.Keys, [Integer(BigInteger(3))]) - self.assertEqual(map.Values, [ByteArray(b'abc')]) - def test_map2(self): + def test_interop_map2(self): map = Map({'a': 1, 'b': 2, 'c': 3}) self.assertEqual(map.Count, 3) - self.assertEqual(map.ContainsKey('a'), True) - self.assertEqual(map.Contains('a'), False) map.Clear() self.assertEqual(map.GetMap(), {}) - def test_map3(self): + def test_interop_map3(self): map = Map({'a': 1, 'b': 2, 'c': 3}) @@ -46,19 +61,143 @@ def test_map3(self): map2 = Map({'a': 1, 'b': 2, 'c': 3}) self.assertEqual(map, map2) - self.assertTrue(map.Remove('a'), True) - self.assertEqual(map.Count, 2) - self.assertNotEqual(map, map2) - self.assertEqual(map.TryGetValue('b'), (True, 2)) - self.assertEqual(map.TryGetValue('h'), (False, None)) map.SetItem('h', 9) self.assertEqual(map.GetItem('h'), 9) - self.assertEqual(map.GetMap(), {'b': 2, 'c': 3, 'h': 9}) + + def test_op_map1(self): + + self.engine.ExecuteOp(OpCode.NEWMAP, self.econtext) + + self.assertEqual(len(self.engine.EvaluationStack.Items), 1) + self.assertIsInstance(self.engine.EvaluationStack.Items[0], Map) + self.assertEqual(self.engine.EvaluationStack.Items[0].GetMap(), {}) + + def test_op_map2(self): + + self.engine.ExecuteOp(OpCode.NEWMAP, self.econtext) + self.engine.EvaluationStack.PushT(StackItem.New('mykey')) + self.engine.EvaluationStack.PushT(StackItem.New('myVal')) + self.engine.ExecuteOp(OpCode.SETITEM, self.econtext) + + self.assertEqual(len(self.engine.EvaluationStack.Items), 0) + + def test_op_map3(self): + + # set item should fail if not enough things on estack + + self.engine.EvaluationStack.PushT(StackItem.New('myvalue')) + self.engine.EvaluationStack.PushT(StackItem.New('mykey')) + + with self.assertRaises(Exception) as context: + self.engine.ExecuteOp(OpCode.SETITEM, self.econtext) + + self.assertEqual(len(self.engine.EvaluationStack.Items), 0) + self.assertEqual(self.engine.State, VMState.BREAK) + + @patch('logzero.logger.error') + def test_op_map4(self, mocked_logger): + + # set item should fail if these are out of order + self.engine.EvaluationStack.PushT(StackItem.New('mykey')) + self.engine.ExecuteOp(OpCode.NEWMAP, self.econtext) + self.engine.EvaluationStack.PushT(StackItem.New('myVal')) + self.engine.ExecuteOp(OpCode.SETITEM, self.econtext) + + self.assertEqual(self.engine.State, VMState.FAULT | VMState.BREAK) + + mocked_logger.assert_called_with(StringIn('VMFault.KEY_IS_COLLECTION')) + + @patch('logzero.logger.error') + def test_op_map5(self, mocked_logger): + + # set item should fail if these are out of order + self.engine.EvaluationStack.PushT(StackItem.New('mykey')) + self.engine.EvaluationStack.PushT(StackItem.New('mykey')) + self.engine.EvaluationStack.PushT(StackItem.New('myVal')) + self.engine.ExecuteOp(OpCode.SETITEM, self.econtext) + + self.assertEqual(self.engine.State, VMState.FAULT | VMState.BREAK) + + mocked_logger.assert_called_with(StringIn('VMFault.SETITEM_INVALID_TYPE')) + + @patch('logzero.logger.error') + def test_op_map6(self, mocked_logger): + # we can pick an item from a dict + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4)})) + self.engine.EvaluationStack.PushT(StackItem.New('a')) + self.engine.ExecuteOp(OpCode.PICKITEM, self.econtext) + + self.assertEqual(len(self.engine.EvaluationStack.Items), 1) + self.assertEqual(self.engine.EvaluationStack.Items[0].GetBigInteger(), 4) + + @patch('logzero.logger.error') + def test_op_map7(self, mocked_logger): + # pick item with key is collection causes error + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4)})) + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4)})) + self.engine.ExecuteOp(OpCode.PICKITEM, self.econtext) + + self.assertEqual(self.engine.State, VMState.FAULT | VMState.BREAK) + + mocked_logger.assert_called_with(StringIn('VMFault.KEY_IS_COLLECTION')) + + @patch('logzero.logger.error') + def test_op_map7(self, mocked_logger): + # pick item on non collection causes error + self.engine.EvaluationStack.PushT(StackItem.New('a')) + self.engine.EvaluationStack.PushT(StackItem.New('a')) + self.engine.ExecuteOp(OpCode.PICKITEM, self.econtext) + + self.assertEqual(self.engine.State, VMState.FAULT | VMState.BREAK) + + mocked_logger.assert_called_with(StringIn('Cannot access item at index') and StringIn('Item is not an array or dict')) + + @patch('logzero.logger.error') + def test_op_map9(self, mocked_logger): + # pick item key not found + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4)})) + self.engine.EvaluationStack.PushT(StackItem.New('b')) + self.engine.ExecuteOp(OpCode.PICKITEM, self.econtext) + + self.assertEqual(self.engine.State, VMState.FAULT | VMState.BREAK) + + mocked_logger.assert_called_with(StringIn('VMFault.DICT_KEY_NOT_FOUND')) + + def test_op_map10(self): + # pick item key not found + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4), StackItem.New('b'): StackItem.New(5)})) + self.engine.ExecuteOp(OpCode.KEYS, self.econtext) + + self.assertIsInstance(self.engine.EvaluationStack.Items[0], Array) + items = self.engine.EvaluationStack.Items[0].GetArray() + self.assertEqual(items, [StackItem.New('a'), StackItem.New('b')]) + + def test_op_map11(self): + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4), StackItem.New('b'): StackItem.New(5)})) + self.engine.ExecuteOp(OpCode.VALUES, self.econtext) + + self.assertIsInstance(self.engine.EvaluationStack.Items[0], Array) + items = self.engine.EvaluationStack.Items[0].GetArray() + self.assertEqual(items, [StackItem.New(4), StackItem.New(5)]) + + def test_op_map12(self): + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4), StackItem.New('b'): StackItem.New(5)})) + self.engine.EvaluationStack.PushT(StackItem.New('b')) + self.engine.ExecuteOp(OpCode.HASKEY, self.econtext) + + self.assertEqual(self.engine.EvaluationStack.Items[0].GetBoolean(), True) + + def test_op_map13(self): + self.engine.EvaluationStack.PushT(Map(dict={StackItem.New('a'): StackItem.New(4), StackItem.New('b'): StackItem.New(5)})) + self.engine.EvaluationStack.PushT(StackItem.New('c')) + self.engine.ExecuteOp(OpCode.HASKEY, self.econtext) + + self.assertEqual(self.engine.EvaluationStack.Items[0].GetBoolean(), False)