> **发布元数据**：Qoder · Kimi K3（评测者确认）· R01
> **来源**：`qoder/输出/qoder/T02/R01/BENCHMARK_RESULT.md`
> **原始文件 SHA-256**：`f5c679543c4281460f77e7f2f1d2d1b99b37bb02bdea96c22c36374f9eb73b56`
> 本公开副本已将评测机绝对路径归一化；下文 PASS/FAIL/BLOCKED 是执行报告自述状态，不等同于独立验收结论。

---

# BENCH-RUN T02 R01

# T02：配置优先级与错误路径（只读）— 结果：PASS

## 题面（逐字）

> # T02：配置优先级与错误路径（只读）
>
> 只读分析 `python3 -m ledger list` 选择交易 CSV 的配置流程。追踪默认文件、JSON 配置文件、`LEDGER_DATA_FILE` 环境变量和全局 `--data-file` 参数；说明实际优先级、相对路径如何解析、配置格式错误或目标文件不存在时的处理，以及最终由哪个模块读取。每项附真实相对路径和函数/类名。不要修改文件；不确定时明确标出，不要猜。

## 配置流程（全部位于 `ledger/settings.py` 的 `resolve_data_file(cli_path=None, environ=None, config_path=None)`，settings.py:8）

调用方：`ledger/cli.py` 的 `main()` 在 cli.py:42 以 `resolve_data_file(cli_path=args.data_file, environ=os.environ, config_path=args.config)` 传入三个来源。

### 实际优先级（settings.py:13-30 的 if/elif 链，高 → 低）

1. **全局 `--data-file` 参数**（settings.py:13-14）：非空即直接选用，不再看其他来源。
2. **`LEDGER_DATA_FILE` 环境变量**（settings.py:15-16）：`environ.get("LEDGER_DATA_FILE")` 为真时选用。
3. **JSON 配置文件 `--config`**（settings.py:17-28）：读取并解析 JSON，取其 `data_file` 字段。
4. **默认文件**（settings.py:29-30）：以上皆无时返回 `DEFAULT_DATA_FILE`（settings.py:5），即 `Path(__file__).resolve().parent / "data" / "transactions.csv"` —— 包内 `ledger/data/transactions.csv` 的绝对路径。

实测验证（本题 workspace，fixture 在临时目录）：

```text
=== 1 default ===            → 输出包内 6 条记录（A-02 等）
=== 2 env ===                LEDGER_DATA_FILE=env.csv → Y-01 | ... | Env
=== 3 config relative ===    --config good.json({"data_file":"rel.csv"}) → X-01 | ... | Cfg
=== 4 cli + env + config === → Z-01 | ... | Cli   （--data-file 胜出）
=== 5 env + config ===       → Y-01 | ... | Env   （env 胜过 config）
```

### 相对路径如何解析（settings.py:31-34）

- 选中值先经 `Path(selected).expanduser()`（settings.py:31），`~` 展开对三种来源一视同仁。
- 仅当来源是配置文件时 `selected_base = config_file.parent`（settings.py:28）；若路径非绝对且 `selected_base` 非 None，则拼为 `config_file.parent / path`（settings.py:32-33），即**配置内的相对路径相对于配置文件所在目录**解析（实证：good.json 与 rel.csv 同目录，成功读取）。
- 来自 `--data-file` 或环境变量的相对路径不做基准目录拼接，直接进入 `path.resolve()`（settings.py:34），即**相对于进程当前工作目录**解析。
- 最终统一 `path.resolve()` 返回绝对路径；`resolve()` 会规范化 `..`/符号链接，但对不存在路径不报错（非 strict）。

### 配置格式错误 / 目标不存在的处理

统一错误出口：`ledger/cli.py` 的 `main()` 在 cli.py:54-56 捕获 `(FileNotFoundError, ValueError, OSError)`，`print(f"ledger: error: {exc}", file=sys.stderr)` 并 `return 2`（stderr + 退出码 2）。

- **配置文件不存在**：settings.py:21-22 捕获 `FileNotFoundError` 并抛 `FileNotFoundError(f"config file not found: {config_file}")`。实测 exit=2。
- **配置文件 JSON 格式错误或编码错误**：settings.py:23-24 捕获 `json.JSONDecodeError`、`UnicodeDecodeError`，抛 `ValueError(f"invalid config file {config_file}: {exc}")`。实测 exit=2。
- **配置不是含非空字符串 `data_file` 的对象**：settings.py:25-27（`config.get("data_file") if isinstance(config, dict) else None`，再校验 `isinstance(selected, str) and selected`），抛 `ValueError("config must contain a non-empty string 'data_file'")`。实测 exit=2。
- **目标 CSV 不存在**：`resolve_data_file` 不检查存在性（`Path.resolve()` 非 strict）；由最终读取方 `ledger/storage.py` 的 `load_transactions(path)`（storage.py:11）在 `Path(path).open("r", ...)`（storage.py:13）时抛出原生 `FileNotFoundError`（`[Errno 2] No such file or directory: ...`），向上冒泡到 cli.py:54 捕获。实测 exit=2。
- 另注：`--data-file` 空字符串会被 settings.py:13 的 `if cli_path:` 判为假而落到下一来源——此为本实现的实际行为，题面未要求改动。

### 最终由哪个模块读取

`ledger/storage.py` 的 `load_transactions(path)`（storage.py:11）：`Path(path).open("r", encoding="utf-8", newline="")` + `csv.DictReader`，逐行构造 `ledger/model.py` 的 `Transaction`（金额经 `ledger/money.py` 的 `parse_money`）。`resolve_data_file` 只负责选路，不读文件内容。

## 实际运行的命令与结果

见上文各实测块；全部在本题 workspace 内执行，fixture 文件均创建于系统临时目录并已说明；退出码均为 2（错误路径）或 0（成功路径）。

## 实际改动

无（只读任务）。`git status --porcelain` 确认为空。

## 耗时

UNKNOWN（未使用计时工具精确测量；本题在数分钟内完成）。

## 未完成项

无。

## 会话隔离状态

本题使用独立 workspace（`输出/qoder/T02/R01/workspace/`，自冻结提交完整复制），未读取或修改其他题目/轮次目录、基准仓库或题面源文件。
