1
1
"""
2
2
Test the functions that load libgmt.
3
3
"""
4
+ import ctypes
4
5
import shutil
5
6
import subprocess
6
7
import sys
12
13
from pygmt .exceptions import GMTCLibError , GMTCLibNotFoundError , GMTOSError
13
14
14
15
16
+ class FakedLibGMT : # pylint: disable=too-few-public-methods
17
+ """
18
+ Class for faking a GMT library.
19
+ """
20
+
21
+ def __init__ (self , name ):
22
+ self ._name = name
23
+
24
+ def __str__ (self ):
25
+ return self ._name
26
+
27
+
15
28
def test_check_libgmt ():
16
29
"""
17
30
Make sure check_libgmt fails when given a bogus library.
18
31
"""
19
- # create a fake library with a "_name" property
20
- def libgmt ():
21
- pass
22
-
23
- libgmt ._name = "/path/to/libgmt.so" # pylint: disable=protected-access
32
+ libgmt = FakedLibGMT ("/path/to/libgmt.so" )
24
33
msg = (
25
34
# pylint: disable=protected-access
26
35
f"Error loading '{ libgmt ._name } '. "
@@ -33,6 +42,22 @@ def libgmt():
33
42
check_libgmt (libgmt )
34
43
35
44
45
+ def test_clib_names ():
46
+ """
47
+ Make sure we get the correct library name for different OS names.
48
+ """
49
+ for linux in ["linux" , "linux2" , "linux3" ]:
50
+ assert clib_names (linux ) == ["libgmt.so" ]
51
+ assert clib_names ("darwin" ) == ["libgmt.dylib" ]
52
+ assert clib_names ("win32" ) == ["gmt.dll" , "gmt_w64.dll" , "gmt_w32.dll" ]
53
+ for freebsd in ["freebsd10" , "freebsd11" , "freebsd12" ]:
54
+ assert clib_names (freebsd ) == ["libgmt.so" ]
55
+ with pytest .raises (GMTOSError ):
56
+ clib_names ("meh" )
57
+
58
+
59
+ ###############################################################################
60
+ # Tests for load_libgmt
36
61
def test_load_libgmt ():
37
62
"""
38
63
Test that loading libgmt works and doesn't crash.
@@ -64,18 +89,122 @@ def test_load_libgmt_with_a_bad_library_path(monkeypatch):
64
89
assert check_libgmt (load_libgmt ()) is None
65
90
66
91
67
- def test_clib_names () :
92
+ class TestLibgmtBrokenLibs :
68
93
"""
69
- Make sure we get the correct library name for different OS names .
94
+ Test that load_libgmt still works when a broken library is found .
70
95
"""
71
- for linux in ["linux" , "linux2" , "linux3" ]:
72
- assert clib_names (linux ) == ["libgmt.so" ]
73
- assert clib_names ("darwin" ) == ["libgmt.dylib" ]
74
- assert clib_names ("win32" ) == ["gmt.dll" , "gmt_w64.dll" , "gmt_w32.dll" ]
75
- for freebsd in ["freebsd10" , "freebsd11" , "freebsd12" ]:
76
- assert clib_names (freebsd ) == ["libgmt.so" ]
77
- with pytest .raises (GMTOSError ):
78
- clib_names ("meh" )
96
+
97
+ # load the GMT library before mocking the ctypes.CDLL function
98
+ loaded_libgmt = load_libgmt ()
99
+ invalid_path = "/invalid/path/to/libgmt.so"
100
+ faked_libgmt1 = FakedLibGMT ("/path/to/faked/libgmt1.so" )
101
+ faked_libgmt2 = FakedLibGMT ("/path/to/faked/libgmt2.so" )
102
+
103
+ def _mock_ctypes_cdll_return (self , libname ):
104
+ """
105
+ Mock the return value of ctypes.CDLL.
106
+
107
+ Parameters
108
+ ----------
109
+ libname : str or FakedLibGMT or ctypes.CDLL
110
+ Path to the GMT library, a faked GMT library, or a working library
111
+ loaded as ctypes.CDLL.
112
+
113
+ Return
114
+ ------
115
+ object
116
+ Either the loaded GMT library or the faked GMT library.
117
+ """
118
+ if isinstance (libname , FakedLibGMT ):
119
+ # libname is a faked GMT library, return the faked library
120
+ return libname
121
+ if isinstance (libname , str ):
122
+ # libname is an invalid library path in string type,
123
+ # raise OSError like the original ctypes.CDLL
124
+ raise OSError (f"Unable to find '{ libname } '" )
125
+ # libname is a loaded GMT library
126
+ return self .loaded_libgmt
127
+
128
+ @pytest .fixture
129
+ def mock_ctypes (self , monkeypatch ):
130
+ """
131
+ Patch the ctypes.CDLL function.
132
+ """
133
+ monkeypatch .setattr (ctypes , "CDLL" , self ._mock_ctypes_cdll_return )
134
+
135
+ def test_two_broken_libraries (self , mock_ctypes ): # pylint: disable=unused-argument
136
+ """
137
+ Case 1: two broken libraries.
138
+
139
+ Raise the GMTCLibNotFoundError exception. Error message should contain
140
+ information of both libraries that failed to load properly.
141
+ """
142
+ # pylint: disable=protected-access
143
+ lib_fullnames = [self .faked_libgmt1 , self .faked_libgmt2 ]
144
+ msg_regex = (
145
+ fr"Error loading GMT shared library at '{ self .faked_libgmt1 ._name } '.\n"
146
+ fr"Error loading '{ self .faked_libgmt1 ._name } '. Couldn't access.*\n"
147
+ fr"Error loading GMT shared library at '{ self .faked_libgmt2 ._name } '.\n"
148
+ f"Error loading '{ self .faked_libgmt2 ._name } '. Couldn't access.*"
149
+ )
150
+ with pytest .raises (GMTCLibNotFoundError , match = msg_regex ):
151
+ load_libgmt (lib_fullnames = lib_fullnames )
152
+
153
+ def test_load_brokenlib_invalidpath (
154
+ self , mock_ctypes
155
+ ): # pylint: disable=unused-argument
156
+ """
157
+ Case 2: broken library + invalid path.
158
+
159
+ Raise the GMTCLibNotFoundError exception. Error message should contain
160
+ information of one library that failed to load and one invalid path.
161
+ """
162
+ # pylint: disable=protected-access
163
+ lib_fullnames = [self .faked_libgmt1 , self .invalid_path ]
164
+ msg_regex = (
165
+ fr"Error loading GMT shared library at '{ self .faked_libgmt1 ._name } '.\n"
166
+ fr"Error loading '{ self .faked_libgmt1 ._name } '. Couldn't access.*\n"
167
+ fr"Error loading GMT shared library at '{ self .invalid_path } '.\n"
168
+ f"Unable to find '{ self .invalid_path } '"
169
+ )
170
+ with pytest .raises (GMTCLibNotFoundError , match = msg_regex ):
171
+ load_libgmt (lib_fullnames = lib_fullnames )
172
+
173
+ def test_brokenlib_invalidpath_workinglib (
174
+ self , mock_ctypes
175
+ ): # pylint: disable=unused-argument
176
+ """
177
+ Case 3: broken library + invalid path + working library.
178
+ """
179
+ lib_fullnames = [self .faked_libgmt1 , self .invalid_path , self .loaded_libgmt ]
180
+ assert check_libgmt (load_libgmt (lib_fullnames = lib_fullnames )) is None
181
+
182
+ def test_invalidpath_brokenlib_workinglib (
183
+ self , mock_ctypes
184
+ ): # pylint: disable=unused-argument
185
+ """
186
+ Case 4: invalid path + broken library + working library.
187
+ """
188
+ lib_fullnames = [self .invalid_path , self .faked_libgmt1 , self .loaded_libgmt ]
189
+ assert check_libgmt (load_libgmt (lib_fullnames = lib_fullnames )) is None
190
+
191
+ def test_workinglib_brokenlib_invalidpath (
192
+ self , mock_ctypes
193
+ ): # pylint: disable=unused-argument
194
+ """
195
+ Case 5: working library + broken library + invalid path.
196
+ """
197
+ lib_fullnames = [self .loaded_libgmt , self .faked_libgmt1 , self .invalid_path ]
198
+ assert check_libgmt (load_libgmt (lib_fullnames = lib_fullnames )) is None
199
+
200
+ def test_brokenlib_brokenlib_workinglib (
201
+ self , mock_ctypes
202
+ ): # pylint: disable=unused-argument
203
+ """
204
+ Case 6: repeating broken libraries + working library.
205
+ """
206
+ lib_fullnames = [self .faked_libgmt1 , self .faked_libgmt1 , self .loaded_libgmt ]
207
+ assert check_libgmt (load_libgmt (lib_fullnames = lib_fullnames )) is None
79
208
80
209
81
210
###############################################################################
0 commit comments