# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import fnmatch
import os.path
import sys
import pytest
from llnl.util.filesystem import HeaderList, LibraryList, find, find_headers, find_libraries
import spack.paths
@pytest.fixture()
def library_list():
"""Returns an instance of LibraryList."""
# Test all valid extensions: ['.a', '.dylib', '.so']
libs = (
[
"/dir1/liblapack.a",
"/dir2/libpython3.6.dylib", # name may contain periods
"/dir1/libblas.a",
"/dir3/libz.so",
"libmpi.so.20.10.1", # shared object libraries may be versioned
]
if sys.platform != "win32"
else [
"/dir1/liblapack.lib",
"/dir2/libpython3.6.dll",
"/dir1/libblas.lib",
"/dir3/libz.dll",
"libmpi.dll.20.10.1",
]
)
return LibraryList(libs)
@pytest.fixture()
def header_list():
"""Returns an instance of header list"""
# Test all valid extensions: ['.h', '.hpp', '.hh', '.cuh']
headers = [
"/dir1/Python.h",
"/dir2/date.time.h",
"/dir1/pyconfig.hpp",
"/dir3/core.hh",
"pymem.cuh",
]
h = HeaderList(headers)
h.add_macro("-DBOOST_LIB_NAME=boost_regex")
h.add_macro("-DBOOST_DYN_LINK")
return h
# TODO: Remove below when llnl.util.filesystem.find_libraries becomes spec aware
plat_static_ext = "lib" if sys.platform == "win32" else "a"
plat_shared_ext = "dll" if sys.platform == "win32" else "so"
plat_apple_shared_ext = "dylib"
class TestLibraryList:
def test_repr(self, library_list):
x = eval(repr(library_list))
assert library_list == x
def test_joined_and_str(self, library_list):
s1 = library_list.joined()
expected = " ".join(
[
"/dir1/liblapack.%s" % plat_static_ext,
"/dir2/libpython3.6.%s"
% (plat_apple_shared_ext if sys.platform != "win32" else "dll"),
"/dir1/libblas.%s" % plat_static_ext,
"/dir3/libz.%s" % plat_shared_ext,
"libmpi.%s.20.10.1" % plat_shared_ext,
]
)
assert s1 == expected
s2 = str(library_list)
assert s1 == s2
s3 = library_list.joined(";")
expected = ";".join(
[
"/dir1/liblapack.%s" % plat_static_ext,
"/dir2/libpython3.6.%s"
% (plat_apple_shared_ext if sys.platform != "win32" else "dll"),
"/dir1/libblas.%s" % plat_static_ext,
"/dir3/libz.%s" % plat_shared_ext,
"libmpi.%s.20.10.1" % plat_shared_ext,
]
)
assert s3 == expected
def test_flags(self, library_list):
search_flags = library_list.search_flags
assert "-L/dir1" in search_flags
assert "-L/dir2" in search_flags
assert "-L/dir3" in search_flags
assert isinstance(search_flags, str)
assert search_flags == "-L/dir1 -L/dir2 -L/dir3"
link_flags = library_list.link_flags
assert "-llapack" in link_flags
assert "-lpython3.6" in link_flags
assert "-lblas" in link_flags
assert "-lz" in link_flags
assert "-lmpi" in link_flags
assert isinstance(link_flags, str)
assert link_flags == "-llapack -lpython3.6 -lblas -lz -lmpi"
ld_flags = library_list.ld_flags
assert isinstance(ld_flags, str)
assert ld_flags == search_flags + " " + link_flags
def test_paths_manipulation(self, library_list):
names = library_list.names
assert names == ["lapack", "python3.6", "blas", "z", "mpi"]
directories = library_list.directories
assert directories == ["/dir1", "/dir2", "/dir3"]
def test_get_item(self, library_list):
a = library_list[0]
assert a == "/dir1/liblapack.%s" % plat_static_ext
b = library_list[:]
assert type(b) is type(library_list)
assert library_list == b
assert library_list is not b
def test_add(self, library_list):
pylist = [
"/dir1/liblapack.%s" % plat_static_ext, # removed from the final list
"/dir2/libmpi.%s" % plat_shared_ext,
"/dir4/libnew.%s" % plat_static_ext,
]
another = LibraryList(pylist)
both = library_list + another
assert len(both) == 7
# Invariant
assert both == both + both
# Always produce an instance of LibraryList
assert type(library_list + pylist) is type(library_list)
assert type(pylist + library_list) is type(library_list)
class TestHeaderList:
def test_repr(self, header_list):
x = eval(repr(header_list))
assert header_list == x
def test_joined_and_str(self, header_list):
s1 = header_list.joined()
expected = " ".join(
[
"/dir1/Python.h",
"/dir2/date.time.h",
"/dir1/pyconfig.hpp",
"/dir3/core.hh",
"pymem.cuh",
]
)
assert s1 == expected
s2 = str(header_list)
assert s1 == s2
s3 = header_list.joined(";")
expected = ";".join(
[
"/dir1/Python.h",
"/dir2/date.time.h",
"/dir1/pyconfig.hpp",
"/dir3/core.hh",
"pymem.cuh",
]
)
assert s3 == expected
def test_flags(self, header_list):
include_flags = header_list.include_flags
assert "-I/dir1" in include_flags
assert "-I/dir2" in include_flags
assert "-I/dir3" in include_flags
assert isinstance(include_flags, str)
assert include_flags == "-I/dir1 -I/dir2 -I/dir3"
macros = header_list.macro_definitions
assert "-DBOOST_LIB_NAME=boost_regex" in macros
assert "-DBOOST_DYN_LINK" in macros
assert isinstance(macros, str)
assert macros == "-DBOOST_LIB_NAME=boost_regex -DBOOST_DYN_LINK"
cpp_flags = header_list.cpp_flags
assert isinstance(cpp_flags, str)
assert cpp_flags == include_flags + " " + macros
def test_paths_manipulation(self, header_list):
names = header_list.names
assert names == ["Python", "date.time", "pyconfig", "core", "pymem"]
directories = header_list.directories
assert directories == ["/dir1", "/dir2", "/dir3"]
def test_get_item(self, header_list):
a = header_list[0]
assert a == "/dir1/Python.h"
b = header_list[:]
assert type(b) is type(header_list)
assert header_list == b
assert header_list is not b
def test_add(self, header_list):
pylist = [
"/dir1/Python.h", # removed from the final list
"/dir2/pyconfig.hpp",
"/dir4/date.time.h",
]
another = HeaderList(pylist)
h = header_list + another
assert len(h) == 7
# Invariant : l == l + l
assert h == h + h
# Always produce an instance of HeaderList
assert type(header_list + pylist) is type(header_list)
assert type(pylist + header_list) is type(header_list)
#: Directory where the data for the test below is stored
search_dir = os.path.join(spack.paths.test_path, "data", "directory_search")
@pytest.mark.parametrize(
"lib_list,kwargs",
[
(["liba"], {"shared": True, "recursive": True}),
(["liba"], {"shared": False, "recursive": True}),
(["libc", "liba"], {"shared": True, "recursive": True}),
(["liba", "libc"], {"shared": False, "recursive": True}),
(["libc", "libb", "liba"], {"shared": True, "recursive": True}),
(["liba", "libb", "libc"], {"shared": False, "recursive": True}),
],
)
def test_library_type_search(lib_list, kwargs):
results = find_libraries(lib_list, search_dir, **kwargs)
assert len(results) != 0
for result in results:
lib_type_ext = plat_shared_ext
if not kwargs["shared"]:
lib_type_ext = plat_static_ext
assert result.endswith(lib_type_ext) or (
kwargs["shared"] and result.endswith(plat_apple_shared_ext)
)
@pytest.mark.parametrize(
"search_fn,search_list,root,kwargs",
[
(find_libraries, "liba", search_dir, {"recursive": True}),
(find_libraries, ["liba"], search_dir, {"recursive": True}),
(find_libraries, "libb", search_dir, {"recursive": True}),
(find_libraries, ["libc"], search_dir, {"recursive": True}),
(find_libraries, ["libc", "liba"], search_dir, {"recursive": True}),
(find_libraries, ["liba", "libc"], search_dir, {"recursive": True}),
(find_libraries, ["libc", "libb", "liba"], search_dir, {"recursive": True}),
(find_libraries, ["liba", "libc"], search_dir, {"recursive": True}),
(
find_libraries,
["libc", "libb", "liba"],
search_dir,
{"recursive": True, "shared": False},
),
(find_headers, "a", search_dir, {"recursive": True}),
(find_headers, ["a"], search_dir, {"recursive": True}),
(find_headers, "b", search_dir, {"recursive": True}),
(find_headers, ["c"], search_dir, {"recursive": True}),
(find_headers, ["c", "a"], search_dir, {"recursive": True}),
(find_headers, ["a", "c"], search_dir, {"recursive": True}),
(find_headers, ["c", "b", "a"], search_dir, {"recursive": True}),
(find_headers, ["a", "c"], search_dir, {"recursive": True}),
(find_libraries, ["liba", "libd"], os.path.join(search_dir, "b"), {"recursive": False}),
(find_headers, ["b", "d"], os.path.join(search_dir, "b"), {"recursive": False}),
],
)
def test_searching_order(search_fn, search_list, root, kwargs):
# Test search
result = search_fn(search_list, root, **kwargs)
# The tests are set-up so that something is always found
assert len(result) != 0
# Now reverse the result and start discarding things
# as soon as you have matches. In the end the list should
# be emptied.
rlist = list(reversed(result))
# At this point make sure the search list is a sequence
if isinstance(search_list, str):
search_list = [search_list]
# Discard entries in the order they appear in search list
for x in search_list:
try:
while fnmatch.fnmatch(rlist[-1], x) or x in rlist[-1]:
rlist.pop()
except IndexError:
# List is empty
pass
# List should be empty here
assert len(rlist) == 0
@pytest.mark.parametrize(
"root,search_list,kwargs,expected",
[
(
search_dir,
"*/*bar.tx?",
{"recursive": False},
[
os.path.join(search_dir, os.path.join("a", "foobar.txt")),
os.path.join(search_dir, os.path.join("b", "bar.txp")),
os.path.join(search_dir, os.path.join("c", "bar.txt")),
],
),
(
search_dir,
"*/*bar.tx?",
{"recursive": True},
[
os.path.join(search_dir, os.path.join("a", "foobar.txt")),
os.path.join(search_dir, os.path.join("b", "bar.txp")),
os.path.join(search_dir, os.path.join("c", "bar.txt")),
],
),
],
)
def test_find_with_globbing(root, search_list, kwargs, expected):
matches = find(root, search_list, **kwargs)
assert sorted(matches) == sorted(expected)