Skip to content

Commit a98fb85

Browse files
seismanweiji14
andauthored
Add comprehensive tests for pygmt.clib.loading.clib_full_names (#872)
This PR adds comprehensive tests for the function pygmt.clib.loading.clib_full_names. See #872 for details. Co-authored-by: Wei Ji <[email protected]>
1 parent 80b75f9 commit a98fb85

File tree

2 files changed

+138
-14
lines changed

2 files changed

+138
-14
lines changed

pygmt/clib/loading.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import subprocess as sp
1010
import sys
1111
from ctypes.util import find_library
12+
from pathlib import Path
1213

1314
from pygmt.exceptions import GMTCLibError, GMTCLibNotFoundError, GMTOSError
1415

@@ -98,33 +99,34 @@ def clib_full_names(env=None):
9899

99100
libnames = clib_names(os_name=sys.platform) # e.g. libgmt.so, libgmt.dylib, gmt.dll
100101

101-
# list of libraries paths to search, sort by priority from high to low
102-
# Search for libraries in GMT_LIBRARY_PATH if defined.
102+
# Search for the library in different ways, sorted by priority.
103+
# 1. Search for the library in GMT_LIBRARY_PATH if defined.
103104
libpath = env.get("GMT_LIBRARY_PATH", "") # e.g. $HOME/miniconda/envs/pygmt/lib
104105
if libpath:
105106
for libname in libnames:
106-
libfullpath = os.path.join(libpath, libname)
107-
if os.path.exists(libfullpath):
108-
yield libfullpath
107+
libfullpath = Path(libpath) / libname
108+
if libfullpath.exists():
109+
yield str(libfullpath)
109110

110-
# Search for the library returned by command "gmt --show-library"
111+
# 2. Search for the library returned by command "gmt --show-library"
112+
# Use `str(Path(realpath))` to avoid mixture of separators "\\" and "/"
111113
try:
112-
libfullpath = sp.check_output(
113-
["gmt", "--show-library"], encoding="utf-8"
114-
).rstrip("\n")
115-
assert os.path.exists(libfullpath)
116-
yield libfullpath
114+
libfullpath = Path(
115+
sp.check_output(["gmt", "--show-library"], encoding="utf-8").rstrip("\n")
116+
)
117+
assert libfullpath.exists()
118+
yield str(libfullpath)
117119
except (FileNotFoundError, AssertionError): # command not found
118120
pass
119121

120-
# Search for DLLs in PATH (done by calling "find_library")
122+
# 3. Search for DLLs in PATH by calling find_library() (Windows only)
121123
if sys.platform == "win32":
122124
for libname in libnames:
123125
libfullpath = find_library(libname)
124126
if libfullpath:
125127
yield libfullpath
126128

127-
# Search for library names in the system default path [the lowest priority]
129+
# 4. Search for library names in the system default path
128130
for libname in libnames:
129131
yield libname
130132

pygmt/tests/test_clib_loading.py

+123-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""
22
Test the functions that load libgmt.
33
"""
4+
import shutil
45
import subprocess
56
import sys
7+
import types
8+
from pathlib import PurePath
69

710
import pytest
8-
from pygmt.clib.loading import check_libgmt, clib_names, load_libgmt
11+
from pygmt.clib.loading import check_libgmt, clib_full_names, clib_names, load_libgmt
912
from pygmt.exceptions import GMTCLibError, GMTCLibNotFoundError, GMTOSError
1013

1114

@@ -73,3 +76,122 @@ def test_clib_names():
7376
assert clib_names(freebsd) == ["libgmt.so"]
7477
with pytest.raises(GMTOSError):
7578
clib_names("meh")
79+
80+
81+
###############################################################################
82+
# Tests for clib_full_names
83+
@pytest.fixture(scope="module", name="gmt_lib_names")
84+
def fixture_gmt_lib_names():
85+
"""
86+
Return a list of the library names for the current operating system.
87+
"""
88+
return clib_names(sys.platform)
89+
90+
91+
@pytest.fixture(scope="module", name="gmt_bin_dir")
92+
def fixture_gmt_bin_dir():
93+
"""
94+
Return GMT's bin directory.
95+
"""
96+
return str(PurePath(shutil.which("gmt")).parent)
97+
98+
99+
@pytest.fixture(scope="module", name="gmt_lib_realpath")
100+
def fixture_gmt_lib_realpath():
101+
"""
102+
Return the real path of the GMT library.
103+
"""
104+
lib_realpath = subprocess.check_output(
105+
["gmt", "--show-library"], encoding="utf-8"
106+
).rstrip("\n")
107+
# On Windows, clib_full_names() returns paths with separator "\\",
108+
# but "gmt --show-library" returns paths with separator "/".
109+
# Use `str(PurePath(realpath)` to mimic the behavior of clib_full_names()
110+
return str(PurePath(lib_realpath))
111+
112+
113+
def test_clib_full_names_gmt_library_path_undefined_path_empty(
114+
monkeypatch, gmt_lib_names
115+
):
116+
"""
117+
Make sure that clib_full_names() returns a generator with expected names
118+
when GMT_LIBRARY_PATH is undefined and PATH is empty.
119+
"""
120+
with monkeypatch.context() as mpatch:
121+
mpatch.delenv("GMT_LIBRARY_PATH", raising=False)
122+
mpatch.setenv("PATH", "")
123+
lib_fullpaths = clib_full_names()
124+
125+
assert isinstance(lib_fullpaths, types.GeneratorType)
126+
assert list(lib_fullpaths) == gmt_lib_names
127+
128+
129+
def test_clib_full_names_gmt_library_path_defined_path_empty(
130+
monkeypatch, gmt_lib_names, gmt_lib_realpath
131+
):
132+
"""
133+
Make sure that clib_full_names() returns a generator with expected names
134+
when GMT_LIBRARY_PATH is defined and PATH is empty.
135+
"""
136+
with monkeypatch.context() as mpatch:
137+
mpatch.setenv("GMT_LIBRARY_PATH", str(PurePath(gmt_lib_realpath).parent))
138+
mpatch.setenv("PATH", "")
139+
lib_fullpaths = clib_full_names()
140+
141+
assert isinstance(lib_fullpaths, types.GeneratorType)
142+
assert list(lib_fullpaths) == [gmt_lib_realpath] + gmt_lib_names
143+
144+
145+
def test_clib_full_names_gmt_library_path_undefined_path_included(
146+
monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir
147+
):
148+
"""
149+
Make sure that clib_full_names() returns a generator with expected names
150+
when GMT_LIBRARY_PATH is undefined and PATH includes GMT's bin path.
151+
"""
152+
with monkeypatch.context() as mpatch:
153+
mpatch.delenv("GMT_LIBRARY_PATH", raising=False)
154+
mpatch.setenv("PATH", gmt_bin_dir)
155+
lib_fullpaths = clib_full_names()
156+
157+
assert isinstance(lib_fullpaths, types.GeneratorType)
158+
# Windows: find_library() searches the library in PATH, so one more
159+
npath = 2 if sys.platform == "win32" else 1
160+
assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names
161+
162+
163+
def test_clib_full_names_gmt_library_path_defined_path_included(
164+
monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir
165+
):
166+
"""
167+
Make sure that clib_full_names() returns a generator with expected names
168+
when GMT_LIBRARY_PATH is defined and PATH includes GMT's bin path.
169+
"""
170+
with monkeypatch.context() as mpatch:
171+
mpatch.setenv("GMT_LIBRARY_PATH", str(PurePath(gmt_lib_realpath).parent))
172+
mpatch.setenv("PATH", gmt_bin_dir)
173+
lib_fullpaths = clib_full_names()
174+
175+
assert isinstance(lib_fullpaths, types.GeneratorType)
176+
# Windows: find_library() searches the library in PATH, so one more
177+
npath = 3 if sys.platform == "win32" else 2
178+
assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names
179+
180+
181+
def test_clib_full_names_gmt_library_path_incorrect_path_included(
182+
monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir
183+
):
184+
"""
185+
Make sure that clib_full_names() returns a generator with expected names
186+
when GMT_LIBRARY_PATH is defined but incorrect and PATH includes GMT's bin
187+
path.
188+
"""
189+
with monkeypatch.context() as mpatch:
190+
mpatch.setenv("GMT_LIBRARY_PATH", "/not/a/valid/library/path")
191+
mpatch.setenv("PATH", gmt_bin_dir)
192+
lib_fullpaths = clib_full_names()
193+
194+
assert isinstance(lib_fullpaths, types.GeneratorType)
195+
# Windows: find_library() searches the library in PATH, so one more
196+
npath = 2 if sys.platform == "win32" else 1
197+
assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names

0 commit comments

Comments
 (0)