forked from HAL9000/cleveragents-core
1878998b7a
Rename the TDD tag system from tdd_bug/tdd_bug_<N> to tdd_issue/tdd_issue_<N> across the entire codebase. The tdd_expected_fail tag is unchanged. The TDD expected-failure workflow is not limited to bug fixes — it applies equally to any issue type (features, tasks, refactors). The _bug suffix was misleading and narrowed the perceived scope. The new _issue suffix accurately reflects that the TDD tagging system applies to any Forgejo issue. Changes span 92 files: - features/environment.py: validate_tdd_tags(), should_invert_result(), and apply_tdd_inversion() updated — regex, variables, error messages - robot/tdd_expected_fail_listener.py: _validate_tdd_tags(), _should_invert_result(), start_test(), end_test() updated consistently - 33 Behave .feature files: all @tdd_bug/@tdd_bug_<N> tags renamed - 29 Robot .robot files: all tdd_bug/tdd_bug_<N> tags renamed - 3 Robot fixture files renamed (tdd_bug_alone, tdd_missing_tdd_bug, tdd_expected_fail_missing_bug_n) with content and references updated - Tag validation tests and helpers updated (function names, command dispatch keys, output strings, fixture references) - CONTRIBUTING.md: section renamed from 'TDD Bug Test Tags' to 'TDD Issue Test Tags', all tag references and examples updated - noxfile.py: comment references updated - Step definition files, mock helpers, and benchmark files: docstring references updated ISSUES CLOSED: #965
200 lines
7.1 KiB
Python
200 lines
7.1 KiB
Python
"""Step definitions for TDD Issue #980 — skill add cross-process persistence.
|
|
|
|
These steps capture bug #980 by exercising the *real* CLI code path
|
|
across independent process-like invocations. The existing persistence
|
|
tests (``skill_add_persist.feature``) verify round-trip within the
|
|
same Python process — they create two ``SkillService`` instances
|
|
sharing the same in-memory SQLAlchemy session factory. That approach
|
|
cannot detect the cross-process regression introduced by PR #640.
|
|
|
|
This test:
|
|
|
|
1. Points the CLI at the per-scenario SQLite database (already set up
|
|
by ``before_scenario`` with all migrations applied).
|
|
2. Runs ``agents skill add --config <file> --format json`` via the
|
|
CLI app with a fresh DI container (simulating process 1).
|
|
3. Resets the DI container and runs ``agents skill list --format json``
|
|
(simulating process 2) — the SkillService is rebuilt from scratch.
|
|
4. Asserts the skill appears in the listing.
|
|
|
|
The ``@tdd_expected_fail`` tag on the feature inverts the assertion
|
|
failure to a CI pass while the bug is unfixed. The root cause is that
|
|
``_build_skill_service`` creates a standalone engine that connects to
|
|
the database but the ``skills`` table does not exist in databases
|
|
initialised by ``agents init`` (the Alembic migration for skills was
|
|
never added, or the skill service bypasses migration-managed tables).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import os
|
|
import tempfile
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.application.container import reset_container
|
|
from cleveragents.cli.main import app as root_app
|
|
|
|
runner = CliRunner()
|
|
|
|
_SKILL_YAML = """\
|
|
name: local/tdd-cross-process
|
|
description: "TDD cross-process persistence test skill"
|
|
|
|
tools:
|
|
- name: builtin/read_file
|
|
"""
|
|
|
|
_SKILL_NAME = "local/tdd-cross-process"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a cross-process skill persistence environment")
|
|
def step_cross_process_env(context: Context) -> None:
|
|
"""Write a skill YAML config file for use in subsequent steps.
|
|
|
|
The per-scenario database is already set up by ``before_scenario``
|
|
in ``environment.py`` (with migrations applied via the template-DB
|
|
fast path). We only need to create the skill YAML and ensure the
|
|
DI container starts from a clean state.
|
|
"""
|
|
# Write skill YAML config to a temp file
|
|
fd, config_path = tempfile.mkstemp(suffix=".yaml", prefix="tdd_skill_")
|
|
with os.fdopen(fd, "w") as fh:
|
|
fh.write(_SKILL_YAML)
|
|
context.tdd_skill_config_path = config_path
|
|
|
|
# Register cleanup for the temp config file
|
|
def _cleanup() -> None:
|
|
with contextlib.suppress(OSError):
|
|
os.unlink(config_path)
|
|
reset_container()
|
|
|
|
context.add_cleanup(_cleanup)
|
|
|
|
# Start with a clean container
|
|
reset_container()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I add a skill via a CLI subprocess")
|
|
def step_add_skill_subprocess(context: Context) -> None:
|
|
"""Run ``agents skill add --config <file> --format json``.
|
|
|
|
Simulates CLI process 1. After this call, the DI container and its
|
|
in-memory SkillService hold the newly added skill. The skill
|
|
*should* also be persisted to the SQLite database so that process 2
|
|
can see it.
|
|
"""
|
|
result = runner.invoke(
|
|
root_app,
|
|
[
|
|
"skill",
|
|
"add",
|
|
"--config",
|
|
context.tdd_skill_config_path,
|
|
"--format",
|
|
"json",
|
|
],
|
|
)
|
|
context.tdd_skill_add_output = result.output
|
|
context.tdd_skill_add_exit_code = result.exit_code
|
|
|
|
|
|
@when("I list skills via a separate CLI subprocess")
|
|
def step_list_skills_subprocess(context: Context) -> None:
|
|
"""Run ``agents skill list --format json`` with a fresh DI container.
|
|
|
|
The DI container is reset before the call to simulate a completely
|
|
independent CLI process — this is the cross-process boundary that
|
|
exposes bug #980. The new SkillService instance must load skills
|
|
from the database to find the skill added in the previous step.
|
|
"""
|
|
# Simulate cross-process boundary: fresh container = fresh SkillService
|
|
reset_container()
|
|
|
|
result = runner.invoke(
|
|
root_app,
|
|
["skill", "list", "--format", "json"],
|
|
)
|
|
context.tdd_skill_list_output = result.output
|
|
context.tdd_skill_list_exit_code = result.exit_code
|
|
|
|
|
|
@when("I show the skill via a separate CLI subprocess")
|
|
def step_show_skill_subprocess(context: Context) -> None:
|
|
"""Run ``agents skill show <name> --format json`` with a fresh container.
|
|
|
|
The DI container is reset before the call to simulate a completely
|
|
independent CLI process.
|
|
"""
|
|
# Simulate cross-process boundary
|
|
reset_container()
|
|
|
|
result = runner.invoke(
|
|
root_app,
|
|
["skill", "show", _SKILL_NAME, "--format", "json"],
|
|
)
|
|
context.tdd_skill_show_output = result.output
|
|
context.tdd_skill_show_exit_code = result.exit_code
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("the cross-process skill list should contain the added skill")
|
|
def step_assert_skill_in_list(context: Context) -> None:
|
|
"""Assert that the skill added in one process is visible in another.
|
|
|
|
Checks the output of ``skill list`` for the skill name. This
|
|
assertion will FAIL while bug #980 is present because the second
|
|
process creates a fresh SkillService that does not find the skill
|
|
in the database (the ``skills`` table is missing or was never
|
|
populated due to the DB-fallback in ``_build_skill_service``).
|
|
|
|
The ``@tdd_expected_fail`` tag inverts this failure to a CI pass.
|
|
"""
|
|
list_output = context.tdd_skill_list_output
|
|
|
|
assert _SKILL_NAME in list_output, (
|
|
f"Expected skill '{_SKILL_NAME}' in cross-process skill list output "
|
|
f"but it was not found.\n"
|
|
f"Add exit code: {context.tdd_skill_add_exit_code}\n"
|
|
f"Add output:\n{context.tdd_skill_add_output}\n"
|
|
f"List exit code: {context.tdd_skill_list_exit_code}\n"
|
|
f"List output:\n{list_output}"
|
|
)
|
|
|
|
|
|
@then("the cross-process skill show output should contain the skill name")
|
|
def step_assert_skill_show_contains_name(context: Context) -> None:
|
|
"""Assert that ``skill show`` in a separate process finds the skill.
|
|
|
|
This assertion will FAIL while bug #980 is present because the
|
|
second process cannot find the skill in its fresh service instance.
|
|
"""
|
|
show_output = context.tdd_skill_show_output
|
|
show_exit = context.tdd_skill_show_exit_code
|
|
|
|
assert show_exit == 0 and _SKILL_NAME in show_output, (
|
|
f"Expected skill '{_SKILL_NAME}' visible via cross-process "
|
|
f"'skill show' but it was not found.\n"
|
|
f"Show exit code: {show_exit}\n"
|
|
f"Show output:\n{show_output}\n"
|
|
f"Add exit code: {context.tdd_skill_add_exit_code}\n"
|
|
f"Add output:\n{context.tdd_skill_add_output}"
|
|
)
|