diff --git a/README.md b/README.md index 9745020db..509e6536b 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/README_CN.md b/README_CN.md index 48fe18faf..6434d70e0 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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) diff --git a/wavetone/agent-harness/cli_anything/wavetone/README.md b/wavetone/agent-harness/cli_anything/wavetone/README.md index 31ce407a8..69f6d2593 100644 --- a/wavetone/agent-harness/cli_anything/wavetone/README.md +++ b/wavetone/agent-harness/cli_anything/wavetone/README.md @@ -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`. diff --git a/wavetone/agent-harness/cli_anything/wavetone/tests/TEST.md b/wavetone/agent-harness/cli_anything/wavetone/tests/TEST.md index 801e5794f..863aa6bc5 100644 --- a/wavetone/agent-harness/cli_anything/wavetone/tests/TEST.md +++ b/wavetone/agent-harness/cli_anything/wavetone/tests/TEST.md @@ -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 diff --git a/wavetone/agent-harness/cli_anything/wavetone/tests/test_core.py b/wavetone/agent-harness/cli_anything/wavetone/tests/test_core.py index e747182c7..4472cf8ab 100644 --- a/wavetone/agent-harness/cli_anything/wavetone/tests/test_core.py +++ b/wavetone/agent-harness/cli_anything/wavetone/tests/test_core.py @@ -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) diff --git a/wavetone/agent-harness/cli_anything/wavetone/tests/test_full_e2e.py b/wavetone/agent-harness/cli_anything/wavetone/tests/test_full_e2e.py index c3e9ab45c..2ceb45661 100644 --- a/wavetone/agent-harness/cli_anything/wavetone/tests/test_full_e2e.py +++ b/wavetone/agent-harness/cli_anything/wavetone/tests/test_full_e2e.py @@ -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"]) diff --git a/wavetone/agent-harness/cli_anything/wavetone/wavetone_cli.py b/wavetone/agent-harness/cli_anything/wavetone/wavetone_cli.py index fc2604042..2df5646f0 100644 --- a/wavetone/agent-harness/cli_anything/wavetone/wavetone_cli.py +++ b/wavetone/agent-harness/cli_anything/wavetone/wavetone_cli.py @@ -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)