| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| """Tests for the tvm-ffi-config command line utility.""" |
| |
| import subprocess |
| import sys |
| from pathlib import Path |
| from typing import Callable |
| |
| import pytest |
| from tvm_ffi import libinfo |
| |
| |
| def _stdout_for(*args: str) -> str: |
| """Invoke tvm-ffi-config with the provided arguments and return stdout with trailing whitespace removed.""" |
| result = subprocess.run( |
| [ |
| sys.executable, |
| "-m", |
| "tvm_ffi.config", |
| *args, |
| ], |
| check=True, |
| capture_output=True, |
| text=True, |
| ) |
| assert result.stderr == "" |
| return result.stdout.strip() |
| |
| |
| @pytest.mark.parametrize( |
| ("flag", "expected_fn", "is_dir"), |
| [ |
| ("--includedir", libinfo.find_include_path, True), |
| ("--dlpack-includedir", libinfo.find_dlpack_include_path, True), |
| ("--cmakedir", libinfo.find_cmake_path, True), |
| ("--sourcedir", libinfo.find_source_path, True), |
| ("--cython-lib-path", libinfo.find_cython_lib, False), |
| ], |
| ) |
| def test_basic_path_flags(flag: str, expected_fn: Callable[[], str], is_dir: bool) -> None: |
| output = _stdout_for(flag) |
| assert output == expected_fn() |
| path = Path(output) |
| assert path.exists() |
| assert path.is_dir() if is_dir else path.is_file() |
| |
| |
| def test_libdir_matches_library_parent() -> None: |
| expected_dir = Path(libinfo.find_libtvm_ffi()).parent |
| output = _stdout_for("--libdir") |
| assert output == str(expected_dir) |
| assert Path(output).is_dir() |
| assert Path(libinfo.find_libtvm_ffi()).is_file() |
| |
| |
| def test_libfiles_reports_platform_library() -> None: |
| output = _stdout_for("--libfiles") |
| if sys.platform.startswith("win32"): |
| expected = libinfo.find_windows_implib() |
| else: |
| expected = libinfo.find_libtvm_ffi() |
| assert output == expected |
| assert Path(output).is_file() |
| |
| |
| def test_libs_reports_link_target() -> None: |
| output = _stdout_for("--libs") |
| if sys.platform.startswith("win32"): |
| assert output == libinfo.find_windows_implib() |
| else: |
| assert output == "-ltvm_ffi" |
| |
| |
| def test_cxxflags_include_paths_and_standard() -> None: |
| include_dir = libinfo.find_include_path() |
| dlpack_dir = libinfo.find_dlpack_include_path() |
| assert _stdout_for("--cxxflags") == f"-I{include_dir} -I{dlpack_dir} -std=c++17" |
| |
| |
| def test_cflags_include_paths() -> None: |
| include_dir = libinfo.find_include_path() |
| dlpack_dir = libinfo.find_dlpack_include_path() |
| assert _stdout_for("--cflags") == f"-I{include_dir} -I{dlpack_dir}" |
| |
| |
| def test_ldflags_only_on_unix() -> None: |
| output = _stdout_for("--ldflags") |
| if sys.platform.startswith("win32"): |
| assert output == "" |
| else: |
| libdir = Path(libinfo.find_libtvm_ffi()).parent |
| assert output == f"-L{libdir}" |
| assert libdir.is_dir() |
| |
| |
| def test_cmakedir_contains_config_file() -> None: |
| cmake_dir = Path(_stdout_for("--cmakedir")) |
| assert (cmake_dir / "tvm_ffi-config.cmake").is_file() |
| |
| |
| def test_find_python_helper_include_path() -> None: |
| path = libinfo.find_python_helper_include_path() |
| assert Path(path).is_dir() |
| assert (Path(path) / "tvm_ffi_python_helpers.h").is_file() |
| |
| |
| def _platform_lib_filename(target_name: str) -> str: |
| """Return the platform-appropriate shared library filename for ``target_name``.""" |
| if sys.platform.startswith("win32"): |
| return f"{target_name}.dll" |
| if sys.platform.startswith("darwin"): |
| return f"lib{target_name}.dylib" |
| return f"lib{target_name}.so" |
| |
| |
| def test_find_library_by_basename_extra_lib_paths(tmp_path: Path) -> None: |
| """A directory passed via ``extra_lib_paths`` is searched in the dev fallback.""" |
| lib_file = tmp_path / _platform_lib_filename("dummy") |
| lib_file.write_bytes(b"") |
| |
| result = libinfo._find_library_by_basename( |
| "nonexistent-dist-name", "dummy", extra_lib_paths=[tmp_path] |
| ) |
| |
| assert isinstance(result, Path) |
| assert result.resolve() == lib_file.resolve() |
| # Sanity: the result must live under the temp tree, not tvm_ffi's own tree. |
| assert tmp_path.resolve() in result.resolve().parents |
| |
| |
| def test_find_library_by_basename_extra_lib_paths_type_error(tmp_path: Path) -> None: |
| """Non-Path elements in ``extra_lib_paths`` raise a TypeError naming the offender.""" |
| with pytest.raises(TypeError, match=r"extra_lib_paths\[1\] must be a pathlib\.Path"): |
| libinfo._find_library_by_basename( |
| "apache-tvm-ffi", |
| "tvm_ffi", |
| extra_lib_paths=[tmp_path, str(tmp_path)], # ty: ignore[invalid-argument-type] |
| ) |