How to test CLI tools¶
Use Copilot's native shell tools for an ordinary CLI workflow. Initialize a disposable workspace, ask for a concrete change, then check the resulting state with your own process—not the assistant's final reply.
Preferred pattern: native shell workflow¶
This test requires Git on PATH. It creates a repository and verifies that
Copilot stages the seeded file without changing its contents:
from __future__ import annotations
import subprocess
from pytest_skill_engineering.copilot import CopilotEval
async def test_git_add(copilot_eval, tmp_path):
subprocess.run(["git", "init", "-q"], cwd=tmp_path, check=True)
(tmp_path / "note.txt").write_text("hello\n", encoding="utf-8")
agent = CopilotEval(
name="git-stage-file",
model="gpt-5.6-sol",
instructions="Use terminal tools for the requested Git operation.",
working_directory=str(tmp_path),
timeout_s=120,
)
result = await copilot_eval(
agent, "Stage note.txt with git add. Do not edit the file or create a commit."
)
assert result.success, result.error
staged = subprocess.run(
["git", "diff", "--cached", "--name-only"],
cwd=tmp_path,
check=True,
capture_output=True,
text=True,
)
assert staged.stdout.splitlines() == ["note.txt"]
content = subprocess.run(
["git", "show", ":note.txt"],
cwd=tmp_path,
check=True,
capture_output=True,
text=True,
)
assert content.stdout == "hello\n"
assert (tmp_path / "note.txt").read_text(encoding="utf-8") == "hello\n"
head = subprocess.run(
["git", "rev-parse", "--verify", "HEAD"],
cwd=tmp_path,
capture_output=True,
text=True,
)
assert head.returncode != 0, "The task must not create a commit"
This verifies the staged artifact, unchanged working file, and absence of a commit.
Keep the test in an approved environment: working_directory is not a sandbox,
and permissions are auto-approved by default.
When you have a wrapped CLI tool¶
CLIServer is a lower-level configuration helper for exposing a CLI as a
single tool. It is not an argument to CopilotEval, and its constructor does
not accept name.
from pytest_skill_engineering import CLIServer
git_server = CLIServer(
command="git",
tool_prefix="git",
shell="none",
discover_help=True,
)
Use a wrapper when you need a constrained command surface rather than general shell access. Your adapter owns command validation, permissions, and state verification.
Configuration options¶
git_server = CLIServer(
command="git",
tool_prefix="git",
shell="none",
cwd="path/to/test-repo",
env={"LC_ALL": "C"},
discover_help=False,
help_flag="--help",
timeout=30,
description="Run approved Git commands.",
)
With shell="none", the command and tool arguments are split into direct process
arguments without shell expansion. Windows backslashes are preserved; quote
paths containing spaces. Grouping quotes are removed, while quoted empty
arguments stay empty arguments. The default shell=None auto-selects a shell;
it is not the same as "none".
Complete Copilot-facing example¶
For a CLI exposed by your own MCP server, attach the server configuration:
import sys
GIT_MCP = {
"git": {
"command": sys.executable,
"args": ["-m", "my_project.git_mcp"],
"tools": ["*"],
}
}
This is an illustrative configuration requiring your my_project.git_mcp
module. Pass it as CopilotEval(mcp_servers=GIT_MCP, ...), seed the server's
test repository, and perform the same independent index checks. Merely
attaching the wrapper or observing a tool call does not verify the Git task.
See Test MCP servers for transports and tool restrictions.