#!/usr/bin/env python3 # Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) import ctypes import ctypes.util import unittest from test_support import pyro_only class CtypesTests(unittest.TestCase): def test_addressof_non_cdata_raises_type_error(self): with self.assertRaises(TypeError) as ctx: ctypes.addressof("not-CData") self.assertEqual(str(ctx.exception), "invalid type") def test_addressof_mmap_returns_int_greater_than_zero(self): import mmap view = memoryview(mmap.mmap(-1, 2)) uint = ctypes.c_uint16.from_buffer(view) addr = ctypes.addressof(uint) self.assertIsInstance(addr, int) self.assertGreater(addr, 0) def test_array_subclass_is_instantiable(self): class ArraySub(ctypes.Array): _length_ = 2 _type_ = ctypes.c_bool self.assertEqual(ctypes.sizeof(ArraySub()), 2) def test_array_without_length_raises_attribute_error(self): with self.assertRaises(AttributeError) as ctx: class ArraySub(ctypes.Array): _type_ = ctypes.c_bool self.assertEqual(str(ctx.exception), "class must define a '_length_' attribute") def test_array_with_non_int_length_raises_type_error(self): with self.assertRaises(TypeError) as ctx: class ArraySub(ctypes.Array): _length_ = 1.0 _type_ = ctypes.c_bool self.assertEqual( str(ctx.exception), "The '_length_' attribute must be an integer" ) def test_array_with_negative_length_raises_attribute_error(self): with self.assertRaises(ValueError) as ctx: class ArraySub(ctypes.Array): _length_ = -1 _type_ = ctypes.c_bool self.assertEqual( str(ctx.exception), "The '_length_' attribute must not be negative" ) def test_array_without_type_raises_attribute_error(self): with self.assertRaises(AttributeError) as ctx: class ArraySub(ctypes.Array): _length_ = 1 self.assertEqual(str(ctx.exception), "class must define a '_type_' attribute") def test_array_with_non_class_type_raises_type_error(self): with self.assertRaises(TypeError) as ctx: class ArraySub(ctypes.Array): _length_ = 1 _type_ = "b" self.assertEqual(str(ctx.exception), "_type_ must have storage info") def test_array_with_non_CData_type_raises_type_error(self): with self.assertRaises(TypeError) as ctx: class ArraySub(ctypes.Array): _length_ = 1 _type_ = int self.assertEqual(str(ctx.exception), "_type_ must have storage info") def test_cfuncptr_dunder_call_no_args_c_int_restype_returns_int(self): lib_name = ctypes.util.find_library("python") lib = ctypes.CDLL(lib_name) recursion_limit = lib.Py_GetRecursionLimit self.assertIs(recursion_limit.restype, ctypes.c_int) result = recursion_limit() self.assertIs(type(result), int) self.assertEqual(result, 1000) def test_cfuncptr_dunder_call_no_args_c_char_p_restype_returns_bytes(self): lib_name = ctypes.util.find_library("python") lib = ctypes.CDLL(lib_name) get_platform = lib.Py_GetPlatform self.assertIs(get_platform.restype, ctypes.c_int) get_platform.restype = ctypes.c_char_p result = get_platform() self.assertIs(type(result), bytes) self.assertTrue(result in [b"linux", b"darwin"]) def test_char_array_from_buffer_is_readable_and_writeable(self): import mmap m = mmap.mmap(-1, 6) array_type = ctypes.c_char * 6 c = array_type.from_buffer(memoryview(m)) view = memoryview(m) view[0] = ord("f") view[1] = ord("b") self.assertEqual(c.value, b"fb") c.value = b"foobar" self.assertEqual(c.value, b"foobar") self.assertEqual(view.tolist(), list(b"foobar")) def test_char_array_with_zero_initialization_rewrites_first_byte_memory(self): import mmap m = mmap.mmap(-1, 3) array_type = ctypes.c_char * 3 view = memoryview(m) view[:3] = b"foo" c = array_type.from_buffer(memoryview(m)) c.__init__(b"\0") self.assertEqual(c.value, b"") self.assertEqual(view.tolist(), list(b"\0oo")) def test_char_array_with_non_bytes_value_raises_type_error(self): c = (ctypes.c_char * 3)() with self.assertRaises(TypeError) as ctx: c.value = 1 self.assertEqual(str(ctx.exception), "bytes expected instead of int instance") def test_char_array_with_too_long_value_raises_value_error(self): c = (ctypes.c_char * 3)() with self.assertRaises(ValueError) as ctx: c.value = b"foobar" self.assertEqual(str(ctx.exception), "byte string too long") def test_non_char_array_class_raises_attribute_error_on_value(self): array_type = ctypes.c_uint16 * 5 with self.assertRaises(AttributeError) as ctx: array_type().value self.assertEqual( str(ctx.exception), "'c_ushort_Array_5' object has no attribute 'value'" ) def test_ctypes_array_creation_with_type_returns_array_type(self): arr = ctypes.c_ubyte * 14 self.assertEqual(arr._type_, ctypes.c_ubyte) self.assertEqual(arr._length_, 14) self.assertEqual(arr.__name__, "c_ubyte_Array_14") def test_ctypes_array_creation_cache_returns_same_instance_for_same_type(self): arr1 = ctypes.c_ubyte * 2 arr2 = ctypes.c_ubyte * 2 self.assertIs(arr1, arr2) # TODO(T47024191): Since inline caches hold strongrefs to their objects, array # types don't get collected on _gc(). @unittest.skip("Disable until our inline caches store weakrefs") @pyro_only def test_ctypes_array_creation_cache_cleared_when_type_gced(self): from _ctypes import _array_from_ctype_cache from _builtins import _gc ctypes.c_ubyte * 2 self.assertGreater(len(_array_from_ctype_cache), 0) _gc() self.assertEqual(len(_array_from_ctype_cache), 0) def test_ctypes_uint16_creation_stores_uint16(self): self.assertEqual(ctypes.c_uint16().value, 0) self.assertEqual(ctypes.c_uint16(10).value, 10) self.assertEqual(ctypes.c_uint16(True).value, 1) def test_ctypes_uint16_value_is_modifiable(self): uint = ctypes.c_uint16() self.assertEqual(uint.value, 0) uint.value = 12345 self.assertEqual(uint.value, 12345) with self.assertRaises(TypeError) as ctx: uint.value = 1.0 self.assertEqual(str(ctx.exception), "int expected instead of float") def test_ctypes_uint16_with_wrong_type_raises_type_error(self): with self.assertRaises(TypeError) as ctx: ctypes.c_uint16("1") self.assertEqual(str(ctx.exception), "an integer is required (got type str)") def test_ctypes_uint16_with_float_raises_type_error(self): with self.assertRaises(TypeError) as ctx: ctypes.c_uint16(1.0) self.assertEqual(str(ctx.exception), "int expected instead of float") def test_ctypes_ulonglong_creation_stores_long(self): self.assertEqual(ctypes.c_ulonglong().value, 0) self.assertEqual(ctypes.c_ulonglong(0xFF << 32).value, 0xFF << 32) def test_from_buffer_with_memory_reflects_memory(self): import mmap m = mmap.mmap(-1, 2) c = ctypes.c_uint16.from_buffer(memoryview(m)) view = memoryview(m) view[0] = ord("f") view[1] = ord("b") self.assertEqual(c.value, (view[1] << 8) + view[0]) def test_from_buffer_with_abstract_class_raises_type_error(self): import _ctypes with self.assertRaises(TypeError) as context: _ctypes._SimpleCData.from_buffer(b"as") self.assertEqual(str(context.exception), "abstract class") def test_from_buffer_with_readonly_buffer_raises_type_error(self): import mmap m = mmap.mmap(-1, 2, prot=mmap.PROT_READ) with self.assertRaises(TypeError) as context: ctypes.c_uint16.from_buffer(memoryview(m)) self.assertEqual(str(context.exception), "underlying buffer is not writable") def test_from_buffer_with_negative_offset_raises_value_error(self): import mmap m = mmap.mmap(-1, 2) with self.assertRaises(ValueError) as context: ctypes.c_uint16.from_buffer(memoryview(m), -1) self.assertEqual(str(context.exception), "offset cannot be negative") def test_from_buffer_with_too_small_buffer_raises_value_error(self): import mmap m = mmap.mmap(-1, 1) with self.assertRaises(ValueError) as context: ctypes.c_uint16.from_buffer(memoryview(m)) self.assertEqual( str(context.exception), "Buffer size too small (1 instead of at least 2 bytes)", ) def test_from_buffer_with_different_sizes_read_correct_number_of_bytes(self): import mmap m = mmap.mmap(-1, 8) view = memoryview(m) view[0] = 0xFF view[3] = 0xFF short = ctypes.c_uint16.from_buffer(view) longlong = ctypes.c_ulonglong.from_buffer(view) self.assertEqual(short.value, 0xFF) self.assertEqual(longlong.value, (0xFF << 24) + 0xFF) def test_loaded_library_has_function_attrs(self): libc_name = ctypes.util.find_library("c") libc = ctypes.CDLL(libc_name) self.assertTrue(hasattr(libc, "abort")) self.assertTrue(hasattr(libc, "printf")) self.assertFalse(hasattr(libc, "DoesNotExist")) self.assertIsNotNone(libc.abort) self.assertIsNotNone(libc.printf) def test_import_uuid(self): # importing uuid exercises ctypes, make sure it doesn't break import uuid self.assertIsNotNone(uuid) def test_memset_sets_memoryview_array(self): import mmap m = mmap.mmap(-1, 4) view = memoryview(m) view[:] = b"1234" array = (ctypes.c_char * 4).from_buffer(view) array_addr = ctypes.addressof(array) self.assertEqual(array_addr, ctypes.memset(array_addr, 1, 2)) self.assertEqual(view.tolist(), list(b"\x01\x0134")) def test_sizeof_non_cdata_type_raises_type_error(self): with self.assertRaises(TypeError) as ctx: ctypes.sizeof(TypeError) self.assertEqual(str(ctx.exception), "this type has no size") def test_sizeof_type_returns_size(self): self.assertEqual(ctypes.sizeof(ctypes.c_bool), 1) self.assertEqual(ctypes.sizeof(ctypes.c_uint16), 2) def test_sizeof_object_with_memory_returns_size_of_type(self): import mmap view = memoryview(mmap.mmap(-1, 6)) uint = ctypes.c_uint16.from_buffer(view) self.assertEqual(ctypes.sizeof(uint), 2) def test_sizeof_array_returns_length(self): self.assertEqual(ctypes.sizeof(ctypes.c_char * 5), 5) self.assertEqual(ctypes.sizeof(ctypes.c_uint16 * 20), 40) self.assertEqual(ctypes.sizeof(ctypes.c_uint16 * 5 * 5), 50) if __name__ == "__main__": unittest.main()