Perl 42:并行聚合与测量边界
原定 E06 的并行 worker 不是“两个进程同时写文件”。LogTool::Course::Parallel::parallel_rows 先把输入记录按下标分片,最多启动 8 个 worker,且 worker 数不会超过记录数。每个 worker 在子进程里使用 aggregate_rows 生成自己的 level 计数,再通过 pipe 回传 Storable::freeze 后的行数组。父进程只接受最多三行、level 限定为 ERROR/INFO/WARN、count 为非负整数的 payload;这就是“受控三等级聚合”的有界数据面。 父进程等待所有 worker 后再合并。任何 worker 非零退出、信号终止、pipe 关闭失败或 payload 解码失败,都会得到 worker failed,publish_parallel_report 不调用 save_report。t/course-engineering.t 先把目标写成 OLD\n,再注入一个会失败的 worker,断言旧文件未变;这覆盖“任一失败不发布部分结果”。若在 fork 或 pipe 创...
Perl 41:引用生命周期与析构边界
原定第 31 章要求把引用计数、循环引用、析构和关键写入的显式 close 分开。Perl 传统对象只是被 bless 过的引用;对象何时销毁,取决于引用何时归零。experiments/course-lifecycle.pl 定义 CourseLifecycle::Node,在 DESTROY 中记录节点名。单个节点离开作用域后,计数从 0 变成 1,说明普通引用归零会触发析构。 第二个场景创建 cycle-left 和 cycle-right,两者互相保存强引用。离开词法作用域后,外部变量没了,但两个对象仍互相引用,DESTROY 计数不增加。这个反例不是内存 profiler,也不证明泄漏规模;它只说明“离开作用域”与“引用计数归零”不是同一件事。若对象持有文件、锁或临时路径,把释放责任只放在 DESTROY 里,就可能被引用环拖住。 第三个场景把其中一条边改成 Scalar::Util::weaken。弱引用不增加引用计数;作用域结束后两个节点都能销毁,计数增加到 3。这个实验给出的是本机 Perl 5.34/5.38 兼容的核心模块行为,不扩展到所有引用图,也不把弱引用...
Perl 40:对象聚合与组合扩展边界
原定第 29 章要求的不只是“见过 bless”,而是把有状态聚合器封装成对象,并证明两个实例互不污染。LogTool::Course::Aggregator->new 只保存一张私有计数表;add($record) 接受散列记录或提供 level 方法的对象;rows 每次返回新的数组引用和新的行散列,行形状固定为 {level,count},按 level 排序。总数不放进对象,由调用方按输入条数计算,避免对象同时承担聚合和报表格式两种职责。 t/course-engineering.t 对这个契约做了四个反例保护:同一输入下 aggregate_rows($records) 与对象版结果相同;第二个 Aggregator 实例只加入 ERROR,不会改变第一个实例的 INFO/WARN 计数;调用方修改 rows 返回值里的 count,再向数组追加 ERROR 行,下一次 rows 仍回到原计数;旧 LogTool::report_rows 只把对象行转换回旧二维数组,保持 CLI 兼容。这样补的是原定第 29 章的“对象状态与函数版 parity”,不是把第 26...
Perl E08:解包能复跑,与断电能恢复是两张验收单
交付有两个容易混淆的方向:素材包能否离开工作树解开后复跑,报表目标在故障后能否保持有效。前者由第 00 篇素材目录中的完整包承担,要求工具源码、隔离 Text::CSV、合成输入、金标、RUN.md、VERSIONS.md、证据和测试都进包,并在全新目录实跑 prove -r;第 37 篇的 MakeMaker distribution 则是另一份安装练习。两者不能互相替代。 正常写入路径下,report-file demo.tsv DEST 退出 0,STDOUT SAVED\n,目标是完整金标。反例用 invalid.tsv 重试:状态 2、STDOUT 空、STDERR line 1: invalid record\n,旧金标可再次打开核对。rename 失败时旧产物仍保留;但这些运行是在操作系统正常工作的场景。真断电后的目录项持久性和重启恢复 NOT_RUN,无文件与目录 fsync 协议就不能对硬件掉电给出保证。也没有真实云端安装、其他平台重试证据。 图:制品复跑与断电恢复的不同步骤 123可复跑:发布包 -> 摘要核验 -> 解压 -> prove ...
Perl E07:路径和启动边界不能用 Linux 测试代填
工具调用 probe 的核心是“固定 $^X、固定脚本、参数列表、限定 token”,而非在各系统拼接路径与 shell 字符串。本机 Linux 正例 probe demo.tsv 'x y' 把空格保留在同一个参数里:STDOUT arg=x y\n、STDERR probe note\n、退出 0;含换行的 token 在启动前被拒绝,STDOUT 空、退出 2,诊断中说明长度和 ASCII 限制。缺失可执行文件由 t/26-31-objects-and-process.t 的直接库调用确认启动失败,不是退出 7。 查冻结 5.38.2 pod/perlport.pod、pod/perlipc.pod 后,仍不能自动宣布 Windows 与 macOS 已支持:原生路径分隔、文件被占用时的 rename、可执行文件查找、管道信号/退出状态及不同文件系统的 flock 都需要在相应平台重跑;IPC::Open3 的顺序双流读取对大量输出本来就不适用。这里的“多平台审计”仅给出待验证矩阵,Windows、macOS 与网络文件系统运行状态均为 NOT_RUN,不把文档推测填成成...
Perl E06:帮助文字也是会被改坏的接口
新增 report-file 后,--help 的 POD SYNOPSIS 必须同步;实际运行 perl bin/logtool.pl --help 应出现 report-file FILE DESTINATION,退出 0 且 STDERR 空。单靠文档审查还不够:t/21-25-modules-and-tap.t 原来把 help 错误用法的输出写成固定旧金标。一次新发行包解压后的 make test 因新增命令而失败,更新金标后再重新打包验证通过;这是一例真实发生过的文档与断言漂移,不要为了绿灯删掉检查。 反例 prove -v fixtures/deliberate-failure.t 故意断言 2 等于 3,STDOUT 出 Result: FAIL、STDERR 给出实际值和期望值、退出 1。它单独存放在 fixtures/,不进默认 t/,从而在证明失败通道的同时不污染正常累计回归。POD 帮助可执行,TAP 可检验,二者属于工具接口不同面的测试;Hexo 页面生成也不能取代它们。 练习:如果故意失败的测试文件误放进 t/,prove -r t 会是什么状态?应是...
Perl E05:能加载 CSV 不说明它从哪里来
第 23 篇隔离安装 Text::CSV,37 篇又把它写进发行元数据。复核时须区分源码包、安装路径、被实际加载的版本和执行后端:上游 Text-CSV-2.06.tar.gz 的 SHA-256 dfcaec925a788b0ba41e51bc6d16e21b0e98b4c7af9b79395090add75f5e506f 与官方 CHECKSUMS 核对过;本仓库 vendor/lib/perl5 中加载到 Text::CSV 2.06 和 Text::CSV_PP 后端。正例 STDOUT 2.06 Text::CSV_PP\n、STDERR 空、退出 0。 反例用同一隔离路径要求 use Text::CSV 999;,STDOUT 空,STDERR 明确说 required version 999,退出非零。它验证版本约束确实可触发,并不代替缺失模块与编译环境的独立测试。VERSIONS.md 逐项列出发行 META 中的直接依赖、实际基础环境的核心模块版本、缺少推荐 XS 后端的事实,以及上游 52,723 项测试里可选 Test::Pod 被跳过;不能简化成“依赖都内...
Perl E04:三个坏样本只是警报,不是复杂度证明
第 19 篇把 \A(a+)+\z 和 \Aa+\z 放在短输入末尾追加 X 的拒绝场景中对照。选修复跑仅取 6、8、10 个 a:两个模式都拒绝,程序打印组合分割数 32、128、512。这个数来自数学上的 2 ** (size - 1),不是 Perl 引擎实测尝试次数、耗时曲线,更不能由一次执行推出任意长输入的复杂度。对抗样本被刻意限在 10 个 a 以内。 另一个反例更可直接观测:在 aXb a.b 中把动态字面数据 a.b 不加引号地插入正则,. 会匹配 X,得到 unquoted=aXb;使用 \Q$needle\E 才得到 literal=a.b。动态模式若来自用户输入,不应该无条件当作正则语法。正例是受限样本全部拒绝且程序有界完成,反例是未经转义时误匹配,两者在 experiments/16-20-patterns.pl 分别可重跑。 练习:为了“证明”回溯严重性,把输入从 10 个 a 改成 100 万个再计时是否合理?不合理:执行可能没有受控时间边界,单次耗时也不构成复杂度证明。想论证引擎行为,需要版本明确、隔离资源、设计输入规模和重复测量,避免把推导的分割数...
Perl E03:规范化之前,先确认手里是字符
同一个可见的 é,可以在字符层写成 U+00E9,也可以是 U+0065 U+0301。选修实验先从合法的字符 "e" . chr(0x301) 做 Unicode::Normalize::NFC,验证结果等于 chr(0xe9);再经 Encode::encode('UTF-8', ..., FB_CROAK) 得到 2 个字节。本机 Unicode::Normalize 1.32 的正例 STDOUT same=1 bytes=2\n,STDERR 空、退出 0。字符的个数与 UTF-8 字节数不是同一个计量单位。 反例让 Encode::decode('UTF-8', pack('C', 255), FB_CROAK) 接收无效字节 FF:STDOUT 空,STDERR 有 UTF-8 映射失败,退出非零。先对不明来源字节做 NFC 并不能“修复”坏编码。文件中的源码字面量如要直接写非 ASCII,use utf8 只影响源码解析;原始日志读入仍必须显式解码,字符处理完后仍必须显式编码输出。 图:源码解析、输入解码与消息规范化 123源码字面量 (use...
Perl E02:class 能运行,但 5.38.2 仍会明确警告
传统 bless 对象是 LogTool::Record 的主线选择;原因不是“新语法在 5.38.2 无法运行”,而是这版 class 尚属实验。use feature 'class' 的正例定义 field $level :param、method level,Event->new(level => 'INFO') 的 STDOUT 为 INFO\n、退出 0,同时 STDERR 如实报告 class is experimental、field is experimental、method is experimental。不要用关闭 warnings 把实验状态伪装成稳态。 12345use feature 'class';class Event { field $level :param; method level { $level }} 反例调用 Event->new() 缺少必需参数,仍出现三类实验提示,并额外报 Required parameter 'level' is miss...










