mirror of
https://github.com/vllm-project/vllm.git
synced 2026-08-13 09:18:12 +00:00
[BugFix] Fix _has_module to verify native deps via trial import (#44035)
Signed-off-by: esmeetu <[email protected]> Signed-off-by: Jeffrey Wang <[email protected]> Signed-off-by: Nick Hill <[email protected]> Co-authored-by: esmeetu <[email protected]> Co-authored-by: Nick Hill <[email protected]>
This commit is contained in:
co-authored by
esmeetu
Nick Hill
parent
4721bb3aa4
commit
29d69332aa
@@ -1,8 +1,10 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from vllm.utils.import_utils import PlaceholderModule
|
||||
from vllm.utils.import_utils import PlaceholderModule, _has_module
|
||||
|
||||
|
||||
def _raises_module_not_found():
|
||||
@@ -44,3 +46,94 @@ def test_placeholder_module_error_handling():
|
||||
with _raises_module_not_found():
|
||||
# Test conflict with internal __module attribute
|
||||
_ = placeholder_attr.module
|
||||
|
||||
|
||||
class TestHasModule:
|
||||
"""Tests for _has_module with trial import verification."""
|
||||
|
||||
def setup_method(self):
|
||||
# Clear the @cache between tests so each test gets a fresh call
|
||||
_has_module.cache_clear()
|
||||
|
||||
def test_returns_true_for_importable_stdlib_module(self):
|
||||
assert _has_module("json") is True
|
||||
|
||||
def test_returns_false_for_nonexistent_module(self):
|
||||
assert _has_module("nonexistent_module_xyz_12345") is False
|
||||
|
||||
def test_returns_false_when_find_spec_succeeds_but_import_fails(self):
|
||||
"""Simulate a native extension whose shared library is missing.
|
||||
|
||||
``find_spec`` finds the package on disk, but the actual import
|
||||
raises ``ImportError`` (e.g. missing ``libcudart.so``).
|
||||
"""
|
||||
fake_spec = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"vllm.utils.import_utils.importlib.util.find_spec",
|
||||
return_value=fake_spec,
|
||||
),
|
||||
patch(
|
||||
"vllm.utils.import_utils.importlib.import_module",
|
||||
side_effect=ImportError(
|
||||
"libcudart.so.12: cannot open shared object file"
|
||||
),
|
||||
),
|
||||
):
|
||||
assert _has_module("fake_native_ext") is False
|
||||
|
||||
def test_returns_false_on_os_error_during_import(self):
|
||||
"""Some shared-library failures surface as ``OSError``."""
|
||||
fake_spec = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"vllm.utils.import_utils.importlib.util.find_spec",
|
||||
return_value=fake_spec,
|
||||
),
|
||||
patch(
|
||||
"vllm.utils.import_utils.importlib.import_module",
|
||||
side_effect=OSError("cannot load library"),
|
||||
),
|
||||
):
|
||||
assert _has_module("fake_native_ext_os") is False
|
||||
|
||||
def test_returns_false_on_unexpected_error_during_import(self):
|
||||
"""A broken extension may raise a non-import error (e.g. ``RuntimeError``).
|
||||
|
||||
Such modules are not usable, so ``_has_module`` should still return
|
||||
``False`` rather than letting the exception propagate.
|
||||
"""
|
||||
fake_spec = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"vllm.utils.import_utils.importlib.util.find_spec",
|
||||
return_value=fake_spec,
|
||||
),
|
||||
patch(
|
||||
"vllm.utils.import_utils.importlib.import_module",
|
||||
side_effect=RuntimeError("CUDA driver version is insufficient"),
|
||||
),
|
||||
):
|
||||
assert _has_module("fake_broken_ext") is False
|
||||
|
||||
def test_returns_false_when_find_spec_raises(self):
|
||||
"""``find_spec`` itself can raise for dotted names whose parent package
|
||||
fails to import. This should be treated as the module being unavailable.
|
||||
"""
|
||||
with patch(
|
||||
"vllm.utils.import_utils.importlib.util.find_spec",
|
||||
side_effect=ModuleNotFoundError("No module named 'fake_parent'"),
|
||||
):
|
||||
assert _has_module("fake_parent.child") is False
|
||||
|
||||
def test_result_is_cached(self):
|
||||
"""Verify the @cache decorator prevents repeated imports."""
|
||||
_has_module("json") # prime the cache
|
||||
|
||||
with patch("vllm.utils.import_utils.importlib.util.find_spec") as mock_spec:
|
||||
result = _has_module("json") # should hit cache
|
||||
mock_spec.assert_not_called()
|
||||
assert result is True
|
||||
|
||||
@@ -392,12 +392,24 @@ class LazyLoader(ModuleType):
|
||||
# Optional dependency detection utilities
|
||||
@cache
|
||||
def _has_module(module_name: str) -> bool:
|
||||
"""Return True if *module_name* can be found in the current environment.
|
||||
"""Return True if *module_name* can be imported in the current environment.
|
||||
|
||||
The result is cached so that subsequent queries for the same module incur
|
||||
no additional overhead.
|
||||
Uses ``importlib.util.find_spec`` as a fast pre-check, then performs a
|
||||
trial import to verify that native dependencies (shared libraries, etc.)
|
||||
are also satisfied. Any failure during the trial import is treated as the
|
||||
module being unavailable. The result is cached so that subsequent queries
|
||||
for the same module incur no additional overhead.
|
||||
"""
|
||||
return importlib.util.find_spec(module_name) is not None
|
||||
try:
|
||||
if importlib.util.find_spec(module_name) is None:
|
||||
return False
|
||||
importlib.import_module(module_name)
|
||||
except ImportError:
|
||||
logger.warning(
|
||||
"Module %s was found but failed to import", module_name, exc_info=True
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def has_deep_ep() -> bool:
|
||||
|
||||
Reference in New Issue
Block a user