mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-23 17:31:33 +08:00
test: guard temporarily-disabled bundles in component/template tests + skip Windows-flaky model spec (#13807)
test: guard disabled bundles in component/template tests and skip Windows-flaky model spec
This commit is contained in:
committed by
GitHub
parent
cf9cabdd5a
commit
d2648b95c7
@ -21,6 +21,27 @@ import pytest
|
||||
from langflow import components
|
||||
from lfx.interface.components import _warm_circular_imports
|
||||
|
||||
# Provider components ship in bundle distributions that can be temporarily
|
||||
# unpublished (see the re-enable note in pyproject.toml). While a bundle is
|
||||
# absent its category stays in
|
||||
# ``components.__all__`` but cannot be imported, so these tests skip it instead
|
||||
# of failing. Each guard is computed from the live import result, so it turns
|
||||
# back into a no-op the moment the bundle is restored -- no revert needed.
|
||||
_OPTIONAL_BUNDLE_CATEGORIES = ("openai", "datastax", "oracle")
|
||||
|
||||
|
||||
def _unavailable_bundle_categories() -> set[str]:
|
||||
unavailable = set()
|
||||
for category_name in _OPTIONAL_BUNDLE_CATEGORIES:
|
||||
try:
|
||||
getattr(components, category_name)
|
||||
except Exception:
|
||||
unavailable.add(category_name)
|
||||
return unavailable
|
||||
|
||||
|
||||
UNAVAILABLE_BUNDLE_CATEGORIES = _unavailable_bundle_categories()
|
||||
|
||||
|
||||
class TestAllModulesImportable:
|
||||
"""Test that all component modules are importable."""
|
||||
@ -30,6 +51,8 @@ class TestAllModulesImportable:
|
||||
failed_imports = []
|
||||
|
||||
for category_name in components.__all__:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
try:
|
||||
category_module = getattr(components, category_name)
|
||||
assert category_module is not None, f"Category {category_name} is None"
|
||||
@ -64,6 +87,8 @@ class TestAllModulesImportable:
|
||||
print(f"Testing component imports across {len(components.__all__)} categories") # noqa: T201
|
||||
|
||||
for category_name in components.__all__:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
try:
|
||||
category_module = getattr(components, category_name)
|
||||
|
||||
@ -110,6 +135,8 @@ class TestAllModulesImportable:
|
||||
failed_mappings = []
|
||||
|
||||
for category_name in components.__all__:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
try:
|
||||
category_module = getattr(components, category_name)
|
||||
|
||||
@ -147,6 +174,8 @@ class TestAllModulesImportable:
|
||||
failed_imports = []
|
||||
|
||||
for module_name, component_name in traditional_imports:
|
||||
if module_name.rsplit(".", 1)[-1] in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
component = getattr(module, component_name)
|
||||
@ -164,6 +193,8 @@ class TestAllModulesImportable:
|
||||
failed_modules = []
|
||||
|
||||
for category_name in components.__all__:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
try:
|
||||
category_module = getattr(components, category_name)
|
||||
|
||||
@ -201,6 +232,8 @@ class TestAllModulesImportable:
|
||||
for order in import_orders:
|
||||
try:
|
||||
for category_name in order:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
category_module = getattr(components, category_name)
|
||||
# Access a component to trigger dynamic import
|
||||
if hasattr(category_module, "__all__") and category_module.__all__:
|
||||
@ -220,6 +253,8 @@ class TestAllModulesImportable:
|
||||
]
|
||||
|
||||
for category_name, component_name in test_cases:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
category_module = getattr(components, category_name)
|
||||
|
||||
# First access
|
||||
@ -240,6 +275,8 @@ class TestAllModulesImportable:
|
||||
]
|
||||
|
||||
for category_name, component_name in test_cases:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
category_module = getattr(components, category_name)
|
||||
|
||||
with pytest.raises(AttributeError, match=f"has no attribute '{component_name}'"):
|
||||
@ -249,12 +286,15 @@ class TestAllModulesImportable:
|
||||
"""Test that __dir__ functionality works for all modules."""
|
||||
# Test main components module
|
||||
main_dir = dir(components)
|
||||
assert "openai" in main_dir
|
||||
if "openai" not in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
assert "openai" in main_dir
|
||||
assert "data" in main_dir
|
||||
assert "models_and_agents" in main_dir
|
||||
|
||||
# Test category modules
|
||||
for category_name in ["openai", "data", "helpers"]:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
category_module = getattr(components, category_name)
|
||||
category_dir = dir(category_module)
|
||||
|
||||
@ -272,6 +312,8 @@ class TestAllModulesImportable:
|
||||
]
|
||||
|
||||
for category_name, component_name in test_components:
|
||||
if category_name in UNAVAILABLE_BUNDLE_CATEGORIES:
|
||||
continue
|
||||
category_module = getattr(components, category_name)
|
||||
component = getattr(category_module, component_name)
|
||||
|
||||
@ -410,6 +452,11 @@ class TestDirectModuleImports:
|
||||
"altk",
|
||||
"langchain_ibm",
|
||||
"ibm_watsonx_ai",
|
||||
# Bundle distributions that can be temporarily unpublished;
|
||||
# the relocation shim raises with the distribution name.
|
||||
"lfx-openai",
|
||||
"lfx-datastax",
|
||||
"lfx-oracle",
|
||||
]
|
||||
):
|
||||
return ("skipped", modname, "missing optional dependency")
|
||||
|
||||
@ -22,6 +22,30 @@ from langflow.utils.template_validation import (
|
||||
validate_template_structure,
|
||||
)
|
||||
|
||||
# Component modules that ship in bundle distributions which can be temporarily
|
||||
# unpublished (see the re-enable note in pyproject.toml). A template that wires
|
||||
# one of these cannot be built while its bundle is absent, so the build/execute
|
||||
# tests skip it instead of failing. The check imports the live distribution, so
|
||||
# it turns back into a no-op the moment the bundle is restored -- no revert.
|
||||
_OPTIONAL_BUNDLE_MODULES = {
|
||||
"lfx.components.datastax": "lfx_datastax",
|
||||
"lfx.components.openai": "lfx_openai",
|
||||
"lfx.components.oracle": "lfx_oracle",
|
||||
}
|
||||
|
||||
|
||||
def _template_unavailable_bundle(template_data: dict) -> str | None:
|
||||
"""Return the missing distribution if the template needs an absent bundle, else None."""
|
||||
for node in template_data.get("data", {}).get("nodes", []):
|
||||
module_path = node.get("data", {}).get("node", {}).get("metadata", {}).get("module", "")
|
||||
for prefix, distribution in _OPTIONAL_BUNDLE_MODULES.items():
|
||||
if module_path.startswith(prefix):
|
||||
try:
|
||||
import_module(distribution)
|
||||
except ImportError:
|
||||
return distribution
|
||||
return None
|
||||
|
||||
|
||||
def get_starter_projects_path() -> Path:
|
||||
"""Get path to starter projects directory."""
|
||||
@ -83,6 +107,10 @@ class TestStarterProjects:
|
||||
with template_file.open(encoding="utf-8") as f:
|
||||
template_data = json.load(f)
|
||||
|
||||
unavailable = _template_unavailable_bundle(template_data)
|
||||
if unavailable:
|
||||
pytest.skip(f"{template_file.name} needs the temporarily-unavailable {unavailable} bundle")
|
||||
|
||||
errors = validate_flow_can_build(template_data, template_file.name)
|
||||
if errors:
|
||||
error_msg = "\n".join(errors)
|
||||
@ -95,6 +123,10 @@ class TestStarterProjects:
|
||||
with template_file.open(encoding="utf-8") as f:
|
||||
template_data = json.load(f)
|
||||
|
||||
unavailable = _template_unavailable_bundle(template_data)
|
||||
if unavailable:
|
||||
pytest.skip(f"{template_file.name} needs the temporarily-unavailable {unavailable} bundle")
|
||||
|
||||
errors = await validate_flow_execution(client, template_data, template_file.name, logged_in_headers)
|
||||
if errors:
|
||||
error_msg = "\n".join(errors)
|
||||
@ -108,6 +140,10 @@ class TestStarterProjects:
|
||||
with template_file.open(encoding="utf-8") as f:
|
||||
template_data = json.load(f)
|
||||
|
||||
unavailable = _template_unavailable_bundle(template_data)
|
||||
if unavailable:
|
||||
pytest.skip(f"{template_file.name} needs the temporarily-unavailable {unavailable} bundle")
|
||||
|
||||
errors = await validate_flow_execution(client, template_data, template_file.name, logged_in_headers)
|
||||
if errors:
|
||||
error_msg = "\n".join(errors)
|
||||
@ -122,6 +158,10 @@ class TestStarterProjects:
|
||||
with template_file.open(encoding="utf-8") as f:
|
||||
template_data = json.load(f)
|
||||
|
||||
unavailable = _template_unavailable_bundle(template_data)
|
||||
if unavailable:
|
||||
pytest.skip(f"{template_file.name} needs the temporarily-unavailable {unavailable} bundle")
|
||||
|
||||
errors = []
|
||||
for node in template_data.get("data", {}).get("nodes", []):
|
||||
node_data = node.get("data", {})
|
||||
|
||||
@ -4,6 +4,13 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
import { TEXTS } from "../../utils/constants/texts";
|
||||
|
||||
test.describe("ModelInputComponent", () => {
|
||||
test.beforeEach(() => {
|
||||
test.skip(
|
||||
process.platform === "win32",
|
||||
"Flaky on Windows CI runners: SQLite 'database is locked' during flow teardown cascades into the next test's bootstrap (new-project modal / sidebar never render). The model-provider coverage is OS-agnostic and runs on Linux/macOS",
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
"should display model selector in a node with model input",
|
||||
{ tag: ["@release", "@components", "@workspace"] },
|
||||
|
||||
Reference in New Issue
Block a user