snowy-yang
|
a05af4a880
|
refactor: 播客流式下载拆解(嵌套 10 层 → 1,三段职责各就其位)
为什么:_podcast_stream_download 是播客单集下载的**唯一落盘路径**(单集 worker
与整台打包的首集都走它),却零测试覆盖;而且它把三段互不相关的职责——全局名额
排队、流式写盘与进度上报、完整性校验与坏文件清理——叠在 try/with/with/for 加四层
if 里,最大嵌套 10 层,是全仓最深的一个。
改法:拆成四个具名函数,主函数只留编排。
_stream_to_part :拿响应 → 写 .part → 按 0.5s 节流上报进度,返回 (已写, 期望)
_report_progress :把一次进度写进任务表(含单集任务的 live_percent)
_verify_downloaded :完整性判定 + 清掉坏文件(精确相等 / 非空两条口径)
_remove_quietly :删文件并吞掉 OSError(Windows 句柄未释放时 remove 会抛)
复杂度(同口径 AST 实测):
_podcast_stream_download 63 行 / 圈 19 / 嵌套 10 → 21 行 / 圈 3 / 嵌套 1
新增 _stream_to_part 25 行/圈 8/嵌套 4 · _report_progress 22/6/3 · _verify_downloaded 14/3/1
文件内嵌套 ≥5 层 2 → 1(余下那个是本次未动的 _podcast_download_worker)
刻意保留的语义(是决定,不是疏漏):
- 取消时 **.part 留存不删**(改名不发生,下次同名下载直接覆盖)——已用测试固化。
不「顺手」改成删除,那是行为变更。
- 进度 msg 的计算与「是否真有可取的任务」无关:task_id 为空时仍会格式化字符串再
return。这是既有的无害浪费,一并保留。
- 完整性口径原样:已知期望大小要求**精确相等**(放宽到 95% 容差会交付截断的坏文件
还照扣次数),期望未知则退化为非空校验。
等价性证据(两道):
1. 反向注入验证:把 HEAD 的旧实现换回去,用同一套 tests/test_podcast_stream.py 跑
→ 12 例**全过**,证明这批测试忠实编码旧行为、对新实现构成等价性验证。
2. 新增 12 例行为契约(该函数此前零覆盖):正常落盘与改名、空分块跳过、
Content-Length 兜底、字节数不符清坏文件、空下载报错、已知大小不容忍部分交付、
上游 4xx、开始前取消、中途取消留 .part、running 任务写 msg/live_percent、
终态任务不被覆盖、无 task_id 不报错。
顺带记录一个测试自身的坑:替身响应最初用 list(chunks) 归一化,导致传入生成器的
副作用(中途置取消位)在**构造时刻**就执行,测出来变成「开始前取消」——两条本不
相同的路径被混成一条。已改为保留原 iterable 并写明原因。
验证:ruff 全过 · pyright 0/0 · pytest 245 passed(233→245)· 生产 data/ 未被触碰。
|
2026-09-27 07:45:49 +08:00 |
|