mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			14 Commits
		
	
	
		
			action_cha
			...
			cache_comp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					94186517e0 | ||
| 
						 | 
					2ff3280e8c | ||
| 
						 | 
					fe0d9828a8 | ||
| 
						 | 
					b6a31a24bc | ||
| 
						 | 
					bb971e7b17 | ||
| 
						 | 
					5d868cd64c | ||
| 
						 | 
					9af24944af | ||
| 
						 | 
					09113e2e02 | ||
| 
						 | 
					b18a2542ee | ||
| 
						 | 
					c4c72ede2c | ||
| 
						 | 
					c6e3261a6a | ||
| 
						 | 
					9f03c40656 | ||
| 
						 | 
					ce4ed4b5a6 | ||
| 
						 | 
					d1be68d808 | 
							
								
								
									
										11
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -192,6 +192,11 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Restore components graph cache
 | 
			
		||||
        uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
			
		||||
        with:
 | 
			
		||||
          path: .temp/components_graph.json
 | 
			
		||||
          key: components-graph-${{ hashFiles('esphome/components/**/__init__.py') }}
 | 
			
		||||
      - name: Determine which tests to run
 | 
			
		||||
        id: determine
 | 
			
		||||
        env:
 | 
			
		||||
@@ -216,6 +221,12 @@ jobs:
 | 
			
		||||
          echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Save components graph cache
 | 
			
		||||
#        if: github.ref == 'refs/heads/dev'
 | 
			
		||||
        uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
			
		||||
        with:
 | 
			
		||||
          path: .temp/components_graph.json
 | 
			
		||||
          key: components-graph-${{ hashFiles('esphome/components/**/__init__.py') }}
 | 
			
		||||
 | 
			
		||||
  integration-tests:
 | 
			
		||||
    name: Run integration tests
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
// X:
 | 
			
		||||
#include "api_connection.h"
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
from collections.abc import Callable
 | 
			
		||||
from functools import cache
 | 
			
		||||
import hashlib
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
import os.path
 | 
			
		||||
@@ -52,6 +53,10 @@ BASE_BUS_COMPONENTS = {
 | 
			
		||||
    "remote_receiver",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Cache version for components graph
 | 
			
		||||
# Increment this when the cache format or graph building logic changes
 | 
			
		||||
COMPONENTS_GRAPH_CACHE_VERSION = 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_list_components_output(output: str) -> list[str]:
 | 
			
		||||
    """Parse the output from list-components.py script.
 | 
			
		||||
@@ -752,20 +757,68 @@ def resolve_auto_load(
 | 
			
		||||
    return auto_load()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cache
 | 
			
		||||
def get_components_graph_cache_key() -> str:
 | 
			
		||||
    """Generate cache key based on all component __init__.py file hashes.
 | 
			
		||||
 | 
			
		||||
    Uses git ls-files with sha1 hashes to generate a stable cache key that works
 | 
			
		||||
    across different machines and CI runs. This is faster and more reliable than
 | 
			
		||||
    reading file contents or using modification times.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        SHA256 hex string uniquely identifying the current component state
 | 
			
		||||
    """
 | 
			
		||||
    # Use git ls-files -s to get sha1 hashes of all component __init__.py files
 | 
			
		||||
    # Format: <mode> <sha1> <stage> <path>
 | 
			
		||||
    # This is fast and works consistently across CI and local dev
 | 
			
		||||
    cmd = ["git", "ls-files", "-s", "esphome/components/**/__init__.py"]
 | 
			
		||||
    result = subprocess.run(
 | 
			
		||||
        cmd, capture_output=True, text=True, check=True, cwd=root_path, close_fds=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Hash the git output (includes file paths and their sha1 hashes)
 | 
			
		||||
    # This changes only when component __init__.py files actually change
 | 
			
		||||
    hasher = hashlib.sha256()
 | 
			
		||||
    hasher.update(result.stdout.encode())
 | 
			
		||||
 | 
			
		||||
    return hasher.hexdigest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_components_graph() -> dict[str, list[str]]:
 | 
			
		||||
    """Create a graph of component dependencies.
 | 
			
		||||
    """Create a graph of component dependencies (cached).
 | 
			
		||||
 | 
			
		||||
    This function is expensive (5-6 seconds) because it imports all ESPHome components
 | 
			
		||||
    to extract their DEPENDENCIES and AUTO_LOAD metadata. The result is cached based
 | 
			
		||||
    on component file modification times, so unchanged components don't trigger a rebuild.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        Dictionary mapping parent components to their children (dependencies)
 | 
			
		||||
    """
 | 
			
		||||
    from pathlib import Path
 | 
			
		||||
    # Check cache first - use fixed filename since GitHub Actions cache doesn't support wildcards
 | 
			
		||||
    cache_file = Path(temp_folder) / "components_graph.json"
 | 
			
		||||
 | 
			
		||||
    if cache_file.exists():
 | 
			
		||||
        try:
 | 
			
		||||
            cached_data = json.loads(cache_file.read_text())
 | 
			
		||||
        except (OSError, json.JSONDecodeError):
 | 
			
		||||
            # Cache file corrupted or unreadable, rebuild
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            # Verify cache version matches
 | 
			
		||||
            if cached_data.get("_version") == COMPONENTS_GRAPH_CACHE_VERSION:
 | 
			
		||||
                # Verify cache is for current component state
 | 
			
		||||
                cache_key = get_components_graph_cache_key()
 | 
			
		||||
                if cached_data.get("_cache_key") == cache_key:
 | 
			
		||||
                    return cached_data.get("graph", {})
 | 
			
		||||
                # Cache key mismatch - stale cache, rebuild
 | 
			
		||||
            # Cache version mismatch - incompatible format, rebuild
 | 
			
		||||
 | 
			
		||||
    from esphome import const
 | 
			
		||||
    from esphome.core import CORE
 | 
			
		||||
    from esphome.loader import ComponentManifest, get_component, get_platform
 | 
			
		||||
 | 
			
		||||
    # The root directory of the repo
 | 
			
		||||
    root = Path(__file__).parent.parent
 | 
			
		||||
    root = Path(root_path)
 | 
			
		||||
    components_dir = root / ESPHOME_COMPONENTS_PATH
 | 
			
		||||
    # Fake some directory so that get_component works
 | 
			
		||||
    CORE.config_path = root
 | 
			
		||||
@@ -842,6 +895,15 @@ def create_components_graph() -> dict[str, list[str]]:
 | 
			
		||||
            # restore config
 | 
			
		||||
            CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
 | 
			
		||||
 | 
			
		||||
    # Save to cache with version and cache key for validation
 | 
			
		||||
    cache_data = {
 | 
			
		||||
        "_version": COMPONENTS_GRAPH_CACHE_VERSION,
 | 
			
		||||
        "_cache_key": get_components_graph_cache_key(),
 | 
			
		||||
        "graph": components_graph,
 | 
			
		||||
    }
 | 
			
		||||
    cache_file.parent.mkdir(exist_ok=True)
 | 
			
		||||
    cache_file.write_text(json.dumps(cache_data))
 | 
			
		||||
 | 
			
		||||
    return components_graph
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -543,6 +543,7 @@ def test_main_filters_components_without_tests(
 | 
			
		||||
    with (
 | 
			
		||||
        patch.object(determine_jobs, "root_path", str(tmp_path)),
 | 
			
		||||
        patch.object(helpers, "root_path", str(tmp_path)),
 | 
			
		||||
        patch.object(helpers, "create_components_graph", return_value={}),
 | 
			
		||||
        patch("sys.argv", ["determine-jobs.py"]),
 | 
			
		||||
        patch.object(
 | 
			
		||||
            determine_jobs,
 | 
			
		||||
@@ -640,6 +641,7 @@ def test_main_detects_components_with_variant_tests(
 | 
			
		||||
    with (
 | 
			
		||||
        patch.object(determine_jobs, "root_path", str(tmp_path)),
 | 
			
		||||
        patch.object(helpers, "root_path", str(tmp_path)),
 | 
			
		||||
        patch.object(helpers, "create_components_graph", return_value={}),
 | 
			
		||||
        patch("sys.argv", ["determine-jobs.py"]),
 | 
			
		||||
        patch.object(
 | 
			
		||||
            determine_jobs,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
"""Unit tests for script/helpers.py module."""
 | 
			
		||||
 | 
			
		||||
from collections.abc import Generator
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
@@ -1099,5 +1100,244 @@ def test_get_component_from_path(
 | 
			
		||||
    file_path: str, expected_component: str | None
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test extraction of component names from file paths."""
 | 
			
		||||
    result = helpers.get_component_from_path(file_path)
 | 
			
		||||
    assert result == expected_component
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Components graph cache tests
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def mock_git_output() -> str:
 | 
			
		||||
    """Fixture for mock git ls-files output."""
 | 
			
		||||
    return (
 | 
			
		||||
        "100644 abc123... 0 esphome/components/wifi/__init__.py\n"
 | 
			
		||||
        "100644 def456... 0 esphome/components/api/__init__.py\n"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def mock_cache_file(tmp_path: Path) -> Path:
 | 
			
		||||
    """Fixture for a temporary cache file path."""
 | 
			
		||||
    return tmp_path / "components_graph.json"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def clear_cache_key_cache() -> None:
 | 
			
		||||
    """Clear the components graph cache key cache before each test."""
 | 
			
		||||
    helpers.get_components_graph_cache_key.cache_clear()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def mock_subprocess_run() -> Generator[Mock, None, None]:
 | 
			
		||||
    """Fixture to mock subprocess.run for git commands."""
 | 
			
		||||
    with patch("subprocess.run") as mock_run:
 | 
			
		||||
        yield mock_run
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_key_generation(mock_git_output: str, mock_subprocess_run: Mock) -> None:
 | 
			
		||||
    """Test that cache key is generated based on git file hashes."""
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    key = helpers.get_components_graph_cache_key()
 | 
			
		||||
 | 
			
		||||
    # Should be a 64-character hex string (SHA256)
 | 
			
		||||
    assert len(key) == 64
 | 
			
		||||
    assert all(c in "0123456789abcdef" for c in key)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_key_consistent_for_same_files(
 | 
			
		||||
    mock_git_output: str, mock_subprocess_run: Mock
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test that same git output produces same cache key."""
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    key1 = helpers.get_components_graph_cache_key()
 | 
			
		||||
    key2 = helpers.get_components_graph_cache_key()
 | 
			
		||||
 | 
			
		||||
    assert key1 == key2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_key_different_for_changed_files(mock_subprocess_run: Mock) -> None:
 | 
			
		||||
    """Test that different git output produces different cache key."""
 | 
			
		||||
    mock_result1 = Mock()
 | 
			
		||||
    mock_result1.stdout = "100644 abc123... 0 esphome/components/wifi/__init__.py\n"
 | 
			
		||||
 | 
			
		||||
    mock_result2 = Mock()
 | 
			
		||||
    mock_result2.stdout = "100644 xyz789... 0 esphome/components/wifi/__init__.py\n"
 | 
			
		||||
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result1
 | 
			
		||||
    key1 = helpers.get_components_graph_cache_key()
 | 
			
		||||
 | 
			
		||||
    helpers.get_components_graph_cache_key.cache_clear()
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result2
 | 
			
		||||
    key2 = helpers.get_components_graph_cache_key()
 | 
			
		||||
 | 
			
		||||
    assert key1 != key2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_key_uses_git_ls_files(
 | 
			
		||||
    mock_git_output: str, mock_subprocess_run: Mock
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test that git ls-files command is called correctly."""
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    helpers.get_components_graph_cache_key()
 | 
			
		||||
 | 
			
		||||
    # Verify git ls-files was called with correct arguments
 | 
			
		||||
    mock_subprocess_run.assert_called_once()
 | 
			
		||||
    call_args = mock_subprocess_run.call_args
 | 
			
		||||
    assert call_args[0][0] == [
 | 
			
		||||
        "git",
 | 
			
		||||
        "ls-files",
 | 
			
		||||
        "-s",
 | 
			
		||||
        "esphome/components/**/__init__.py",
 | 
			
		||||
    ]
 | 
			
		||||
    assert call_args[1]["capture_output"] is True
 | 
			
		||||
    assert call_args[1]["text"] is True
 | 
			
		||||
    assert call_args[1]["check"] is True
 | 
			
		||||
    assert call_args[1]["close_fds"] is False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_hit_returns_cached_graph(
 | 
			
		||||
    tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test that cache hit returns cached data without rebuilding."""
 | 
			
		||||
    mock_graph = {"wifi": ["network"], "api": ["socket"]}
 | 
			
		||||
    cache_key = "a" * 64
 | 
			
		||||
    cache_data = {
 | 
			
		||||
        "_version": helpers.COMPONENTS_GRAPH_CACHE_VERSION,
 | 
			
		||||
        "_cache_key": cache_key,
 | 
			
		||||
        "graph": mock_graph,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Write cache file
 | 
			
		||||
    cache_file = tmp_path / "components_graph.json"
 | 
			
		||||
    cache_file.write_text(json.dumps(cache_data))
 | 
			
		||||
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    with (
 | 
			
		||||
        patch("helpers.get_components_graph_cache_key", return_value=cache_key),
 | 
			
		||||
        patch("helpers.temp_folder", str(tmp_path)),
 | 
			
		||||
    ):
 | 
			
		||||
        result = helpers.create_components_graph()
 | 
			
		||||
        assert result == mock_graph
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_miss_no_cache_file(
 | 
			
		||||
    tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test that cache miss rebuilds graph when no cache file exists."""
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    # Create minimal components directory structure
 | 
			
		||||
    components_dir = tmp_path / "esphome" / "components"
 | 
			
		||||
    components_dir.mkdir(parents=True)
 | 
			
		||||
 | 
			
		||||
    with (
 | 
			
		||||
        patch("helpers.root_path", str(tmp_path)),
 | 
			
		||||
        patch("helpers.temp_folder", str(tmp_path / ".temp")),
 | 
			
		||||
        patch("helpers.get_components_graph_cache_key", return_value="test_key"),
 | 
			
		||||
    ):
 | 
			
		||||
        result = helpers.create_components_graph()
 | 
			
		||||
        # Should return empty graph for empty components directory
 | 
			
		||||
        assert result == {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_miss_version_mismatch(
 | 
			
		||||
    tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test that cache miss rebuilds graph when version doesn't match."""
 | 
			
		||||
    cache_data = {
 | 
			
		||||
        "_version": 999,  # Wrong version
 | 
			
		||||
        "_cache_key": "test_key",
 | 
			
		||||
        "graph": {"old": ["data"]},
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cache_file = tmp_path / ".temp" / "components_graph.json"
 | 
			
		||||
    cache_file.parent.mkdir(parents=True)
 | 
			
		||||
    cache_file.write_text(json.dumps(cache_data))
 | 
			
		||||
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    # Create minimal components directory structure
 | 
			
		||||
    components_dir = tmp_path / "esphome" / "components"
 | 
			
		||||
    components_dir.mkdir(parents=True)
 | 
			
		||||
 | 
			
		||||
    with (
 | 
			
		||||
        patch("helpers.root_path", str(tmp_path)),
 | 
			
		||||
        patch("helpers.temp_folder", str(tmp_path / ".temp")),
 | 
			
		||||
        patch("helpers.get_components_graph_cache_key", return_value="test_key"),
 | 
			
		||||
    ):
 | 
			
		||||
        result = helpers.create_components_graph()
 | 
			
		||||
        # Should rebuild and return empty graph, not use cached data
 | 
			
		||||
        assert result == {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_miss_key_mismatch(
 | 
			
		||||
    tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test that cache miss rebuilds graph when cache key doesn't match."""
 | 
			
		||||
    cache_data = {
 | 
			
		||||
        "_version": helpers.COMPONENTS_GRAPH_CACHE_VERSION,
 | 
			
		||||
        "_cache_key": "old_key",
 | 
			
		||||
        "graph": {"old": ["data"]},
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cache_file = tmp_path / ".temp" / "components_graph.json"
 | 
			
		||||
    cache_file.parent.mkdir(parents=True)
 | 
			
		||||
    cache_file.write_text(json.dumps(cache_data))
 | 
			
		||||
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    # Create minimal components directory structure
 | 
			
		||||
    components_dir = tmp_path / "esphome" / "components"
 | 
			
		||||
    components_dir.mkdir(parents=True)
 | 
			
		||||
 | 
			
		||||
    with (
 | 
			
		||||
        patch("helpers.root_path", str(tmp_path)),
 | 
			
		||||
        patch("helpers.temp_folder", str(tmp_path / ".temp")),
 | 
			
		||||
        patch("helpers.get_components_graph_cache_key", return_value="new_key"),
 | 
			
		||||
    ):
 | 
			
		||||
        result = helpers.create_components_graph()
 | 
			
		||||
        # Should rebuild and return empty graph, not use cached data with old key
 | 
			
		||||
        assert result == {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_cache_miss_corrupted_json(
 | 
			
		||||
    tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test that cache miss rebuilds graph when cache file has invalid JSON."""
 | 
			
		||||
    cache_file = tmp_path / ".temp" / "components_graph.json"
 | 
			
		||||
    cache_file.parent.mkdir(parents=True)
 | 
			
		||||
    cache_file.write_text("{invalid json")
 | 
			
		||||
 | 
			
		||||
    mock_result = Mock()
 | 
			
		||||
    mock_result.stdout = mock_git_output
 | 
			
		||||
    mock_subprocess_run.return_value = mock_result
 | 
			
		||||
 | 
			
		||||
    # Create minimal components directory structure
 | 
			
		||||
    components_dir = tmp_path / "esphome" / "components"
 | 
			
		||||
    components_dir.mkdir(parents=True)
 | 
			
		||||
 | 
			
		||||
    with (
 | 
			
		||||
        patch("helpers.root_path", str(tmp_path)),
 | 
			
		||||
        patch("helpers.temp_folder", str(tmp_path / ".temp")),
 | 
			
		||||
        patch("helpers.get_components_graph_cache_key", return_value="test_key"),
 | 
			
		||||
    ):
 | 
			
		||||
        result = helpers.create_components_graph()
 | 
			
		||||
        # Should handle corruption gracefully and rebuild
 | 
			
		||||
        assert result == {}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user