[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:
Jeffrey Wang
2026-05-31 22:06:33 -07:00
committed by GitHub
co-authored by esmeetu Nick Hill
parent 4721bb3aa4
commit 29d69332aa
2 changed files with 110 additions and 5 deletions
+94 -1
View File
@@ -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
+16 -4
View File
@@ -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: