this repo has no description
1#!/usr/bin/env python3
2# Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
3import ctypes
4import ctypes.util
5import unittest
6
7from test_support import pyro_only
8
9
10class CtypesTests(unittest.TestCase):
11 def test_addressof_non_cdata_raises_type_error(self):
12 with self.assertRaises(TypeError) as ctx:
13 ctypes.addressof("not-CData")
14 self.assertEqual(str(ctx.exception), "invalid type")
15
16 def test_addressof_mmap_returns_int_greater_than_zero(self):
17 import mmap
18
19 view = memoryview(mmap.mmap(-1, 2))
20 uint = ctypes.c_uint16.from_buffer(view)
21 addr = ctypes.addressof(uint)
22 self.assertIsInstance(addr, int)
23 self.assertGreater(addr, 0)
24
25 def test_array_subclass_is_instantiable(self):
26 class ArraySub(ctypes.Array):
27 _length_ = 2
28 _type_ = ctypes.c_bool
29
30 self.assertEqual(ctypes.sizeof(ArraySub()), 2)
31
32 def test_array_without_length_raises_attribute_error(self):
33 with self.assertRaises(AttributeError) as ctx:
34
35 class ArraySub(ctypes.Array):
36 _type_ = ctypes.c_bool
37
38 self.assertEqual(str(ctx.exception), "class must define a '_length_' attribute")
39
40 def test_array_with_non_int_length_raises_type_error(self):
41 with self.assertRaises(TypeError) as ctx:
42
43 class ArraySub(ctypes.Array):
44 _length_ = 1.0
45 _type_ = ctypes.c_bool
46
47 self.assertEqual(
48 str(ctx.exception), "The '_length_' attribute must be an integer"
49 )
50
51 def test_array_with_negative_length_raises_attribute_error(self):
52 with self.assertRaises(ValueError) as ctx:
53
54 class ArraySub(ctypes.Array):
55 _length_ = -1
56 _type_ = ctypes.c_bool
57
58 self.assertEqual(
59 str(ctx.exception), "The '_length_' attribute must not be negative"
60 )
61
62 def test_array_without_type_raises_attribute_error(self):
63 with self.assertRaises(AttributeError) as ctx:
64
65 class ArraySub(ctypes.Array):
66 _length_ = 1
67
68 self.assertEqual(str(ctx.exception), "class must define a '_type_' attribute")
69
70 def test_array_with_non_class_type_raises_type_error(self):
71 with self.assertRaises(TypeError) as ctx:
72
73 class ArraySub(ctypes.Array):
74 _length_ = 1
75 _type_ = "b"
76
77 self.assertEqual(str(ctx.exception), "_type_ must have storage info")
78
79 def test_array_with_non_CData_type_raises_type_error(self):
80 with self.assertRaises(TypeError) as ctx:
81
82 class ArraySub(ctypes.Array):
83 _length_ = 1
84 _type_ = int
85
86 self.assertEqual(str(ctx.exception), "_type_ must have storage info")
87
88 def test_cfuncptr_dunder_call_no_args_c_int_restype_returns_int(self):
89 lib_name = ctypes.util.find_library("python")
90 lib = ctypes.CDLL(lib_name)
91 recursion_limit = lib.Py_GetRecursionLimit
92 self.assertIs(recursion_limit.restype, ctypes.c_int)
93 result = recursion_limit()
94 self.assertIs(type(result), int)
95 self.assertEqual(result, 1000)
96
97 def test_cfuncptr_dunder_call_no_args_c_char_p_restype_returns_bytes(self):
98 lib_name = ctypes.util.find_library("python")
99 lib = ctypes.CDLL(lib_name)
100 get_platform = lib.Py_GetPlatform
101 self.assertIs(get_platform.restype, ctypes.c_int)
102 get_platform.restype = ctypes.c_char_p
103 result = get_platform()
104 self.assertIs(type(result), bytes)
105 self.assertTrue(result in [b"linux", b"darwin"])
106
107 def test_char_array_from_buffer_is_readable_and_writeable(self):
108 import mmap
109
110 m = mmap.mmap(-1, 6)
111 array_type = ctypes.c_char * 6
112 c = array_type.from_buffer(memoryview(m))
113 view = memoryview(m)
114 view[0] = ord("f")
115 view[1] = ord("b")
116 self.assertEqual(c.value, b"fb")
117 c.value = b"foobar"
118 self.assertEqual(c.value, b"foobar")
119 self.assertEqual(view.tolist(), list(b"foobar"))
120
121 def test_char_array_with_zero_initialization_rewrites_first_byte_memory(self):
122 import mmap
123
124 m = mmap.mmap(-1, 3)
125 array_type = ctypes.c_char * 3
126 view = memoryview(m)
127 view[:3] = b"foo"
128 c = array_type.from_buffer(memoryview(m))
129 c.__init__(b"\0")
130 self.assertEqual(c.value, b"")
131 self.assertEqual(view.tolist(), list(b"\0oo"))
132
133 def test_char_array_with_non_bytes_value_raises_type_error(self):
134 c = (ctypes.c_char * 3)()
135 with self.assertRaises(TypeError) as ctx:
136 c.value = 1
137 self.assertEqual(str(ctx.exception), "bytes expected instead of int instance")
138
139 def test_char_array_with_too_long_value_raises_value_error(self):
140 c = (ctypes.c_char * 3)()
141 with self.assertRaises(ValueError) as ctx:
142 c.value = b"foobar"
143 self.assertEqual(str(ctx.exception), "byte string too long")
144
145 def test_non_char_array_class_raises_attribute_error_on_value(self):
146 array_type = ctypes.c_uint16 * 5
147 with self.assertRaises(AttributeError) as ctx:
148 array_type().value
149 self.assertEqual(
150 str(ctx.exception), "'c_ushort_Array_5' object has no attribute 'value'"
151 )
152
153 def test_ctypes_array_creation_with_type_returns_array_type(self):
154 arr = ctypes.c_ubyte * 14
155 self.assertEqual(arr._type_, ctypes.c_ubyte)
156 self.assertEqual(arr._length_, 14)
157 self.assertEqual(arr.__name__, "c_ubyte_Array_14")
158
159 def test_ctypes_array_creation_cache_returns_same_instance_for_same_type(self):
160 arr1 = ctypes.c_ubyte * 2
161 arr2 = ctypes.c_ubyte * 2
162 self.assertIs(arr1, arr2)
163
164 # TODO(T47024191): Since inline caches hold strongrefs to their objects, array
165 # types don't get collected on _gc().
166 @unittest.skip("Disable until our inline caches store weakrefs")
167 @pyro_only
168 def test_ctypes_array_creation_cache_cleared_when_type_gced(self):
169 from _ctypes import _array_from_ctype_cache
170
171 from _builtins import _gc
172
173 ctypes.c_ubyte * 2
174 self.assertGreater(len(_array_from_ctype_cache), 0)
175 _gc()
176 self.assertEqual(len(_array_from_ctype_cache), 0)
177
178 def test_ctypes_uint16_creation_stores_uint16(self):
179 self.assertEqual(ctypes.c_uint16().value, 0)
180 self.assertEqual(ctypes.c_uint16(10).value, 10)
181 self.assertEqual(ctypes.c_uint16(True).value, 1)
182
183 def test_ctypes_uint16_value_is_modifiable(self):
184 uint = ctypes.c_uint16()
185 self.assertEqual(uint.value, 0)
186 uint.value = 12345
187 self.assertEqual(uint.value, 12345)
188 with self.assertRaises(TypeError) as ctx:
189 uint.value = 1.0
190 self.assertEqual(str(ctx.exception), "int expected instead of float")
191
192 def test_ctypes_uint16_with_wrong_type_raises_type_error(self):
193 with self.assertRaises(TypeError) as ctx:
194 ctypes.c_uint16("1")
195 self.assertEqual(str(ctx.exception), "an integer is required (got type str)")
196
197 def test_ctypes_uint16_with_float_raises_type_error(self):
198 with self.assertRaises(TypeError) as ctx:
199 ctypes.c_uint16(1.0)
200 self.assertEqual(str(ctx.exception), "int expected instead of float")
201
202 def test_ctypes_ulonglong_creation_stores_long(self):
203 self.assertEqual(ctypes.c_ulonglong().value, 0)
204 self.assertEqual(ctypes.c_ulonglong(0xFF << 32).value, 0xFF << 32)
205
206 def test_from_buffer_with_memory_reflects_memory(self):
207 import mmap
208
209 m = mmap.mmap(-1, 2)
210 c = ctypes.c_uint16.from_buffer(memoryview(m))
211 view = memoryview(m)
212 view[0] = ord("f")
213 view[1] = ord("b")
214 self.assertEqual(c.value, (view[1] << 8) + view[0])
215
216 def test_from_buffer_with_abstract_class_raises_type_error(self):
217 import _ctypes
218
219 with self.assertRaises(TypeError) as context:
220 _ctypes._SimpleCData.from_buffer(b"as")
221 self.assertEqual(str(context.exception), "abstract class")
222
223 def test_from_buffer_with_readonly_buffer_raises_type_error(self):
224 import mmap
225
226 m = mmap.mmap(-1, 2, prot=mmap.PROT_READ)
227 with self.assertRaises(TypeError) as context:
228 ctypes.c_uint16.from_buffer(memoryview(m))
229 self.assertEqual(str(context.exception), "underlying buffer is not writable")
230
231 def test_from_buffer_with_negative_offset_raises_value_error(self):
232 import mmap
233
234 m = mmap.mmap(-1, 2)
235 with self.assertRaises(ValueError) as context:
236 ctypes.c_uint16.from_buffer(memoryview(m), -1)
237 self.assertEqual(str(context.exception), "offset cannot be negative")
238
239 def test_from_buffer_with_too_small_buffer_raises_value_error(self):
240 import mmap
241
242 m = mmap.mmap(-1, 1)
243 with self.assertRaises(ValueError) as context:
244 ctypes.c_uint16.from_buffer(memoryview(m))
245 self.assertEqual(
246 str(context.exception),
247 "Buffer size too small (1 instead of at least 2 bytes)",
248 )
249
250 def test_from_buffer_with_different_sizes_read_correct_number_of_bytes(self):
251 import mmap
252
253 m = mmap.mmap(-1, 8)
254 view = memoryview(m)
255 view[0] = 0xFF
256 view[3] = 0xFF
257 short = ctypes.c_uint16.from_buffer(view)
258 longlong = ctypes.c_ulonglong.from_buffer(view)
259 self.assertEqual(short.value, 0xFF)
260 self.assertEqual(longlong.value, (0xFF << 24) + 0xFF)
261
262 def test_loaded_library_has_function_attrs(self):
263 libc_name = ctypes.util.find_library("c")
264 libc = ctypes.CDLL(libc_name)
265 self.assertTrue(hasattr(libc, "abort"))
266 self.assertTrue(hasattr(libc, "printf"))
267 self.assertFalse(hasattr(libc, "DoesNotExist"))
268 self.assertIsNotNone(libc.abort)
269 self.assertIsNotNone(libc.printf)
270
271 def test_import_uuid(self):
272 # importing uuid exercises ctypes, make sure it doesn't break
273 import uuid
274
275 self.assertIsNotNone(uuid)
276
277 def test_memset_sets_memoryview_array(self):
278 import mmap
279
280 m = mmap.mmap(-1, 4)
281 view = memoryview(m)
282 view[:] = b"1234"
283 array = (ctypes.c_char * 4).from_buffer(view)
284 array_addr = ctypes.addressof(array)
285 self.assertEqual(array_addr, ctypes.memset(array_addr, 1, 2))
286 self.assertEqual(view.tolist(), list(b"\x01\x0134"))
287
288 def test_sizeof_non_cdata_type_raises_type_error(self):
289 with self.assertRaises(TypeError) as ctx:
290 ctypes.sizeof(TypeError)
291 self.assertEqual(str(ctx.exception), "this type has no size")
292
293 def test_sizeof_type_returns_size(self):
294 self.assertEqual(ctypes.sizeof(ctypes.c_bool), 1)
295 self.assertEqual(ctypes.sizeof(ctypes.c_uint16), 2)
296
297 def test_sizeof_object_with_memory_returns_size_of_type(self):
298 import mmap
299
300 view = memoryview(mmap.mmap(-1, 6))
301 uint = ctypes.c_uint16.from_buffer(view)
302 self.assertEqual(ctypes.sizeof(uint), 2)
303
304 def test_sizeof_array_returns_length(self):
305 self.assertEqual(ctypes.sizeof(ctypes.c_char * 5), 5)
306 self.assertEqual(ctypes.sizeof(ctypes.c_uint16 * 20), 40)
307 self.assertEqual(ctypes.sizeof(ctypes.c_uint16 * 5 * 5), 50)
308
309
310if __name__ == "__main__":
311 unittest.main()