Skip to content

Commit 4cad7f7

Browse files
Merge pull request #5 from godaddy/bugfixes
Fix load_library and str_to_buf
2 parents e62e74e + 837d2d5 commit 4cad7f7

File tree

5 files changed

+134
-15
lines changed

5 files changed

+134
-15
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,8 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
# Editors
132+
.vscode/
133+
.idea/
134+
*.swp

CHANGELOG.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
## [0.2.1] - 2022-03-04
2+
3+
- Added `minimum_allocation` and `header_size` properties to the `Cobhan` class
4+
- Fixed `load_library` and added tests
5+
- Fixed allocation of empty buffers in `str_to_buf` and added tests
6+
17
## [0.2.0] - 2022-03-03
28

3-
- Rename the `_load_*` methods to `load_*` to make them part of the public API,
9+
- Renamed the `_load_*` methods to `load_*` to make them part of the public API,
410
rather than protected.
5-
- Add the `int_to_buf` and `buf_to_int` methods.
11+
- Added the `int_to_buf` and `buf_to_int` methods.
612

713
## [0.1.0] - 2022-02-28
814

cobhan/cobhan.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ def __init__(self):
2727
self.__sizeof_int32, byteorder="little", signed=True
2828
)
2929

30+
@property
31+
def minimum_allocation(self):
32+
"""The minimum buffer size, in bytes, that will be allocated for a string"""
33+
return self.__minimum_allocation
34+
35+
@property
36+
def header_size(self):
37+
"""The size, in bytes, of a buffer's header"""
38+
return self.__sizeof_header
39+
3040
def load_library(self, library_path: str, library_name: str, cdefines: str) -> None:
3141
"""Locate and load a library based on the current platform.
3242
@@ -47,7 +57,7 @@ def load_library(self, library_path: str, library_name: str, cdefines: str) -> N
4757
os_ext = "-musl.so"
4858
need_chdir = True
4959
else:
50-
os_path = ".so"
60+
os_ext = ".so"
5161
elif system == "Darwin":
5262
os_ext = ".dylib"
5363
elif system == "Windows":
@@ -64,9 +74,7 @@ def load_library(self, library_path: str, library_name: str, cdefines: str) -> N
6474
raise UnsupportedOperation("Unsupported CPU")
6575

6676
# Get absolute library path
67-
resolved_library_path = pathlib.Path(
68-
os.path.join(library_path, os_path, arch_part)
69-
).resolve()
77+
resolved_library_path = pathlib.Path(library_path).resolve()
7078

7179
# Build library path with file name
7280
library_file_path = os.path.join(
@@ -154,12 +162,14 @@ def bytearray_to_buf(self, payload: ByteString) -> CBuf:
154162
self.__set_payload(buf, payload, length)
155163
return buf
156164

157-
def str_to_buf(self, string: str) -> CBuf:
165+
def str_to_buf(self, string: Optional[str]) -> CBuf:
158166
"""Encode a string in utf8 and copy into a Cobhan buffer.
159167
160168
:param string: The string to be copied
161169
:returns: A new Cobhan buffer containing the utf8 encoded string
162170
"""
171+
if not string:
172+
return self.__ffi.new(f"char[{self.header_size}]")
163173
encoded_bytes = string.encode("utf8")
164174
length = len(encoded_bytes)
165175
buf = self.allocate_buf(length)

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "cobhan"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
description = "Cobhan FFI"
55
authors = [
66
"Jeremiah Gowdy <[email protected]>",

tests/test_cobhan.py

+105-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,109 @@
1-
"""Tests for the main Cobhan module"""
1+
# pylint: disable=missing-function-docstring,missing-class-docstring,missing-module-docstring
22

3-
from unittest import TestCase
3+
from pathlib import Path
4+
from unittest import mock, TestCase
45

6+
from cobhan import Cobhan
57

6-
class CobhanTest(TestCase):
7-
"""Tests for the main Cobhan class"""
88

9-
def test_pass(self):
10-
"""Fake test to start off"""
11-
self.assertEqual(True, True)
9+
class LoadLibraryTests(TestCase):
10+
"""Tests for Cobhan.load_library"""
11+
12+
def setUp(self) -> None:
13+
self.ffi_patcher = mock.patch("cobhan.cobhan.FFI")
14+
self.platform_patcher = mock.patch("cobhan.cobhan.platform")
15+
16+
self.mock_ffi = self.ffi_patcher.start()
17+
self.mock_dlopen = self.mock_ffi.return_value.dlopen
18+
self.mock_platform = self.platform_patcher.start()
19+
20+
self.addCleanup(self.ffi_patcher.stop)
21+
self.addCleanup(self.platform_patcher.stop)
22+
23+
self.cobhan = Cobhan()
24+
return super().setUp()
25+
26+
def test_load_linux_x64(self):
27+
self.mock_platform.system.return_value = "Linux"
28+
self.mock_platform.machine.return_value = "x86_64"
29+
30+
self.cobhan.load_library("libfoo", "libbar", "")
31+
self.mock_dlopen.assert_called_once_with(
32+
str(Path("libfoo/libbar-x64.so").resolve())
33+
)
34+
35+
def test_load_linux_arm64(self):
36+
self.mock_platform.system.return_value = "Linux"
37+
self.mock_platform.machine.return_value = "arm64"
38+
39+
self.cobhan.load_library("libfoo", "libbar", "")
40+
self.mock_dlopen.assert_called_once_with(
41+
str(Path("libfoo/libbar-arm64.so").resolve())
42+
)
43+
44+
def test_load_macos_x64(self):
45+
self.mock_platform.system.return_value = "Darwin"
46+
self.mock_platform.machine.return_value = "x86_64"
47+
48+
self.cobhan.load_library("libfoo", "libbar", "")
49+
self.mock_dlopen.assert_called_once_with(
50+
str(Path("libfoo/libbar-x64.dylib").resolve())
51+
)
52+
53+
def test_load_macos_arm64(self):
54+
self.mock_platform.system.return_value = "Darwin"
55+
self.mock_platform.machine.return_value = "arm64"
56+
57+
self.cobhan.load_library("libfoo", "libbar", "")
58+
self.mock_dlopen.assert_called_once_with(
59+
str(Path("libfoo/libbar-arm64.dylib").resolve())
60+
)
61+
62+
def test_load_windows_x64(self):
63+
self.mock_platform.system.return_value = "Windows"
64+
self.mock_platform.machine.return_value = "x86_64"
65+
66+
self.cobhan.load_library("libfoo", "libbar", "")
67+
self.mock_dlopen.assert_called_once_with(
68+
str(Path("libfoo/libbar-x64.dll").resolve())
69+
)
70+
71+
def test_load_windows_arm64(self):
72+
self.mock_platform.system.return_value = "Windows"
73+
self.mock_platform.machine.return_value = "arm64"
74+
75+
self.cobhan.load_library("libfoo", "libbar", "")
76+
self.mock_dlopen.assert_called_once_with(
77+
str(Path("libfoo/libbar-arm64.dll").resolve())
78+
)
79+
80+
81+
class StringTests(TestCase):
82+
def setUp(self) -> None:
83+
84+
self.cobhan = Cobhan()
85+
return super().setUp()
86+
87+
def test_minimum_allocation_is_enforced(self):
88+
buf = self.cobhan.str_to_buf("foo")
89+
self.assertEqual(
90+
len(buf), (self.cobhan.minimum_allocation + self.cobhan.header_size)
91+
)
92+
93+
def test_can_allocate_beyond_minimum(self):
94+
long_str = "foobar" * 1000 # This will be 6k characters in length
95+
buf = self.cobhan.str_to_buf(long_str)
96+
self.assertEqual(len(buf), (len(long_str) + self.cobhan.header_size))
97+
98+
def test_two_way_conversion_maintains_string(self):
99+
buf = self.cobhan.str_to_buf("foobar")
100+
result = self.cobhan.buf_to_str(buf)
101+
self.assertEqual(result, "foobar")
102+
103+
def test_empty_string_returns_empty_buffer(self):
104+
buf = self.cobhan.str_to_buf("")
105+
self.assertEqual(len(buf), self.cobhan.header_size)
106+
107+
def test_input_of_none_returns_empty_buffer(self):
108+
buf = self.cobhan.str_to_buf(None)
109+
self.assertEqual(len(buf), self.cobhan.header_size)

0 commit comments

Comments
 (0)