|
9 | 9 | from datetime import datetime, timedelta |
10 | 10 | from os.path import join, dirname |
11 | 11 | from threading import Thread |
12 | | -from unittest.mock import patch |
13 | 12 |
|
14 | 13 | import extism |
| 14 | +from extism.extism import CompiledPlugin, _ExtismFunctionMetadata, TypeInferredFunction |
15 | 15 |
|
16 | 16 |
|
17 | 17 | # A pickle-able object. |
@@ -69,6 +69,52 @@ def test_plugin_del_frees_native_resources(self): |
69 | 69 | self.assertEqual(plugin.plugin, -1, |
70 | 70 | "Expected plugin.plugin to be -1 after __del__, indicating extism_plugin_free was called") |
71 | 71 |
|
| 72 | + def test_compiled_plugin_del_frees_native_resources(self): |
| 73 | + """Test that CompiledPlugin.__del__ properly frees native resources. |
| 74 | + |
| 75 | + Unlike Plugin, CompiledPlugin has no context manager so __del__ is only |
| 76 | + called once by garbage collection. This also tests that __del__ can be |
| 77 | + safely called multiple times without causing double-free errors. |
| 78 | + """ |
| 79 | + compiled = CompiledPlugin(self._manifest(), functions=[]) |
| 80 | + # Verify pointer exists before deletion |
| 81 | + self.assertTrue(hasattr(compiled, 'pointer')) |
| 82 | + self.assertNotEqual(compiled.pointer, -1) |
| 83 | + |
| 84 | + # Create a plugin from compiled to ensure it works |
| 85 | + plugin = extism.Plugin(compiled) |
| 86 | + j = json.loads(plugin.call("count_vowels", "test")) |
| 87 | + self.assertEqual(j["count"], 1) |
| 88 | + |
| 89 | + # Clean up plugin first |
| 90 | + plugin.__del__() |
| 91 | + self.assertEqual(plugin.plugin, -1) |
| 92 | + |
| 93 | + # Now clean up compiled plugin |
| 94 | + compiled.__del__() |
| 95 | + |
| 96 | + # Verify compiled plugin was freed |
| 97 | + self.assertEqual(compiled.pointer, -1, |
| 98 | + "Expected compiled.pointer to be -1 after __del__, indicating extism_compiled_plugin_free was called") |
| 99 | + |
| 100 | + def test_extism_function_metadata_del_frees_native_resources(self): |
| 101 | + """Test that _ExtismFunctionMetadata.__del__ properly frees native resources.""" |
| 102 | + def test_host_fn(inp: str) -> str: |
| 103 | + return inp |
| 104 | + |
| 105 | + func = TypeInferredFunction(None, "test_func", test_host_fn, []) |
| 106 | + metadata = _ExtismFunctionMetadata(func) |
| 107 | + |
| 108 | + # Verify pointer exists before deletion |
| 109 | + self.assertTrue(hasattr(metadata, 'pointer')) |
| 110 | + self.assertIsNotNone(metadata.pointer) |
| 111 | + |
| 112 | + metadata.__del__() |
| 113 | + |
| 114 | + # Verify function was freed (pointer set to None) |
| 115 | + self.assertIsNone(metadata.pointer, |
| 116 | + "Expected metadata.pointer to be None after __del__, indicating extism_function_free was called") |
| 117 | + |
72 | 118 | def test_errors_on_bad_manifest(self): |
73 | 119 | self.assertRaises( |
74 | 120 | extism.Error, lambda: extism.Plugin({"invalid_manifest": True}) |
|
0 commit comments