Add PyArmNN to work with ArmNN API of 20.02
* Add Swig rules for generating python wrapper
* Add documentation
* Add tests and testing data

Change-Id: If48eda08931514fa21e72214dfead2835f07237c
Signed-off-by: Richard Burton <richard.burton@arm.com>
Signed-off-by: Derek Lamberti <derek.lamberti@arm.com>
diff --git a/python/pyarmnn/test/test_caffe_parser.py b/python/pyarmnn/test/test_caffe_parser.py
new file mode 100644
index 0000000..d744b90
--- /dev/null
+++ b/python/pyarmnn/test/test_caffe_parser.py
@@ -0,0 +1,131 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+
+import pytest
+import pyarmnn as ann
+import numpy as np
+
+
+@pytest.fixture()
+def parser(shared_data_folder):
+    """
+    Parse and setup the test network to be used for the tests below
+    """
+
+    # Create caffe parser
+    parser = ann.ICaffeParser()
+
+    # Specify path to model
+    path_to_model = os.path.join(shared_data_folder, 'mock_model.caffemodel')
+
+    # Specify the tensor shape relative to the input [1, 1, 28, 28]
+    tensor_shape = {'Placeholder': ann.TensorShape((1, 1, 28, 28))}
+
+    # Specify the requested_outputs
+    requested_outputs = ["output"]
+
+    # Parse caffe binary & create network
+    parser.CreateNetworkFromBinaryFile(path_to_model, tensor_shape, requested_outputs)
+
+    yield parser
+
+
+def test_caffe_parser_swig_destroy():
+    assert ann.ICaffeParser.__swig_destroy__, "There is a swig python destructor defined"
+    assert ann.ICaffeParser.__swig_destroy__.__name__ == "delete_ICaffeParser"
+
+
+def test_check_caffe_parser_swig_ownership(parser):
+    # Check to see that SWIG has ownership for parser. This instructs SWIG to take
+    # ownership of the return value. This allows the value to be automatically
+    # garbage-collected when it is no longer in use
+    assert parser.thisown
+
+
+def test_get_network_input_binding_info(parser):
+    input_binding_info = parser.GetNetworkInputBindingInfo("Placeholder")
+
+    tensor = input_binding_info[1]
+    assert tensor.GetDataType() == 1
+    assert tensor.GetNumDimensions() == 4
+    assert tensor.GetNumElements() == 784
+
+
+def test_get_network_output_binding_info(parser):
+    output_binding_info1 = parser.GetNetworkOutputBindingInfo("output")
+
+    # Check the tensor info retrieved from GetNetworkOutputBindingInfo
+    tensor1 = output_binding_info1[1]
+
+    assert tensor1.GetDataType() == 1
+    assert tensor1.GetNumDimensions() == 2
+    assert tensor1.GetNumElements() == 10
+
+
+def test_filenotfound_exception(shared_data_folder):
+    parser = ann.ICaffeParser()
+
+    # path to model
+    path_to_model = os.path.join(shared_data_folder, 'some_unknown_network.caffemodel')
+
+    # generic tensor shape [1, 1, 1, 1]
+    tensor_shape = {'data': ann.TensorShape((1, 1, 1, 1))}
+
+    # requested_outputs
+    requested_outputs = [""]
+
+    with pytest.raises(RuntimeError) as err:
+        parser.CreateNetworkFromBinaryFile(path_to_model, tensor_shape, requested_outputs)
+
+    # Only check for part of the exception since the exception returns
+    # absolute path which will change on different machines.
+    assert 'Failed to open graph file' in str(err.value)
+
+
+def test_caffe_parser_end_to_end(shared_data_folder):
+    parser = ann.ICaffeParser = ann.ICaffeParser()
+
+    # Load the network specifying the inputs and outputs
+    input_name = "Placeholder"
+    tensor_shape = {input_name: ann.TensorShape((1, 1, 28, 28))}
+    requested_outputs = ["output"]
+
+    network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.caffemodel'),
+                                                 tensor_shape, requested_outputs)
+
+    # Specify preferred backend
+    preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')]
+
+    input_binding_info = parser.GetNetworkInputBindingInfo(input_name)
+
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    assert 0 == len(messages)
+
+    net_id, messages = runtime.LoadNetwork(opt_network)
+
+    assert "" == messages
+
+    # Load test image data stored in input_caffe.npy
+    input_tensor_data = np.load(os.path.join(shared_data_folder, 'caffe_parser/input_caffe.npy')).astype(np.float32)
+    input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data])
+
+    # Load output binding info and
+    outputs_binding_info = []
+    for output_name in requested_outputs:
+        outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(output_name))
+    output_tensors = ann.make_output_tensors(outputs_binding_info)
+
+    runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+    output_vectors = ann.workload_tensors_to_ndarray(output_tensors)
+
+    # Load golden output file for result comparison.
+    expected_output = np.load(os.path.join(shared_data_folder, 'caffe_parser/golden_output_caffe.npy'))
+
+    # Check that output matches golden output to 4 decimal places (there are slight rounding differences after this)
+    np.testing.assert_almost_equal(output_vectors[0], expected_output, 4)
diff --git a/python/pyarmnn/test/test_const_tensor.py b/python/pyarmnn/test/test_const_tensor.py
new file mode 100644
index 0000000..fa6327f
--- /dev/null
+++ b/python/pyarmnn/test/test_const_tensor.py
@@ -0,0 +1,251 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import pytest
+import numpy as np
+
+import pyarmnn as ann
+
+
+def _get_tensor_info(dt):
+    tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), dt)
+
+    return tensor_info
+
+
+@pytest.mark.parametrize("dt, data",
+                         [
+                             (ann.DataType_Float32, np.random.randint(1, size=(2, 4)).astype(np.float32)),
+                             (ann.DataType_Float16, np.random.randint(1, size=(2, 4)).astype(np.float16)),
+                             (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 4)).astype(np.uint8)),
+                             (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 4)).astype(np.int8)),
+                             (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 4)).astype(np.int8)),
+                             (ann.DataType_Signed32, np.random.randint(1, size=(2, 4)).astype(np.int32)),
+                             (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 4)).astype(np.int16))
+                         ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16'])
+def test_const_tensor_too_many_elements(dt, data):
+    tensor_info = _get_tensor_info(dt)
+    num_bytes = tensor_info.GetNumBytes()
+
+    with pytest.raises(ValueError) as err:
+        ann.ConstTensor(tensor_info, data)
+
+    assert 'ConstTensor requires {} bytes, {} provided.'.format(num_bytes, data.nbytes) in str(err.value)
+
+
+@pytest.mark.parametrize("dt, data",
+                         [
+                             (ann.DataType_Float32, np.random.randint(1, size=(2, 2)).astype(np.float32)),
+                             (ann.DataType_Float16, np.random.randint(1, size=(2, 2)).astype(np.float16)),
+                             (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 2)).astype(np.uint8)),
+                             (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 2)).astype(np.int8)),
+                             (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 2)).astype(np.int8)),
+                             (ann.DataType_Signed32, np.random.randint(1, size=(2, 2)).astype(np.int32)),
+                             (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 2)).astype(np.int16))
+                         ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16'])
+def test_const_tensor_too_little_elements(dt, data):
+    tensor_info = _get_tensor_info(dt)
+    num_bytes = tensor_info.GetNumBytes()
+
+    with pytest.raises(ValueError) as err:
+        ann.ConstTensor(tensor_info, data)
+
+    assert 'ConstTensor requires {} bytes, {} provided.'.format(num_bytes, data.nbytes) in str(err.value)
+
+
+@pytest.mark.parametrize("dt, data",
+                         [
+                             (ann.DataType_Float32, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.float32)),
+                             (ann.DataType_Float16, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.float16)),
+                             (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.uint8)),
+                             (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int8)),
+                             (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int8)),
+                             (ann.DataType_Signed32, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int32)),
+                             (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int16))
+                         ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16'])
+def test_const_tensor_multi_dimensional_input(dt, data):
+    tensor = ann.ConstTensor(ann.TensorInfo(ann.TensorShape((2, 2, 3, 3)), dt), data)
+
+    assert data.size == tensor.GetNumElements()
+    assert data.nbytes == tensor.GetNumBytes()
+    assert dt == tensor.GetDataType()
+    assert tensor.get_memory_area().data
+
+
+def test_create_const_tensor_from_tensor():
+    tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_Float32)
+    tensor = ann.Tensor(tensor_info)
+    copied_tensor = ann.ConstTensor(tensor)
+
+    assert copied_tensor != tensor, "Different objects"
+    assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects"
+    assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data, "Same memory area"
+    assert copied_tensor.GetNumElements() == tensor.GetNumElements()
+    assert copied_tensor.GetNumBytes() == tensor.GetNumBytes()
+    assert copied_tensor.GetDataType() == tensor.GetDataType()
+
+
+def test_const_tensor_from_tensor_has_memory_area_access_after_deletion_of_original_tensor():
+    tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_Float32)
+    tensor = ann.Tensor(tensor_info)
+
+    tensor.get_memory_area()[0] = 100
+
+    copied_mem = tensor.get_memory_area().copy()
+
+    assert 100 == copied_mem[0], "Memory was copied correctly"
+
+    copied_tensor = ann.ConstTensor(tensor)
+
+    tensor.get_memory_area()[0] = 200
+
+    assert 200 == tensor.get_memory_area()[0], "Tensor and copied Tensor point to the same memory"
+    assert 200 == copied_tensor.get_memory_area()[0], "Tensor and copied Tensor point to the same memory"
+
+    assert 100 == copied_mem[0], "Copied test memory not affected"
+
+    copied_mem[0] = 200  # modify test memory to equal copied Tensor
+
+    del tensor
+    np.testing.assert_array_equal(copied_tensor.get_memory_area(), copied_mem), "After initial tensor was deleted, " \
+                                                                                "copied Tensor still has " \
+                                                                                "its memory as expected"
+
+
+def test_create_const_tensor_incorrect_args():
+    with pytest.raises(ValueError) as err:
+        ann.ConstTensor('something', 'something')
+
+    expected_error_message = "Incorrect number of arguments or type of arguments provided to create Const Tensor."
+    assert expected_error_message in str(err.value)
+
+
+@pytest.mark.parametrize("dt, data",
+                         [
+                             # -1 not in data type enum
+                             (-1, np.random.randint(1, size=(2, 3)).astype(np.float32)),
+                         ], ids=['unknown'])
+def test_const_tensor_unsupported_datatype(dt, data):
+    tensor_info = _get_tensor_info(dt)
+
+    with pytest.raises(ValueError) as err:
+        ann.ConstTensor(tensor_info, data)
+
+    assert 'The data type provided for this Tensor is not supported: -1' in str(err.value)
+
+
+@pytest.mark.parametrize("dt, data",
+                         [
+                             (ann.DataType_Float32, [[1, 1, 1], [1, 1, 1]]),
+                             (ann.DataType_Float16, [[1, 1, 1], [1, 1, 1]]),
+                             (ann.DataType_QAsymmU8, [[1, 1, 1], [1, 1, 1]]),
+                             (ann.DataType_QAsymmS8, [[1, 1, 1], [1, 1, 1]]),
+                             (ann.DataType_QSymmS8, [[1, 1, 1], [1, 1, 1]])
+                         ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8'])
+def test_const_tensor_incorrect_input_datatype(dt, data):
+    tensor_info = _get_tensor_info(dt)
+
+    with pytest.raises(TypeError) as err:
+        ann.ConstTensor(tensor_info, data)
+
+    assert 'Data must be provided as a numpy array.' in str(err.value)
+
+
+@pytest.mark.parametrize("dt, data",
+                         [
+                             (ann.DataType_Float32, np.random.randint(1, size=(2, 3)).astype(np.float32)),
+                             (ann.DataType_Float16, np.random.randint(1, size=(2, 3)).astype(np.float16)),
+                             (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 3)).astype(np.uint8)),
+                             (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 3)).astype(np.int8)),
+                             (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 3)).astype(np.int8)),
+                             (ann.DataType_Signed32, np.random.randint(1, size=(2, 3)).astype(np.int32)),
+                             (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 3)).astype(np.int16))
+                         ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16'])
+class TestNumpyDataTypes:
+
+    def test_copy_const_tensor(self, dt, data):
+        tensor_info = _get_tensor_info(dt)
+        tensor = ann.ConstTensor(tensor_info, data)
+        copied_tensor = ann.ConstTensor(tensor)
+
+        assert copied_tensor != tensor, "Different objects"
+        assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects"
+        assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data, "Same memory area"
+        assert copied_tensor.GetNumElements() == tensor.GetNumElements()
+        assert copied_tensor.GetNumBytes() == tensor.GetNumBytes()
+        assert copied_tensor.GetDataType() == tensor.GetDataType()
+
+    def test_const_tensor__str__(self, dt, data):
+        tensor_info = _get_tensor_info(dt)
+        d_type = tensor_info.GetDataType()
+        num_dimensions = tensor_info.GetNumDimensions()
+        num_bytes = tensor_info.GetNumBytes()
+        num_elements = tensor_info.GetNumElements()
+        tensor = ann.ConstTensor(tensor_info, data)
+
+        assert str(tensor) == "ConstTensor{{DataType: {}, NumBytes: {}, NumDimensions: " \
+                              "{}, NumElements: {}}}".format(d_type, num_bytes, num_dimensions, num_elements)
+
+    def test_const_tensor_with_info(self, dt, data):
+        tensor_info = _get_tensor_info(dt)
+        elements = tensor_info.GetNumElements()
+        num_bytes = tensor_info.GetNumBytes()
+        d_type = dt
+
+        tensor = ann.ConstTensor(tensor_info, data)
+
+        assert tensor_info != tensor.GetInfo(), "Different objects"
+        assert elements == tensor.GetNumElements()
+        assert num_bytes == tensor.GetNumBytes()
+        assert d_type == tensor.GetDataType()
+
+    def test_immutable_memory(self, dt, data):
+        tensor_info = _get_tensor_info(dt)
+
+        tensor = ann.ConstTensor(tensor_info, data)
+
+        with pytest.raises(ValueError) as err:
+            tensor.get_memory_area()[0] = 0
+
+        assert 'is read-only' in str(err.value)
+
+    def test_numpy_dtype_matches_ann_dtype(self, dt, data):
+        np_data_type_mapping = {ann.DataType_QAsymmU8: np.uint8,
+                                ann.DataType_QAsymmS8: np.int8,
+                                ann.DataType_QSymmS8: np.int8,
+                                ann.DataType_Float32: np.float32,
+                                ann.DataType_QSymmS16: np.int16,
+                                ann.DataType_Signed32: np.int32,
+                                ann.DataType_Float16: np.float16}
+
+        tensor_info = _get_tensor_info(dt)
+        tensor = ann.ConstTensor(tensor_info, data)
+        assert np_data_type_mapping[tensor.GetDataType()] == data.dtype
+
+
+# This test checks that mismatched numpy and PyArmNN datatypes with same number of bits raises correct error.
+@pytest.mark.parametrize("dt, data",
+                         [
+                             (ann.DataType_Float32, np.random.randint(1, size=(2, 3)).astype(np.int32)),
+                             (ann.DataType_Float16, np.random.randint(1, size=(2, 3)).astype(np.int16)),
+                             (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 3)).astype(np.int8)),
+                             (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 3)).astype(np.uint8)),
+                             (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 3)).astype(np.uint8)),
+                             (ann.DataType_Signed32, np.random.randint(1, size=(2, 3)).astype(np.float32)),
+                             (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 3)).astype(np.float16))
+                         ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16'])
+def test_numpy_dtype_mismatch_ann_dtype(dt, data):
+    np_data_type_mapping = {ann.DataType_QAsymmU8: np.uint8,
+                            ann.DataType_QAsymmS8: np.int8,
+                            ann.DataType_QSymmS8: np.int8,
+                            ann.DataType_Float32: np.float32,
+                            ann.DataType_QSymmS16: np.int16,
+                            ann.DataType_Signed32: np.int32,
+                            ann.DataType_Float16: np.float16}
+
+    tensor_info = _get_tensor_info(dt)
+    with pytest.raises(TypeError) as err:
+        ann.ConstTensor(tensor_info, data)
+
+    assert str(err.value) == "Expected data to have type {} for type {} but instead got numpy.{}".format(
+        np_data_type_mapping[dt], dt, data.dtype)
+
diff --git a/python/pyarmnn/test/test_descriptors.py b/python/pyarmnn/test/test_descriptors.py
new file mode 100644
index 0000000..1a41105
--- /dev/null
+++ b/python/pyarmnn/test/test_descriptors.py
@@ -0,0 +1,535 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import inspect
+
+import pytest
+
+import pyarmnn as ann
+import numpy as np
+import pyarmnn._generated.pyarmnn as generated
+
+
+def test_activation_descriptor_default_values():
+    desc = ann.ActivationDescriptor()
+    assert desc.m_Function == ann.ActivationFunction_Sigmoid
+    assert desc.m_A == 0
+    assert desc.m_B == 0
+
+
+def test_argminmax_descriptor_default_values():
+    desc = ann.ArgMinMaxDescriptor()
+    assert desc.m_Function == ann.ArgMinMaxFunction_Min
+    assert desc.m_Axis == -1
+
+
+def test_batchnormalization_descriptor_default_values():
+    desc = ann.BatchNormalizationDescriptor()
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+    np.allclose(0.0001, desc.m_Eps)
+
+
+def test_batchtospacend_descriptor_default_values():
+    desc = ann.BatchToSpaceNdDescriptor()
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+    assert [1, 1] == desc.m_BlockShape
+    assert [(0, 0), (0, 0)] == desc.m_Crops
+
+
+def test_batchtospacend_descriptor_assignment():
+    desc = ann.BatchToSpaceNdDescriptor()
+    desc.m_BlockShape = (1, 2, 3)
+
+    ololo = [(1, 2), (3, 4)]
+    size_1 = len(ololo)
+    desc.m_Crops = ololo
+
+    assert size_1 == len(ololo)
+    desc.m_DataLayout = ann.DataLayout_NHWC
+    assert ann.DataLayout_NHWC == desc.m_DataLayout
+    assert [1, 2, 3] == desc.m_BlockShape
+    assert [(1, 2), (3, 4)] == desc.m_Crops
+
+
+@pytest.mark.parametrize("input_shape, value, vtype", [([-1], -1, 'int'), (("one", "two"), "'one'", 'str'),
+                                                       ([1.33, 4.55], 1.33, 'float'),
+                                                       ([{1: "one"}], "{1: 'one'}", 'dict')], ids=lambda x: str(x))
+def test_batchtospacend_descriptor_rubbish_assignment_shape(input_shape, value, vtype):
+    desc = ann.BatchToSpaceNdDescriptor()
+    with pytest.raises(TypeError) as err:
+        desc.m_BlockShape = input_shape
+
+    assert "Failed to convert python input value {} of type '{}' to C type 'j'".format(value, vtype) in str(err.value)
+
+
+@pytest.mark.parametrize("input_crops, value, vtype", [([(1, 2), (3, 4, 5)], '(3, 4, 5)', 'tuple'),
+                                                       ([(1, 'one')], "(1, 'one')", 'tuple'),
+                                                       ([-1], -1, 'int'),
+                                                       ([(1, (1, 2))], '(1, (1, 2))', 'tuple'),
+                                                       ([[1, [1, 2]]], '[1, [1, 2]]', 'list')
+                                                       ], ids=lambda x: str(x))
+def test_batchtospacend_descriptor_rubbish_assignment_crops(input_crops, value, vtype):
+    desc = ann.BatchToSpaceNdDescriptor()
+    with pytest.raises(TypeError) as err:
+        desc.m_Crops = input_crops
+
+    assert "Failed to convert python input value {} of type '{}' to C type".format(value, vtype) in str(err.value)
+
+
+def test_batchtospacend_descriptor_empty_assignment():
+    desc = ann.BatchToSpaceNdDescriptor()
+    desc.m_BlockShape = []
+    assert [] == desc.m_BlockShape
+
+
+def test_batchtospacend_descriptor_ctor():
+    desc = ann.BatchToSpaceNdDescriptor([1, 2, 3], [(4, 5), (6, 7)])
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+    assert [1, 2, 3] == desc.m_BlockShape
+    assert [(4, 5), (6, 7)] == desc.m_Crops
+
+
+def test_convolution2d_descriptor_default_values():
+    desc = ann.Convolution2dDescriptor()
+    assert desc.m_PadLeft == 0
+    assert desc.m_PadTop == 0
+    assert desc.m_PadRight == 0
+    assert desc.m_PadBottom == 0
+    assert desc.m_StrideX == 0
+    assert desc.m_StrideY == 0
+    assert desc.m_DilationX == 1
+    assert desc.m_DilationY == 1
+    assert desc.m_BiasEnabled == False
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+
+
+def test_depthtospace_descriptor_default_values():
+    desc = ann.DepthToSpaceDescriptor()
+    assert desc.m_BlockSize == 1
+    assert desc.m_DataLayout == ann.DataLayout_NHWC
+
+
+def test_depthwise_convolution2d_descriptor_default_values():
+    desc = ann.DepthwiseConvolution2dDescriptor()
+    assert desc.m_PadLeft == 0
+    assert desc.m_PadTop == 0
+    assert desc.m_PadRight == 0
+    assert desc.m_PadBottom == 0
+    assert desc.m_StrideX == 0
+    assert desc.m_StrideY == 0
+    assert desc.m_DilationX == 1
+    assert desc.m_DilationY == 1
+    assert desc.m_BiasEnabled == False
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+
+
+def test_detectionpostprocess_descriptor_default_values():
+    desc = ann.DetectionPostProcessDescriptor()
+    assert desc.m_MaxDetections == 0
+    assert desc.m_MaxClassesPerDetection == 1
+    assert desc.m_DetectionsPerClass == 1
+    assert desc.m_NmsScoreThreshold == 0
+    assert desc.m_NmsIouThreshold == 0
+    assert desc.m_NumClasses == 0
+    assert desc.m_UseRegularNms == False
+    assert desc.m_ScaleH == 0
+    assert desc.m_ScaleW == 0
+    assert desc.m_ScaleX == 0
+    assert desc.m_ScaleY == 0
+
+
+def test_fakequantization_descriptor_default_values():
+    desc = ann.FakeQuantizationDescriptor()
+    np.allclose(6, desc.m_Max)
+    np.allclose(-6, desc.m_Min)
+
+
+def test_fully_connected_descriptor_default_values():
+    desc = ann.FullyConnectedDescriptor()
+    assert desc.m_BiasEnabled == False
+    assert desc.m_TransposeWeightMatrix == False
+
+
+def test_instancenormalization_descriptor_default_values():
+    desc = ann.InstanceNormalizationDescriptor()
+    assert desc.m_Gamma == 1
+    assert desc.m_Beta == 0
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+    np.allclose(1e-12, desc.m_Eps)
+
+
+def test_lstm_descriptor_default_values():
+    desc = ann.LstmDescriptor()
+    assert desc.m_ActivationFunc == 1
+    assert desc.m_ClippingThresCell == 0
+    assert desc.m_ClippingThresProj == 0
+    assert desc.m_CifgEnabled == True
+    assert desc.m_PeepholeEnabled == False
+    assert desc.m_ProjectionEnabled == False
+    assert desc.m_LayerNormEnabled == False
+
+
+def test_l2normalization_descriptor_default_values():
+    desc = ann.L2NormalizationDescriptor()
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+    np.allclose(1e-12, desc.m_Eps)
+
+
+def test_mean_descriptor_default_values():
+    desc = ann.MeanDescriptor()
+    assert desc.m_KeepDims == False
+
+
+def test_normalization_descriptor_default_values():
+    desc = ann.NormalizationDescriptor()
+    assert desc.m_NormChannelType == ann.NormalizationAlgorithmChannel_Across
+    assert desc.m_NormMethodType == ann.NormalizationAlgorithmMethod_LocalBrightness
+    assert desc.m_NormSize == 0
+    assert desc.m_Alpha == 0
+    assert desc.m_Beta == 0
+    assert desc.m_K == 0
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+
+
+def test_origin_descriptor_default_values():
+    desc = ann.ConcatDescriptor()
+    assert 0 == desc.GetNumViews()
+    assert 0 == desc.GetNumDimensions()
+    assert 1 == desc.GetConcatAxis()
+
+
+def test_origin_descriptor_incorrect_views():
+    desc = ann.ConcatDescriptor(2, 2)
+    with pytest.raises(RuntimeError) as err:
+        desc.SetViewOriginCoord(1000, 100, 1000)
+    assert "Failed to set view origin coordinates." in str(err.value)
+
+
+def test_origin_descriptor_ctor():
+    desc = ann.ConcatDescriptor(2, 2)
+    value = 5
+    for i in range(desc.GetNumViews()):
+        for j in range(desc.GetNumDimensions()):
+            desc.SetViewOriginCoord(i, j, value+i)
+    desc.SetConcatAxis(1)
+
+    assert 2 == desc.GetNumViews()
+    assert 2 == desc.GetNumDimensions()
+    assert [5, 5] == desc.GetViewOrigin(0)
+    assert [6, 6] == desc.GetViewOrigin(1)
+    assert 1 == desc.GetConcatAxis()
+
+
+def test_pad_descriptor_default_values():
+    desc = ann.PadDescriptor()
+    assert desc.m_PadValue == 0
+
+
+def test_permute_descriptor_default_values():
+    pv = ann.PermutationVector((0, 2, 3, 1))
+    desc = ann.PermuteDescriptor(pv)
+    assert desc.m_DimMappings.GetSize() == 4
+    assert desc.m_DimMappings[0] == 0
+    assert desc.m_DimMappings[1] == 2
+    assert desc.m_DimMappings[2] == 3
+    assert desc.m_DimMappings[3] == 1
+
+
+def test_pooling_descriptor_default_values():
+    desc = ann.Pooling2dDescriptor()
+    assert desc.m_PoolType == ann.PoolingAlgorithm_Max
+    assert desc.m_PadLeft == 0
+    assert desc.m_PadTop == 0
+    assert desc.m_PadRight == 0
+    assert desc.m_PadBottom == 0
+    assert desc.m_PoolHeight == 0
+    assert desc.m_PoolWidth == 0
+    assert desc.m_StrideX == 0
+    assert desc.m_StrideY == 0
+    assert desc.m_OutputShapeRounding == ann.OutputShapeRounding_Floor
+    assert desc.m_PaddingMethod == ann.PaddingMethod_Exclude
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+
+
+def test_reshape_descriptor_default_values():
+    desc = ann.ReshapeDescriptor()
+    # check the empty Targetshape
+    assert desc.m_TargetShape.GetNumDimensions() == 0
+
+
+def test_slice_descriptor_default_values():
+    desc = ann.SliceDescriptor()
+    assert desc.m_TargetWidth == 0
+    assert desc.m_TargetHeight == 0
+    assert desc.m_Method == ann.ResizeMethod_NearestNeighbor
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+
+
+def test_resize_descriptor_default_values():
+    desc = ann.ResizeDescriptor()
+    assert desc.m_TargetWidth == 0
+    assert desc.m_TargetHeight == 0
+    assert desc.m_Method == ann.ResizeMethod_NearestNeighbor
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+    assert desc.m_BilinearAlignCorners == False
+
+
+def test_spacetobatchnd_descriptor_default_values():
+    desc = ann.SpaceToBatchNdDescriptor()
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+
+
+def test_spacetodepth_descriptor_default_values():
+    desc = ann.SpaceToDepthDescriptor()
+    assert desc.m_BlockSize == 1
+    assert desc.m_DataLayout == ann.DataLayout_NHWC
+
+
+def test_stack_descriptor_default_values():
+    desc = ann.StackDescriptor()
+    assert desc.m_Axis == 0
+    assert desc.m_NumInputs == 0
+    # check the empty Inputshape
+    assert desc.m_InputShape.GetNumDimensions() == 0
+
+
+def test_slice_descriptor_default_values():
+    desc = ann.SliceDescriptor()
+    desc.m_Begin = [1, 2, 3, 4, 5]
+    desc.m_Size = (1, 2, 3, 4)
+
+    assert [1, 2, 3, 4, 5] == desc.m_Begin
+    assert [1, 2, 3, 4] == desc.m_Size
+
+
+def test_slice_descriptor_ctor():
+    desc = ann.SliceDescriptor([1, 2, 3, 4, 5], (1, 2, 3, 4))
+
+    assert [1, 2, 3, 4, 5] == desc.m_Begin
+    assert [1, 2, 3, 4] == desc.m_Size
+
+
+def test_strided_slice_descriptor_default_values():
+    desc = ann.StridedSliceDescriptor()
+    desc.m_Begin = [1, 2, 3, 4, 5]
+    desc.m_End = [6, 7, 8, 9, 10]
+    desc.m_Stride = (10, 10)
+    desc.m_BeginMask = 1
+    desc.m_EndMask = 2
+    desc.m_ShrinkAxisMask = 3
+    desc.m_EllipsisMask = 4
+    desc.m_NewAxisMask = 5
+
+    assert [1, 2, 3, 4, 5] == desc.m_Begin
+    assert [6, 7, 8, 9, 10] == desc.m_End
+    assert [10, 10] == desc.m_Stride
+    assert 1 == desc.m_BeginMask
+    assert 2 == desc.m_EndMask
+    assert 3 == desc.m_ShrinkAxisMask
+    assert 4 == desc.m_EllipsisMask
+    assert 5 == desc.m_NewAxisMask
+
+
+def test_strided_slice_descriptor_ctor():
+    desc = ann.StridedSliceDescriptor([1, 2, 3, 4, 5], [6, 7, 8, 9, 10], (10, 10))
+    desc.m_Begin = [1, 2, 3, 4, 5]
+    desc.m_End = [6, 7, 8, 9, 10]
+    desc.m_Stride = (10, 10)
+
+    assert [1, 2, 3, 4, 5] == desc.m_Begin
+    assert [6, 7, 8, 9, 10] == desc.m_End
+    assert [10, 10] == desc.m_Stride
+
+
+def test_softmax_descriptor_default_values():
+    desc = ann.SoftmaxDescriptor()
+    assert desc.m_Axis == -1
+    np.allclose(1.0, desc.m_Beta)
+
+
+def test_space_to_batch_nd_descriptor_default_values():
+    desc = ann.SpaceToBatchNdDescriptor()
+    assert [1, 1] == desc.m_BlockShape
+    assert [(0, 0), (0, 0)] == desc.m_PadList
+    assert ann.DataLayout_NCHW == desc.m_DataLayout
+
+
+def test_space_to_batch_nd_descriptor_assigned_values():
+    desc = ann.SpaceToBatchNdDescriptor()
+    desc.m_BlockShape = (90, 100)
+    desc.m_PadList = [(1, 2), (3, 4)]
+    assert [90, 100] == desc.m_BlockShape
+    assert [(1, 2), (3, 4)] == desc.m_PadList
+    assert ann.DataLayout_NCHW == desc.m_DataLayout
+
+
+def test_space_to_batch_nd_descriptor_ctor():
+    desc = ann.SpaceToBatchNdDescriptor((1, 2, 3), [(1, 2), (3, 4)])
+    assert [1, 2, 3] == desc.m_BlockShape
+    assert [(1, 2), (3, 4)] == desc.m_PadList
+    assert ann.DataLayout_NCHW == desc.m_DataLayout
+
+
+def test_transpose_convolution2d_descriptor_default_values():
+    desc = ann.DepthwiseConvolution2dDescriptor()
+    assert desc.m_PadLeft == 0
+    assert desc.m_PadTop == 0
+    assert desc.m_PadRight == 0
+    assert desc.m_PadBottom == 0
+    assert desc.m_StrideX == 0
+    assert desc.m_StrideY == 0
+    assert desc.m_BiasEnabled == False
+    assert desc.m_DataLayout == ann.DataLayout_NCHW
+
+
+def test_view_descriptor_default_values():
+    desc = ann.SplitterDescriptor()
+    assert 0 == desc.GetNumViews()
+    assert 0 == desc.GetNumDimensions()
+
+
+def test_elementwise_unary_descriptor_default_values():
+    desc = ann.ElementwiseUnaryDescriptor()
+    assert desc.m_Operation == ann.UnaryOperation_Abs
+
+
+def test_view_descriptor_incorrect_input():
+    desc = ann.SplitterDescriptor(2, 3)
+    with pytest.raises(RuntimeError) as err:
+        desc.SetViewOriginCoord(1000, 100, 1000)
+    assert "Failed to set view origin coordinates." in str(err.value)
+
+    with pytest.raises(RuntimeError) as err:
+        desc.SetViewSize(1000, 100, 1000)
+    assert "Failed to set view size." in str(err.value)
+
+
+def test_view_descriptor_ctor():
+    desc = ann.SplitterDescriptor(2, 3)
+    value_size = 1
+    value_orig_coord = 5
+    for i in range(desc.GetNumViews()):
+        for j in range(desc.GetNumDimensions()):
+            desc.SetViewOriginCoord(i, j, value_orig_coord+i)
+            desc.SetViewSize(i, j, value_size+i)
+
+    assert 2 == desc.GetNumViews()
+    assert 3 == desc.GetNumDimensions()
+    assert [5, 5] == desc.GetViewOrigin(0)
+    assert [6, 6] == desc.GetViewOrigin(1)
+    assert [1, 1] == desc.GetViewSizes(0)
+    assert [2, 2] == desc.GetViewSizes(1)
+
+
+def test_createdescriptorforconcatenation_ctor():
+    input_shape_vector = [ann.TensorShape((2, 1)), ann.TensorShape((3, 1)), ann.TensorShape((4, 1))]
+    desc = ann.CreateDescriptorForConcatenation(input_shape_vector, 0)
+    assert 3 == desc.GetNumViews()
+    assert 0 == desc.GetConcatAxis()
+    assert 2 == desc.GetNumDimensions()
+    c = desc.GetViewOrigin(1)
+    d = desc.GetViewOrigin(0)
+
+
+def test_createdescriptorforconcatenation_wrong_shape_for_axis():
+    input_shape_vector = [ann.TensorShape((1, 2)), ann.TensorShape((3, 4)), ann.TensorShape((5, 6))]
+    with pytest.raises(RuntimeError) as err:
+        desc = ann.CreateDescriptorForConcatenation(input_shape_vector, 0)
+
+    assert "All inputs to concatenation must be the same size along all dimensions  except the concatenation dimension" in str(
+        err.value)
+
+
+@pytest.mark.parametrize("input_shape_vector", [([-1, "one"]),
+                                                ([1.33, 4.55]),
+                                                ([{1: "one"}])], ids=lambda x: str(x))
+def test_createdescriptorforconcatenation_rubbish_assignment_shape_vector(input_shape_vector):
+    with pytest.raises(TypeError) as err:
+        desc = ann.CreateDescriptorForConcatenation(input_shape_vector, 0)
+
+    assert "in method 'CreateDescriptorForConcatenation', argument 1 of type 'std::vector< armnn::TensorShape,std::allocator< armnn::TensorShape > >'" in str(
+        err.value)
+
+
+generated_classes = inspect.getmembers(generated, inspect.isclass)
+generated_classes_names = list(map(lambda x: x[0], generated_classes))
+@pytest.mark.parametrize("desc_name", ['ActivationDescriptor',
+                                       'ArgMinMaxDescriptor',
+                                       'PermuteDescriptor',
+                                       'SoftmaxDescriptor',
+                                       'ConcatDescriptor',
+                                       'SplitterDescriptor',
+                                       'Pooling2dDescriptor',
+                                       'FullyConnectedDescriptor',
+                                       'Convolution2dDescriptor',
+                                       'DepthwiseConvolution2dDescriptor',
+                                       'DetectionPostProcessDescriptor',
+                                       'NormalizationDescriptor',
+                                       'L2NormalizationDescriptor',
+                                       'BatchNormalizationDescriptor',
+                                       'InstanceNormalizationDescriptor',
+                                       'BatchToSpaceNdDescriptor',
+                                       'FakeQuantizationDescriptor',
+                                       'ResizeDescriptor',
+                                       'ReshapeDescriptor',
+                                       'SpaceToBatchNdDescriptor',
+                                       'SpaceToDepthDescriptor',
+                                       'LstmDescriptor',
+                                       'MeanDescriptor',
+                                       'PadDescriptor',
+                                       'SliceDescriptor',
+                                       'StackDescriptor',
+                                       'StridedSliceDescriptor',
+                                       'TransposeConvolution2dDescriptor',
+                                       'ElementwiseUnaryDescriptor'])
+class TestDescriptorMassChecks:
+
+    def test_desc_implemented(self, desc_name):
+        assert desc_name in generated_classes_names
+
+    def test_desc_equal(self, desc_name):
+        desc_class = next(filter(lambda x: x[0] == desc_name, generated_classes))[1]
+
+        assert desc_class() == desc_class()
+
+
+generated_classes = inspect.getmembers(generated, inspect.isclass)
+generated_classes_names = list(map(lambda x: x[0], generated_classes))
+@pytest.mark.parametrize("desc_name", ['ActivationDescriptor',
+                                       'ArgMinMaxDescriptor',
+                                       'PermuteDescriptor',
+                                       'SoftmaxDescriptor',
+                                       'ConcatDescriptor',
+                                       'SplitterDescriptor',
+                                       'Pooling2dDescriptor',
+                                       'FullyConnectedDescriptor',
+                                       'Convolution2dDescriptor',
+                                       'DepthwiseConvolution2dDescriptor',
+                                       'DetectionPostProcessDescriptor',
+                                       'NormalizationDescriptor',
+                                       'L2NormalizationDescriptor',
+                                       'BatchNormalizationDescriptor',
+                                       'InstanceNormalizationDescriptor',
+                                       'BatchToSpaceNdDescriptor',
+                                       'FakeQuantizationDescriptor',
+                                       'ResizeDescriptor',
+                                       'ReshapeDescriptor',
+                                       'SpaceToBatchNdDescriptor',
+                                       'SpaceToDepthDescriptor',
+                                       'LstmDescriptor',
+                                       'MeanDescriptor',
+                                       'PadDescriptor',
+                                       'SliceDescriptor',
+                                       'StackDescriptor',
+                                       'StridedSliceDescriptor',
+                                       'TransposeConvolution2dDescriptor',
+                                       'ElementwiseUnaryDescriptor'])
+class TestDescriptorMassChecks:
+
+    def test_desc_implemented(self, desc_name):
+        assert desc_name in generated_classes_names
+
+    def test_desc_equal(self, desc_name):
+        desc_class = next(filter(lambda x: x[0] == desc_name, generated_classes))[1]
+
+        assert desc_class() == desc_class()
+
diff --git a/python/pyarmnn/test/test_generated.py b/python/pyarmnn/test/test_generated.py
new file mode 100644
index 0000000..24765c7
--- /dev/null
+++ b/python/pyarmnn/test/test_generated.py
@@ -0,0 +1,52 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import inspect
+from typing import Tuple
+
+import pytest
+
+import pyarmnn._generated.pyarmnn as generated_armnn
+import pyarmnn._generated.pyarmnn_caffeparser as generated_caffe
+import pyarmnn._generated.pyarmnn_onnxparser as generated_onnx
+import pyarmnn._generated.pyarmnn_tfliteparser as generated_tflite
+import pyarmnn._generated.pyarmnn_tfparser as generated_tf
+
+swig_independent_classes = ('IBackend',
+                            'IDeviceSpec',
+                            'IConnectableLayer',
+                            'IInputSlot',
+                            'IOutputSlot',
+                            'IProfiler')
+
+
+def get_classes(swig_independent_classes: Tuple):
+    # We need to ignore some swig generated_armnn classes. This is because some are abstract classes
+    # They cannot be created with the swig generated_armnn wrapper, therefore they don't need a destructor.
+    # Swig also generates its own meta class - this needs to be ignored.
+    ignored_class_names = (*swig_independent_classes, '_SwigNonDynamicMeta')
+    return list(filter(lambda x: x[0] not in ignored_class_names,
+                       inspect.getmembers(generated_armnn, inspect.isclass) +
+                       inspect.getmembers(generated_caffe, inspect.isclass) +
+                       inspect.getmembers(generated_tflite, inspect.isclass) +
+                       inspect.getmembers(generated_onnx, inspect.isclass) +
+                       inspect.getmembers(generated_tf, inspect.isclass)))
+
+
+@pytest.mark.parametrize("class_instance", get_classes(swig_independent_classes), ids=lambda x: 'class={}'.format(x[0]))
+class TestPyOwnedClasses:
+
+    def test_destructors_exist_per_class(self, class_instance):
+        assert getattr(class_instance[1], '__swig_destroy__', None)
+
+    def test_owned(self, class_instance):
+        assert getattr(class_instance[1], 'thisown', None)
+
+
+@pytest.mark.parametrize("class_instance", swig_independent_classes)
+class TestPyIndependentClasses:
+
+    def test_destructors_does_not_exist_per_class(self, class_instance):
+        assert not getattr(class_instance[1], '__swig_destroy__', None)
+
+    def test_not_owned(self, class_instance):
+        assert not getattr(class_instance[1], 'thisown', None)
diff --git a/python/pyarmnn/test/test_iconnectable.py b/python/pyarmnn/test/test_iconnectable.py
new file mode 100644
index 0000000..0d15be5
--- /dev/null
+++ b/python/pyarmnn/test/test_iconnectable.py
@@ -0,0 +1,142 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import pytest
+
+import pyarmnn as ann
+
+
+@pytest.fixture(scope="function")
+def network():
+    return ann.INetwork()
+
+
+class TestIInputIOutputIConnectable:
+
+    def test_input_slot(self, network):
+        # Create input, addition & output layer
+        input1 = network.AddInputLayer(0, "input1")
+        input2 = network.AddInputLayer(1, "input2")
+        add = network.AddAdditionLayer("addition")
+        output = network.AddOutputLayer(0, "output")
+
+        # Connect the input/output slots for each layer
+        input1.GetOutputSlot(0).Connect(add.GetInputSlot(0))
+        input2.GetOutputSlot(0).Connect(add.GetInputSlot(1))
+        add.GetOutputSlot(0).Connect(output.GetInputSlot(0))
+
+        # Check IInputSlot GetConnection()
+        input_slot = add.GetInputSlot(0)
+        input_slot_connection = input_slot.GetConnection()
+
+        assert isinstance(input_slot_connection, ann.IOutputSlot)
+
+        del input_slot_connection
+
+        assert input_slot.GetConnection()
+        assert isinstance(input_slot.GetConnection(), ann.IOutputSlot)
+
+        del input_slot
+
+        assert add.GetInputSlot(0)
+
+    def test_output_slot(self, network):
+
+        # Create input, addition & output layer
+        input1 = network.AddInputLayer(0, "input1")
+        input2 = network.AddInputLayer(1, "input2")
+        add = network.AddAdditionLayer("addition")
+        output = network.AddOutputLayer(0, "output")
+
+        # Connect the input/output slots for each layer
+        input1.GetOutputSlot(0).Connect(add.GetInputSlot(0))
+        input2.GetOutputSlot(0).Connect(add.GetInputSlot(1))
+        add.GetOutputSlot(0).Connect(output.GetInputSlot(0))
+
+        # Check IInputSlot GetConnection()
+        add_get_input_connection = add.GetInputSlot(0).GetConnection()
+        output_get_input_connection = output.GetInputSlot(0).GetConnection()
+
+        # Check IOutputSlot GetConnection()
+        add_get_output_connect = add.GetOutputSlot(0).GetConnection(0)
+        assert isinstance(add_get_output_connect.GetConnection(), ann.IOutputSlot)
+
+        # Test IOutputSlot GetNumConnections() & CalculateIndexOnOwner()
+        assert add_get_input_connection.GetNumConnections() == 1
+        assert len(add_get_input_connection) == 1
+        assert add_get_input_connection[0]
+        assert add_get_input_connection.CalculateIndexOnOwner() == 0
+
+        # Check GetOwningLayerGuid(). Check that it is different for add and output layer
+        assert add_get_input_connection.GetOwningLayerGuid() != output_get_input_connection.GetOwningLayerGuid()
+
+        # Set TensorInfo
+        test_tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_Float32)
+
+        # Check IsTensorInfoSet()
+        assert not add_get_input_connection.IsTensorInfoSet()
+        add_get_input_connection.SetTensorInfo(test_tensor_info)
+        assert add_get_input_connection.IsTensorInfoSet()
+
+        # Check GetTensorInfo()
+        output_tensor_info = add_get_input_connection.GetTensorInfo()
+        assert 2 == output_tensor_info.GetNumDimensions()
+        assert 6 == output_tensor_info.GetNumElements()
+
+        # Check Disconnect()
+        assert output_get_input_connection.GetNumConnections() == 1  # 1 connection to Outputslot0 from input1
+        add.GetOutputSlot(0).Disconnect(output.GetInputSlot(0))  # disconnect add.OutputSlot0 from Output.InputSlot0
+        assert output_get_input_connection.GetNumConnections() == 0
+
+    def test_output_slot__out_of_range(self, network):
+        # Create input layer to check output slot get item handling
+        input1 = network.AddInputLayer(0, "input1")
+
+        outputSlot = input1.GetOutputSlot(0)
+        with pytest.raises(ValueError) as err:
+                outputSlot[1]
+
+        assert "Invalid index 1 provided" in str(err.value)
+
+    def test_iconnectable_guid(self, network):
+
+        # Check IConnectable GetGuid()
+        # Note Guid can change based on which tests are run so
+        # checking here that each layer does not have the same guid
+        add_id = network.AddAdditionLayer().GetGuid()
+        output_id = network.AddOutputLayer(0).GetGuid()
+        assert add_id != output_id
+
+    def test_iconnectable_layer_functions(self, network):
+
+        # Create input, addition & output layer
+        input1 = network.AddInputLayer(0, "input1")
+        input2 = network.AddInputLayer(1, "input2")
+        add = network.AddAdditionLayer("addition")
+        output = network.AddOutputLayer(0, "output")
+
+        # Check GetNumInputSlots(), GetName() & GetNumOutputSlots()
+        assert input1.GetNumInputSlots() == 0
+        assert input1.GetName() == "input1"
+        assert input1.GetNumOutputSlots() == 1
+
+        assert input2.GetNumInputSlots() == 0
+        assert input2.GetName() == "input2"
+        assert input2.GetNumOutputSlots() == 1
+
+        assert add.GetNumInputSlots() == 2
+        assert add.GetName() == "addition"
+        assert add.GetNumOutputSlots() == 1
+
+        assert output.GetNumInputSlots() == 1
+        assert output.GetName() == "output"
+        assert output.GetNumOutputSlots() == 0
+
+        # Check GetOutputSlot()
+        input1_get_output = input1.GetOutputSlot(0)
+        assert input1_get_output.GetNumConnections() == 0
+        assert len(input1_get_output) == 0
+
+        # Check GetInputSlot()
+        add_get_input = add.GetInputSlot(0)
+        add_get_input.GetConnection()
+        assert isinstance(add_get_input, ann.IInputSlot)
diff --git a/python/pyarmnn/test/test_network.py b/python/pyarmnn/test/test_network.py
new file mode 100644
index 0000000..fc2591c
--- /dev/null
+++ b/python/pyarmnn/test/test_network.py
@@ -0,0 +1,288 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+import stat
+
+import pytest
+import pyarmnn as ann
+
+
+@pytest.fixture(scope="function")
+def get_runtime(shared_data_folder, network_file):
+    parser= ann.ITfLiteParser()
+    preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')]
+    network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, network_file))
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    yield preferred_backends, network, runtime
+
+
+@pytest.mark.parametrize("network_file",
+                         [
+                             'mock_model.tflite',
+                         ],
+                         ids=['mock_model'])
+def test_optimize_executes_successfully(network_file, get_runtime):
+    preferred_backends = [ann.BackendId('CpuRef')]
+    network = get_runtime[1]
+    runtime = get_runtime[2]
+
+    opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    assert len(messages) == 0, 'With only CpuRef, there should be no warnings irrelevant of architecture.'
+    assert opt_network
+
+
+@pytest.mark.parametrize("network_file",
+                         [
+                             'mock_model.tflite',
+                         ],
+                         ids=['mock_model'])
+def test_optimize_owned_by_python(network_file, get_runtime):
+    preferred_backends = get_runtime[0]
+    network = get_runtime[1]
+    runtime = get_runtime[2]
+
+    opt_network, _ = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+    assert opt_network.thisown
+
+
+@pytest.mark.aarch64
+@pytest.mark.parametrize("network_file",
+                         [
+                             'mock_model.tflite'
+                         ],
+                         ids=['mock_model'])
+def test_optimize_executes_successfully_for_neon_backend_only(network_file, get_runtime):
+    preferred_backends = [ann.BackendId('CpuAcc')]
+    network = get_runtime[1]
+    runtime = get_runtime[2]
+
+    opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+    assert 0 == len(messages)
+    assert opt_network
+
+
+@pytest.mark.parametrize("network_file",
+                         [
+                             'mock_model.tflite'
+                         ],
+                         ids=['mock_model'])
+def test_optimize_fails_for_invalid_backends(network_file, get_runtime):
+    invalid_backends = [ann.BackendId('Unknown')]
+    network = get_runtime[1]
+    runtime = get_runtime[2]
+
+    with pytest.raises(RuntimeError) as err:
+        ann.Optimize(network, invalid_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    expected_error_message = "None of the preferred backends [Unknown ] are supported."
+    assert expected_error_message in str(err.value)
+
+
+@pytest.mark.parametrize("network_file",
+                         [
+                             'mock_model.tflite'
+                         ],
+                         ids=['mock_model'])
+def test_optimize_fails_for_no_backends_specified(network_file, get_runtime):
+    empty_backends = []
+    network = get_runtime[1]
+    runtime = get_runtime[2]
+
+    with pytest.raises(RuntimeError) as err:
+        ann.Optimize(network, empty_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    expected_error_message = "Invoked Optimize with no backends specified"
+    assert expected_error_message in str(err.value)
+
+
+@pytest.mark.parametrize("network_file",
+                         [
+                             'mock_model.tflite'
+                         ],
+                         ids=['mock_model'])
+def test_serialize_to_dot(network_file, get_runtime, tmpdir):
+    preferred_backends = get_runtime[0]
+    network = get_runtime[1]
+    runtime = get_runtime[2]
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+    dot_file_path = os.path.join(tmpdir, 'mock_model.dot')
+    """Check that serialized file does not exist at the start, gets created after SerializeToDot and is not empty"""
+    assert not os.path.exists(dot_file_path)
+    opt_network.SerializeToDot(dot_file_path)
+
+    assert os.path.exists(dot_file_path)
+
+    with open(dot_file_path) as res_file:
+        expected_data = res_file.read()
+        assert len(expected_data) > 1
+        assert '[label=< [1,28,28,1] >]' in expected_data
+
+
+@pytest.mark.x86_64
+@pytest.mark.parametrize("network_file",
+                         [
+                             'mock_model.tflite'
+                         ],
+                         ids=['mock_model'])
+def test_serialize_to_dot_mode_readonly(network_file, get_runtime, tmpdir):
+    preferred_backends = get_runtime[0]
+    network = get_runtime[1]
+    runtime = get_runtime[2]
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+    """Create file, write to it and change mode to read-only"""
+    dot_file_path = os.path.join(tmpdir, 'mock_model.dot')
+    f = open(dot_file_path, "w+")
+    f.write("test")
+    f.close()
+    os.chmod(dot_file_path, stat.S_IREAD)
+    assert os.path.exists(dot_file_path)
+
+    with pytest.raises(RuntimeError) as err:
+        opt_network.SerializeToDot(dot_file_path)
+
+    expected_error_message = "Failed to open dot file"
+    assert expected_error_message in str(err.value)
+
+
+@pytest.mark.parametrize("method", [
+    'AddActivationLayer',
+    'AddAdditionLayer',
+    'AddArgMinMaxLayer',
+    'AddBatchNormalizationLayer',
+    'AddBatchToSpaceNdLayer',
+    'AddComparisonLayer',
+    'AddConcatLayer',
+    'AddConstantLayer',
+    'AddConvolution2dLayer',
+    'AddDepthToSpaceLayer',
+    'AddDepthwiseConvolution2dLayer',
+    'AddDequantizeLayer',
+    'AddDetectionPostProcessLayer',
+    'AddDivisionLayer',
+    'AddElementwiseUnaryLayer',
+    'AddFloorLayer',
+    'AddFullyConnectedLayer',
+    'AddGatherLayer',
+    'AddInputLayer',
+    'AddInstanceNormalizationLayer',
+    'AddLogSoftmaxLayer',
+    'AddL2NormalizationLayer',
+    'AddLstmLayer',
+    'AddMaximumLayer',
+    'AddMeanLayer',
+    'AddMergeLayer',
+    'AddMinimumLayer',
+    'AddMultiplicationLayer',
+    'AddNormalizationLayer',
+    'AddOutputLayer',
+    'AddPadLayer',
+    'AddPermuteLayer',
+    'AddPooling2dLayer',
+    'AddPreluLayer',
+    'AddQuantizeLayer',
+    'AddQuantizedLstmLayer',
+    'AddReshapeLayer',
+    'AddResizeLayer',
+    'AddSliceLayer',
+    'AddSoftmaxLayer',
+    'AddSpaceToBatchNdLayer',
+    'AddSpaceToDepthLayer',
+    'AddSplitterLayer',
+    'AddStackLayer',
+    'AddStandInLayer',
+    'AddStridedSliceLayer',
+    'AddSubtractionLayer',
+    'AddSwitchLayer',
+    'AddTransposeConvolution2dLayer'
+])
+def test_network_method_exists(method):
+    assert getattr(ann.INetwork, method, None)
+
+
+def test_fullyconnected_layer_optional_none():
+    net = ann.INetwork()
+    layer = net.AddFullyConnectedLayer(fullyConnectedDescriptor=ann.FullyConnectedDescriptor(),
+                               weights=ann.ConstTensor())
+
+    assert layer
+
+
+def test_fullyconnected_layer_optional_provided():
+    net = ann.INetwork()
+    layer = net.AddFullyConnectedLayer(fullyConnectedDescriptor=ann.FullyConnectedDescriptor(),
+                               weights=ann.ConstTensor(),
+                               biases=ann.ConstTensor())
+
+    assert layer
+
+
+def test_fullyconnected_layer_all_args():
+    net = ann.INetwork()
+    layer = net.AddFullyConnectedLayer(fullyConnectedDescriptor=ann.FullyConnectedDescriptor(),
+                                       weights=ann.ConstTensor(),
+                                       biases=ann.ConstTensor(),
+                                       name='NAME1')
+
+    assert layer
+    assert 'NAME1' == layer.GetName()
+
+
+def test_DepthwiseConvolution2d_layer_optional_none():
+    net = ann.INetwork()
+    layer = net.AddDepthwiseConvolution2dLayer(convolution2dDescriptor=ann.DepthwiseConvolution2dDescriptor(),
+                               weights=ann.ConstTensor())
+
+    assert layer
+
+
+def test_DepthwiseConvolution2d_layer_optional_provided():
+    net = ann.INetwork()
+    layer = net.AddDepthwiseConvolution2dLayer(convolution2dDescriptor=ann.DepthwiseConvolution2dDescriptor(),
+                               weights=ann.ConstTensor(),
+                               biases=ann.ConstTensor())
+
+    assert layer
+
+
+def test_DepthwiseConvolution2d_layer_all_args():
+    net = ann.INetwork()
+    layer = net.AddDepthwiseConvolution2dLayer(convolution2dDescriptor=ann.DepthwiseConvolution2dDescriptor(),
+                                       weights=ann.ConstTensor(),
+                                       biases=ann.ConstTensor(),
+                                       name='NAME1')
+
+    assert layer
+    assert 'NAME1' == layer.GetName()
+
+
+def test_Convolution2d_layer_optional_none():
+    net = ann.INetwork()
+    layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(),
+                               weights=ann.ConstTensor())
+
+    assert layer
+
+
+def test_Convolution2d_layer_optional_provided():
+    net = ann.INetwork()
+    layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(),
+                               weights=ann.ConstTensor(),
+                               biases=ann.ConstTensor())
+
+    assert layer
+
+
+def test_Convolution2d_layer_all_args():
+    net = ann.INetwork()
+    layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(),
+                                       weights=ann.ConstTensor(),
+                                       biases=ann.ConstTensor(),
+                                       name='NAME1')
+
+    assert layer
+    assert 'NAME1' == layer.GetName()
diff --git a/python/pyarmnn/test/test_onnx_parser.py b/python/pyarmnn/test/test_onnx_parser.py
new file mode 100644
index 0000000..99353a0
--- /dev/null
+++ b/python/pyarmnn/test/test_onnx_parser.py
@@ -0,0 +1,111 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+
+import pytest
+import pyarmnn as ann
+import numpy as np
+from typing import List
+
+
+@pytest.fixture()
+def parser(shared_data_folder):
+    """
+    Parse and setup the test network to be used for the tests below
+    """
+
+    # create onnx parser
+    parser = ann.IOnnxParser()
+
+    # path to model
+    path_to_model = os.path.join(shared_data_folder, 'mock_model.onnx')
+
+    # parse onnx binary & create network
+    parser.CreateNetworkFromBinaryFile(path_to_model)
+
+    yield parser
+
+
+def test_onnx_parser_swig_destroy():
+    assert ann.IOnnxParser.__swig_destroy__, "There is a swig python destructor defined"
+    assert ann.IOnnxParser.__swig_destroy__.__name__ == "delete_IOnnxParser"
+
+
+def test_check_onnx_parser_swig_ownership(parser):
+    # Check to see that SWIG has ownership for parser. This instructs SWIG to take
+    # ownership of the return value. This allows the value to be automatically
+    # garbage-collected when it is no longer in use
+    assert parser.thisown
+
+
+def test_onnx_parser_get_network_input_binding_info(parser):
+    input_binding_info = parser.GetNetworkInputBindingInfo("input")
+
+    tensor = input_binding_info[1]
+    assert tensor.GetDataType() == 1
+    assert tensor.GetNumDimensions() == 4
+    assert tensor.GetNumElements() == 784
+    assert tensor.GetQuantizationOffset() == 0
+    assert tensor.GetQuantizationScale() == 0
+
+
+def test_onnx_parser_get_network_output_binding_info(parser):
+    output_binding_info = parser.GetNetworkOutputBindingInfo("output")
+
+    tensor = output_binding_info[1]
+    assert tensor.GetDataType() == 1
+    assert tensor.GetNumDimensions() == 4
+    assert tensor.GetNumElements() == 10
+    assert tensor.GetQuantizationOffset() == 0
+    assert tensor.GetQuantizationScale() == 0
+
+
+def test_onnx_filenotfound_exception(shared_data_folder):
+    parser = ann.IOnnxParser()
+
+    # path to model
+    path_to_model = os.path.join(shared_data_folder, 'some_unknown_model.onnx')
+
+    # parse onnx binary & create network
+
+    with pytest.raises(RuntimeError) as err:
+        parser.CreateNetworkFromBinaryFile(path_to_model)
+
+    # Only check for part of the exception since the exception returns
+    # absolute path which will change on different machines.
+    assert 'Invalid (null) filename' in str(err.value)
+
+
+def test_onnx_parser_end_to_end(shared_data_folder):
+    parser = ann.IOnnxParser = ann.IOnnxParser()
+
+    network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.onnx'))
+
+    # load test image data stored in input_onnx.npy
+    input_binding_info = parser.GetNetworkInputBindingInfo("input")
+    input_tensor_data = np.load(os.path.join(shared_data_folder, 'onnx_parser/input_onnx.npy')).astype(np.float32)
+
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')]
+    opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    assert 0 == len(messages)
+
+    net_id, messages = runtime.LoadNetwork(opt_network)
+
+    assert "" == messages
+
+    input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data])
+    output_tensors = ann.make_output_tensors([parser.GetNetworkOutputBindingInfo("output")])
+
+    runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+    output = ann.workload_tensors_to_ndarray(output_tensors)
+
+    # Load golden output file for result comparison.
+    golden_output = np.load(os.path.join(shared_data_folder, 'onnx_parser/golden_output_onnx.npy'))
+
+    # Check that output matches golden output to 4 decimal places (there are slight rounding differences after this)
+    np.testing.assert_almost_equal(output[0], golden_output, decimal=4)
diff --git a/python/pyarmnn/test/test_profiling_utilities.py b/python/pyarmnn/test/test_profiling_utilities.py
new file mode 100644
index 0000000..b7b91b5
--- /dev/null
+++ b/python/pyarmnn/test/test_profiling_utilities.py
@@ -0,0 +1,68 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+
+import pytest
+
+import pyarmnn as ann
+
+
+class MockIProfiler:
+    def __init__(self, json_string):
+        self._profile_json = json_string
+
+    def as_json(self):
+        return self._profile_json
+
+
+@pytest.fixture()
+def mock_profiler(shared_data_folder):
+    path_to_file = os.path.join(shared_data_folder, 'mock_profile_out.json')
+    with open(path_to_file, 'r') as file:
+        profiler_output = file.read()
+        return MockIProfiler(profiler_output)
+
+
+def test_inference_exec(mock_profiler):
+    profiling_data_obj = ann.get_profiling_data(mock_profiler)
+
+    assert (len(profiling_data_obj.inference_data) > 0)
+    assert (len(profiling_data_obj.per_workload_execution_data) > 0)
+
+    # Check each total execution time
+    assert (profiling_data_obj.inference_data["execution_time"] == [1.1, 2.2, 3.3, 4.4, 5.5, 6.6])
+    assert (profiling_data_obj.inference_data["time_unit"] == "us")
+
+
+@pytest.mark.parametrize("exec_times, unit, backend, workload", [([2, 2,
+                                                                   2, 2,
+                                                                   2, 2],
+                                                                  'us',
+                                                                  'CpuRef',
+                                                                  'RefSomeMock1dWorkload_Execute_#5'),
+                                                                 ([2, 2,
+                                                                   2, 2,
+                                                                   2, 2],
+                                                                  'us',
+                                                                  'CpuAcc',
+                                                                  'NeonSomeMock2Workload_Execute_#6'),
+                                                                 ([2, 2,
+                                                                   2, 2,
+                                                                   2, 2],
+                                                                  'us',
+                                                                  'GpuAcc',
+                                                                  'ClSomeMock3dWorkload_Execute_#7'),
+                                                                 ([2, 2,
+                                                                   2, 2,
+                                                                   2, 2],
+                                                                  'us',
+                                                                  'EthosNAcc',
+                                                                  'EthosNSomeMock4dWorkload_Execute_#8')
+                                                                 ])
+def test_profiler_workloads(mock_profiler, exec_times, unit, backend, workload):
+    profiling_data_obj = ann.get_profiling_data(mock_profiler)
+
+    work_load_exec = profiling_data_obj.per_workload_execution_data[workload]
+    assert work_load_exec["execution_time"] == exec_times
+    assert work_load_exec["time_unit"] == unit
+    assert work_load_exec["backend"] == backend
diff --git a/python/pyarmnn/test/test_quantize_and_dequantize.py b/python/pyarmnn/test/test_quantize_and_dequantize.py
new file mode 100644
index 0000000..08fea39
--- /dev/null
+++ b/python/pyarmnn/test/test_quantize_and_dequantize.py
@@ -0,0 +1,91 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import pytest
+import numpy as np
+
+import pyarmnn as ann
+
+# import generated so we can test for Dequantize_* and Quantize_*
+# functions not available in the public API.
+import pyarmnn._generated.pyarmnn as gen_ann
+
+
+@pytest.mark.parametrize('method', ['Quantize_int8_t',
+                                    'Quantize_uint8_t',
+                                    'Quantize_int16_t',
+                                    'Quantize_int32_t',
+                                    'Dequantize_int8_t',
+                                    'Dequantize_uint8_t',
+                                    'Dequantize_int16_t',
+                                    'Dequantize_int32_t'])
+def test_quantize_exists(method):
+    assert method in dir(gen_ann) and callable(getattr(gen_ann, method))
+
+
+@pytest.mark.parametrize('dt, min, max', [('uint8', 0, 255),
+                                          ('int8', -128, 127),
+                                          ('int16', -32768, 32767),
+                                          ('int32', -2147483648, 2147483647)])
+def test_quantize_uint8_output(dt, min, max):
+    result = ann.quantize(3.3274056911468506, 0.02620004490017891, 128, dt)
+    assert type(result) is int and min <= result <= max
+
+
+@pytest.mark.parametrize('dt', ['uint8',
+                                'int8',
+                                'int16',
+                                'int32'])
+def test_dequantize_uint8_output(dt):
+    result = ann.dequantize(3, 0.02620004490017891, 128, dt)
+    assert type(result) is float
+
+
+def test_quantize_unsupported_dtype():
+    with pytest.raises(ValueError) as err:
+        ann.quantize(3.3274056911468506, 0.02620004490017891, 128, 'uint16')
+
+    assert 'Unexpected target datatype uint16 given.' in str(err.value)
+
+
+def test_dequantize_unsupported_dtype():
+    with pytest.raises(ValueError) as err:
+        ann.dequantize(3, 0.02620004490017891, 128, 'uint16')
+
+    assert 'Unexpected value datatype uint16 given.' in str(err.value)
+
+
+def test_dequantize_value_range():
+    with pytest.raises(ValueError) as err:
+        ann.dequantize(-1, 0.02620004490017891, 128, 'uint8')
+
+    assert 'Value is not within range of the given datatype uint8' in str(err.value)
+
+
+@pytest.mark.parametrize('dt, data', [('uint8', np.uint8(255)),
+                                      ('int8',  np.int8(127)),
+                                      ('int16', np.int16(32767)),
+                                      ('int32', np.int32(2147483647)),
+
+                                      ('uint8', np.int8(127)),
+                                      ('uint8', np.int16(255)),
+                                      ('uint8', np.int32(255)),
+
+                                      ('int8', np.uint8(127)),
+                                      ('int8', np.int16(127)),
+                                      ('int8', np.int32(127)),
+
+                                      ('int16', np.int8(127)),
+                                      ('int16', np.uint8(255)),
+                                      ('int16', np.int32(32767)),
+
+                                      ('int32', np.uint8(255)),
+                                      ('int16', np.int8(127)),
+                                      ('int32', np.int16(32767))
+
+                                      ])
+def test_dequantize_numpy_dt(dt, data):
+    result = ann.dequantize(data, 1, 0, dt)
+
+    assert type(result) is float
+
+    assert np.float32(data) == result
diff --git a/python/pyarmnn/test/test_runtime.py b/python/pyarmnn/test/test_runtime.py
new file mode 100644
index 0000000..2943be8
--- /dev/null
+++ b/python/pyarmnn/test/test_runtime.py
@@ -0,0 +1,257 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+
+import pytest
+import numpy as np
+
+import pyarmnn as ann
+
+
+@pytest.fixture(scope="function")
+def random_runtime(shared_data_folder):
+    parser = ann.ITfLiteParser()
+    network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite'))
+    preferred_backends = [ann.BackendId('CpuRef')]
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    graphs_count = parser.GetSubgraphCount()
+
+    graph_id = graphs_count - 1
+    input_names = parser.GetSubgraphInputTensorNames(graph_id)
+
+    input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, input_names[0])
+    input_tensor_id = input_binding_info[0]
+
+    input_tensor_info = input_binding_info[1]
+
+    output_names = parser.GetSubgraphOutputTensorNames(graph_id)
+
+    input_data = np.random.randint(255, size=input_tensor_info.GetNumElements(), dtype=np.uint8)
+
+    const_tensor_pair = (input_tensor_id, ann.ConstTensor(input_tensor_info, input_data))
+
+    input_tensors = [const_tensor_pair]
+
+    output_tensors = []
+
+    for index, output_name in enumerate(output_names):
+        out_bind_info = parser.GetNetworkOutputBindingInfo(graph_id, output_name)
+
+        out_tensor_info = out_bind_info[1]
+        out_tensor_id = out_bind_info[0]
+
+        output_tensors.append((out_tensor_id,
+                               ann.Tensor(out_tensor_info)))
+
+    yield preferred_backends, network, runtime, input_tensors, output_tensors
+
+
+@pytest.fixture(scope='function')
+def mock_model_runtime(shared_data_folder):
+    parser = ann.ITfLiteParser()
+    network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite'))
+    graph_id = 0
+
+    input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, "input_1")
+
+    input_tensor_data = np.load(os.path.join(shared_data_folder, 'tflite_parser/input_lite.npy'))
+
+    preferred_backends = [ann.BackendId('CpuRef')]
+
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    print(messages)
+
+    net_id, messages = runtime.LoadNetwork(opt_network)
+
+    print(messages)
+
+    input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data])
+
+    output_names = parser.GetSubgraphOutputTensorNames(graph_id)
+    outputs_binding_info = []
+
+    for output_name in output_names:
+        outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(graph_id, output_name))
+
+    output_tensors = ann.make_output_tensors(outputs_binding_info)
+
+    yield runtime, net_id, input_tensors, output_tensors
+
+
+def test_python_disowns_network(random_runtime):
+    preferred_backends = random_runtime[0]
+    network = random_runtime[1]
+    runtime = random_runtime[2]
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    runtime.LoadNetwork(opt_network)
+
+    assert not opt_network.thisown
+
+
+def test_load_network(random_runtime):
+    preferred_backends = random_runtime[0]
+    network = random_runtime[1]
+    runtime = random_runtime[2]
+
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    net_id, messages = runtime.LoadNetwork(opt_network)
+    assert "" == messages
+    assert net_id == 0
+
+
+def test_load_network_properties_provided(random_runtime):
+    preferred_backends = random_runtime[0]
+    network = random_runtime[1]
+    runtime = random_runtime[2]
+
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    properties = ann.INetworkProperties(True, True)
+    net_id, messages = runtime.LoadNetwork(opt_network, properties)
+    assert "" == messages
+    assert net_id == 0
+
+
+def test_unload_network_fails_for_invalid_net_id(random_runtime):
+    preferred_backends = random_runtime[0]
+    network = random_runtime[1]
+    runtime = random_runtime[2]
+
+    ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    with pytest.raises(RuntimeError) as err:
+        runtime.UnloadNetwork(9)
+
+    expected_error_message = "Failed to unload network."
+    assert expected_error_message in str(err.value)
+
+
+def test_enqueue_workload(random_runtime):
+    preferred_backends = random_runtime[0]
+    network = random_runtime[1]
+    runtime = random_runtime[2]
+    input_tensors = random_runtime[3]
+    output_tensors = random_runtime[4]
+
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    net_id, _ = runtime.LoadNetwork(opt_network)
+    runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+
+def test_enqueue_workload_fails_with_empty_input_tensors(random_runtime):
+    preferred_backends = random_runtime[0]
+    network = random_runtime[1]
+    runtime = random_runtime[2]
+    input_tensors = []
+    output_tensors = random_runtime[4]
+
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    net_id, _ = runtime.LoadNetwork(opt_network)
+    with pytest.raises(RuntimeError) as err:
+        runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+    expected_error_message = "Number of inputs provided does not match network."
+    assert expected_error_message in str(err.value)
+
+
+@pytest.mark.x86_64
+@pytest.mark.parametrize('count', [5])
+def test_multiple_inference_runs_yield_same_result(count, mock_model_runtime):
+    """
+    Test that results remain consistent among multiple runs of the same inference.
+    """
+    runtime = mock_model_runtime[0]
+    net_id = mock_model_runtime[1]
+    input_tensors = mock_model_runtime[2]
+    output_tensors = mock_model_runtime[3]
+
+    expected_results = np.array([[4,  85, 108,  29,   8,  16,   0,   2,   5,   0]])
+
+    for _ in range(count):
+        runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+        output_vectors = ann.workload_tensors_to_ndarray(output_tensors)
+
+        for i in range(len(expected_results)):
+            assert output_vectors[i].all() == expected_results[i].all()
+
+
+@pytest.mark.aarch64
+def test_aarch64_inference_results(mock_model_runtime):
+
+    runtime = mock_model_runtime[0]
+    net_id = mock_model_runtime[1]
+    input_tensors = mock_model_runtime[2]
+    output_tensors = mock_model_runtime[3]
+
+    runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+    output_vectors = ann.workload_tensors_to_ndarray(output_tensors)
+
+    expected_outputs = expected_results = np.array([[4,  85, 108,  29,   8,  16,   0,   2,   5,   0]])
+
+    for i in range(len(expected_outputs)):
+        assert output_vectors[i].all() == expected_results[i].all()
+
+
+def test_enqueue_workload_with_profiler(random_runtime):
+    """
+    Tests ArmNN's profiling extension
+    """
+    preferred_backends = random_runtime[0]
+    network = random_runtime[1]
+    runtime = random_runtime[2]
+    input_tensors = random_runtime[3]
+    output_tensors = random_runtime[4]
+
+    opt_network, _ = ann.Optimize(network, preferred_backends,
+                                  runtime.GetDeviceSpec(), ann.OptimizerOptions())
+    net_id, _ = runtime.LoadNetwork(opt_network)
+
+    profiler = runtime.GetProfiler(net_id)
+    # By default profiling should be turned off:
+    assert profiler.IsProfilingEnabled() is False
+
+    # Enable profiling:
+    profiler.EnableProfiling(True)
+    assert profiler.IsProfilingEnabled() is True
+
+    # Run the inference:
+    runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+    # Get profile output as a string:
+    str_profile = profiler.as_json()
+
+    # Verify that certain markers are present:
+    assert len(str_profile) != 0
+    assert str_profile.find('\"ArmNN\": {') > 0
+
+    # Get events analysis output as a string:
+    str_events_analysis = profiler.event_log()
+
+    assert "Event Sequence - Name | Duration (ms) | Start (ms) | Stop (ms) | Device" in str_events_analysis
+
+    assert profiler.thisown == 0
+
+
+def test_check_runtime_swig_ownership(random_runtime):
+    # Check to see that SWIG has ownership for runtime. This instructs SWIG to take
+    # ownership of the return value. This allows the value to be automatically
+    # garbage-collected when it is no longer in use
+    runtime = random_runtime[2]
+    assert runtime.thisown
diff --git a/python/pyarmnn/test/test_setup.py b/python/pyarmnn/test/test_setup.py
new file mode 100644
index 0000000..8396ca0
--- /dev/null
+++ b/python/pyarmnn/test/test_setup.py
@@ -0,0 +1,100 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+import sys
+import shutil
+
+import pytest
+
+sys.path.append(os.path.abspath('..'))
+from setup import find_armnn, find_includes, linux_gcc_lib_search, check_armnn_version
+
+
+@pytest.fixture(autouse=True)
+def _setup_armnn(tmpdir):
+    includes = str(os.path.join(tmpdir, 'include'))
+    libs = str(os.path.join(tmpdir, 'lib'))
+    os.environ["TEST_ARMNN_INCLUDE"] = includes
+    os.environ["TEST_ARMNN_LIB"] = libs
+    os.environ["EMPTY_ARMNN_INCLUDE"] = ''
+
+    os.mkdir(includes)
+    os.mkdir(libs)
+
+    with open(os.path.join(libs, "libarmnn.so"), "w"):
+        pass
+
+    with open(os.path.join(libs, "libarmnnSomeThing1.so"), "w"):
+        pass
+    with open(os.path.join(libs, "libarmnnSomeThing1.so.1"), "w"):
+        pass
+    with open(os.path.join(libs, "libarmnnSomeThing1.so.1.2"), "w"):
+        pass
+
+    with open(os.path.join(libs, "libarmnnSomeThing2.so"), "w"):
+        pass
+
+    with open(os.path.join(libs, "libSomeThing3.so"), "w"):
+        pass
+
+    yield
+
+    del os.environ["TEST_ARMNN_INCLUDE"]
+    del os.environ["TEST_ARMNN_LIB"]
+    del os.environ["EMPTY_ARMNN_INCLUDE"]
+    shutil.rmtree(includes)
+    shutil.rmtree(libs)
+
+
+def test_find_armnn(tmpdir):
+    lib_names, lib_paths = find_armnn(lib_name='libarmnn*.so',
+                                      armnn_libs_env="TEST_ARMNN_LIB",
+                                      default_lib_search=("/lib",))
+    armnn_includes = find_includes(armnn_include_env="TEST_ARMNN_INCLUDE")
+
+    assert [':libarmnn.so', ':libarmnnSomeThing1.so', ':libarmnnSomeThing2.so'] == sorted(lib_names)
+    assert [os.path.join(tmpdir, 'lib')] == lib_paths
+    assert [os.path.join(tmpdir, 'include')] == armnn_includes
+
+
+def test_find_armnn_default_path(tmpdir):
+    lib_names, lib_paths = find_armnn(lib_name='libarmnn*.so',
+                                      armnn_libs_env="RUBBISH_LIB",
+                                      default_lib_search=(os.environ["TEST_ARMNN_LIB"],))
+    armnn_includes = find_includes('TEST_ARMNN_INCLUDE')
+    assert [':libarmnn.so', ':libarmnnSomeThing1.so', ':libarmnnSomeThing2.so'] == sorted(lib_names)
+    assert [os.path.join(tmpdir, 'lib')] == lib_paths
+    assert [os.path.join(tmpdir, 'include')] == armnn_includes
+
+
+def test_not_find_armnn(tmpdir):
+    with pytest.raises(RuntimeError) as err:
+        find_armnn(lib_name='libarmnn*.so', armnn_libs_env="RUBBISH_LIB",
+                   default_lib_search=("/lib",))
+
+    assert 'ArmNN library libarmnn*.so was not found in (\'/lib\',)' in str(err.value)
+
+
+@pytest.mark.parametrize("env", ["RUBBISH_INCLUDE", "EMPTY_ARMNN_INCLUDE"])
+def test_rubbish_armnn_include(tmpdir, env):
+    includes = find_includes(armnn_include_env=env)
+    assert includes == ['/usr/local/include', '/usr/include']
+
+
+def test_gcc_serch_path():
+    assert linux_gcc_lib_search()
+
+
+def test_armnn_version():
+    check_armnn_version('20190800', '20190800')
+
+
+def test_incorrect_armnn_version():
+    with pytest.raises(AssertionError) as err:
+        check_armnn_version('20190800', '20190500')
+
+    assert 'Expected ArmNN version is 201905 but installed ArmNN version is 201908' in str(err.value)
+
+
+def test_armnn_version_patch_does_not_matter():
+    check_armnn_version('20190800', '20190801')
diff --git a/python/pyarmnn/test/test_supported_backends.py b/python/pyarmnn/test/test_supported_backends.py
new file mode 100644
index 0000000..e1ca5ee
--- /dev/null
+++ b/python/pyarmnn/test/test_supported_backends.py
@@ -0,0 +1,50 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+import platform
+import pytest
+import pyarmnn as ann
+
+
+@pytest.fixture()
+def get_supported_backends_setup(shared_data_folder):
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    get_device_spec = runtime.GetDeviceSpec()
+    supported_backends = get_device_spec.GetSupportedBackends()
+
+    yield supported_backends
+
+
+def test_ownership():
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    device_spec = runtime.GetDeviceSpec()
+
+    assert not device_spec.thisown
+
+
+def test_to_string():
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    device_spec = runtime.GetDeviceSpec()
+    expected_str = "IDeviceSpec {{ supportedBackends: [" \
+                   "{}" \
+                   "]}}".format(', '.join(map(lambda b: str(b), device_spec.GetSupportedBackends())))
+
+    assert expected_str == str(device_spec)
+
+
+def test_get_supported_backends_cpu_ref(get_supported_backends_setup):
+    assert "CpuRef" in map(lambda b: str(b), get_supported_backends_setup)
+
+
+@pytest.mark.aarch64
+class TestNoneCpuRefBackends:
+
+    @pytest.mark.parametrize("backend", ["CpuAcc"])
+    def test_get_supported_backends_cpu_acc(self, get_supported_backends_setup, backend):
+        assert backend in map(lambda b: str(b), get_supported_backends_setup)
diff --git a/python/pyarmnn/test/test_tensor.py b/python/pyarmnn/test/test_tensor.py
new file mode 100644
index 0000000..8b57169
--- /dev/null
+++ b/python/pyarmnn/test/test_tensor.py
@@ -0,0 +1,144 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+from copy import copy
+
+import pytest
+import numpy as np
+import pyarmnn as ann
+
+
+def __get_tensor_info(dt):
+    tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), dt)
+
+    return tensor_info
+
+
+@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16,
+                                ann.DataType_QAsymmU8, ann.DataType_QSymmS8,
+                                ann.DataType_QAsymmS8])
+def test_create_tensor_with_info(dt):
+    tensor_info = __get_tensor_info(dt)
+    elements = tensor_info.GetNumElements()
+    num_bytes = tensor_info.GetNumBytes()
+    d_type = dt
+
+    tensor = ann.Tensor(tensor_info)
+
+    assert tensor_info != tensor.GetInfo(), "Different objects"
+    assert elements == tensor.GetNumElements()
+    assert num_bytes == tensor.GetNumBytes()
+    assert d_type == tensor.GetDataType()
+
+
+def test_create_tensor_undefined_datatype():
+    tensor_info = ann.TensorInfo()
+    tensor_info.SetDataType(99)
+
+    with pytest.raises(ValueError) as err:
+        ann.Tensor(tensor_info)
+
+    assert 'The data type provided for this Tensor is not supported.' in str(err.value)
+
+
+@pytest.mark.parametrize("dt", [ann.DataType_Float32])
+def test_tensor_memory_output(dt):
+    tensor_info = __get_tensor_info(dt)
+    tensor = ann.Tensor(tensor_info)
+
+    # empty memory area because inference has not yet been run.
+    assert tensor.get_memory_area().tolist()  # has random stuff
+    assert 4 == tensor.get_memory_area().itemsize, "it is float32"
+
+
+@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16,
+                                ann.DataType_QAsymmU8, ann.DataType_QSymmS8,
+                                ann.DataType_QAsymmS8])
+def test_tensor__str__(dt):
+    tensor_info = __get_tensor_info(dt)
+    elements = tensor_info.GetNumElements()
+    num_bytes = tensor_info.GetNumBytes()
+    d_type = dt
+    dimensions = tensor_info.GetNumDimensions()
+
+    tensor = ann.Tensor(tensor_info)
+
+    assert str(tensor) == "Tensor{{DataType: {}, NumBytes: {}, NumDimensions: " \
+                               "{}, NumElements: {}}}".format(d_type, num_bytes, dimensions, elements)
+
+
+def test_create_empty_tensor():
+    tensor = ann.Tensor()
+
+    assert 0 == tensor.GetNumElements()
+    assert 0 == tensor.GetNumBytes()
+    assert tensor.get_memory_area() is None
+
+
+@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16,
+                                ann.DataType_QAsymmU8, ann.DataType_QSymmS8,
+                                ann.DataType_QAsymmS8])
+def test_create_tensor_from_tensor(dt):
+    tensor_info = __get_tensor_info(dt)
+    tensor = ann.Tensor(tensor_info)
+    copied_tensor = ann.Tensor(tensor)
+
+    assert copied_tensor != tensor, "Different objects"
+    assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects"
+    assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data,  "Same memory area"
+    assert copied_tensor.GetNumElements() == tensor.GetNumElements()
+    assert copied_tensor.GetNumBytes() == tensor.GetNumBytes()
+    assert copied_tensor.GetDataType() == tensor.GetDataType()
+
+
+@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16,
+                                ann.DataType_QAsymmU8, ann.DataType_QSymmS8,
+                                ann.DataType_QAsymmS8])
+def test_copy_tensor(dt):
+    tensor = ann.Tensor(__get_tensor_info(dt))
+    copied_tensor = copy(tensor)
+
+    assert copied_tensor != tensor, "Different objects"
+    assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects"
+    assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data,  "Same memory area"
+    assert copied_tensor.GetNumElements() == tensor.GetNumElements()
+    assert copied_tensor.GetNumBytes() == tensor.GetNumBytes()
+    assert copied_tensor.GetDataType() == tensor.GetDataType()
+
+
+@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16,
+                                ann.DataType_QAsymmU8, ann.DataType_QSymmS8,
+                                ann.DataType_QAsymmS8])
+def test_copied_tensor_has_memory_area_access_after_deletion_of_original_tensor(dt):
+
+    tensor = ann.Tensor(__get_tensor_info(dt))
+
+    tensor.get_memory_area()[0] = 100
+
+    initial_mem_copy = np.array(tensor.get_memory_area())
+
+    assert 100 == initial_mem_copy[0]
+
+    copied_tensor = ann.Tensor(tensor)
+
+    del tensor
+    np.testing.assert_array_equal(copied_tensor.get_memory_area(), initial_mem_copy)
+    assert 100 == copied_tensor.get_memory_area()[0]
+
+
+def test_create_const_tensor_incorrect_args():
+    with pytest.raises(ValueError) as err:
+        ann.Tensor('something', 'something')
+
+    expected_error_message = "Incorrect number of arguments or type of arguments provided to create Tensor."
+    assert expected_error_message in str(err.value)
+
+
+@pytest.mark.parametrize("dt", [ann.DataType_Float16])
+def test_tensor_memory_output_fp16(dt):
+    # Check Tensor with float16
+    tensor_info = __get_tensor_info(dt)
+    tensor = ann.Tensor(tensor_info)
+
+    assert tensor.GetNumElements() == 6
+    assert tensor.GetNumBytes() == 12
+    assert tensor.GetDataType() == ann.DataType_Float16
diff --git a/python/pyarmnn/test/test_tensor_conversion.py b/python/pyarmnn/test/test_tensor_conversion.py
new file mode 100644
index 0000000..a48b00f
--- /dev/null
+++ b/python/pyarmnn/test/test_tensor_conversion.py
@@ -0,0 +1,99 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+
+import pytest
+import pyarmnn as ann
+import numpy as np
+
+
+@pytest.fixture(scope="function")
+def get_tensor_info_input(shared_data_folder):
+    """
+    Sample input tensor information.
+    """
+    parser = ann.ITfLiteParser()
+    parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite'))
+    graph_id = 0
+
+    input_binding_info = [parser.GetNetworkInputBindingInfo(graph_id, 'input_1')]
+
+    yield input_binding_info
+
+
+@pytest.fixture(scope="function")
+def get_tensor_info_output(shared_data_folder):
+    """
+    Sample output tensor information.
+    """
+    parser = ann.ITfLiteParser()
+    parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite'))
+    graph_id = 0
+
+    output_names = parser.GetSubgraphOutputTensorNames(graph_id)
+    outputs_binding_info = []
+
+    for output_name in output_names:
+        outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(graph_id, output_name))
+
+    yield outputs_binding_info
+
+
+def test_make_input_tensors(get_tensor_info_input):
+    input_tensor_info = get_tensor_info_input
+    input_data = []
+
+    for tensor_id, tensor_info in input_tensor_info:
+        input_data.append(np.random.randint(0, 255, size=(1, tensor_info.GetNumElements())).astype(np.uint8))
+
+    input_tensors = ann.make_input_tensors(input_tensor_info, input_data)
+    assert len(input_tensors) == 1
+
+    for tensor, tensor_info in zip(input_tensors, input_tensor_info):
+        # Because we created ConstTensor function, we cannot check type directly.
+        assert type(tensor[1]).__name__ == 'ConstTensor'
+        assert str(tensor[1].GetInfo()) == str(tensor_info[1])
+
+
+def test_make_output_tensors(get_tensor_info_output):
+    output_binding_info = get_tensor_info_output
+
+    output_tensors = ann.make_output_tensors(output_binding_info)
+    assert len(output_tensors) == 1
+
+    for tensor, tensor_info in zip(output_tensors, output_binding_info):
+        assert type(tensor[1]) == ann.Tensor
+        assert str(tensor[1].GetInfo()) == str(tensor_info[1])
+
+
+def test_workload_tensors_to_ndarray(get_tensor_info_output):
+    # Check shape and size of output from workload_tensors_to_ndarray matches expected.
+    output_binding_info = get_tensor_info_output
+    output_tensors = ann.make_output_tensors(output_binding_info)
+
+    data = ann.workload_tensors_to_ndarray(output_tensors)
+
+    for i in range(0, len(output_tensors)):
+        assert data[i].shape == tuple(output_tensors[i][1].GetShape())
+        assert data[i].size == output_tensors[i][1].GetNumElements()
+
+
+def test_make_input_tensors_fp16(get_tensor_info_input):
+    # Check ConstTensor with float16
+    input_tensor_info = get_tensor_info_input
+    input_data = []
+
+    for tensor_id, tensor_info in input_tensor_info:
+        input_data.append(np.random.randint(0, 255, size=(1, tensor_info.GetNumElements())).astype(np.float16))
+        tensor_info.SetDataType(ann.DataType_Float16)  # set datatype to float16
+
+    input_tensors = ann.make_input_tensors(input_tensor_info, input_data)
+    assert len(input_tensors) == 1
+
+    for tensor, tensor_info in zip(input_tensors, input_tensor_info):
+        # Because we created ConstTensor function, we cannot check type directly.
+        assert type(tensor[1]).__name__ == 'ConstTensor'
+        assert str(tensor[1].GetInfo()) == str(tensor_info[1])
+        assert tensor[1].GetDataType() == ann.DataType_Float16
+        assert tensor[1].GetNumElements() == 28*28*1
+        assert tensor[1].GetNumBytes() == (28*28*1)*2  # check each element is two byte
diff --git a/python/pyarmnn/test/test_tensor_info.py b/python/pyarmnn/test/test_tensor_info.py
new file mode 100644
index 0000000..dc73533
--- /dev/null
+++ b/python/pyarmnn/test/test_tensor_info.py
@@ -0,0 +1,27 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import pyarmnn as ann
+
+
+def test_tensor_info_ctor_shape():
+    tensor_shape = ann.TensorShape((1, 1, 2))
+
+    tensor_info = ann.TensorInfo(tensor_shape, ann.DataType_QAsymmU8, 0.5, 1)
+
+    assert 2 == tensor_info.GetNumElements()
+    assert 3 == tensor_info.GetNumDimensions()
+    assert ann.DataType_QAsymmU8 == tensor_info.GetDataType()
+    assert 0.5 == tensor_info.GetQuantizationScale()
+    assert 1 == tensor_info.GetQuantizationOffset()
+
+    shape = tensor_info.GetShape()
+
+    assert 2 == shape.GetNumElements()
+    assert 3 == shape.GetNumDimensions()
+
+
+def test_tensor_info__str__():
+    tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_QAsymmU8, 0.5, 1)
+
+    assert tensor_info.__str__() == "TensorInfo{DataType: 2, IsQuantized: 1, QuantizationScale: 0.500000, " \
+                                    "QuantizationOffset: 1, NumDimensions: 2, NumElements: 6}"
diff --git a/python/pyarmnn/test/test_tensor_shape.py b/python/pyarmnn/test/test_tensor_shape.py
new file mode 100644
index 0000000..c6f731f
--- /dev/null
+++ b/python/pyarmnn/test/test_tensor_shape.py
@@ -0,0 +1,78 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import pytest
+import pyarmnn as ann
+
+
+def test_tensor_shape_tuple():
+    tensor_shape = ann.TensorShape((1, 2, 3))
+
+    assert 3 == tensor_shape.GetNumDimensions()
+    assert 6 == tensor_shape.GetNumElements()
+
+
+def test_tensor_shape_one():
+    tensor_shape = ann.TensorShape((10,))
+    assert 1 == tensor_shape.GetNumDimensions()
+    assert 10 == tensor_shape.GetNumElements()
+
+
+def test_tensor_shape_empty():
+    with pytest.raises(RuntimeError) as err:
+        ann.TensorShape(())
+
+    assert "Tensor numDimensions must be greater than 0" in str(err.value)
+
+
+def test_tensor_shape_tuple_mess():
+    tensor_shape = ann.TensorShape((1, "2", 3.0))
+
+    assert 3 == tensor_shape.GetNumDimensions()
+    assert 6 == tensor_shape.GetNumElements()
+
+
+def test_tensor_shape_list():
+
+    with pytest.raises(TypeError) as err:
+        ann.TensorShape([1, 2, 3])
+
+    assert "Argument is not a tuple" in str(err.value)
+
+
+def test_tensor_shape_tuple_mess_fail():
+
+    with pytest.raises(TypeError) as err:
+        ann.TensorShape((1, "two", 3.0))
+
+    assert "All elements must be numbers" in str(err.value)
+
+
+def test_tensor_shape_varags():
+    with pytest.raises(TypeError) as err:
+        ann.TensorShape(1, 2, 3)
+
+    assert "__init__() takes 2 positional arguments but 4 were given" in str(err.value)
+
+
+def test_tensor_shape__get_item_out_of_bounds():
+    tensor_shape = ann.TensorShape((1, 2, 3))
+    with pytest.raises(ValueError) as err:
+        for i in range(4):
+            tensor_shape[i]
+
+    assert "Invalid dimension index: 3 (number of dimensions is 3)" in str(err.value)
+
+
+def test_tensor_shape__set_item_out_of_bounds():
+    tensor_shape = ann.TensorShape((1, 2, 3))
+    with pytest.raises(ValueError) as err:
+        for i in range(4):
+            tensor_shape[i] = 1
+
+    assert "Invalid dimension index: 3 (number of dimensions is 3)" in str(err.value)
+
+
+def test_tensor_shape___str__():
+    tensor_shape = ann.TensorShape((1, 2, 3))
+
+    assert str(tensor_shape) == "TensorShape{Shape(1, 2, 3), NumDimensions: 3, NumElements: 6}"
diff --git a/python/pyarmnn/test/test_tf_parser.py b/python/pyarmnn/test/test_tf_parser.py
new file mode 100644
index 0000000..796dd71
--- /dev/null
+++ b/python/pyarmnn/test/test_tf_parser.py
@@ -0,0 +1,133 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+
+import pytest
+import pyarmnn as ann
+import numpy as np
+
+
+@pytest.fixture()
+def parser(shared_data_folder):
+    """
+    Parse and setup the test network to be used for the tests below
+    """
+
+    # create tf parser
+    parser = ann.ITfParser()
+
+    # path to model
+    path_to_model = os.path.join(shared_data_folder, 'mock_model.pb')
+
+    # tensor shape [1, 28, 28, 1]
+    tensorshape = {'input': ann.TensorShape((1, 28, 28, 1))}
+
+    # requested_outputs
+    requested_outputs = ["output"]
+
+    # parse tf binary & create network
+    parser.CreateNetworkFromBinaryFile(path_to_model, tensorshape, requested_outputs)
+
+    yield parser
+
+
+def test_tf_parser_swig_destroy():
+    assert ann.ITfParser.__swig_destroy__, "There is a swig python destructor defined"
+    assert ann.ITfParser.__swig_destroy__.__name__ == "delete_ITfParser"
+
+
+def test_check_tf_parser_swig_ownership(parser):
+    # Check to see that SWIG has ownership for parser. This instructs SWIG to take
+    # ownership of the return value. This allows the value to be automatically
+    # garbage-collected when it is no longer in use
+    assert parser.thisown
+
+
+def test_tf_parser_get_network_input_binding_info(parser):
+    input_binding_info = parser.GetNetworkInputBindingInfo("input")
+
+    tensor = input_binding_info[1]
+    assert tensor.GetDataType() == 1
+    assert tensor.GetNumDimensions() == 4
+    assert tensor.GetNumElements() == 28*28*1
+    assert tensor.GetQuantizationOffset() == 0
+    assert tensor.GetQuantizationScale() == 0
+
+
+def test_tf_parser_get_network_output_binding_info(parser):
+    output_binding_info = parser.GetNetworkOutputBindingInfo("output")
+
+    tensor = output_binding_info[1]
+    assert tensor.GetDataType() == 1
+    assert tensor.GetNumDimensions() == 2
+    assert tensor.GetNumElements() == 10
+    assert tensor.GetQuantizationOffset() == 0
+    assert tensor.GetQuantizationScale() == 0
+
+
+def test_tf_filenotfound_exception(shared_data_folder):
+    parser = ann.ITfParser()
+
+    # path to model
+    path_to_model = os.path.join(shared_data_folder, 'some_unknown_model.pb')
+
+    # tensor shape [1, 1, 1, 1]
+    tensorshape = {'input': ann.TensorShape((1, 1, 1, 1))}
+
+    # requested_outputs
+    requested_outputs = [""]
+
+    # parse tf binary & create network
+
+    with pytest.raises(RuntimeError) as err:
+        parser.CreateNetworkFromBinaryFile(path_to_model, tensorshape, requested_outputs)
+
+    # Only check for part of the exception since the exception returns
+    # absolute path which will change on different machines.
+    assert 'failed to open' in str(err.value)
+
+
+def test_tf_parser_end_to_end(shared_data_folder):
+    parser = ann.ITfParser = ann.ITfParser()
+
+    tensorshape = {'input': ann.TensorShape((1, 28, 28, 1))}
+    requested_outputs = ["output"]
+
+    network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.pb'),
+                                                 tensorshape, requested_outputs)
+
+    input_binding_info = parser.GetNetworkInputBindingInfo("input")
+
+    # load test image data stored in input_tf.npy
+    input_tensor_data = np.load(os.path.join(shared_data_folder, 'tf_parser/input_tf.npy')).astype(np.float32)
+
+    preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')]
+
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+
+    assert 0 == len(messages)
+
+    net_id, messages = runtime.LoadNetwork(opt_network)
+
+    assert "" == messages
+
+    input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data])
+
+    outputs_binding_info = []
+
+    for output_name in requested_outputs:
+        outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(output_name))
+
+    output_tensors = ann.make_output_tensors(outputs_binding_info)
+
+    runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+    output_vectors = ann.workload_tensors_to_ndarray(output_tensors)
+
+    # Load golden output file for result comparison.
+    golden_output = np.load(os.path.join(shared_data_folder, 'tf_parser/golden_output_tf.npy'))
+
+    # Check that output matches golden output to 4 decimal places (there are slight rounding differences after this)
+    np.testing.assert_almost_equal(output_vectors[0], golden_output, decimal=4)
diff --git a/python/pyarmnn/test/test_tflite_parser.py b/python/pyarmnn/test/test_tflite_parser.py
new file mode 100644
index 0000000..344ec7c
--- /dev/null
+++ b/python/pyarmnn/test/test_tflite_parser.py
@@ -0,0 +1,147 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+
+import pytest
+import pyarmnn as ann
+import numpy as np
+
+
+@pytest.fixture()
+def parser(shared_data_folder):
+    """
+    Parse and setup the test network to be used for the tests below
+    """
+    parser = ann.ITfLiteParser()
+    parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite'))
+
+    yield parser
+
+
+def test_tflite_parser_swig_destroy():
+    assert ann.ITfLiteParser.__swig_destroy__, "There is a swig python destructor defined"
+    assert ann.ITfLiteParser.__swig_destroy__.__name__ == "delete_ITfLiteParser"
+
+
+def test_check_tflite_parser_swig_ownership(parser):
+    # Check to see that SWIG has ownership for parser. This instructs SWIG to take
+    # ownership of the return value. This allows the value to be automatically
+    # garbage-collected when it is no longer in use
+    assert parser.thisown
+
+
+def test_tflite_get_sub_graph_count(parser):
+    graphs_count = parser.GetSubgraphCount()
+    assert graphs_count == 1
+
+
+def test_tflite_get_network_input_binding_info(parser):
+    graphs_count = parser.GetSubgraphCount()
+    graph_id = graphs_count - 1
+
+    input_names = parser.GetSubgraphInputTensorNames(graph_id)
+
+    input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, input_names[0])
+
+    tensor = input_binding_info[1]
+    assert tensor.GetDataType() == 2
+    assert tensor.GetNumDimensions() == 4
+    assert tensor.GetNumElements() == 784
+    assert tensor.GetQuantizationOffset() == 128
+    assert tensor.GetQuantizationScale() == 0.007843137718737125
+
+
+def test_tflite_get_network_output_binding_info(parser):
+    graphs_count = parser.GetSubgraphCount()
+    graph_id = graphs_count - 1
+
+    output_names = parser.GetSubgraphOutputTensorNames(graph_id)
+
+    output_binding_info1 = parser.GetNetworkOutputBindingInfo(graph_id, output_names[0])
+
+    # Check the tensor info retrieved from GetNetworkOutputBindingInfo
+    tensor1 = output_binding_info1[1]
+
+    assert tensor1.GetDataType() == 2
+    assert tensor1.GetNumDimensions() == 2
+    assert tensor1.GetNumElements() == 10
+    assert tensor1.GetQuantizationOffset() == 0
+    assert tensor1.GetQuantizationScale() == 0.00390625
+
+
+def test_tflite_get_subgraph_input_tensor_names(parser):
+    graphs_count = parser.GetSubgraphCount()
+    graph_id = graphs_count - 1
+
+    input_names = parser.GetSubgraphInputTensorNames(graph_id)
+
+    assert input_names == ('input_1',)
+
+
+def test_tflite_get_subgraph_output_tensor_names(parser):
+    graphs_count = parser.GetSubgraphCount()
+    graph_id = graphs_count - 1
+
+    output_names = parser.GetSubgraphOutputTensorNames(graph_id)
+
+    assert output_names[0] == 'dense/Softmax'
+
+
+def test_tflite_filenotfound_exception(shared_data_folder):
+    parser = ann.ITfLiteParser()
+
+    with pytest.raises(RuntimeError) as err:
+        parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'some_unknown_network.tflite'))
+
+    # Only check for part of the exception since the exception returns
+    # absolute path which will change on different machines.
+    assert 'Cannot find the file' in str(err.value)
+
+
+def test_tflite_parser_end_to_end(shared_data_folder):
+    parser = ann.ITfLiteParser()
+
+    network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, "mock_model.tflite"))
+
+    graphs_count = parser.GetSubgraphCount()
+    graph_id = graphs_count - 1
+
+    input_names = parser.GetSubgraphInputTensorNames(graph_id)
+    input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, input_names[0])
+
+    output_names = parser.GetSubgraphOutputTensorNames(graph_id)
+
+    preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')]
+
+    options = ann.CreationOptions()
+    runtime = ann.IRuntime(options)
+
+    opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions())
+    assert 0 == len(messages)
+
+    net_id, messages = runtime.LoadNetwork(opt_network)
+    assert "" == messages
+
+    # Load test image data stored in input_lite.npy
+    input_tensor_data = np.load(os.path.join(shared_data_folder, 'tflite_parser/input_lite.npy'))
+    input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data])
+
+    output_tensors = []
+    for index, output_name in enumerate(output_names):
+        out_bind_info = parser.GetNetworkOutputBindingInfo(graph_id, output_name)
+        out_tensor_info = out_bind_info[1]
+        out_tensor_id = out_bind_info[0]
+        output_tensors.append((out_tensor_id,
+                               ann.Tensor(out_tensor_info)))
+
+    runtime.EnqueueWorkload(net_id, input_tensors, output_tensors)
+
+    output_vectors = []
+    for index, out_tensor in enumerate(output_tensors):
+        output_vectors.append(out_tensor[1].get_memory_area())
+
+    # Load golden output file for result comparison.
+    expected_outputs = np.load(os.path.join(shared_data_folder, 'tflite_parser/golden_output_lite.npy'))
+
+    # Check that output matches golden output
+    assert (expected_outputs == output_vectors[0]).all()
diff --git a/python/pyarmnn/test/test_types.py b/python/pyarmnn/test/test_types.py
new file mode 100644
index 0000000..dfe1429
--- /dev/null
+++ b/python/pyarmnn/test/test_types.py
@@ -0,0 +1,29 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import pytest
+import pyarmnn as ann
+
+
+def test_activation_function():
+    assert 0 == ann.ActivationFunction_Sigmoid
+    assert 1 == ann.ActivationFunction_TanH
+    assert 2 == ann.ActivationFunction_Linear
+    assert 3 == ann.ActivationFunction_ReLu
+    assert 4 == ann.ActivationFunction_BoundedReLu
+    assert 5 == ann.ActivationFunction_SoftReLu
+    assert 6 == ann.ActivationFunction_LeakyReLu
+    assert 7 == ann.ActivationFunction_Abs
+    assert 8 == ann.ActivationFunction_Sqrt
+    assert 9 == ann.ActivationFunction_Square
+
+
+def test_permutation_vector():
+    pv = ann.PermutationVector((0, 2, 3, 1))
+    assert pv[0] == 0
+    assert pv[2] == 3
+
+    pv2 = ann.PermutationVector((0, 2, 3, 1))
+    assert pv == pv2
+
+    pv4 = ann.PermutationVector((0, 3, 1, 2))
+    assert pv.IsInverse(pv4)
diff --git a/python/pyarmnn/test/test_version.py b/python/pyarmnn/test/test_version.py
new file mode 100644
index 0000000..2ea0fd8
--- /dev/null
+++ b/python/pyarmnn/test/test_version.py
@@ -0,0 +1,35 @@
+# Copyright © 2020 Arm Ltd. All rights reserved.
+# SPDX-License-Identifier: MIT
+import os
+import importlib
+
+
+def test_rel_version():
+    import pyarmnn._version as v
+    importlib.reload(v)
+    assert "dev" not in v.__version__
+    del v
+
+
+def test_dev_version():
+    import pyarmnn._version as v
+    os.environ["PYARMNN_DEV_VER"] = "1"
+
+    importlib.reload(v)
+
+    assert "20.2.0.dev1" == v.__version__
+
+    del os.environ["PYARMNN_DEV_VER"]
+    del v
+
+
+def test_arm_version_not_affected():
+    import pyarmnn._version as v
+    os.environ["PYARMNN_DEV_VER"] = "1"
+
+    importlib.reload(v)
+
+    assert "20200200" == v.__arm_ml_version__
+
+    del os.environ["PYARMNN_DEV_VER"]
+    del v
diff --git a/python/pyarmnn/test/testdata/shared/caffe_parser/golden_output_caffe.npy b/python/pyarmnn/test/testdata/shared/caffe_parser/golden_output_caffe.npy
new file mode 100644
index 0000000..007141c
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/caffe_parser/golden_output_caffe.npy
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/caffe_parser/input_caffe.npy b/python/pyarmnn/test/testdata/shared/caffe_parser/input_caffe.npy
new file mode 100644
index 0000000..15df758
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/caffe_parser/input_caffe.npy
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/license.txt b/python/pyarmnn/test/testdata/shared/license.txt
new file mode 100644
index 0000000..1e95a68
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/license.txt
@@ -0,0 +1,10 @@
+This folder contains models and data needed for the testing of PyArmNN.
+
+All models and files found in this folder were created by ARM for the purpose
+of testing PyArmNN.
+
+All the contents of this folder are distributed with the following license.
+
+Copyright © 2020 Arm Ltd. All rights reserved.
+SPDX-License-Identifier: MIT
+
diff --git a/python/pyarmnn/test/testdata/shared/mock_model.caffemodel b/python/pyarmnn/test/testdata/shared/mock_model.caffemodel
new file mode 100644
index 0000000..df4079b
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/mock_model.caffemodel
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/mock_model.onnx b/python/pyarmnn/test/testdata/shared/mock_model.onnx
new file mode 100644
index 0000000..c1b506c
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/mock_model.onnx
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/mock_model.pb b/python/pyarmnn/test/testdata/shared/mock_model.pb
new file mode 100644
index 0000000..cff9dc7
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/mock_model.pb
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/mock_model.tflite b/python/pyarmnn/test/testdata/shared/mock_model.tflite
new file mode 100644
index 0000000..0b8944d
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/mock_model.tflite
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/mock_profile_out.json b/python/pyarmnn/test/testdata/shared/mock_profile_out.json
new file mode 100644
index 0000000..8e10561
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/mock_profile_out.json
@@ -0,0 +1,216 @@
+{
+	"ArmNN": {
+		"inference_measurements_#1": {
+			"type": "Event",
+			"Wall clock time_#1": {
+				"type": "Measurement",
+				"raw": [
+					1.1,
+					2.2,
+					3.3,
+					4.4,
+					5.5,
+					6.6
+				],
+				"unit": "us"
+			},
+
+			"Execute_#2": {
+				"type": "Event",
+				"Wall clock time_#2": {
+					"type": "Measurement",
+					"raw": [
+						1.1,
+						2.2,
+						3.3,
+						4.4,
+						5.5,
+						6.6
+					],
+					"unit": "us"
+				},
+				"Wall clock time (Start)_#2": {
+					"type": "Measurement",
+					"raw": [
+						1,
+						1,
+						1,
+						1,
+						1,
+						1
+					],
+					"unit": "us"
+				},
+				"Wall clock time (Stop)_#2": {
+					"type": "Measurement",
+					"raw": [
+						2,
+						2,
+						2,
+						2,
+						2,
+						2
+					],
+					"unit": "us"
+				},
+
+				"RefSomeMock1dWorkload_Execute_#5": {
+					"type": "Event",
+					"Wall clock time_#5": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Start)_#5": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Stop)_#5": {
+						"type": "Measurement",
+						"raw": [
+							4,
+							4,
+							4,
+							4,
+							4,
+							4
+						],
+						"unit": "us"
+					}
+				},
+				"NeonSomeMock2Workload_Execute_#6": {
+					"type": "Event",
+					"Wall clock time_#6": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Start)_#6": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Stop)_#6": {
+						"type": "Measurement",
+						"raw": [
+							4,
+							4,
+							4,
+							4,
+							4,
+							4
+						],
+						"unit": "us"
+					}
+				},
+				"ClSomeMock3dWorkload_Execute_#7": {
+					"type": "Event",
+					"Wall clock time_#7": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Start)_#7": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Stop)_#7": {
+						"type": "Measurement",
+						"raw": [
+							4,
+							4,
+							4,
+							4,
+							4,
+							4
+						],
+						"unit": "us"
+					}
+				},
+				"EthosNSomeMock4dWorkload_Execute_#8": {
+					"type": "Event",
+					"Wall clock time_#8": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Start)_#8": {
+						"type": "Measurement",
+						"raw": [
+							2,
+							2,
+							2,
+							2,
+							2,
+							2
+						],
+						"unit": "us"
+					},
+					"Wall clock time (Stop)_#8": {
+						"type": "Measurement",
+						"raw": [
+							4,
+							4,
+							4,
+							4,
+							4,
+							4
+						],
+						"unit": "us"
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/python/pyarmnn/test/testdata/shared/onnx_parser/golden_output_onnx.npy b/python/pyarmnn/test/testdata/shared/onnx_parser/golden_output_onnx.npy
new file mode 100644
index 0000000..f83d6ea
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/onnx_parser/golden_output_onnx.npy
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/onnx_parser/input_onnx.npy b/python/pyarmnn/test/testdata/shared/onnx_parser/input_onnx.npy
new file mode 100644
index 0000000..15df758
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/onnx_parser/input_onnx.npy
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/tf_parser/golden_output_tf.npy b/python/pyarmnn/test/testdata/shared/tf_parser/golden_output_tf.npy
new file mode 100644
index 0000000..007141c
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/tf_parser/golden_output_tf.npy
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/tf_parser/input_tf.npy b/python/pyarmnn/test/testdata/shared/tf_parser/input_tf.npy
new file mode 100644
index 0000000..a21802e
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/tf_parser/input_tf.npy
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/tflite_parser/golden_output_lite.npy b/python/pyarmnn/test/testdata/shared/tflite_parser/golden_output_lite.npy
new file mode 100644
index 0000000..099f7fe
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/tflite_parser/golden_output_lite.npy
Binary files differ
diff --git a/python/pyarmnn/test/testdata/shared/tflite_parser/input_lite.npy b/python/pyarmnn/test/testdata/shared/tflite_parser/input_lite.npy
new file mode 100644
index 0000000..5317468
--- /dev/null
+++ b/python/pyarmnn/test/testdata/shared/tflite_parser/input_lite.npy
Binary files differ