Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02c2e9b596 |
@@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Added Robot Framework integration test for Specification Workflow Example 10
|
||||
(full-auto batch formatting and linting, full-auto profile). (#774)
|
||||
- Added TDD bug-capture tests for bug #1076 — `use_action()` does not
|
||||
propagate `automation_profile` to Plan. Three Behave BDD scenarios
|
||||
(`@tdd_bug @tdd_bug_1076 @tdd_expected_fail`) verify the full precedence
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
@lsp @client
|
||||
Feature: LSP Client
|
||||
As a developer
|
||||
I want a robust LSP client
|
||||
So that I can communicate with language servers
|
||||
|
||||
Scenario: Initialize handshake success
|
||||
Given an LSP client with a mock transport
|
||||
When I initialize the client with workspace "/tmp/test-ws"
|
||||
Then the client should send an "initialize" request
|
||||
And the client should send an "initialized" notification
|
||||
And the client should be initialized
|
||||
|
||||
Scenario: Shutdown sequence
|
||||
Given an initialized LSP client
|
||||
When I shutdown the client
|
||||
Then the client should send a "shutdown" request
|
||||
And the client should send an "exit" notification
|
||||
And the client state should be uninitialized
|
||||
|
||||
Scenario: Text document notifications
|
||||
Given an initialized LSP client
|
||||
When I open a document "file:///test.py" with content "print('hello')"
|
||||
Then the client should send a "textDocument/didOpen" notification
|
||||
When I close the document "file:///test.py"
|
||||
Then the client should send a "textDocument/didClose" notification
|
||||
|
||||
Scenario: Diagnostics handling
|
||||
Given an initialized LSP client
|
||||
When the server publishes diagnostics for "file:///test.py"
|
||||
Then the client should have diagnostics for "file:///test.py"
|
||||
|
||||
Scenario: Completion request
|
||||
Given an initialized LSP client
|
||||
When I request completions for "file:///test.py" at line 1 character 5
|
||||
Then the client should send a "textDocument/completion" request
|
||||
@@ -0,0 +1,153 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from behave import given, then, when
|
||||
|
||||
from cleveragents.lsp.client import LspClient
|
||||
from cleveragents.lsp.transport import StdioTransport
|
||||
|
||||
|
||||
class MockTransport(StdioTransport):
|
||||
def __init__(self):
|
||||
self.sent_messages = []
|
||||
self.responses = []
|
||||
self._is_alive = True
|
||||
|
||||
@property
|
||||
def is_alive(self) -> bool:
|
||||
return self._is_alive
|
||||
|
||||
def start(self) -> None:
|
||||
pass
|
||||
|
||||
def stop(self, timeout=None) -> None:
|
||||
pass
|
||||
|
||||
def send_message(self, body: dict) -> None:
|
||||
self.sent_messages.append(body)
|
||||
|
||||
def read_message(self, timeout=None) -> dict | None:
|
||||
if self.responses:
|
||||
return self.responses.pop(0)
|
||||
return None
|
||||
|
||||
def queue_response(self, response: dict):
|
||||
self.responses.append(response)
|
||||
|
||||
|
||||
@given("an LSP client with a mock transport")
|
||||
def step_client_with_mock_transport(context):
|
||||
context.transport = MockTransport()
|
||||
# Mock __init__ of StdioTransport is bypassed by our subclass,
|
||||
# but LspClient checks it.
|
||||
context.client = LspClient(context.transport, server_name="test-server")
|
||||
|
||||
|
||||
@when('I initialize the client with workspace "{workspace}"')
|
||||
def step_initialize_client(context, workspace):
|
||||
# Queue the response for the initialize request
|
||||
# ID will be 1 because it's the first request
|
||||
context.transport.queue_response(
|
||||
{"jsonrpc": "2.0", "id": 1, "result": {"capabilities": {"textDocumentSync": 1}}}
|
||||
)
|
||||
context.client.initialize(workspace)
|
||||
|
||||
|
||||
@then('the client should send an "initialize" request')
|
||||
def step_check_initialize_request(context):
|
||||
msg = context.transport.sent_messages[0]
|
||||
assert msg["method"] == "initialize"
|
||||
|
||||
|
||||
@then('the client should send an "initialized" notification')
|
||||
def step_check_initialized_notification(context):
|
||||
msg = context.transport.sent_messages[1]
|
||||
assert msg["method"] == "initialized"
|
||||
|
||||
|
||||
@then("the client should be initialized")
|
||||
def step_check_initialized(context):
|
||||
assert context.client.is_initialized
|
||||
|
||||
|
||||
@given("an initialized LSP client")
|
||||
def step_initialized_client(context):
|
||||
step_client_with_mock_transport(context)
|
||||
step_initialize_client(context, "/tmp/ws")
|
||||
context.transport.sent_messages.clear() # Clear init messages
|
||||
# Reset request ID counter if needed, or track IDs.
|
||||
# LspClient._request_id will be 1. Next request is 2.
|
||||
|
||||
|
||||
@when("I shutdown the client")
|
||||
def step_shutdown_client(context):
|
||||
# Next ID is 2
|
||||
context.transport.queue_response({"jsonrpc": "2.0", "id": 2, "result": None})
|
||||
context.client.shutdown()
|
||||
|
||||
|
||||
@then('the client should send a "shutdown" request')
|
||||
def step_check_shutdown_request(context):
|
||||
msg = next(m for m in context.transport.sent_messages if m["method"] == "shutdown")
|
||||
assert msg
|
||||
|
||||
|
||||
@then('the client should send an "exit" notification')
|
||||
def step_check_exit_notification(context):
|
||||
msg = next(m for m in context.transport.sent_messages if m["method"] == "exit")
|
||||
assert msg
|
||||
|
||||
|
||||
@then("the client state should be uninitialized")
|
||||
def step_check_not_initialized(context):
|
||||
assert not context.client.is_initialized
|
||||
|
||||
|
||||
@when('I open a document "{uri}" with content "{content}"')
|
||||
def step_open_document(context, uri, content):
|
||||
context.client.did_open(uri, "python", 1, content)
|
||||
|
||||
|
||||
@then('the client should send a "textDocument/didOpen" notification')
|
||||
def step_check_did_open(context):
|
||||
msg = context.transport.sent_messages[-1]
|
||||
assert msg["method"] == "textDocument/didOpen"
|
||||
|
||||
|
||||
@when('I close the document "{uri}"')
|
||||
def step_close_document(context, uri):
|
||||
context.client.did_close(uri)
|
||||
|
||||
|
||||
@then('the client should send a "textDocument/didClose" notification')
|
||||
def step_check_did_close(context):
|
||||
msg = context.transport.sent_messages[-1]
|
||||
assert msg["method"] == "textDocument/didClose"
|
||||
|
||||
|
||||
@when('the server publishes diagnostics for "{uri}"')
|
||||
def step_server_publishes_diagnostics(context, uri):
|
||||
notification = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/publishDiagnostics",
|
||||
"params": {"uri": uri, "diagnostics": [{"message": "error", "range": {}}]},
|
||||
}
|
||||
context.client._handle_notification(notification)
|
||||
|
||||
|
||||
@then('the client should have diagnostics for "{uri}"')
|
||||
def step_check_diagnostics(context, uri):
|
||||
diags = context.client.get_diagnostics(uri)
|
||||
assert len(diags) > 0
|
||||
|
||||
|
||||
@when('I request completions for "{uri}" at line {line:d} character {char:d}')
|
||||
def step_request_completions(context, uri, line, char):
|
||||
# Next ID is 2 (after init)
|
||||
context.transport.queue_response({"jsonrpc": "2.0", "id": 2, "result": []})
|
||||
context.client.get_completions(uri, line, char)
|
||||
|
||||
|
||||
@then('the client should send a "textDocument/completion" request')
|
||||
def step_check_completion_request(context):
|
||||
msg = context.transport.sent_messages[-1]
|
||||
assert msg["method"] == "textDocument/completion"
|
||||
@@ -0,0 +1,483 @@
|
||||
"""Helper script for wf10_batch_auto.robot E2E tests.
|
||||
|
||||
Workflow Example 10: Full-Auto Batch Operations — Formatting and Linting.
|
||||
Exercises the ``full-auto`` automation profile with batch plan execution
|
||||
across multiple packages using mocked LLM providers.
|
||||
|
||||
Each subcommand is self-contained, sets up its own workspace, and prints
|
||||
a sentinel on success. Exit code 0 = pass, 1 = failure.
|
||||
|
||||
Usage::
|
||||
|
||||
python robot/helper_wf10_batch_auto.py <command>
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
||||
if _SRC not in sys.path:
|
||||
sys.path.insert(0, _SRC)
|
||||
_ROBOT = str(Path(__file__).resolve().parent)
|
||||
if _ROBOT not in sys.path:
|
||||
sys.path.insert(0, _ROBOT)
|
||||
|
||||
from helper_e2e_common import ( # noqa: E402
|
||||
cleanup_workspace,
|
||||
fail,
|
||||
init_bare_git_repo,
|
||||
run_cli,
|
||||
setup_workspace,
|
||||
write_yaml,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _init_workspace(prefix: str) -> str:
|
||||
"""Create a workspace with Alembic migrations applied."""
|
||||
return setup_workspace(prefix=prefix)
|
||||
|
||||
|
||||
def _create_action(workspace: str, name: str) -> str:
|
||||
"""Create a reusable full-auto action.
|
||||
|
||||
Returns the path to the temporary YAML file so callers can clean it up.
|
||||
"""
|
||||
yaml_path = write_yaml(
|
||||
f"name: {name}\n"
|
||||
"description: Batch format and lint package\n"
|
||||
"automation_profile: full-auto\n"
|
||||
"strategy_actor: openai/gpt-4\n"
|
||||
"execution_actor: openai/gpt-4\n"
|
||||
"definition_of_done: Code is formatted and lint-clean\n"
|
||||
)
|
||||
result = run_cli("action", "create", "--config", yaml_path, workspace=workspace)
|
||||
if result.returncode != 0:
|
||||
fail(
|
||||
f"action create {name} rc={result.returncode}\n"
|
||||
f"stdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
return yaml_path
|
||||
|
||||
|
||||
def _register_resource(workspace: str) -> str:
|
||||
"""Register a git-checkout resource.
|
||||
|
||||
Returns the path to the temporary git repo so callers can clean it up.
|
||||
"""
|
||||
repo_dir = init_bare_git_repo()
|
||||
result = run_cli(
|
||||
"resource",
|
||||
"add",
|
||||
"git-checkout",
|
||||
"local/wf10-repo",
|
||||
"--path",
|
||||
repo_dir,
|
||||
"--branch",
|
||||
"main",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(
|
||||
f"resource add rc={result.returncode}\n"
|
||||
f"stdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
return repo_dir
|
||||
|
||||
|
||||
def _create_project(
|
||||
workspace: str,
|
||||
name: str,
|
||||
resource_name: str = "local/wf10-repo",
|
||||
) -> None:
|
||||
"""Create a project linked to a resource."""
|
||||
result = run_cli(
|
||||
"project",
|
||||
"create",
|
||||
name,
|
||||
"--resource",
|
||||
resource_name,
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(
|
||||
f"project create {name} rc={result.returncode}\n"
|
||||
f"stdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Subcommands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def create_reusable_action() -> None:
|
||||
"""Create a full-auto action that can be reused across packages."""
|
||||
workspace = _init_workspace("wf10_action_")
|
||||
yaml_path: str | None = None
|
||||
try:
|
||||
yaml_path = _create_action(workspace, "local/batch-format-lint")
|
||||
print("action local/batch-format-lint created")
|
||||
print("wf10-create-action-ok")
|
||||
finally:
|
||||
if yaml_path:
|
||||
os.unlink(yaml_path)
|
||||
cleanup_workspace(workspace)
|
||||
|
||||
|
||||
def batch_plan_launch() -> None:
|
||||
"""Launch plans for 3 packages using the same action."""
|
||||
workspace = _init_workspace("wf10_batch_")
|
||||
yaml_path: str | None = None
|
||||
repo_dir: str | None = None
|
||||
try:
|
||||
yaml_path = _create_action(workspace, "local/batch-fmt")
|
||||
repo_dir = _register_resource(workspace)
|
||||
|
||||
# Create 3 projects simulating 3 packages
|
||||
projects = ["local/pkg-alpha", "local/pkg-beta", "local/pkg-gamma"]
|
||||
for proj in projects:
|
||||
_create_project(workspace, proj)
|
||||
|
||||
# Launch plan for each project using the same action
|
||||
plan_ids: list[str] = []
|
||||
for proj in projects:
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"use",
|
||||
"local/batch-fmt",
|
||||
proj,
|
||||
"--automation-profile",
|
||||
"full-auto",
|
||||
"--format",
|
||||
"plain",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(
|
||||
f"plan use for {proj} rc={result.returncode}\n"
|
||||
f"stdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
# Extract plan ID from plain-format output
|
||||
for line in result.stdout.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("plan_id:"):
|
||||
pid = stripped.split(":", 1)[1].strip()
|
||||
plan_ids.append(pid)
|
||||
break
|
||||
|
||||
if not plan_ids:
|
||||
fail(
|
||||
"No plan IDs extracted from output after launching "
|
||||
f"{len(projects)} plans"
|
||||
)
|
||||
|
||||
print(f"Launched {len(plan_ids)} plans: {plan_ids}")
|
||||
for proj in projects:
|
||||
print(f"project: {proj}")
|
||||
print("wf10-batch-plan-launch-ok")
|
||||
finally:
|
||||
if yaml_path:
|
||||
os.unlink(yaml_path)
|
||||
if repo_dir:
|
||||
shutil.rmtree(repo_dir, ignore_errors=True)
|
||||
cleanup_workspace(workspace)
|
||||
|
||||
|
||||
def plan_list_monitoring() -> None:
|
||||
"""Launch plans and verify ``plan lifecycle-list`` shows them."""
|
||||
workspace = _init_workspace("wf10_list_")
|
||||
yaml_path: str | None = None
|
||||
repo_dir: str | None = None
|
||||
try:
|
||||
yaml_path = _create_action(workspace, "local/lint-batch")
|
||||
repo_dir = _register_resource(workspace)
|
||||
|
||||
projects = ["local/svc-one", "local/svc-two", "local/svc-three"]
|
||||
for proj in projects:
|
||||
_create_project(workspace, proj)
|
||||
|
||||
# Launch plans
|
||||
for proj in projects:
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"use",
|
||||
"local/lint-batch",
|
||||
proj,
|
||||
"--automation-profile",
|
||||
"full-auto",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(f"plan use {proj} rc={result.returncode}\nstderr: {result.stderr}")
|
||||
|
||||
# List all plans
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"lifecycle-list",
|
||||
"--format",
|
||||
"plain",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(
|
||||
f"plan lifecycle-list rc={result.returncode}\n"
|
||||
f"stdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
if "Traceback" in result.stderr or "INTERNAL" in result.stderr:
|
||||
fail(f"plan lifecycle-list errors:\nstderr: {result.stderr}")
|
||||
|
||||
# Verify that we see plans in the output
|
||||
stdout_lower = result.stdout.lower()
|
||||
if "total: 0" in stdout_lower or not result.stdout.strip():
|
||||
fail(
|
||||
"plan lifecycle-list shows no plans after launching 3\n"
|
||||
f"stdout: {result.stdout}"
|
||||
)
|
||||
print(f"plan-count: {len(projects)}")
|
||||
print("Plan lifecycle-list monitoring: OK")
|
||||
print("wf10-plan-list-monitoring-ok")
|
||||
finally:
|
||||
if yaml_path:
|
||||
os.unlink(yaml_path)
|
||||
if repo_dir:
|
||||
shutil.rmtree(repo_dir, ignore_errors=True)
|
||||
cleanup_workspace(workspace)
|
||||
|
||||
|
||||
def plan_list_filter_by_phase() -> None:
|
||||
"""Verify ``plan lifecycle-list --phase`` filtering works."""
|
||||
workspace = _init_workspace("wf10_filter_")
|
||||
yaml_path: str | None = None
|
||||
repo_dir: str | None = None
|
||||
try:
|
||||
yaml_path = _create_action(workspace, "local/fmt-filter")
|
||||
repo_dir = _register_resource(workspace)
|
||||
_create_project(workspace, "local/filter-proj")
|
||||
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"use",
|
||||
"local/fmt-filter",
|
||||
"local/filter-proj",
|
||||
"--automation-profile",
|
||||
"full-auto",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(f"plan use rc={result.returncode}\nstderr: {result.stderr}")
|
||||
|
||||
# Filter by strategize phase (plans start in strategize/queued)
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"lifecycle-list",
|
||||
"--phase",
|
||||
"strategize",
|
||||
"--format",
|
||||
"plain",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(
|
||||
"plan lifecycle-list --phase strategize "
|
||||
f"rc={result.returncode}\nstderr: {result.stderr}"
|
||||
)
|
||||
if "Traceback" in result.stderr or "INTERNAL" in result.stderr:
|
||||
fail(f"lifecycle-list --phase errors:\nstderr: {result.stderr}")
|
||||
print("phase: strategize")
|
||||
print("Plan lifecycle-list phase filter: OK")
|
||||
print("wf10-plan-filter-phase-ok")
|
||||
finally:
|
||||
if yaml_path:
|
||||
os.unlink(yaml_path)
|
||||
if repo_dir:
|
||||
shutil.rmtree(repo_dir, ignore_errors=True)
|
||||
cleanup_workspace(workspace)
|
||||
|
||||
|
||||
def batch_execute_graceful() -> None:
|
||||
"""Execute plans in batch; verify graceful handling with mock AI."""
|
||||
workspace = _init_workspace("wf10_exec_")
|
||||
yaml_path: str | None = None
|
||||
repo_dir: str | None = None
|
||||
try:
|
||||
yaml_path = _create_action(workspace, "local/batch-exec")
|
||||
repo_dir = _register_resource(workspace)
|
||||
|
||||
projects = ["local/exec-a", "local/exec-b"]
|
||||
for proj in projects:
|
||||
_create_project(workspace, proj)
|
||||
|
||||
# Launch plans for each project
|
||||
plan_ids: list[str] = []
|
||||
for proj in projects:
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"use",
|
||||
"local/batch-exec",
|
||||
proj,
|
||||
"--automation-profile",
|
||||
"full-auto",
|
||||
"--format",
|
||||
"plain",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(f"plan use {proj} rc={result.returncode}\nstderr: {result.stderr}")
|
||||
# Extract plan ID from plain-format output
|
||||
for line in result.stdout.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("plan_id:"):
|
||||
pid = stripped.split(":", 1)[1].strip()
|
||||
plan_ids.append(pid)
|
||||
break
|
||||
|
||||
# Execute each plan — with mock AI the plans are in
|
||||
# strategize/queued so execute returns gracefully
|
||||
for pid in plan_ids:
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"execute",
|
||||
pid,
|
||||
workspace=workspace,
|
||||
)
|
||||
# Non-zero is acceptable (plan not ready) but no crashes
|
||||
if "Traceback" in result.stderr:
|
||||
fail(f"plan execute {pid} produced Traceback:\nstderr: {result.stderr}")
|
||||
print(f"plan execute {pid}: rc={result.returncode}")
|
||||
|
||||
# Verify lifecycle-list still works after execute attempts
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"lifecycle-list",
|
||||
"--format",
|
||||
"plain",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(f"lifecycle-list rc={result.returncode}\nstderr: {result.stderr}")
|
||||
if "Traceback" in result.stderr or "INTERNAL" in result.stderr:
|
||||
fail(f"batch execute errors:\nstderr: {result.stderr}")
|
||||
|
||||
print("Batch execute graceful: OK")
|
||||
print("wf10-batch-execute-ok")
|
||||
finally:
|
||||
if yaml_path:
|
||||
os.unlink(yaml_path)
|
||||
if repo_dir:
|
||||
shutil.rmtree(repo_dir, ignore_errors=True)
|
||||
cleanup_workspace(workspace)
|
||||
|
||||
|
||||
def error_handling() -> None:
|
||||
"""Demonstrate error handling when a plan references invalid action."""
|
||||
workspace = _init_workspace("wf10_error_")
|
||||
repo_dir: str | None = None
|
||||
try:
|
||||
repo_dir = _register_resource(workspace)
|
||||
_create_project(workspace, "local/error-proj")
|
||||
|
||||
# Try to use a non-existent action
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"use",
|
||||
"local/nonexistent-action",
|
||||
"local/error-proj",
|
||||
"--automation-profile",
|
||||
"full-auto",
|
||||
workspace=workspace,
|
||||
)
|
||||
# Should fail gracefully (non-zero exit code, no crash)
|
||||
if result.returncode == 0:
|
||||
fail("plan use with nonexistent action should fail")
|
||||
if "Traceback" in result.stderr:
|
||||
fail(
|
||||
f"plan use with bad action produced Traceback:\nstderr: {result.stderr}"
|
||||
)
|
||||
print(f"error-rc: {result.returncode}")
|
||||
print("Error handling for invalid action: graceful failure")
|
||||
print("wf10-error-handling-ok")
|
||||
finally:
|
||||
if repo_dir:
|
||||
shutil.rmtree(repo_dir, ignore_errors=True)
|
||||
cleanup_workspace(workspace)
|
||||
|
||||
|
||||
def full_auto_profile_verification() -> None:
|
||||
"""Verify full-auto profile is accepted in plan use."""
|
||||
workspace = _init_workspace("wf10_profile_")
|
||||
yaml_path: str | None = None
|
||||
repo_dir: str | None = None
|
||||
try:
|
||||
yaml_path = _create_action(workspace, "local/auto-lint")
|
||||
repo_dir = _register_resource(workspace)
|
||||
_create_project(workspace, "local/auto-proj")
|
||||
|
||||
result = run_cli(
|
||||
"plan",
|
||||
"use",
|
||||
"local/auto-lint",
|
||||
"local/auto-proj",
|
||||
"--automation-profile",
|
||||
"full-auto",
|
||||
workspace=workspace,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
fail(
|
||||
"plan use --automation-profile full-auto "
|
||||
f"rc={result.returncode}\n"
|
||||
f"stdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
if "Traceback" in result.stderr or "INTERNAL" in result.stderr:
|
||||
fail(f"full-auto profile errors:\nstderr: {result.stderr}")
|
||||
print("automation-profile: full-auto")
|
||||
print("Full-auto profile accepted: OK")
|
||||
print("wf10-full-auto-profile-ok")
|
||||
finally:
|
||||
if yaml_path:
|
||||
os.unlink(yaml_path)
|
||||
if repo_dir:
|
||||
shutil.rmtree(repo_dir, ignore_errors=True)
|
||||
cleanup_workspace(workspace)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dispatcher
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_COMMANDS: dict[str, Callable[[], None]] = {
|
||||
"create-action": create_reusable_action,
|
||||
"batch-plan-launch": batch_plan_launch,
|
||||
"plan-list-monitoring": plan_list_monitoring,
|
||||
"plan-filter-phase": plan_list_filter_by_phase,
|
||||
"batch-execute": batch_execute_graceful,
|
||||
"error-handling": error_handling,
|
||||
"full-auto-profile": full_auto_profile_verification,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Dispatch to the requested subcommand."""
|
||||
if len(sys.argv) < 2:
|
||||
cmds = "|".join(_COMMANDS)
|
||||
print(f"Usage: helper_wf10_batch_auto.py <{cmds}>")
|
||||
return 1
|
||||
command = sys.argv[1]
|
||||
handler = _COMMANDS.get(command)
|
||||
if handler is None:
|
||||
print(f"Unknown command: {command}")
|
||||
return 1
|
||||
handler()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,82 @@
|
||||
*** Settings ***
|
||||
Documentation WF10: Full-auto batch formatting and linting E2E tests.
|
||||
... Exercises the full-auto profile with batch plan execution
|
||||
... across multiple packages using mocked LLM providers.
|
||||
Resource ${CURDIR}/common.resource
|
||||
Force Tags wf10 batch full-auto integration
|
||||
Suite Setup Setup Test Environment
|
||||
Suite Teardown Cleanup Test Environment
|
||||
|
||||
*** Variables ***
|
||||
${HELPER} ${CURDIR}/helper_wf10_batch_auto.py
|
||||
|
||||
*** Test Cases ***
|
||||
WF10 Create Reusable Full Auto Action
|
||||
[Documentation] Create a full-auto action for batch formatting.
|
||||
[Tags] action create
|
||||
${result}= Run Process ${PYTHON} ${HELPER} create-action cwd=${SUITE_HOME} timeout=120s on_timeout=kill
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} wf10-create-action-ok
|
||||
Should Contain ${result.stdout} batch-format-lint
|
||||
|
||||
WF10 Batch Plan Launch Three Packages
|
||||
[Documentation] Launch plans for 3 packages using the same action.
|
||||
[Tags] plan batch launch
|
||||
${result}= Run Process ${PYTHON} ${HELPER} batch-plan-launch cwd=${SUITE_HOME} timeout=120s on_timeout=kill
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} wf10-batch-plan-launch-ok
|
||||
Should Contain ${result.stdout} project: local/pkg-alpha
|
||||
|
||||
WF10 Plan List Monitoring
|
||||
[Documentation] Verify plan lifecycle-list shows launched plans.
|
||||
[Tags] plan lifecycle list
|
||||
${result}= Run Process ${PYTHON} ${HELPER} plan-list-monitoring cwd=${SUITE_HOME} timeout=120s on_timeout=kill
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} wf10-plan-list-monitoring-ok
|
||||
Should Contain ${result.stdout} plan-count: 3
|
||||
|
||||
WF10 Plan List Filter By Phase
|
||||
[Documentation] Verify plan lifecycle-list --phase filtering works.
|
||||
[Tags] plan lifecycle filter
|
||||
${result}= Run Process ${PYTHON} ${HELPER} plan-filter-phase cwd=${SUITE_HOME} timeout=120s on_timeout=kill
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} wf10-plan-filter-phase-ok
|
||||
Should Contain ${result.stdout} phase: strategize
|
||||
|
||||
WF10 Batch Execute Graceful
|
||||
[Documentation] Execute plans in batch with graceful mock-AI handling.
|
||||
[Tags] plan execute batch
|
||||
${result}= Run Process ${PYTHON} ${HELPER} batch-execute cwd=${SUITE_HOME} timeout=120s on_timeout=kill
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} wf10-batch-execute-ok
|
||||
Should Contain ${result.stdout} plan execute
|
||||
|
||||
WF10 Error Handling Invalid Action
|
||||
[Documentation] Demonstrate graceful error when referencing invalid action.
|
||||
[Tags] plan error negative
|
||||
${result}= Run Process ${PYTHON} ${HELPER} error-handling cwd=${SUITE_HOME} timeout=120s on_timeout=kill
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} wf10-error-handling-ok
|
||||
Should Contain ${result.stdout} error-rc:
|
||||
|
||||
WF10 Full Auto Profile Verification
|
||||
[Documentation] Verify full-auto automation profile is accepted.
|
||||
[Tags] plan profile full-auto
|
||||
${result}= Run Process ${PYTHON} ${HELPER} full-auto-profile cwd=${SUITE_HOME} timeout=120s on_timeout=kill
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} wf10-full-auto-profile-ok
|
||||
Should Contain ${result.stdout} automation-profile: full-auto
|
||||
Reference in New Issue
Block a user