Coverage for tests/test_native_handle.py: 94.51%

354 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-14 17:01 +1100

1"""Tests for the handling of native pointers via wrappers and utilities.""" 

2 

3import gc 

4import os 

5import sys 

6from typing import TYPE_CHECKING, Any, Callable, List, Optional 

7 

8import pytest 

9from cffi import FFI 

10 

11if TYPE_CHECKING: 

12 from refcount.interop import CffiData 

13 

14from refcount.interop import ( 

15 CffiNativeHandle, 

16 CffiWrapperFactory, 

17 DeletableCffiNativeHandle, 

18 GenericWrapper, 

19 OwningCffiNativeHandle, 

20 cffi_arg_error_external_obj_type, 

21 is_cffi_native_handle, 

22 unwrap_cffi_native_handle, 

23 wrap_as_pointer_handle, 

24 wrap_cffi_native_handle, 

25) 

26from refcount.putils import library_short_filename 

27 

28fname = library_short_filename("test_native_library") 

29 

30pkg_dir = os.path.join(os.path.dirname(__file__), "..") 

31sys.path.insert(0, pkg_dir) 

32if sys.platform == "win32": 32 ↛ 33line 32 didn't jump to line 33 because the condition on line 32 was never true

33 dir_path = os.path.join(pkg_dir, "tests", "test_native_library", "build", "Debug") 

34 if not os.path.exists(os.path.join(dir_path, fname)): 

35 # fallback on AppVeyor output location 

36 dir_path = os.path.join(pkg_dir, "tests", "test_native_library", "x64", "Debug") 

37 

38else: 

39 dir_path = os.path.join(pkg_dir, "tests", "test_native_library", "build") 

40 

41native_lib_path = os.path.join(dir_path, fname) 

42 

43assert os.path.exists(native_lib_path) 

44 

45ut_ffi = FFI() 

46 

47ut_ffi.cdef( 

48 """ 

49typedef struct _date_time_interop 

50{ 

51 int year; 

52 int month; 

53 int day; 

54 int hour; 

55 int minute; 

56 int second; 

57} date_time_interop; 

58 

59typedef struct _interval_interop 

60{ 

61 date_time_interop start; 

62 date_time_interop end; 

63} interval_interop; 

64""", 

65) 

66 

67ut_ffi.cdef( 

68 "extern void create_date(date_time_interop* start, int year, int month, int day, int hour, int min, int sec);", 

69) 

70ut_ffi.cdef( 

71 "extern int test_date(date_time_interop* start, int year, int month, int day, int hour, int min, int sec);", 

72) 

73ut_ffi.cdef("extern void* create_dog();") 

74ut_ffi.cdef("extern int get_dog_refcount( void* obj);") 

75ut_ffi.cdef("extern int remove_dog_reference( void* obj);") 

76ut_ffi.cdef("extern int add_dog_reference( void* obj);") 

77ut_ffi.cdef("extern void* create_croc();") 

78ut_ffi.cdef("extern int get_croc_refcount( void* obj);") 

79ut_ffi.cdef("extern int remove_croc_reference( void* obj);") 

80ut_ffi.cdef("extern int add_croc_reference( void* obj);") 

81ut_ffi.cdef("extern void* create_owner( void* d);") 

82ut_ffi.cdef("extern int get_owner_refcount( void* obj);") 

83ut_ffi.cdef("extern int remove_owner_reference( void* obj);") 

84ut_ffi.cdef("extern int add_owner_reference( void* obj);") 

85ut_ffi.cdef("extern int num_dogs();") 

86ut_ffi.cdef("extern int num_owners();") 

87ut_ffi.cdef("extern void say_walk( void* owner);") 

88ut_ffi.cdef("extern void release( void* obj);") 

89 

90ut_ffi.cdef("extern void register_exception_callback(const void* callback);") 

91ut_ffi.cdef("extern void trigger_callback();") 

92 

93ut_dll = ut_ffi.dlopen(native_lib_path, ut_ffi.RTLD_LAZY) # Lazy loading 

94 

95_message_from_c: str = "<none>" 

96 

97 

98@ut_ffi.callback("void(char *)") 

99def called_back_from_c(some_string: str) -> None: 

100 """This function is called when uchronia raises an exception. 

101 

102 It sets the global variable ``_exception_txt_raised_uchronia``. 

103 

104 :param cdata exception_string: Exception string. 

105 """ 

106 global _message_from_c # noqa: PLW0603 

107 _message_from_c = ut_ffi.string(some_string) 

108 

109 

110class CustomCffiNativeHandle(CffiNativeHandle): 

111 """a custom native resource handle for testing purposes.""" 

112 

113 def __init__(self, pointer: "CffiData", type_id: str = "", prior_ref_count: int = 0): 

114 """Initialize a reference counter for a resource handle, with an initial reference count.""" 

115 super(CustomCffiNativeHandle, self).__init__( 

116 pointer, 

117 type_id=type_id, 

118 prior_ref_count=prior_ref_count, 

119 ) 

120 

121 def _release_handle(self) -> bool: 

122 ut_dll.release(self.get_handle()) 

123 return True 

124 

125 

126class Dog(CustomCffiNativeHandle): 

127 """A custom class for testing purposes.""" 

128 

129 def __init__(self, pointer: "CffiData" = None): 

130 """A custom class for testing purposes.""" 

131 if pointer is None: 

132 pointer = ut_dll.create_dog() 

133 super(Dog, self).__init__(pointer, type_id="DOG_PTR") 

134 

135 @property 

136 def native_reference_count(self) -> int: 

137 return ut_dll.get_dog_refcount(self.get_handle()) 

138 

139 @staticmethod 

140 def num_native_instances(): 

141 return ut_dll.num_dogs() 

142 

143 

144class DogOwner(CustomCffiNativeHandle): 

145 def __init__(self, dog): 

146 super(DogOwner, self).__init__(None, type_id="DOG_OWNER_PTR") 

147 self._set_handle(ut_dll.create_owner(dog.get_handle())) 

148 self.dog = dog 

149 self.dog.add_ref() 

150 

151 @property 

152 def native_reference_count(self) -> int: 

153 return ut_dll.get_owner_refcount(self.get_handle()) 

154 

155 @staticmethod 

156 def num_native_instances(): 

157 return ut_dll.num_owners() 

158 

159 def say_walk(self): 

160 ut_dll.say_walk(self.get_handle()) 

161 

162 def _release_handle(self) -> bool: 

163 super(DogOwner, self)._release_handle() 

164 # super(DogOwner, self)._release_handle() 

165 self.dog.release() 

166 return True 

167 

168 

169class CrocFiveParameters(CffiNativeHandle): 

170 def __init__( 

171 self, 

172 pointer: Any, 

173 release_native: Callable, 

174 type_id: str = "", 

175 prior_ref_count: int = 0, 

176 some_fifth_parameter: float = 0.0, 

177 ): 

178 super(CrocFiveParameters, self).__init__( 

179 pointer, 

180 type_id=type_id, 

181 prior_ref_count=prior_ref_count, 

182 ) 

183 self._release_native_handle = release_native 

184 self.some_fifth_parameter = some_fifth_parameter 

185 

186 def _release_handle(self) -> bool: 

187 self._release_native_handle(self.get_handle()) 

188 return True 

189 

190 

191class CrocFourParameters(CffiNativeHandle): 

192 def __init__( 

193 self, 

194 pointer: Any, 

195 release_native: Callable, 

196 type_id: str = "", 

197 prior_ref_count: int = 0, 

198 ): 

199 super(CrocFourParameters, self).__init__( 

200 pointer, 

201 type_id=type_id, 

202 prior_ref_count=prior_ref_count, 

203 ) 

204 self._release_native_handle = release_native 

205 

206 def _release_handle(self) -> bool: 

207 self._release_native_handle(self.get_handle()) 

208 return True 

209 

210 

211class CrocFourParametersWrongFourthParameter(CffiNativeHandle): 

212 def __init__( 

213 self, 

214 pointer: Any, 

215 release_native: Callable, 

216 type_id: str = "", 

217 unsupported_argument_type: Optional[List] = None, 

218 ): 

219 super(CrocFourParametersWrongFourthParameter, self).__init__( 

220 pointer, 

221 type_id=type_id, 

222 prior_ref_count=0, 

223 ) 

224 self.unsupported_argument_type = unsupported_argument_type 

225 self._release_native_handle = release_native 

226 

227 def _release_handle(self) -> bool: 

228 self._release_native_handle(self.get_handle()) 

229 return True 

230 

231 

232class CrocThreeParameters(CrocFourParameters): 

233 def __init__(self, pointer: Any, release_native: Callable, type_id: str = ""): 

234 super(CrocThreeParameters, self).__init__( 

235 pointer, 

236 release_native=release_native, 

237 type_id=type_id, 

238 prior_ref_count=0, 

239 ) 

240 

241 

242class CrocTwoParameters(CrocThreeParameters): 

243 def __init__(self, pointer: Any, release_native: Callable): 

244 super(CrocTwoParameters, self).__init__( 

245 pointer, 

246 release_native=release_native, 

247 type_id="CROC_PTR", 

248 ) 

249 

250 

251class CrocOneParameters(CrocTwoParameters): 

252 def __init__(self, pointer: Any): 

253 super(CrocOneParameters, self).__init__(pointer, release_native=ut_dll.release) 

254 

255 

256class CrocZeroParameters(CrocOneParameters): 

257 def __init__(self): 

258 raise ValueError( 

259 "This class should not have been used to create a wrapper, since it has no constuctor argument.", 

260 ) 

261 super(CrocZeroParameters, self).__init__(None) 

262 

263 

264def test_native_obj_ref_counting(): 

265 dog = Dog() 

266 assert dog.reference_count == 1 

267 assert dog.native_reference_count == 1 

268 dog.add_ref() 

269 assert dog.reference_count == 2 

270 assert dog.native_reference_count == 1 

271 dog.add_ref() 

272 assert dog.reference_count == 3 

273 assert dog.native_reference_count == 1 

274 dog.decrement_ref() 

275 assert dog.reference_count == 2 

276 assert dog.native_reference_count == 1 

277 owner = DogOwner(dog) 

278 assert owner.reference_count == 1 

279 assert dog.reference_count == 3 

280 assert dog.native_reference_count == 1 

281 dog.release() 

282 assert owner.reference_count == 1 

283 assert dog.reference_count == 2 

284 assert dog.native_reference_count == 1 

285 dog.release() 

286 assert owner.reference_count == 1 

287 assert owner.native_reference_count == 1 

288 assert dog.reference_count == 1 

289 assert dog.native_reference_count == 1 

290 assert not dog.is_invalid 

291 owner.say_walk() 

292 owner.release() 

293 assert owner.reference_count == 0 

294 assert dog.reference_count == 0 

295 # Cannot check on the native ref count - deleted objects. 

296 # TODO think of a simple way to test these 

297 # assert 0, owner.native_reference_count) 

298 # assert 0, dog.native_reference_count) 

299 assert dog.is_invalid 

300 assert owner.is_invalid 

301 

302 

303def test_cffi_native_handle_finalizers(): 

304 init_dog_count = Dog.num_native_instances() 

305 dog = Dog() 

306 assert (init_dog_count + 1) == Dog.num_native_instances() 

307 assert dog.reference_count == 1 

308 assert dog.native_reference_count == 1 

309 # if dog reference a new instance and we force garbage GC: 

310 gc.collect() 

311 dog = Dog() 

312 gc.collect() 

313 gc.collect() 

314 assert dog.reference_count == 1 

315 assert dog.native_reference_count == 1 

316 nn = Dog.num_native_instances() 

317 assert (init_dog_count + 1) == nn 

318 dog = None 

319 gc.collect() 

320 assert init_dog_count == Dog.num_native_instances() 

321 

322 

323def test_cffi_exceptions(): 

324 import datetime 

325 

326 incorrect_handle = datetime.datetime(2000, 1, 1, 1, 1, 1) 

327 with pytest.raises(RuntimeError): 

328 x = DeletableCffiNativeHandle(incorrect_handle, release_native=None) 

329 

330 

331def test_generic_wrappers(): 

332 x = ut_ffi.new("char[10]") 

333 o_wrapper = OwningCffiNativeHandle(x) 

334 assert str(o_wrapper).startswith("CFFI pointer handle to a native pointer") 

335 assert o_wrapper.reference_count == 1 

336 gw = GenericWrapper(o_wrapper.ptr) 

337 assert gw.ptr == o_wrapper.ptr 

338 

339 

340def test_str_repr(): 

341 dog = Dog() 

342 assert str(dog).startswith( 

343 'CFFI pointer handle to a native pointer of type id "DOG', 

344 ) 

345 assert repr(dog).startswith( 

346 'CFFI pointer handle to a native pointer of type id "DOG', 

347 ) 

348 

349 

350def test_cffi_native_handle_dispose(): 

351 init_dog_count = Dog.num_native_instances() 

352 dog = Dog() 

353 assert str(dog).startswith("CFFI pointer handle to a native pointer") 

354 assert (init_dog_count + 1) == Dog.num_native_instances() 

355 assert dog.reference_count == 1 

356 assert dog.native_reference_count == 1 

357 dog.dispose() 

358 assert init_dog_count == Dog.num_native_instances() 

359 assert dog.reference_count == 0 

360 # assert 0 == dog.native_reference_count 

361 # it should be all right to call dispose, even if already called and already zero ref counts. 

362 dog.dispose() 

363 

364 

365def test_cffi_handle_access(): 

366 x = ut_ffi.new("char[10]", init=b"foobarbaz0") 

367 o_wrapper = OwningCffiNativeHandle(x) 

368 assert str(o_wrapper.ptr) == "<cdata 'char[10]' owning 10 bytes>" 

369 assert isinstance(o_wrapper.obj, bytes) 

370 assert o_wrapper.obj == b"f" 

371 

372 

373from datetime import datetime 

374 

375 

376def test_wrapper_helper_functions(): 

377 assert isinstance(wrap_cffi_native_handle(dict()), dict) 

378 pointer = ut_dll.create_dog() 

379 dog = wrap_cffi_native_handle(pointer, "dog", ut_dll.release) 

380 assert isinstance(dog, CffiNativeHandle) 

381 assert dog.is_invalid == False 

382 assert dog.reference_count == 1 

383 assert is_cffi_native_handle(dog, "dog") 

384 assert is_cffi_native_handle(dog, "cat") == False 

385 assert is_cffi_native_handle(dict()) == False 

386 assert is_cffi_native_handle(1) == False 

387 assert is_cffi_native_handle(1, "cat") == False 

388 assert is_cffi_native_handle(None) == False 

389 assert pointer == unwrap_cffi_native_handle(dog, False) 

390 assert pointer == unwrap_cffi_native_handle(dog.ptr, False) 

391 assert unwrap_cffi_native_handle(None, False) is None 

392 assert pointer == unwrap_cffi_native_handle(dog, True) 

393 assert pointer == unwrap_cffi_native_handle(dog.ptr, True) 

394 assert unwrap_cffi_native_handle(None, True) is None 

395 x = datetime(2000, 1, 1, 1, 1) 

396 assert unwrap_cffi_native_handle(x, False) == x 

397 with pytest.raises(TypeError): 

398 assert unwrap_cffi_native_handle(x, True) == x 

399 

400 from refcount.interop import ( 

401 type_error_cffi, # backward compat; maintain unit test coverage 

402 ) 

403 

404 for func in [cffi_arg_error_external_obj_type, type_error_cffi]: 

405 msg = func(1, "") 

406 assert msg == "Expected a 'CffiNativeHandle' but instead got object of type '<class 'int'>'" 

407 msg = func(dog, "cat") 

408 assert ( 

409 msg == "Expected a 'CffiNativeHandle' with underlying type id 'cat' but instead got one with type id 'dog'" 

410 ) 

411 msg = func(None, "cat") 

412 assert msg == "Expected a 'CffiNativeHandle' but instead got 'None'" 

413 dog = None 

414 gc.collect() 

415 

416 

417def test_wrap_as_pointer_handle(): 

418 pointer = ut_dll.create_dog() 

419 dog = wrap_cffi_native_handle(pointer, "dog", ut_dll.release) 

420 

421 # Allow passing None via a wrapper, to facilitate uniform code generation with c-api-wrapper-generation 

422 assert isinstance(wrap_as_pointer_handle(None, False), GenericWrapper) 

423 assert isinstance(wrap_as_pointer_handle(None, True), GenericWrapper) 

424 assert wrap_as_pointer_handle(None, True).ptr is None 

425 assert wrap_as_pointer_handle(None, False).ptr is None 

426 

427 x = ut_ffi.new("char[10]", init=b"foobarbaz0") 

428 assert isinstance(wrap_as_pointer_handle(x, False), OwningCffiNativeHandle) 

429 assert isinstance(wrap_as_pointer_handle(x, True), OwningCffiNativeHandle) 

430 assert wrap_as_pointer_handle(dog, False) == dog 

431 assert wrap_as_pointer_handle(dog, True) == dog 

432 

433 bb = b"foobarbaz0" 

434 assert isinstance(wrap_as_pointer_handle(bb, False), GenericWrapper) 

435 assert isinstance(wrap_as_pointer_handle(bb, True), GenericWrapper) 

436 

437 d = datetime(2000, 1, 1, 1, 1) 

438 assert wrap_as_pointer_handle(d, False) == d 

439 with pytest.raises(TypeError): 

440 assert unwrap_cffi_native_handle(d, True) == d 

441 

442 with pytest.raises( 

443 TypeError, 

444 match="Argument is neither a CffiNativeHandle nor a CFFI external pointer, nor bytes", 

445 ): 

446 nothing = wrap_as_pointer_handle(d, True) 

447 

448 dog = None 

449 gc.collect() 

450 

451 

452def test_cffi_wrapper_factory(): 

453 _api_type_wrapper = { 

454 "DOG_PTR": Dog, 

455 "DOG_OWNER_PTR": DogOwner, 

456 } 

457 

458 wf_not_strict = CffiWrapperFactory(_api_type_wrapper, False) 

459 wf_strict = CffiWrapperFactory(_api_type_wrapper, True) 

460 pointer = ut_dll.create_dog() 

461 with pytest.raises(ValueError): 

462 _ = wf_not_strict.create_wrapper(pointer, None, ut_dll.release) 

463 # https://en.wikipedia.org/wiki/The_Thing_(1982_film) 

464 x = wf_not_strict.create_wrapper(pointer, "THE_THING_PTR", ut_dll.release) 

465 assert isinstance(x, DeletableCffiNativeHandle) 

466 assert not isinstance(x, Dog) 

467 del x 

468 gc.collect() 

469 pointer = ut_dll.create_dog() 

470 # if strict, we refuse to construct a wrapper outside of the known type identifiers 

471 with pytest.raises(ValueError): 

472 _ = wf_strict.create_wrapper(pointer, "THE_THING_PTR", ut_dll.release) 

473 dog = wf_not_strict.create_wrapper(pointer, "DOG_PTR", ut_dll.release) 

474 assert isinstance(dog, Dog) 

475 del dog 

476 gc.collect() 

477 

478 # Test the unexpected cases, where we have a pointer but no 

479 # identified native type, and a strict requirement to have one. 

480 pointer_croc = ut_dll.create_croc() 

481 with pytest.raises(ValueError): 

482 wf_strict.create_wrapper(pointer_croc, "CROC_PTR", ut_dll.release) 

483 # To increase UT coverage mostly, we will test the case where we have a pointer but no identified python wrapper type. 

484 _api_type_wrapper.update({"CROC_PTR": None}) 

485 with pytest.raises(NotImplementedError): 

486 wf_strict.create_wrapper(pointer_croc, "CROC_PTR", ut_dll.release) 

487 # Test the case where we have a pointer but no identified python wrapper type, but we are not strict ad use a generic wrapper. 

488 anonymous_croc = wf_not_strict.create_wrapper( 

489 pointer_croc, 

490 "CROC_PTR", 

491 ut_dll.release, 

492 ) 

493 assert isinstance(anonymous_croc, DeletableCffiNativeHandle) 

494 del anonymous_croc 

495 gc.collect() 

496 

497 

498def test_cffi_wrapper_factory_various_ctors(): 

499 """Sweep the various supported wrapper constructors for the wrapper factory""" 

500 _api_type_wrapper = {"DOG_PTR": Dog, "DOG_OWNER_PTR": DogOwner, "CROC_PTR": None} 

501 wf_strict = CffiWrapperFactory(_api_type_wrapper, True) 

502 # we cannot create a wrapper for a type that has no constructor: how would it know the native pointer? 

503 _api_type_wrapper.update({"CROC_PTR": CrocZeroParameters}) 

504 pointer_croc = ut_dll.create_croc() 

505 with pytest.raises(TypeError): 

506 wf_strict.create_wrapper(pointer_croc, "CROC_PTR", release_native=None) 

507 # we can create a wrapper with a constructor that has one argument, the pointer 

508 _api_type_wrapper.update({"CROC_PTR": CrocOneParameters}) 

509 croc_one = wf_strict.create_wrapper(pointer_croc, "CROC_PTR", release_native=None) 

510 assert isinstance(croc_one, CrocOneParameters) 

511 assert croc_one.type_id == "CROC_PTR" 

512 assert croc_one._release_handle is not None 

513 del croc_one 

514 gc.collect() 

515 

516 # two parameters 

517 pointer_croc = ut_dll.create_croc() 

518 _api_type_wrapper.update({"CROC_PTR": CrocTwoParameters}) 

519 # if we have two parameters, we need to provide the release function 

520 with pytest.raises( 

521 ValueError, 

522 match="Wrapper class 'CrocTwoParameters' has two constructor arguments; the argument 'release_native' cannot be None", 

523 ): 

524 _ = wf_strict.create_wrapper(pointer_croc, "CROC_PTR", release_native=None) 

525 croc_two = wf_strict.create_wrapper( 

526 pointer_croc, 

527 "CROC_PTR", 

528 release_native=ut_dll.release, 

529 ) 

530 assert isinstance(croc_two, CrocTwoParameters) 

531 assert croc_two.type_id == "CROC_PTR" 

532 assert croc_two._release_handle is not None 

533 # we cannot test the function equality easily SFAIK 

534 # assert croc_two._release_handle == ut_dll.release 

535 del croc_two 

536 gc.collect() 

537 

538 # three 

539 pointer_croc = ut_dll.create_croc() 

540 _api_type_wrapper.update({"CROC_PTR": CrocThreeParameters}) 

541 

542 # if we have three parameters, we need to provide the release function 

543 with pytest.raises(ValueError): 

544 _ = wf_strict.create_wrapper(pointer_croc, "CROC_PTR", release_native=None) 

545 

546 croc_three = wf_strict.create_wrapper( 

547 pointer_croc, 

548 "CROC_PTR", 

549 release_native=ut_dll.release, 

550 ) 

551 assert isinstance(croc_three, CrocThreeParameters) 

552 assert croc_three.type_id == "CROC_PTR" 

553 assert croc_three._release_handle is not None 

554 # we cannot test the function equality easily SFAIK 

555 # assert croc_three._release_handle == ut_dll.release 

556 del croc_three 

557 gc.collect() 

558 

559 # four 

560 pointer_croc = ut_dll.create_croc() 

561 _api_type_wrapper.update({"CROC_PTR": CrocFourParameters}) 

562 # This used not to be supported for a few months, but there is a 

563 # legacy of classes (in the swift app and more) with an initial ref counter with a zero value default 

564 # with pytest.raises(NotImplementedError): 

565 # _ = wf_strict.create_wrapper( 

566 # pointer_croc, "CROC_PTR", release_native=ut_dll.release 

567 # ) 

568 croc_four = wf_strict.create_wrapper( 

569 pointer_croc, 

570 "CROC_PTR", 

571 release_native=ut_dll.release, 

572 ) 

573 assert isinstance(croc_four, CrocFourParameters) 

574 assert croc_four.type_id == "CROC_PTR" 

575 assert croc_four._release_handle is not None 

576 # we cannot test the function equality easily SFAIK 

577 # assert croc_four._release_handle == ut_dll.release 

578 del croc_four 

579 gc.collect() 

580 

581 # four, but not with the expected fourth parameter 

582 pointer_croc = ut_dll.create_croc() 

583 _api_type_wrapper.update({"CROC_PTR": CrocFourParametersWrongFourthParameter}) 

584 with pytest.raises(TypeError): 

585 _ = wf_strict.create_wrapper(pointer_croc, "CROC_PTR", release_native=ut_dll.release) 

586 # manual cleanup for the sake of being pedantic 

587 ut_dll.release(pointer_croc) 

588 gc.collect() 

589 

590 # five, not supported 

591 pointer_croc = ut_dll.create_croc() 

592 _api_type_wrapper.update({"CROC_PTR": CrocFiveParameters}) 

593 with pytest.raises(NotImplementedError): 

594 _ = wf_strict.create_wrapper(pointer_croc, "CROC_PTR", release_native=ut_dll.release) 

595 

596 # manual cleanup for the sake of being pedantic 

597 ut_dll.release(pointer_croc) 

598 gc.collect() 

599 

600 

601def test_nativehandle_default_check_valid() -> None: 

602 # mostly added to increase UT coverage 

603 # locks in the behavior of the default implementation 

604 import pytest 

605 

606 from refcount.base import NativeHandle 

607 

608 rc = NativeHandle() 

609 pointer = ut_dll.create_dog() 

610 dog = wrap_cffi_native_handle(pointer, "dog", ut_dll.release) 

611 with pytest.raises(NotImplementedError): 

612 rc._is_valid_handle(dog) 

613 dog = None 

614 gc.collect() 

615 

616 

617def test_callback_via_cffi() -> None: 

618 # https://github.com/csiro-hydroinformatics/uchronia-time-series/issues/1 

619 global _message_from_c 

620 ut_dll.register_exception_callback(called_back_from_c) 

621 ut_dll.trigger_callback() 

622 assert _message_from_c != b"<none>" 

623 

624 

625if __name__ == "__main__": 625 ↛ 626line 625 didn't jump to line 626 because the condition on line 625 was never true

626 test_callback_via_cffi()