import array
import ctypes
import unittest

import mmap

import app.memory_utils as mu


class TestMemoryUtils(unittest.TestCase):

    def test_virtual_to_physical_addr(self):
        initial_value = 0
        dummy_item = ctypes.c_int(initial_value)
        virtual_address = ctypes.addressof(dummy_item)
        p_addr_info = mu.virtual_to_physical_addr(virtual_address)
        self.assertEqual(p_addr_info.frame_start + p_addr_info.offset, p_addr_info.p_addr)
        self.assertEqual(initial_value, dummy_item.value)

        new_value = 123
        with mu.mmap_dev_mem(p_addr_info.frame_start) as m:
            m[p_addr_info.offset] = new_value
        self.assertEqual(new_value, dummy_item.value)

    def test_get_mem_view_phys_addr_info(self):
        view_len = 10
        mv = memoryview(array.array('L', [0] * view_len))
        c_type_mv = ctypes.c_char.from_buffer(mv)
        v_addr = ctypes.addressof(c_type_mv)
        p_addr_info_expected = mu.virtual_to_physical_addr(v_addr)

        p_addr_info = mu.get_mem_view_phys_addr_info(mv)

        self.assertEqual(p_addr_info_expected.frame_start, p_addr_info.frame_start)
        self.assertEqual(p_addr_info_expected.offset, p_addr_info.offset)
        self.assertEqual(p_addr_info_expected.p_addr, p_addr_info.p_addr)

    def test_write_word_to_byte_array(self):
        word = 0xa0b00c0d
        byte_arr = bytearray(4)
        mu.write_word_to_byte_array(byte_arr, 0, word)

        self.assertEqual(byte_arr[0], 0x0d)
        self.assertEqual(byte_arr[1], 0x0c)
        self.assertEqual(byte_arr[2], 0xb0)
        self.assertEqual(byte_arr[3], 0xa0)

    def test_check_int_mem_view_physical_contiguous(self):
        # Make a memoryview of a single byte, so that it must be contiguous
        mv_contiguous = memoryview(array.array('L', [0] * 1))
        self.assertTrue(mu.check_int_mem_view_physical_contiguous(mv_contiguous))

        # Make a memoryview larger than system page size, so that it cannot be contiguous
        mv_non_contiguous = memoryview(array.array('L', [0] * mmap.PAGESIZE * 2))
        self.assertFalse(mu.check_int_mem_view_physical_contiguous(mv_non_contiguous))

    def test_create_int_view_is_contiguous_for_length_256(self):
        mv = mu.create_int_view(256)
        self.assertTrue(mu.check_int_mem_view_physical_contiguous(mv))

    def test_create_int_view_is_not_contiguous_for_length_4096(self):
        mv = mu.create_int_view(4096)
        self.assertFalse(mu.check_int_mem_view_physical_contiguous(mv))

    def test_create_phys_contig_int_view_succeeds_for_small_lengths(self):
        mv = mu.create_phys_contig_int_view(1)
        self.assertTrue(isinstance(mv, memoryview))
        self.assertEqual(1, len(mv))
        self.assertTrue(isinstance(mv[0], int))

    def test_create_phys_contig_int_view_raises_exception_for_large_lengths(self):
        self.assertRaises(Exception, mu.create_phys_contig_int_view, mmap.PAGESIZE * 2)

    def test_create_phys_contig_int_views(self):
        view_len = 3
        num_views = 2
        mv_array = mu.create_phys_contig_int_views(view_len, num_views)
        self.assertEqual(num_views, len(mv_array))
        self.assertEqual(view_len, len(mv_array[0]))
        self.assertEqual(view_len, len(mv_array[1]))
        self.assertTrue(isinstance(mv_array[0], memoryview))
        self.assertTrue(isinstance(mv_array[1], memoryview))
        self.assertTrue(isinstance(mv_array[0][0], int))
        self.assertTrue(isinstance(mv_array[1][0], int))


if __name__ == '__main__':
    unittest.main()
