|
1 | 1 | from collections import namedtuple |
2 | | -import unittest |
3 | | -import extism |
| 2 | +import gc |
4 | 3 | import hashlib |
5 | 4 | import json |
| 5 | +import pickle |
6 | 6 | import time |
7 | | -from threading import Thread |
| 7 | +import typing |
| 8 | +import unittest |
8 | 9 | from datetime import datetime, timedelta |
9 | 10 | from os.path import join, dirname |
10 | | -import typing |
11 | | -import pickle |
| 11 | +from threading import Thread |
| 12 | +from unittest.mock import patch |
| 13 | + |
| 14 | +import extism |
12 | 15 |
|
13 | 16 |
|
14 | 17 | # A pickle-able object. |
@@ -47,6 +50,25 @@ def test_can_free_plugin(self): |
47 | 50 | plugin = extism.Plugin(self._manifest()) |
48 | 51 | del plugin |
49 | 52 |
|
| 53 | + def test_plugin_del_frees_native_resources(self): |
| 54 | + """Test that Plugin.__del__ properly frees native resources. |
| 55 | + |
| 56 | + This tests the fix for a bug where Plugin.__del__ checked for |
| 57 | + 'self.pointer' instead of 'self.plugin', causing extism_plugin_free |
| 58 | + to never be called and leading to memory leaks. |
| 59 | + |
| 60 | + This also tests that __del__ can be safely called multiple times |
| 61 | + (via context manager exit and garbage collection) without causing |
| 62 | + double-free errors. |
| 63 | + """ |
| 64 | + with extism.Plugin(self._manifest(), functions=[]) as plugin: |
| 65 | + j = json.loads(plugin.call("count_vowels", "test")) |
| 66 | + self.assertEqual(j["count"], 1) |
| 67 | + |
| 68 | + # Verify plugin was freed after exiting context |
| 69 | + self.assertEqual(plugin.plugin, -1, |
| 70 | + "Expected plugin.plugin to be -1 after __del__, indicating extism_plugin_free was called") |
| 71 | + |
50 | 72 | def test_errors_on_bad_manifest(self): |
51 | 73 | self.assertRaises( |
52 | 74 | extism.Error, lambda: extism.Plugin({"invalid_manifest": True}) |
|
0 commit comments