---
date: 2025-10-10T12:04:57+08:00
researcher: Claude (Sonnet 4.5)
git_commit: 9947424b826f3f28eb7857c9fdad2abc2428e347
branch: makefile
repository: fwauto
topic: "WSL 環境測試失敗原因分析"
tags: [research, testing, cross-platform, gitignore, fixtures, makefile, tab-space]
status: in_progress
last_updated: 2025-10-10
last_updated_by: Claude (Sonnet 4.5)
last_updated_note: "Added follow-up research for remaining 2 test failures after .gitignore fix"
---

# Research: WSL 環境測試失敗原因分析

**Date**: 2025-10-10T12:04:57+08:00
**Researcher**: Claude (Sonnet 4.5)
**Git Commit**: 9947424b826f3f28eb7857c9fdad2abc2428e347
**Branch**: makefile
**Repository**: fwauto

## Research Question

為什麼 pytest 在 Windows 韌體開發環境下全部通過，但在 WSL 環境下有 4 個測試失敗？

**失敗的測試**:
- `tests/integration/test_build_integration.py::test_build_integration_success`
- `tests/integration/test_build_integration.py::test_build_integration_compile_error`
- `tests/integration/test_build_integration.py::test_build_integration_missing_hex`
- `tests/test_story_006_error_handling.py::TestStory006ErrorHandlingAndUX::test_handles_prompt_loading_error`

## Summary

研究發現兩個獨立的根本原因導致測試失敗：

1. **Build Integration 測試失敗（3 個）**: `.gitignore` 規則意外忽略了測試 fixture 檔案
   - `.gitignore` 中的 `build/` 規則過於寬鬆，導致 `tests/fixtures/makefiles/build/` 目錄無法被追蹤
   - Windows 環境中有本地建立的檔案，但這些檔案從未被 commit
   - WSL 環境 clone 倉庫時缺少這些關鍵的測試 fixture

2. **Error Handling 測試失敗（1 個）**: 程式執行順序問題導致 mock 無效
   - `log_node` 在載入 prompt 之前會先檢查檔案存在性
   - 測試提供不存在的檔案路徑，觸發「檔案不存在」錯誤
   - Mock 的 `prompt_manager.get_prompt` 永遠不會被調用

## Detailed Findings

### 1. Build Integration 測試失敗分析

#### 測試架構設計模式

FWAuto 使用 **replay pattern** 來模擬真實的編譯器和燒錄工具行為：

```mermaid
graph LR
    A[測試] --> B[測試用 Makefile]
    B --> C[replay_output.py]
    C --> D[真實輸出檔案]
    D --> E[重播輸出]
    E --> F[節點處理]
```

**運作方式**:
1. 測試複製特定的 `.mk` 檔案到測試專案的 `.fwauto/build/Makefile`
2. 測試用 Makefile 調用 `replay_output.py` 腳本
3. 腳本讀取並重播真實環境捕獲的輸出（來自 `outputs/` 目錄）
4. 節點接收重播的輸出，如同真實執行一樣

#### Git 追蹤狀態對比

| 目錄/檔案 | Git 狀態 | 說明 |
|----------|---------|------|
| `tests/fixtures/makefiles/flash/` | ✅ 追蹤 | Flash 測試檔案正常 |
| `tests/fixtures/makefiles/flash/success.mk` | ✅ 追蹤 | 重播成功的燒錄輸出 |
| `tests/fixtures/makefiles/flash/hardware_error.mk` | ✅ 追蹤 | 重播硬體錯誤 |
| `tests/fixtures/makefiles/build/` | ❌ **被忽略** | 被 `.gitignore` 的 `build/` 規則忽略 |
| `tests/fixtures/makefiles/build/success.mk` | ❌ **缺失** | 應重播 `keil_build_success.txt` |
| `tests/fixtures/makefiles/build/compile_error.mk` | ❌ **缺失** | 應重播 `keil_build_compile_error.txt` |
| `tests/fixtures/makefiles/build/missing_hex.mk` | ❌ **缺失** | 測試 HEX 檔案遺失場景 |

**真實輸出檔案（已追蹤）**:
- ✅ `tests/fixtures/outputs/keil_build_success.txt` - Keil 成功編譯輸出
- ✅ `tests/fixtures/outputs/keil_build_compile_error.txt` - Keil 編譯錯誤輸出
- ✅ `tests/fixtures/outputs/stlink_flash_success.txt` - ST-LINK 成功燒錄
- ✅ `tests/fixtures/outputs/stlink_flash_hardware_error.txt` - ST-LINK 硬體錯誤

#### .gitignore 規則分析

**檔案**: `.gitignore:7`

```gitignore
build/
```

**影響範圍**:
- 此規則會忽略任何名為 `build/` 的目錄
- 包括測試 fixture 目錄 `tests/fixtures/makefiles/build/`
- 導致測試關鍵檔案無法被版本控制

**驗證**:
```bash
$ git ls-files tests/fixtures/makefiles/
tests/fixtures/makefiles/flash/hardware_error.mk
tests/fixtures/makefiles/flash/success.mk
tests/fixtures/makefiles/replay_output.py
# 沒有任何 build/ 下的檔案
```

#### Flash 測試 Makefile 參考範例

**檔案**: `tests/fixtures/makefiles/flash/success.mk:1-12`

```makefile
# Test Makefile - Flash Success Scenario

PROJECT_ROOT ?= ../..
HEX_FILE ?= $(PROJECT_ROOT)/OBJ/Template.hex
MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
FIXTURES_DIR := $(abspath $(MAKEFILE_DIR)/../../../../../fixtures)

.PHONY: flash

flash:
	@python "$(FIXTURES_DIR)/makefiles/replay_output.py" "$(FIXTURES_DIR)/outputs/stlink_flash_success.txt"
```

**設計特點**:
- 使用 `replay_output.py` 重播真實輸出
- 動態計算 `FIXTURES_DIR` 路徑，適應測試目錄結構
- 模擬成功的燒錄場景

#### 跨平台差異根源

```mermaid
graph TD
    A[開發流程] --> B{Windows 環境}
    A --> C{WSL 環境}
    B --> D[本地建立 build/*.mk]
    D --> E[測試通過 ✅]
    D --> F[檔案未 commit]
    F --> G[.gitignore 阻擋]
    C --> H[git clone]
    H --> I[缺少 build/*.mk]
    I --> J[測試失敗 ❌]
```

**時間序列**:
1. 開發者在 Windows 環境中建立 `tests/fixtures/makefiles/build/*.mk`
2. 測試在 Windows 上通過（使用本地檔案）
3. 嘗試 commit 時，`.gitignore` 規則阻止這些檔案被追蹤
4. 其他環境（如 WSL）clone 倉庫時沒有這些檔案
5. 測試失敗，報告 `FileNotFoundError`

#### 錯誤訊息解讀

**pytest 輸出**:
```python
FileNotFoundError: [Errno 2] No such file or directory:
'/home/kent/dev/playground/fwauto/tests/fixtures/makefiles/build/success.mk'
```

**發生位置**: `tests/integration/test_build_integration.py:46`

```python
def test_build_integration_success(base_state, test_project_root, fixtures_dir):
    # 複製測試用 Makefile 到測試專案
    test_makefile = fixtures_dir / "makefiles" / "build" / "success.mk"
    target_makefile = test_project_root / ".fwauto" / "build" / "Makefile"
    shutil.copy(test_makefile, target_makefile)  # ← 第 46 行失敗
```

**執行流程**:
1. 測試嘗試從 fixtures 目錄複製 `build/success.mk`
2. 檔案不存在（未被 git 追蹤）
3. `shutil.copy()` 拋出 `FileNotFoundError`
4. 測試中止

### 2. Error Handling 測試失敗分析

#### 測試意圖 vs 實際行為

**測試目標**: 驗證當 `PromptManager.get_prompt()` 失敗時，能返回友善錯誤訊息

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

```python
def test_handles_prompt_loading_error(self):
    """測試處理 prompt 載入錯誤"""
    test_state: FirmwareState = {
        # ...
        "log_file_path": "logs/uart.log",  # ← 不存在的檔案
        # ...
    }

    # 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()  # ← 斷言失敗
```

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

#### 程式執行順序問題

**檔案**: `src/fwauto/nodes/log.py`

```mermaid
sequenceDiagram
    participant T as Test
    participant L as log_node
    participant PM as PromptManager

    T->>L: log_node(state)
    Note over L: log_file_path = "logs/uart.log"

    L->>L: 55-61: 檢查檔案是否存在
    Note over L: os.path.exists() → False
    L->>T: 返回 "Error: Log file not found"

    Note over PM: Mock 永遠不會被調用
    Note over L: 76: prompt_manager.get_prompt()<br/>永遠不會執行到這裡
```

**關鍵程式碼段**:

```python
# log.py:55-61 - 檔案檢查（第一道關卡）
if not os.path.exists(log_file_path):
    logger.error(f"❌ Log Analysis: Log file not found: {log_file_path}")
    return {
        **state,
        "log_analysis_result": f"Error: Log file not found: {log_file_path}",
    }

# log.py:74-83 - Prompt 載入（第二道關卡，永遠到不了）
try:
    prompt = prompt_manager.get_prompt("log", dict(state))
    logger.debug(f"📊 Log Analysis: Prompt loaded ({len(prompt)} chars)")
except Exception as e:
    logger.error(f"❌ Log Analysis: Failed to load prompt: {e}")
    return {
        **state,
        "log_analysis_result": f"Error: Failed to load prompt template: {e}",
    }
```

**執行順序**:
1. **Line 55**: 檢查 `log_file_path` 是否存在 ← 測試在這裡就結束了
2. **Line 57-61**: 檔案不存在，返回錯誤
3. **Line 76**: `prompt_manager.get_prompt()` 永遠不會被執行
4. Mock 完全沒有機會生效

#### Mock 策略問題

**問題**: 測試同時設定了兩個會觸發錯誤的條件
1. 不存在的檔案路徑（`logs/uart.log`）
2. Mock `prompt_manager.get_prompt` 拋出異常

**結果**: 條件 1 先被檢查，測試在條件 2 之前就結束

**Mock 設定** (第 135-136 行):
```python
with patch("src.fwauto.nodes.log.prompt_manager") as mock_pm:
    mock_pm.get_prompt.side_effect = Exception("Template not found")
```

**期望行為 vs 實際行為**:

| 階段 | 期望 | 實際 |
|-----|------|------|
| 檔案檢查 | 通過 | ❌ 失敗（檔案不存在） |
| Prompt 載入 | ❌ Mock 拋出異常 | 未執行 |
| 錯誤訊息 | 包含 "prompt" | 包含 "Log file not found" |

## Code References

| 檔案路徑 | 行號 | 說明 |
|---------|------|------|
| `.gitignore` | 7 | `build/` 規則導致測試 fixture 被忽略 |
| `tests/integration/test_build_integration.py` | 44 | 定義缺失的 fixture 路徑 |
| `tests/integration/test_build_integration.py` | 46 | `shutil.copy()` 失敗點 |
| `tests/integration/test_build_integration.py` | 65 | compile_error 測試失敗點 |
| `tests/integration/test_build_integration.py` | 87 | missing_hex 測試失敗點 |
| `tests/fixtures/README.md` | 8 | 文檔說明應有 `build/` 目錄 |
| `tests/fixtures/makefiles/replay_output.py` | 1-18 | 重播輸出的腳本實現 |
| `tests/fixtures/makefiles/flash/success.mk` | 1-12 | Flash 測試 Makefile 範例 |
| `src/fwauto/nodes/log.py` | 55-61 | 檔案存在性檢查（阻擋 mock） |
| `src/fwauto/nodes/log.py` | 74-83 | Prompt 載入（永遠到不了） |
| `tests/test_story_006_error_handling.py` | 130 | 設定不存在的 log_file_path |
| `tests/test_story_006_error_handling.py` | 135-136 | Mock prompt_manager |
| `tests/test_story_006_error_handling.py` | 142 | 斷言失敗點 |

## Architecture Insights

### 測試 Fixture 架構設計

FWAuto 採用 **output replay pattern** 來實現跨平台測試：

**設計優點**:
- ✅ 無需真實硬體即可測試韌體工作流程
- ✅ 可重現真實編譯器和燒錄工具的行為
- ✅ 測試執行速度快，不依賴外部工具

**實現方式**:
1. **捕獲階段** (Windows + 真實環境):
   - 使用真實的 Keil 和 ST-LINK 工具
   - 捕獲標準輸出到 `outputs/*.txt` 檔案
   - Commit 這些輸出檔案到版本控制

2. **重播階段** (任何環境):
   - 測試用 Makefile 調用 `replay_output.py`
   - 腳本讀取並輸出捕獲的內容
   - 節點解析輸出，如同真實執行

3. **測試階段**:
   - 複製對應的 `.mk` 檔案到測試專案
   - 執行節點，觸發 Makefile
   - 驗證節點是否正確處理輸出

### .gitignore 規則粒度問題

**當前規則** (過於寬鬆):
```gitignore
build/
```

**影響範圍**:
- ✅ 正確忽略: `build/` (Python build 產物)
- ✅ 正確忽略: `dist/` (distribution 產物)
- ❌ **意外忽略**: `tests/fixtures/makefiles/build/` (測試 fixture)

**根本問題**: 規則缺乏路徑限定，影響範圍過大

### 防禦性程式設計 vs 測試策略

`log_node` 實現了多層錯誤檢查（防禦性程式設計）：

```mermaid
graph TD
    A[log_node] --> B{log_file_path 存在?}
    B -->|否| C[返回檔案不存在錯誤]
    B -->|是| D{user_prompt 有效?}
    D -->|否| E[返回缺少 prompt 錯誤]
    D -->|是| F{載入 prompt template}
    F -->|失敗| G[返回載入失敗錯誤]
    F -->|成功| H[執行分析]
```

**檢查順序**:
1. Line 43-48: `log_file_path` 是否提供
2. Line 55-61: 檔案是否存在
3. Line 64-69: `user_prompt` 是否提供
4. Line 74-83: Prompt template 是否載入成功

**測試挑戰**:
- 要測試第 N 層錯誤處理，必須讓前 N-1 層全部通過
- Mock 只在對應的程式碼被執行時才有效
- 需要精確控制測試條件，避免過早觸發錯誤

## Technical Feasibility

### 跨平台測試可行性

**發現**: 測試架構本身是跨平台的
- `replay_output.py` 使用純 Python，無平台依賴
- `outputs/*.txt` 是純文字檔案
- Makefile 使用 Python 腳本而非平台特定工具

**限制**: 版本控制配置不當導致 fixture 缺失

**方向**:
- 問題不在測試設計，而在 `.gitignore` 配置
- 調整 `.gitignore` 規則可以解決跨平台問題
- 無需修改測試架構或實現

### Mock 測試策略調整方向

**發現**: 多層防禦性檢查需要更精細的測試策略

**當前限制**:
- 測試無法跳過前置檢查直接測試特定錯誤處理
- 需要滿足所有前置條件才能測試目標邏輯

**可行方向**:
- 為每層錯誤處理提供通過前置條件的測試數據
- 使用真實存在的檔案路徑測試 prompt loading 錯誤
- 或重構 `log_node` 將檔案檢查邏輯分離為獨立函數

## Open Questions

1. **Build fixture 檔案來源**:
   - Windows 環境中的 `build/*.mk` 檔案是如何建立的？
   - 是手動建立還是有自動生成機制？
   - 檔案內容應該如何定義？

2. **.gitignore 規則影響範圍**:
   - 是否有其他測試或開發檔案也受到 `build/` 規則影響？
   - 是否有 CI/CD 環境也遇到類似問題？

3. **測試覆蓋率**:
   - `log_node` 的其他錯誤處理分支是否都有對應測試？
   - 是否還有其他測試因類似原因失敗但未被發現？

4. **Fixture 管理策略**:
   - 是否需要建立 fixture 生成腳本確保一致性？
   - 是否需要文檔說明 fixture 的建立和維護流程？

5. **測試環境差異**:
   - Windows 和 WSL 環境還有哪些其他差異可能影響測試？
   - 路徑分隔符、換行符等是否已妥善處理？

---

## Follow-up Research [2025-10-10T12:19:02+08:00]

### 研究背景

在修復 `.gitignore` 問題後（commit `5f3f70c`），build fixtures 檔案已成功加入版本控制，3 個 build integration 測試中有 2 個通過：
- ✅ `test_build_integration_compile_error` - 通過
- ✅ `test_build_integration_missing_hex` - 通過  
- ❌ `test_build_integration_success` - **仍然失敗**
- ❌ `test_handles_prompt_loading_error` - **仍然失敗**

本次 follow-up 聚焦於分析這兩個仍然失敗的測試根本原因。

### 失敗測試的新錯誤訊息

#### 1. Build Integration Success 測試

**錯誤輸出**:
```
Makefile:8: *** missing separator.  Stop.

AssertionError: assert 'error' == 'success'
```

**關鍵發現**: 錯誤從 `FileNotFoundError` 變成了 `Makefile syntax error`，表示檔案已存在但內容有問題。

#### 2. Prompt Loading Error 測試

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

**狀況**: 與初始研究一致，mock 無法生效因為檔案檢查在 prompt loading 之前。

### 新發現：Makefile Tab vs Space 問題

#### 問題根源

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

使用 `cat -A` 和 `hexdump` 分析後發現關鍵差異：

| 檔案 | 第 8 行內容 | Tab 表示 | 實際字元 |
|-----|-----------|---------|---------|
| `build/success.mk` | `\t@bash "..."` | `\t` (字面字串) | `5c 74` (反斜線 + t) |
| `flash/success.mk` | <Tab>`@python "..."` | `^I` (真正的 Tab) | `09` (Tab 字元) |

**驗證**:
```bash
$ hexdump -C tests/fixtures/makefiles/build/success.mk | grep -A1 "build:"
000000a0  5c 74 40 62 61 73 68 20  22 24 28 4d 41 4b 45 46  |\t@bash "$(MAKEF|
```

- `5c` = `\` (反斜線)
- `74` = `t`
- 合起來是字面上的 `\t` 字串，**不是 Tab 字元**

#### Makefile 語法要求

Makefile 要求 **recipe 行必須以真正的 Tab 字元 (ASCII 0x09) 開頭**。

```makefile
target:
<TAB>command  # 必須是真正的 Tab，不是 \t 或空格
```

如果使用其他字元（包括空格、`\t` 字串等），make 會報錯：
```
*** missing separator.  Stop.
```

#### 檔案對比分析

**成功的檔案** (`compile_error.mk`, `missing_hex.mk`, `flash/success.mk`):
```bash
$ cat -A tests/fixtures/makefiles/build/compile_error.mk | grep -A1 "^build:"
build:$
^I@python3 "..."$
```
- `^I` = 真正的 Tab 字元

**失敗的檔案** (`build/success.mk`):
```bash
$ cat -A tests/fixtures/makefiles/build/success.mk | grep -A1 "^build:"
build:$
\t@bash "..."$
```
- `\t` = 字面字串（兩個字元）

### 測試設計洞察對比

#### Test Returns Analysis Result (通過的測試)

```python
test_state: FirmwareState = {
    # ...
    "log_file_path": "prompts/log.md",  # ✅ 使用存在的檔案
    # ...
}

with patch("src.fwauto.nodes.log.query") as mock_query:
    # Mock query 而非 prompt_manager
```

**成功原因**:
1. 使用真實存在的檔案 `prompts/log.md`
2. 通過了檔案存在性檢查（`log.py:55-61`）
3. Mock `query` 函數而非 `prompt_manager`
4. 測試真正執行到了想測試的邏輯

#### Test Handles Prompt Loading Error (失敗的測試)

```python
test_state: FirmwareState = {
    # ...
    "log_file_path": "logs/uart.log",  # ❌ 不存在的檔案
    # ...
}

with patch("src.fwauto.nodes.log.prompt_manager") as mock_pm:
    # Mock prompt_manager.get_prompt
```

**失敗原因**:
1. 使用不存在的檔案路徑
2. 在檔案檢查階段就返回錯誤
3. 永遠執行不到 `prompt_manager.get_prompt()` 的調用
4. Mock 完全沒有機會生效

**關鍵差異**: 測試意圖是測試「prompt loading 失敗」，但實際測試了「檔案不存在」的錯誤處理。

### 架構發現：字元編碼與跨平台編輯器問題

#### 可能的檔案建立方式

`success.mk` 檔案內容顯示使用了字面的 `\t`，這通常發生在：

1. **程式化生成**: 使用字串模板生成檔案時誤用 `\t` escape sequence
2. **錯誤的編輯器設定**: 編輯器將 Tab 轉換為 `\t` 字面字串
3. **複製貼上問題**: 從格式化的文本（如 Markdown 程式碼塊）複製時失去真正的 Tab

#### Commit 歷史分析

```bash
$ git log --oneline -3 tests/fixtures/makefiles/build/success.mk
5f3f70c add build make file
```

**發現**: 所有 build fixtures 都在同一個 commit 中加入，但只有 `success.mk` 有 Tab 問題。

#### 其他檔案為何正確

`compile_error.mk` 和 `missing_hex.mk` 使用真正的 Tab：
```bash
$ cat -A tests/fixtures/makefiles/build/compile_error.mk | head -12
# Test Makefile - Build Compile Error Scenario$
# 重播真實 Keil 編譯錯誤輸出$
$
PROJECT_ROOT ?= ../..$
...
build:$
^I@python3 "$(MAKEFILE_DIR)/replay_output.py" keil_build_compile_error.txt$
^I@exit 1$
```

**推測**: 這些檔案可能：
- 從 `flash/success.mk` 複製並修改而來（保留了真正的 Tab）
- 使用支援 Makefile 語法的編輯器建立
- 手動輸入真正的 Tab 字元

### 測試策略對比分析

| 測試名稱 | 測試目標 | 檔案路徑 | 檔案存在? | Mock 對象 | 執行到 Mock? | 結果 |
|---------|---------|---------|---------|----------|------------|------|
| `test_returns_analysis_result_in_state` | 正常流程 | `prompts/log.md` | ✅ 是 | `query` | ✅ 是 | ✅ 通過 |
| `test_handles_missing_user_prompt` | 缺少 prompt | `logs/uart.log` | ❌ 否 | 無 | N/A | ✅ 通過 |
| `test_handles_prompt_loading_error` | Prompt 載入失敗 | `logs/uart.log` | ❌ **否** | `prompt_manager` | ❌ **否** | ❌ **失敗** |

**模式發現**:
- 測試想測試「第 N 層錯誤處理」
- 但不小心觸發了「第 N-1 層錯誤處理」
- 導致測試的目標程式碼永遠不會被執行

### Code References (Follow-up)

| 檔案路徑 | 行號 | 說明 |
|---------|------|------|
| `tests/fixtures/makefiles/build/success.mk` | 8 | 使用 `\t` 字面字串而非真正的 Tab |
| `tests/fixtures/makefiles/build/compile_error.mk` | 12-13 | 正確使用真正的 Tab (`^I`) |
| `tests/fixtures/makefiles/build/missing_hex.mk` | 9-15 | 正確使用真正的 Tab (`^I`) |
| `tests/test_story_006_error_handling.py` | 86 | 通過測試使用存在的檔案 `prompts/log.md` |
| `tests/test_story_006_error_handling.py` | 72 | 失敗測試使用不存在的 `logs/uart.log` |

### Technical Feasibility (Follow-up)

#### Makefile Tab 問題修復方向

**發現**: 只有 `success.mk` 受影響，其他檔案都正常

**可行方向**:
- 直接修改檔案，將 `\t` 替換為真正的 Tab 字元
- 使用 `sed` 或 Python 腳本自動化修復
- 使用支援 Makefile 的編輯器重新編輯檔案
- 考慮建立 pre-commit hook 檢查 Makefile 中的 Tab

#### Prompt Loading Error 測試修復方向

**發現**: 測試條件設定不當，觸發錯誤的錯誤處理層

**可行方向**:
- 使用真實存在的檔案（如 `prompts/log.md`）測試 prompt loading 錯誤
- Mock `os.path.exists()` 使其返回 `True`，跳過檔案檢查
- 建立臨時測試檔案確保檔案存在性檢查通過
- 重新設計測試策略，為每層錯誤處理提供正確的前置條件

### 根本原因總結 (Updated)

經過 follow-up 研究，4 個測試失敗的完整原因：

1. **Build Integration 測試 (3 個)**:
   - **初始問題** (已解決): `.gitignore` 規則導致 fixture 檔案缺失
   - **後續問題** (待解決): `success.mk` 使用字面 `\t` 而非真正的 Tab 字元
   - **狀態**: 2/3 通過，1/3 仍失敗

2. **Error Handling 測試 (1 個)**:
   - **根本問題**: 測試策略設計問題，前置條件未滿足
   - **現象**: 檔案不存在導致過早返回，Mock 未生效
   - **狀態**: 持續失敗，需要重新設計測試

### 新的 Open Questions

1. **Tab 問題來源**:
   - `success.mk` 是如何建立的？使用什麼工具？
   - 為何只有這個檔案有問題，其他檔案正常？
   - 是否有程式化生成 Makefile 的腳本？

2. **測試設計原則**:
   - 如何確保測試真正測試目標邏輯而非前置檢查？
   - 是否需要建立測試輔助函數來設定正確的前置條件？
   - 多層錯誤處理的測試策略最佳實踐是什麼？

3. **自動化檢查**:
   - 是否需要 linter 檢查 Makefile 中的 Tab？
   - 是否需要 pre-commit hook 防止此類問題？
   - 測試 fixture 的品質保證機制？
