Files
computer-playbook/tests/unit/cli/build/test_tree_shadow_folder.py
T

122 lines
3.9 KiB
Python

import os
import sys
import json
import tempfile
import shutil
import unittest
from contextlib import redirect_stdout
from io import StringIO
from unittest.mock import patch
# Absolute path to the tree/__main__.py script
SCRIPT_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../../../cli/build/tree/__main__.py")
)
class TestTreeShadowFolder(unittest.TestCase):
def setUp(self):
# Create a temporary roles directory and a dummy role
self.roles_dir = tempfile.mkdtemp()
self.role_name = "dummyrole"
self.role_path = os.path.join(self.roles_dir, self.role_name)
os.makedirs(os.path.join(self.role_path, "meta"))
# Create a temporary shadow folder
self.shadow_dir = tempfile.mkdtemp()
# Patch sys.argv so the script picks up our dirs
self.orig_argv = sys.argv[:]
sys.argv = [
SCRIPT_PATH,
"-d", self.roles_dir,
"-s", self.shadow_dir,
"-o", "json"
]
# Ensure project root is on sys.path so `import cli.build.tree` works
project_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../../../")
)
if project_root not in sys.path:
sys.path.insert(0, project_root)
def tearDown(self):
# Restore original argv and clean up
sys.argv = self.orig_argv
shutil.rmtree(self.roles_dir)
shutil.rmtree(self.shadow_dir)
@staticmethod
def _sync_as_completed(futures):
return list(futures)
@patch("cli.build.tree._main.as_completed", side_effect=_sync_as_completed.__func__)
@patch("cli.build.tree._main.ProcessPoolExecutor")
@patch("cli.build.tree.build_mappings")
@patch("cli.build.tree.output_graph")
def test_tree_json_written_to_shadow_folder(
self,
mock_output_graph,
mock_build_mappings,
mock_executor_cls,
_mock_as_completed,
):
# Prepare the dummy graph that build_mappings should return
dummy_graph = {"dummy": {"test": 42}}
mock_build_mappings.return_value = dummy_graph
class DummyFuture:
def __init__(self, value=None, exc=None):
self._value = value
self._exc = exc
def result(self):
if self._exc is not None:
raise self._exc
return self._value
class DummyExecutor:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def submit(self, fn, *args, **kwargs):
try:
return DummyFuture(fn(*args, **kwargs))
except Exception as exc:
return DummyFuture(exc=exc)
mock_executor_cls.return_value = DummyExecutor()
# Import the script module by name (so our @patch applies) and call main()
import importlib
tree_mod = importlib.import_module("cli.build.tree")
with redirect_stdout(StringIO()):
tree_mod.main()
# Verify that tree.json was written into the shadow folder
expected_tree_path = os.path.join(
self.shadow_dir, self.role_name, "meta", "tree.json"
)
self.assertTrue(
os.path.isfile(expected_tree_path),
f"tree.json not found at {expected_tree_path}"
)
# Verify contents match our dummy_graph
with open(expected_tree_path, 'r') as f:
data = json.load(f)
self.assertEqual(data, dummy_graph, "tree.json content mismatch")
# Ensure that no tree.json was written to the real meta/ folder
original_tree_path = os.path.join(self.role_path, "meta", "tree.json")
self.assertFalse(
os.path.exists(original_tree_path),
"tree.json should NOT be written to the real meta/ folder"
)
if __name__ == "__main__":
unittest.main()