diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-07-11 19:57:02 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-07-11 19:57:02 +0900 |
| commit | e16782c208320138d77b88f2ff359e3e9e059e76 (patch) | |
| tree | 5e19fde4fa841b9b3f2ddef9f5efdb342ba6e911 /tests | |
| parent | c2e5098d030fe902eff258e10d2fefc01226e4bf (diff) | |
fix: レビュー指摘事項の修正(エラー戻り値統一、タイ延長、ネストリピート、専用エラーコード)
High severity:
- compose action エラー戻り値の validation キー統一(server.py)
- ppmck/pyxel タイ & の duration 延長修正(_tie_pending フラグ導入)
- pyxel ネストリピート対応(RepeatFrame + イベントスナップショット方式)
Medium severity:
- 専用エラーコード追加: VALIDATION_MISSING_PARAMETER, VALIDATION_INVALID_MODE, VALIDATION_INVALID_ACTION
- SYSTEM_INTERNAL_ERROR キャッチ範囲拡大(compose/validate全体をtry/except)
Low severity:
- 設計書 line/column を1始まりに明記、APIレベルエラーコード追記
テスト: 129 passed, ruff: All checks passed
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_compose_mml_tool.py | 8 | ||||
| -rw-r--r-- | tests/test_error_codes.py | 41 | ||||
| -rw-r--r-- | tests/test_parser_detailed.py | 37 |
3 files changed, 80 insertions, 6 deletions
diff --git a/tests/test_compose_mml_tool.py b/tests/test_compose_mml_tool.py index af1e3e2..eb1b093 100644 --- a/tests/test_compose_mml_tool.py +++ b/tests/test_compose_mml_tool.py @@ -85,13 +85,13 @@ def test_template_invalid_fallback(compose_mml): def test_compose_missing_mml(compose_mml): result = compose_mml(action="compose", mml="", mode="ppmck") assert result["success"] is False - assert any(e["severity"] == "error" for e in result["errors"]) + assert any(e["severity"] == "error" for e in result["validation"]["errors"]) def test_compose_missing_mode(compose_mml): result = compose_mml(action="compose", mml="A c", mode="") assert result["success"] is False - assert any(e["severity"] == "error" for e in result["errors"]) + assert any(e["severity"] == "error" for e in result["validation"]["errors"]) def test_validate_missing_mode(compose_mml): @@ -103,7 +103,7 @@ def test_compose_unknown_mode(compose_mml): result = compose_mml(action="compose", mml="A c", mode="xyz") assert result["success"] is False assert any( - e["code"] == "SYNTAX_INVALID_TOKEN" for e in result["validation"]["errors"] + e["code"] == "VALIDATION_INVALID_MODE" for e in result["validation"]["errors"] ) @@ -127,7 +127,7 @@ def test_compose_with_warning(compose_mml, tmp_output_dir): def test_action_invalid(compose_mml): result = compose_mml(action="unknown") assert result["success"] is False - assert any("action" in e["message"] for e in result["errors"]) + assert any("action" in e["message"] for e in result["validation"]["errors"]) def test_compose_sample_rates(compose_mml, tmp_output_dir): diff --git a/tests/test_error_codes.py b/tests/test_error_codes.py index 19b4575..496ce84 100644 --- a/tests/test_error_codes.py +++ b/tests/test_error_codes.py @@ -17,14 +17,16 @@ def _find_code(result, code, key="errors"): def _has_error(result): - return any(e["severity"] == "error" for e in result["validation"]["errors"]) + if "validation" in result: + return any(e["severity"] == "error" for e in result["validation"]["errors"]) + return any(e["severity"] == "error" for e in result["errors"]) # --- SYNTAX_INVALID_TOKEN --- def test_syntax_invalid_token_mode(compose_mml): result = compose_mml(action="compose", mml="A c", mode="xyz") assert result["success"] is False - assert _find_code(result, ErrorCode.SYNTAX_INVALID_TOKEN.value) + assert _find_code(result, ErrorCode.VALIDATION_INVALID_MODE.value) # --- SYNTAX_INVALID_NUMBER --- @@ -201,6 +203,27 @@ def test_syntax_unterminated_header(compose_mml): assert _find_code(result, ErrorCode.SYNTAX_UNTERMINATED_HEADER.value) +# --- VALIDATION_MISSING_PARAMETER --- +def test_validation_missing_parameter(compose_mml): + result = compose_mml(action="compose", mml="", mode="ppmck") + assert result["success"] is False + assert _find_code(result, ErrorCode.VALIDATION_MISSING_PARAMETER.value) + + +# --- VALIDATION_INVALID_MODE --- +def test_validation_invalid_mode(compose_mml): + result = compose_mml(action="compose", mml="A c", mode="xyz") + assert result["success"] is False + assert _find_code(result, ErrorCode.VALIDATION_INVALID_MODE.value) + + +# --- VALIDATION_INVALID_ACTION --- +def test_validation_invalid_action(compose_mml): + result = compose_mml(action="unknown") + assert result["success"] is False + assert _find_code(result, ErrorCode.VALIDATION_INVALID_ACTION.value) + + # --- SYSTEM_SYNTHESIS_FAILED --- def test_system_synthesis_failed(compose_mml): with patch( @@ -234,6 +257,20 @@ def test_system_wav_write_failed(tmp_path): # --- SYSTEM_INTERNAL_ERROR --- +def test_system_internal_error(compose_mml): + with patch( + "mml_composemusic_mcp.server._parse_mml", + side_effect=RuntimeError("unexpected internal error"), + ): + result = compose_mml( + action="compose", + mml="A t120 l4 o4\n c", + mode="ppmck", + ) + assert result["success"] is False + assert _find_code(result, ErrorCode.SYSTEM_INTERNAL_ERROR.value) + + def test_system_internal_error_not_exposed(compose_mml): result = compose_mml(action="compose", mml="A t120 l4 o4\n c", mode="ppmck") assert isinstance(result, dict) diff --git a/tests/test_parser_detailed.py b/tests/test_parser_detailed.py index 88d7095..3752562 100644 --- a/tests/test_parser_detailed.py +++ b/tests/test_parser_detailed.py @@ -143,6 +143,28 @@ def test_pyxel_repeat_count3(): assert len(notes) == 6 +def test_pyxel_nested_repeat(): + source = "0: T120 L4 O4\n[ [ C D ]2 E ]2" + tokens = tokenize(source, "pyxel") + ns, errors = parse_pyxel(source, tokens) + assert not any(e.severity == "error" for e in errors) + notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)] + # Inner: [C D]2 -> C D C D (4 notes) + # Outer: [ ... E ]2 -> (C D C D E) * 2 = 10 notes + assert len(notes) == 10 + + +def test_pyxel_nested_repeat_count3(): + source = "0: T120 L4 O4\n[ [ C D ]2 E ]3" + tokens = tokenize(source, "pyxel") + ns, errors = parse_pyxel(source, tokens) + assert not any(e.severity == "error" for e in errors) + notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)] + # Inner: [C D]2 -> C D C D (4 notes) + # Outer: [ ... E ]3 -> (C D C D E) * 3 = 15 notes + assert len(notes) == 15 + + # --- Tie --- @@ -152,6 +174,21 @@ def test_ppmck_tie_note(): ns, errors = parse_ppmck(source, tokens) # Tie should merge notes; check no error assert not any(e.severity == "error" for e in errors) + notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)] + # c4 & c4 should produce a single note with doubled duration (192*2=384) + assert len(notes) == 1 + assert notes[0].duration == 384 + + +def test_pyxel_tie_note(): + source = "0: T120 L4 O4\n C4 & C4" + tokens = tokenize(source, "pyxel") + ns, errors = parse_pyxel(source, tokens) + assert not any(e.severity == "error" for e in errors) + notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)] + # C4 & C4 should produce a single note with doubled duration (192*2=384) + assert len(notes) == 1 + assert notes[0].duration == 384 def test_pyxel_tie_length(): |
