# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import pytest from mirage.io import IOResult from mirage.io.types import materialize from mirage.resource.ram import RAMResource from mirage.shell.job_table import JobTable from mirage.shell.parse import parse from mirage.types import MountMode from mirage.workspace.mount import MountRegistry from mirage.workspace.mount.namespace import Namespace from mirage.workspace.node import run_command_tree as _run_command_tree from mirage.workspace.session import Session from mirage.workspace.workspace import Workspace def run_command_tree(dispatch, registry, *args, **kwargs): return _run_command_tree(dispatch, registry, Namespace(registry), *args, **kwargs) @pytest.fixture def registry(): """Minimal registry with a RAM mount at root.""" reg = MountRegistry() res = RAMResource() reg.mount("/", res, MountMode.WRITE) return reg async def _dispatch_noop(op, path, **kwargs): return None, IOResult() async def _noop_execute(command, **kwargs): return IOResult() def _session(): return Session(session_id="test", cwd="/") @pytest.mark.asyncio async def test_run_command_tree_materializes_stdout(registry): ast = parse("echo hello") io, exec_node = await run_command_tree( _dispatch_noop, registry, JobTable(), _noop_execute, "agent", ast, _session(), None, None, ) assert io.exit_code == 0 assert b"hello" in await materialize(io.stdout) assert exec_node is not None @pytest.mark.asyncio async def test_run_command_tree_propagates_exit_code(registry): ast = parse("false") io, _ = await run_command_tree( _dispatch_noop, registry, JobTable(), _noop_execute, "agent", ast, _session(), None, None, ) assert io.exit_code != 0 async def _cross_node(cmd: str): # A real two-mount workspace wires dispatch/cache; run_command_tree is the # seam returning the recorded ExecutionNode (Workspace.execute drops it). ws = Workspace({ "/a": RAMResource(), "/b": RAMResource() }, mode=MountMode.WRITE) await ws.execute("mkdir -p /a/dir") await ws.execute("printf 'x\\n' > /a/f.txt") io, exec_node = await run_command_tree(ws.dispatch, ws._registry, ws.job_table, _noop_execute, "agent", parse(cmd), Session(session_id="t", cwd="/"), None, None) return io, exec_node @pytest.mark.asyncio async def test_cross_mount_exec_node_records_stderr(): # cp of a directory without -r across mounts: the cross-mount branch builds # the node via _exec_node, so its stderr/exit_code must match io. io, exec_node = await _cross_node("cp /a/dir /b/x") assert io.exit_code == 1 assert b"omitting directory" in await materialize(io.stderr) assert exec_node.exit_code == 1 assert b"omitting directory" in (exec_node.stderr or b"") @pytest.mark.asyncio async def test_cross_mount_exec_node_success_has_no_stderr(): io, exec_node = await _cross_node("cp /a/f.txt /b/f.txt") assert exec_node.exit_code == 0 assert not (exec_node.stderr or b"")