---
date: 2025-10-10T12:31:12+08:00
author: Claude (Sonnet 4.5)
git_commit: 5f3f70c2c907aa17122b488f2436f4b108138ab7
branch: makefile
repository: fwauto
status: draft
research_reference: .ai/rpi/research/2025-10-10-wsl-test-failures.md
tags: [testing, cross-platform, makefile, mock, bugfix]
---

# 修復 WSL 環境測試失敗 Implementation Plan

## Overview

修復 2 個在 WSL 環境下失敗的測試，這些測試在 Windows 韌體開發環境下正常運作。根據研究發現，失敗原因分為兩個獨立問題：

1. **Makefile Tab 字元問題** - `tests/fixtures/makefiles/build/success.mk` 使用字面 `\t` 字串而非真正的 Tab 字元
2. **Mock 測試策略問題** - `test_handles_prompt_loading_error` 測試的前置條件設定不當，導致 Mock 無法生效

## Current State Analysis

### 測試執行現況

執行 `uv run pytest` 結果：
- **46 個測試通過**
- **2 個測試失敗**:
  1. `tests/integration/test_build_integration.py::test_build_integration_success`
  2. `tests/test_story_006_error_handling.py::TestStory006ErrorHandlingAndUX::test_handles_prompt_loading_error`

### 問題 1: Makefile Tab 字元問題

**檔案**: `tests/fixtures/makefiles/build/success.mk:8`

**現況**:
```makefile
build:
\t@bash "$(MAKEFILE_DIR)/success_simple.sh"
```

- 第 8 行使用字面 `\t` 字串 (ASCII: `5c 74`)
- 應該使用真正的 Tab 字元 (ASCII: `09`)

**影響**:
- Makefile 解析器報錯：`Makefile:8: *** missing separator.  Stop.`
- 導致 `test_build_integration_success` 測試失敗

**對照檔案** (`compile_error.mk:12`):
```makefile
build:
	@python3 "$(MAKEFILE_DIR)/replay_output.py" keil_build_compile_error.txt
```
- 正確使用真正的 Tab 字元

### 問題 2: Mock 測試策略問題

**檔案**: `tests/test_story_006_error_handling.py:104-142`

**測試意圖**: 驗證當 `PromptManager.get_prompt()` 失敗時，能返回包含 "prompt" 的友善錯誤訊息

**實際問題**:
```python
test_state: FirmwareState = {
    # ...
    "log_file_path": "logs/uart.log",  # ❌ 檔案不存在
    # ...
}

with patch("src.fwauto.nodes.log.prompt_manager") as mock_pm:
    mock_pm.get_prompt.side_effect = Exception("Template not found")
    result = log_node(test_state)
    # Mock 永遠不會被調用，因為檔案檢查在 prompt loading 之前
```

**執行順序**:
1. `log_node` 執行檔案存在性檢查 (`log.py:55-61`)
2. 檔案不存在，返回 "Error: Log file not found"
3. 函數提前返回，永遠不會執行到 `prompt_manager.get_prompt()` (line 76)
4. Mock 完全沒有機會生效

**錯誤訊息**:
```
AssertionError: assert 'prompt' in 'error: log file not found: /home/kent/dev/playground/fwauto/logs/uart.log'
```

**成功的測試範例** (`test_returns_analysis_result_in_state:86`):
```python
test_state: FirmwareState = {
    # ...
    "log_file_path": "prompts/log.md",  # ✅ 使用存在的檔案
    # ...
}
```

### Key Discoveries

- **Makefile 語法嚴格性**: Makefile 要求 recipe 行必須以真正的 Tab 字元 (ASCII 0x09) 開頭
- **測試分層邏輯**: `log_node` 實現了多層防禦性檢查，要測試第 N 層錯誤處理，必須讓前 N-1 層全部通過
- **跨平台一致性**: 其他 3 個 build fixtures 都使用正確的 Tab，只有 `success.mk` 有問題
- **測試模式參考**: 同一測試檔案中的 `test_returns_analysis_result_in_state` 提供了正確的測試模式

## Desired End State

修復完成後：
- ✅ 所有 48 個測試通過 (`uv run pytest`)
- ✅ `success.mk` 使用真正的 Tab 字元，與其他 Makefile 一致
- ✅ `test_handles_prompt_loading_error` 能正確測試 prompt loading 錯誤處理

**驗證方式**:
```bash
# 所有測試通過
uv run pytest
# 輸出: 48 passed

# 特定測試通過
uv run pytest tests/integration/test_build_integration.py::test_build_integration_success -v
uv run pytest tests/test_story_006_error_handling.py::TestStory006ErrorHandlingAndUX::test_handles_prompt_loading_error -v
```

## What We're NOT Doing

本計劃**不包含**以下項目，以避免 scope creep：

- ❌ 修改其他 Makefile 檔案（已經正確）
- ❌ 重構 `log_node` 的錯誤處理邏輯
- ❌ 建立 pre-commit hook 檢查 Tab
- ❌ 建立自動化工具掃描 Makefile Tab 問題
- ❌ 修改其他測試檔案
- ❌ 調整 `.gitignore` 規則（已在前次 commit 修復）
- ❌ 修改測試架構或 replay pattern

## Implementation Approach

採用**最小化、低風險**的修復策略：

1. **Phase 1**: 修復 Makefile Tab 字元 - 直接編輯檔案替換字元
2. **Phase 2**: 修復 Mock 測試 - 調整測試條件使用存在的檔案

**優點**:
- 每個 Phase 都是獨立的、可驗證的
- 不影響產品程式碼，只修改測試相關檔案
- 修改範圍極小，降低引入新問題的風險

---

## Phase 1: 修復 Makefile Tab 字元問題

### Overview

修復 `tests/fixtures/makefiles/build/success.mk` 檔案第 8 行，將字面 `\t` 字串替換為真正的 Tab 字元，使其符合 Makefile 語法要求。

### Changes Required

#### 1. 修改 success.mk 檔案

**File**: `tests/fixtures/makefiles/build/success.mk`

**修改前** (第 8 行):
```makefile
\t@bash "$(MAKEFILE_DIR)/success_simple.sh"
```

**修改後** (第 8 行):
```makefile
	@bash "$(MAKEFILE_DIR)/success_simple.sh"
```

**重要**:
- 第 8 行開頭必須是**真正的 Tab 字元** (ASCII 0x09)
- 不是空格、不是 `\t` 字串
- 使用支援顯示 Tab 的編輯器（如 Neovim, VS Code）確認

**驗證 Tab 字元**:
```bash
# 使用 cat -A 確認
cat -A tests/fixtures/makefiles/build/success.mk | grep -A1 "^build:"
# 應該顯示:
# build:$
# ^I@bash "$(MAKEFILE_DIR)/success_simple.sh"$
#  ↑ ^I 表示真正的 Tab

# 或使用 hexdump
hexdump -C tests/fixtures/makefiles/build/success.mk | grep -A1 "build:"
# 第 8 行開頭應該是 09 (Tab)，不是 5c 74 (\t)
```

### Success Criteria

#### Automated Verification

- [x] Makefile 語法正確，無 syntax error: `make -f tests/fixtures/makefiles/build/success.mk -n build`
- [x] Tab 字元驗證通過: `cat -A tests/fixtures/makefiles/build/success.mk | grep "^I@bash"`
- [ ] Build integration 測試通過: `uv run pytest tests/integration/test_build_integration.py::test_build_integration_success -v`
- [ ] 所有 build integration 測試通過: `uv run pytest tests/integration/test_build_integration.py -v`

#### Manual Verification

- [ ] 使用文字編輯器打開檔案，確認第 8 行開頭顯示為 Tab 而非空格或 `\t` 字串
- [ ] 檢查檔案大小未異常增加（應該減少 1 byte，因為 `\t` 兩字元變成 Tab 一字元）

**Implementation Note**: 完成本階段並通過所有自動化驗證後，暫停並確認測試通過，再繼續 Phase 2。

---

## Phase 2: 修復 Mock 測試策略問題

### Overview

修改 `test_handles_prompt_loading_error` 測試，使用存在的檔案路徑，讓測試能通過檔案存在性檢查，真正測試到 prompt loading 錯誤處理邏輯。

### Changes Required

#### 1. 修改測試檔案路徑設定

**File**: `tests/test_story_006_error_handling.py`

**修改前** (第 130 行):
```python
"log_file_path": "logs/uart.log",  # ❌ 檔案不存在
```

**修改後** (第 130 行):
```python
"log_file_path": "prompts/log.md",  # ✅ 使用存在的檔案
```

**理由**:
- `prompts/log.md` 是專案中確實存在的檔案
- 與同檔案中成功的測試 `test_returns_analysis_result_in_state` 保持一致
- 能通過 `log_node` 的檔案存在性檢查 (`log.py:55-61`)
- 讓測試真正執行到 `prompt_manager.get_prompt()` (line 76)

**完整修改**:
```python
def test_handles_prompt_loading_error(self):
    """
    測試處理 prompt 載入錯誤

    Given: PromptManager 無法載入 template
    When: 調用 log_node
    Then: 應該返回友善的錯誤訊息
    """
    test_state: FirmwareState = {
        "user_prompt": "測試分析",
        "project_root": os.getcwd(),
        "project_number": "01",
        "project_initialized": True,
        "build_status": "pending",
        "build_errors": [],
        "build_log": "",
        "flash_status": "pending",
        "flash_errors": [],
        "flash_log": "",
        "iteration": 0,
        "max_retries": 3,
        "execution_mode": "log",
        "command": "log-analysis",
        "command_args": None,
        "mode_metadata": None,
        "command_error": None,
        "log_file_path": "prompts/log.md",  # ← 修改這裡
        "log_analysis_result": None,
    }

    # Mock PromptManager to raise exception
    with patch("src.fwauto.nodes.log.prompt_manager") as mock_pm:
        mock_pm.get_prompt.side_effect = Exception("Template not found")

        result = log_node(test_state)

        assert "log_analysis_result" in result
        assert "Error" in result["log_analysis_result"]
        assert "prompt" in result["log_analysis_result"].lower()
```

### Success Criteria

#### Automated Verification

- [ ] 修改後的測試通過: `uv run pytest tests/test_story_006_error_handling.py::TestStory006ErrorHandlingAndUX::test_handles_prompt_loading_error -v`
- [ ] 所有 error handling 測試通過: `uv run pytest tests/test_story_006_error_handling.py -v`
- [ ] 全部測試通過: `uv run pytest`
- [ ] 測試覆蓋率未降低: `uv run pytest --cov=src/fwauto/nodes/log --cov-report=term-missing`

#### Manual Verification

- [ ] 檢查測試確實驗證了 prompt loading 錯誤（而非檔案不存在錯誤）
- [ ] 確認錯誤訊息包含 "prompt" 關鍵字
- [ ] 確認 Mock 確實被調用（可透過 debug 或 print 驗證）

**Implementation Note**: 完成本階段並通過所有自動化驗證後，確認整個修復計劃完成。

---

## Testing Strategy

### Unit Tests

**Phase 1**:
- `test_build_integration_success` - 驗證 Makefile 能正確執行
- `test_build_integration_compile_error` - 確保其他測試未受影響
- `test_build_integration_missing_hex` - 確保其他測試未受影響

**Phase 2**:
- `test_handles_prompt_loading_error` - 驗證 prompt loading 錯誤處理
- `test_handles_missing_user_prompt` - 確保其他錯誤處理測試未受影響
- `test_returns_analysis_result_in_state` - 確保正常流程測試未受影響

### Integration Tests

執行完整測試套件確認無 regression:
```bash
# 執行所有測試
uv run pytest

# 執行特定模組
uv run pytest tests/integration/
uv run pytest tests/nodes/
```

### Manual Testing Steps

1. **驗證 Makefile Tab 修復**:
   ```bash
   # 進入測試專案臨時目錄
   cd /tmp/test_makefile

   # 複製修復後的 Makefile
   cp tests/fixtures/makefiles/build/success.mk .

   # 執行 make（應該成功）
   make -f success.mk build

   # 確認無 "missing separator" 錯誤
   ```

2. **驗證 Mock 測試修復**:
   ```bash
   # 執行測試並顯示詳細輸出
   uv run pytest tests/test_story_006_error_handling.py::TestStory006ErrorHandlingAndUX::test_handles_prompt_loading_error -v -s

   # 確認:
   # 1. 測試通過
   # 2. 錯誤訊息包含 "prompt"
   # 3. 無 "Log file not found" 訊息
   ```

3. **整體回歸測試**:
   ```bash
   # 執行完整測試套件
   uv run pytest -v

   # 確認所有 48 個測試通過
   # 無新的失敗測試
   ```

## Performance Considerations

**本修復無 performance 影響**:
- Phase 1: 只修改測試 fixture，不影響執行時效能
- Phase 2: 只修改測試程式碼，不影響產品程式碼

## Migration Notes

**無需 migration**:
- 修改僅影響測試環境
- 不影響產品程式碼或資料結構
- 不需要更新文檔或配置

## References

- **研究文檔**: `.ai/rpi/research/2025-10-10-wsl-test-failures.md`
- **相關 Commit**: `5f3f70c` - 加入 build fixtures
- **Makefile 語法參考**: [GNU Make Manual - Rule Syntax](https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html)
- **相關程式碼**:
  - `src/fwauto/nodes/log.py:55-61` - 檔案存在性檢查
  - `src/fwauto/nodes/log.py:74-83` - Prompt loading 邏輯
  - `tests/fixtures/makefiles/flash/success.mk` - 正確的 Makefile 範例
  - `tests/test_story_006_error_handling.py:86` - 正確的測試範例

## Risk Assessment

### Phase 1 風險

**風險等級**: 極低

**潛在風險**:
- 編輯器可能自動將 Tab 轉換為空格

**緩解措施**:
- 使用 `cat -A` 驗證修改後的字元
- 執行 `make -n` 驗證 Makefile 語法
- 執行測試確認修復成功

### Phase 2 風險

**風險等級**: 極低

**潛在風險**:
- `prompts/log.md` 檔案可能被刪除或移動

**緩解措施**:
- 在測試執行前確認檔案存在
- 測試中加入檔案存在性假設（或使用 pytest fixture 確保）
- 如果未來檔案位置變動，測試會立即失敗並提示

### 整體風險

**風險等級**: 極低

**理由**:
- 修改範圍極小（2 個檔案，各 1 行）
- 不影響產品程式碼
- 有完整的自動化測試驗證
- 與現有成功測試保持一致的模式

---

## 總結

這個實施計劃採用 **KISS 原則**，用最簡單直接的方式修復 2 個測試失敗：

1. **Phase 1**: 替換 1 個字元（`\t` → Tab）
2. **Phase 2**: 修改 1 行測試設定（檔案路徑）

每個 Phase 都有明確的成功標準，可獨立驗證，風險極低。
