mirror of
https://github.com/HKUDS/CLI-Anything.git
synced 2026-10-30 18:12:56 +08:00
fix: make WaveTone e2e resolver deterministic
This commit is contained in:
@@ -1228,7 +1228,7 @@ gimp 107 passed ✅ (64 unit + 43 e2e)
|
||||
blender 208 passed ✅ (150 unit + 58 e2e)
|
||||
inkscape 202 passed ✅ (148 unit + 54 e2e)
|
||||
audacity 161 passed ✅ (107 unit + 54 e2e)
|
||||
wavetone 28 passed ✅ (23 unit + 5 default e2e, 2 backend-gated e2e skipped; 30 passed opt-in)
|
||||
wavetone 30 passed ✅ (24 unit + 6 default e2e, 2 backend-gated e2e skipped; 32 passed opt-in)
|
||||
libreoffice 158 passed ✅ (89 unit + 69 e2e)
|
||||
mubu 96 passed ✅ (85 unit + 11 e2e)
|
||||
obs-studio 153 passed ✅ (116 unit + 37 e2e)
|
||||
@@ -1308,7 +1308,7 @@ cli-anything/
|
||||
├── 🧊 blender/agent-harness/ # Blender CLI (208 tests)
|
||||
├── ✏️ inkscape/agent-harness/ # Inkscape CLI (202 tests)
|
||||
├── 🎵 audacity/agent-harness/ # Audacity CLI (161 tests)
|
||||
├── wavetone/agent-harness/ # WaveTone CLI (30 tests: 28 default + 2 backend-gated e2e)
|
||||
├── wavetone/agent-harness/ # WaveTone CLI (32 tests: 30 default + 2 backend-gated e2e)
|
||||
├── 🌐 browser/agent-harness/ # Browser CLI (DOMShell MCP, new)
|
||||
├── 📄 libreoffice/agent-harness/ # LibreOffice CLI (158 tests)
|
||||
├── 📧 mailchimp/agent-harness/ # Mailchimp Marketing API CLI (303 commands, 36 unit tests)
|
||||
|
||||
+2
-2
@@ -679,7 +679,7 @@ gimp 107 passed ✅ (64 unit + 43 e2e)
|
||||
blender 208 passed ✅ (150 unit + 58 e2e)
|
||||
inkscape 202 passed ✅ (148 unit + 54 e2e)
|
||||
audacity 161 passed ✅ (107 unit + 54 e2e)
|
||||
wavetone 28 passed ✅ (23 unit + 5 default e2e,2 backend-gated e2e skipped;30 passed opt-in)
|
||||
wavetone 30 passed ✅ (24 unit + 6 default e2e,2 backend-gated e2e skipped;32 passed opt-in)
|
||||
libreoffice 158 passed ✅ (89 unit + 69 e2e)
|
||||
obs-studio 153 passed ✅ (116 unit + 37 e2e)
|
||||
kdenlive 155 passed ✅ (111 unit + 44 e2e)
|
||||
@@ -753,7 +753,7 @@ cli-anything/
|
||||
├── 🧊 blender/agent-harness/ # Blender CLI(208 项测试)
|
||||
├── ✏️ inkscape/agent-harness/ # Inkscape CLI(202 项测试)
|
||||
├── 🎵 audacity/agent-harness/ # Audacity CLI(161 项测试)
|
||||
├── wavetone/agent-harness/ # WaveTone CLI(30 项测试:28 default + 2 backend-gated e2e)
|
||||
├── wavetone/agent-harness/ # WaveTone CLI(32 项测试:30 default + 2 backend-gated e2e)
|
||||
├── 📄 libreoffice/agent-harness/ # LibreOffice CLI(158 项测试)
|
||||
├── 📚 zotero/agent-harness/ # Zotero CLI(新增,支持文献导入)
|
||||
├── 📓 joplin/agent-harness/ # Joplin CLI(134 测试:107 单元 + 27 e2e)
|
||||
|
||||
@@ -56,6 +56,5 @@ without WaveTone can still run the CLI-only suite. To opt into the real Windows
|
||||
smoke, set `CLI_ANYTHING_WAVETONE_REAL_BACKEND=1` plus `WAVETONE_EXE` or
|
||||
`WAVETONE_HOME` pointing at a WaveTone 2.61 extraction with `wavetone.exe` and
|
||||
the bundled `data` directory. E2E subprocess tests run the in-tree
|
||||
`python -m cli_anything.wavetone.wavetone_cli` module by default; set
|
||||
`CLI_ANYTHING_FORCE_INSTALLED=1` only when intentionally testing an installed
|
||||
entry point.
|
||||
`python -m cli_anything.wavetone.wavetone_cli` module by default and do not
|
||||
silently prefer an installed `cli-anything-wavetone` from `PATH`.
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
## Test Inventory Plan
|
||||
|
||||
- `test_core.py`: 23 unit tests for project manifests, audio probing, session logs,
|
||||
- `test_core.py`: 24 unit tests for project manifests, audio probing, session logs,
|
||||
and backend discovery.
|
||||
- `test_full_e2e.py`: 7 tests covering default real-backend opt-in gating,
|
||||
- `test_full_e2e.py`: 8 tests covering default real-backend opt-in gating,
|
||||
source-module subprocess resolution, CLI workflows, and real WaveTone launch
|
||||
smoke coverage.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
- Add labels in sorted time order.
|
||||
- Set tempo and analysis options.
|
||||
- Preserve omitted CLI boolean analysis flags as tri-state values.
|
||||
- Require attached WFD analysis files to exist and use the `.wfd` suffix.
|
||||
- Reject non-finite numeric project values before JSON serialization.
|
||||
- Probe a generated WAV file with the Python stdlib.
|
||||
- Fall back to stat metadata for malformed WAV files.
|
||||
@@ -48,7 +49,8 @@ Operations:
|
||||
|
||||
1. Generate a real WAV fixture.
|
||||
2. Resolve the in-tree `python -m cli_anything.wavetone.wavetone_cli` module by
|
||||
default, even if an installed `cli-anything-wavetone` exists in `PATH`.
|
||||
default, even if an installed `cli-anything-wavetone` exists in `PATH` or
|
||||
`CLI_ANYTHING_FORCE_INSTALLED` is set externally.
|
||||
3. Run `cli-anything-wavetone --json project new`.
|
||||
4. Run `project set-tempo`.
|
||||
5. Run `project add-label`.
|
||||
@@ -86,7 +88,7 @@ Default command:
|
||||
|
||||
```bash
|
||||
$env:PATH = "$env:APPDATA\Python\Python313\Scripts;$env:PATH"
|
||||
Remove-Item Env:CLI_ANYTHING_FORCE_INSTALLED -ErrorAction SilentlyContinue
|
||||
$env:CLI_ANYTHING_FORCE_INSTALLED = "1"
|
||||
Remove-Item Env:CLI_ANYTHING_WAVETONE_REAL_BACKEND -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:WAVETONE_EXE -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:WAVETONE_HOME -ErrorAction SilentlyContinue
|
||||
@@ -96,7 +98,7 @@ python -m pytest cli_anything\wavetone\tests\ -v -s
|
||||
Default result:
|
||||
|
||||
```text
|
||||
collected 30 items
|
||||
collected 32 items
|
||||
|
||||
cli_anything/wavetone/tests/test_core.py::test_create_project_manifest PASSED
|
||||
cli_anything/wavetone/tests/test_core.py::test_rejects_unsupported_audio PASSED
|
||||
@@ -104,6 +106,7 @@ cli_anything/wavetone/tests/test_core.py::test_save_load_project_roundtrip PASSE
|
||||
cli_anything/wavetone/tests/test_core.py::test_labels_are_sorted PASSED
|
||||
cli_anything/wavetone/tests/test_core.py::test_update_analysis_settings PASSED
|
||||
cli_anything/wavetone/tests/test_core.py::test_cli_analysis_preserves_omitted_boolean_flags PASSED
|
||||
cli_anything/wavetone/tests/test_core.py::test_cli_attach_wfd_requires_existing_wfd_file PASSED
|
||||
cli_anything/wavetone/tests/test_core.py::test_rejects_non_finite_project_numbers PASSED
|
||||
cli_anything/wavetone/tests/test_core.py::test_load_project_rejects_non_object_json PASSED
|
||||
cli_anything/wavetone/tests/test_core.py::test_probe_wav_metadata PASSED
|
||||
@@ -123,20 +126,21 @@ cli_anything/wavetone/tests/test_core.py::test_repl_skin_uses_wavetone_branding_
|
||||
cli_anything/wavetone/tests/test_core.py::test_repl_reports_click_exit_without_unexpected_error PASSED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::test_real_backend_requires_explicit_opt_in PASSED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::test_resolve_cli_defaults_to_source_module PASSED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::test_resolve_cli_uses_installed_only_when_requested PASSED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::TestCLISubprocess::test_help PASSED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::TestCLISubprocess::test_project_audio_workflow_json PASSED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::TestCLISubprocess::test_formats_json PASSED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::TestRealWaveToneBackend::test_doctor_real_backend SKIPPED
|
||||
cli_anything/wavetone/tests/test_full_e2e.py::TestRealWaveToneBackend::test_launch_real_backend_with_wav SKIPPED
|
||||
|
||||
28 passed, 2 skipped in 2.10s
|
||||
30 passed, 2 skipped in 1.44s
|
||||
```
|
||||
|
||||
Real backend opt-in command:
|
||||
|
||||
```bash
|
||||
$env:PATH = "$env:APPDATA\Python\Python313\Scripts;$env:PATH"
|
||||
Remove-Item Env:CLI_ANYTHING_FORCE_INSTALLED -ErrorAction SilentlyContinue
|
||||
$env:CLI_ANYTHING_FORCE_INSTALLED = "1"
|
||||
$env:CLI_ANYTHING_WAVETONE_REAL_BACKEND = "1"
|
||||
$env:WAVETONE_HOME = "C:\Users\Hp\Desktop\wavetone2.6.1"
|
||||
Remove-Item Env:WAVETONE_EXE -ErrorAction SilentlyContinue
|
||||
@@ -146,22 +150,23 @@ python -m pytest cli_anything\wavetone\tests\ -v -s
|
||||
Real backend result:
|
||||
|
||||
```text
|
||||
30 passed in 3.44s
|
||||
32 passed in 3.03s
|
||||
```
|
||||
|
||||
## Coverage Notes
|
||||
|
||||
- Unit tests cover manifest creation, validation, persistence, labels, tempo,
|
||||
analysis settings, CLI analysis flag tri-state behavior, finite numeric
|
||||
validation, project JSON schema validation, audio probing, malformed WAV
|
||||
fallback, session logs, session schema and payload validation, backend
|
||||
discovery, backend data artifact validation, ffprobe argument construction,
|
||||
ffprobe metadata parsing, inherited CLI project and JSON context, failed
|
||||
launch smoke reporting, launch runtime error reporting, Windows launch gating,
|
||||
REPL Windows path splitting, REPL branding and local skill path display, and
|
||||
REPL Click exit handling.
|
||||
validation, WFD attachment validation, project JSON schema validation, audio
|
||||
probing, malformed WAV fallback, session logs, session schema and payload
|
||||
validation, backend discovery, backend data artifact validation, ffprobe
|
||||
argument construction, ffprobe metadata parsing, inherited CLI project and
|
||||
JSON context, failed launch smoke reporting, launch runtime error reporting,
|
||||
Windows launch gating, REPL Windows path splitting, REPL branding and local
|
||||
skill path display, and REPL Click exit handling.
|
||||
- CLI subprocess tests resolve and use the in-tree
|
||||
`python -m cli_anything.wavetone.wavetone_cli` module by default.
|
||||
`python -m cli_anything.wavetone.wavetone_cli` module by default, regardless
|
||||
of ambient `CLI_ANYTHING_FORCE_INSTALLED`.
|
||||
- Real backend coverage launches `C:\Users\Hp\Desktop\wavetone2.6.1\wavetone.exe`
|
||||
with a generated WAV and terminates it after a short wait.
|
||||
- Real backend tests are skipped by default unless
|
||||
|
||||
@@ -99,6 +99,30 @@ def test_cli_analysis_preserves_omitted_boolean_flags(tmp_path: Path) -> None:
|
||||
assert loaded["analysis"]["skip_analysis_dialog"] is True
|
||||
|
||||
|
||||
def test_cli_attach_wfd_requires_existing_wfd_file(tmp_path: Path) -> None:
|
||||
wav = make_wav(tmp_path / "tone.wav")
|
||||
project_path = save_project(create_project(wav), tmp_path / "tone.wt.json")
|
||||
|
||||
missing = tmp_path / "missing.wfd"
|
||||
result = CliRunner().invoke(cli, ["--project", str(project_path), "project", "attach-wfd", str(missing)])
|
||||
assert result.exit_code != 0
|
||||
assert "does not exist" in result.output
|
||||
|
||||
wrong_suffix = tmp_path / "analysis.txt"
|
||||
wrong_suffix.write_text("wfd", encoding="utf-8")
|
||||
result = CliRunner().invoke(cli, ["--project", str(project_path), "project", "attach-wfd", str(wrong_suffix)])
|
||||
assert result.exit_code == 1
|
||||
assert ".wfd" in result.output
|
||||
|
||||
wfd_path = tmp_path / "analysis.wfd"
|
||||
wfd_path.write_text("wfd", encoding="utf-8")
|
||||
result = CliRunner().invoke(cli, ["--project", str(project_path), "--json", "project", "attach-wfd", str(wfd_path)])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
data = json.loads(result.output)
|
||||
assert data["wfd_path"] == str(wfd_path.resolve())
|
||||
|
||||
|
||||
def test_rejects_non_finite_project_numbers(tmp_path: Path) -> None:
|
||||
wav = make_wav(tmp_path / "tone.wav")
|
||||
project = create_project(wav)
|
||||
|
||||
@@ -15,11 +15,10 @@ from cli_anything.wavetone.utils import wavetone_backend
|
||||
REAL_BACKEND_ENV = "CLI_ANYTHING_WAVETONE_REAL_BACKEND"
|
||||
|
||||
|
||||
def _resolve_cli(name: str) -> list[str]:
|
||||
def _resolve_cli(name: str, force_installed: bool = False) -> list[str]:
|
||||
"""Run the in-tree module by default; installed entry points are opt-in."""
|
||||
force = os.environ.get("CLI_ANYTHING_FORCE_INSTALLED", "").strip() == "1"
|
||||
module = "cli_anything.wavetone.wavetone_cli"
|
||||
if not force:
|
||||
if not force_installed:
|
||||
print(f"[_resolve_cli] Using source module: {sys.executable} -m {module}")
|
||||
return [sys.executable, "-m", module]
|
||||
|
||||
@@ -66,17 +65,21 @@ def test_real_backend_requires_explicit_opt_in(monkeypatch: pytest.MonkeyPatch)
|
||||
|
||||
|
||||
def test_resolve_cli_defaults_to_source_module(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("CLI_ANYTHING_FORCE_INSTALLED", raising=False)
|
||||
monkeypatch.setattr(shutil, "which", lambda name: "C:/foreign/cli-anything-wavetone.exe")
|
||||
monkeypatch.setenv("CLI_ANYTHING_FORCE_INSTALLED", "1")
|
||||
monkeypatch.setattr(shutil, "which", lambda name: pytest.fail("default resolver should not inspect PATH"))
|
||||
|
||||
assert _resolve_cli("cli-anything-wavetone") == [sys.executable, "-m", "cli_anything.wavetone.wavetone_cli"]
|
||||
|
||||
|
||||
class TestCLISubprocess:
|
||||
CLI_BASE = _resolve_cli("cli-anything-wavetone")
|
||||
def test_resolve_cli_uses_installed_only_when_requested(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(shutil, "which", lambda name: "C:/installed/cli-anything-wavetone.exe")
|
||||
|
||||
assert _resolve_cli("cli-anything-wavetone", force_installed=True) == ["C:/installed/cli-anything-wavetone.exe"]
|
||||
|
||||
|
||||
class TestCLISubprocess:
|
||||
def _run(self, args: list[str], check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(self.CLI_BASE + args, capture_output=True, text=True, check=check)
|
||||
return subprocess.run(_resolve_cli("cli-anything-wavetone") + args, capture_output=True, text=True, check=check)
|
||||
|
||||
def test_help(self) -> None:
|
||||
result = self._run(["--help"])
|
||||
@@ -115,10 +118,8 @@ class TestCLISubprocess:
|
||||
|
||||
@pytest.mark.skipif(not _REAL_BACKEND_READY, reason=_REAL_BACKEND_SKIP_REASON)
|
||||
class TestRealWaveToneBackend:
|
||||
CLI_BASE = _resolve_cli("cli-anything-wavetone")
|
||||
|
||||
def _run(self, args: list[str], check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(self.CLI_BASE + args, capture_output=True, text=True, check=check)
|
||||
return subprocess.run(_resolve_cli("cli-anything-wavetone") + args, capture_output=True, text=True, check=check)
|
||||
|
||||
def test_doctor_real_backend(self) -> None:
|
||||
result = self._run(["--json", "wavetone", "doctor"])
|
||||
|
||||
@@ -227,12 +227,14 @@ def project_analysis(
|
||||
|
||||
|
||||
@project_group.command("attach-wfd")
|
||||
@click.argument("wfd_path", type=click.Path(dir_okay=False))
|
||||
@click.argument("wfd_path", type=click.Path(exists=True, dir_okay=False))
|
||||
@click.argument("project_path", required=False, type=click.Path(exists=True, dir_okay=False))
|
||||
@click.pass_context
|
||||
def project_attach_wfd(ctx: click.Context, wfd_path: str, project_path: str | None) -> None:
|
||||
"""Attach a WFD analysis file saved by WaveTone."""
|
||||
try:
|
||||
if Path(wfd_path).suffix.lower() != ".wfd":
|
||||
raise ValueError("WFD path must use the .wfd extension")
|
||||
project, path = load_ctx_project(ctx, project_path)
|
||||
set_wfd_path(project, wfd_path)
|
||||
save_project(project, path)
|
||||
|
||||
Reference in New Issue
Block a user