このプロジェクトは pjcj さんの作った Perl モジュール Devel::Cover のプロジ

Tool Interactions

Tool: Task

{
  "subagent_type": "Explore",
  "prompt": "このプロジェクトは Perl モジュール Devel::Cover です。以下の点について徹底的に調査してください:\n\n1. プロジェクトの全体構造(ディレクトリ構成、主要ファイル)\n2. 主要な Perl モジュール (.pm ファイル) の役割\n3. XS/C コード(もしあれば)の役割\n4. テストの構成\n5. ビルドシステム(Makefile.PL など)\n6. 設定ファイルや依存関係\n\n特に Devel::Cover がどのようにコードカバレッジを計測しているかの仕組みに注目してください。",
  "model": "sonnet"
}

Results

Error:

<tool_use_error>InputValidationError: Task failed due to the following issue: The required parameter description is missing</tool_use_error>

description パラメータを追加して再試行します。

Tool: Task

{
  "subagent_type": "Explore",
  "description": "Explore Devel::Cover codebase",
  "prompt": "このプロジェクトは Perl モジュール Devel::Cover です。以下の点について徹底的に調査してください:\n\n1. プロジェクトの全体構造(ディレクトリ構成、主要ファイル)\n2. 主要な Perl モジュール (.pm ファイル) の役割\n3. XS/C コード(もしあれば)の役割\n4. テストの構成\n5. ビルドシステム(Makefile.PL など)\n6. 設定ファイルや依存関係\n\n特に Devel::Cover がどのようにコードカバレッジを計測しているかの仕組みに注目してください。",
  "model": "sonnet"
}

Results

完璧です。全体像が把握できました。詳細なレポートを作成します。

Devel::Cover プロジェクト調査レポート

Devel::Coverは、Perlのコードカバレッジ計測ツールで、非常に洗練されたアーキテクチャを持っています。以下、調査結果を報告します。


1. プロジェクト全体構造

ディレクトリ構成

/home/hkoba/db/monthly/202512/tmp/Devel--Cover/
├── Cover.xs                    # XSコア実装 (1600行)
├── Makefile.PL                 # ビルドシステム (415行)
├── bin/                        # 実行可能ファイル
│   ├── cover                   # メインコマンドラインツール
│   ├── cpancover              # CPAN全体のカバレッジ
│   ├── gcov2perl              # Cコードカバレッジ変換
│   └── queue
├── lib/Devel/
│   └── Cover/                 # Perlモジュール群
│       ├── Cover.pm           # メインモジュール (1891行)
│       ├── DB.pm              # データベース管理 (1037行)
│       ├── Collection.pm      # カバレッジデータ収集 (810行)
│       ├── Statement.pm       # 文カバレッジ
│       ├── Branch.pm          # 分岐カバレッジ
│       ├── Condition.pm       # 条件カバレッジ
│       ├── Subroutine.pm      # サブルーチンカバレッジ
│       ├── DB/                # データベースサブシステム
│       │   ├── IO.pm          # データ永続化
│       │   ├── IO/
│       │   │   ├── Storable.pm
│       │   │   ├── JSON.pm
│       │   │   └── Sereal.pm
│       │   ├── Structure.pm   # コード構造管理
│       │   └── Digests.pm     # ファイルダイジェスト
│       └── Report/            # レポート生成
│           ├── Html_basic.pm
│           ├── Html_minimal.pm
│           ├── Html_subtle.pm
│           ├── Text.pm
│           ├── Vim.pm
│           ├── Nvim.pm
│           └── Json.pm
├── t/                         # 内部テスト (6ファイル)
├── tests/                     # E2Eテスト (93ファイル)
├── docs/                      # ドキュメント
└── utils/                     # ユーティリティ

2. 主要なPerlモジュールの役割

コアモジュール

Devel::Cover.pm (1891行)

  • カバレッジ計測の中核エンジン
  • Perl OPツリーの走査とB::Deparseの活用
  • 条件分岐、文、サブルーチンの検出
  • XSコードとのインターフェース

主要機能:

  • import(): カバレッジ収集の初期化
  • get_cover(): CVからカバレッジ情報抽出
  • add_statement_cover(): 文カバレッジ記録
  • add_branch_cover(): 分岐カバレッジ記録
  • add_condition_cover(): 条件カバレッジ記録
  • walksymtable(): シンボルテーブル走査でサブルーチン発見

Devel::Cover::DB.pm (1037行)

  • カバレッジデータベースの管理
  • データの読み書き、マージ、削除
  • 複数実行結果の統合
  • サマリー計算

Devel::Cover::DB::Structure.pm

  • コード構造のメタデータ管理
  • ファイル、行、サブルーチンの関連付け
  • ダイジェストによる変更検出

Devel::Cover::DB::IO.pm

  • 複数フォーマット対応の永続化層
  • 優先順位: Sereal > JSON > Storable
  • 環境変数 DEVEL_COVER_DB_FORMAT で選択可能

カバレッジ基準クラス

Devel::Cover::Criterion - 基底クラス

  • covered(), total(), percentage(), error() の共通インターフェース

Devel::Cover::Statement - 文カバレッジ

  • 単純カウント: 実行されたか/されてないか

Devel::Cover::Branch - 分岐カバレッジ

  • if/elsif/else の各パスが実行されたか追跡

Devel::Cover::Condition - 条件カバレッジ

  • 論理演算子 (&&, ||, //, xor) の各条件組み合わせを追跡
  • and_2, and_3, or_2, or_3, xor_4 の特殊化サブクラス

Devel::Cover::Subroutine - サブルーチンカバレッジ

  • サブルーチンが呼び出されたか記録

Devel::Cover::Pod - PODドキュメントカバレッジ

  • Pod::Coverage統合

Devel::Cover::Time - 時間プロファイリング

  • オプション機能

3. XS/Cコードの役割 (Cover.xs - 1600行)

計測の仕組み: 2つの動作モード

モード1: runops関数置換 (レガシーモード)

static int runops_cover(pTHX) {
    for (;;) {
        // 各OPの実行前にカバレッジチェック
        if (PL_op->op_type == OP_NEXTSTATE)
            check_if_collecting(aTHX_ cCOP);
        else if (PL_op->op_type == OP_ENTERSUB)
            store_return(aTHX);
        
        // 文カバレッジ記録
        if (collecting_here(aTHX) && collecting(Statement))
            cover_current_statement(aTHX);
        
        // 論理演算カバレッジ
        if (collecting(Condition))
            switch (PL_op->op_type) {
                case OP_AND: case OP_OR: case OP_DOR:
                case OP_ANDASSIGN: case OP_ORASSIGN:
                    logop(aTHX);
                    break;
            }
        
        // 元のPP関数実行
        call_fptr:
            PL_op = CALLOP PL_op->op_ppaddr(aTHX);
        if (!PL_op) return 0;
    }
}

モード2: op_ppaddr置換 (デフォルト、高速)

static void replace_ops(pTHX) {
    // 元のppaddr保存
    for (i = 0; i < MAXO; i++)
        MY_CXT.ppaddr[i] = PL_ppaddr[i];
    
    // 重要なOP置き換え
    PL_ppaddr[OP_NEXTSTATE] = dc_nextstate;
    PL_ppaddr[OP_DBSTATE]   = dc_dbstate;
    PL_ppaddr[OP_ENTERSUB]  = dc_entersub;
    PL_ppaddr[OP_AND]       = dc_and;
    PL_ppaddr[OP_OR]        = dc_or;
    PL_ppaddr[OP_DOR]       = dc_dor;
    PL_ppaddr[OP_XOR]       = dc_xor;
    // ...
}

主要なC関数

データ構造

typedef struct {
    unsigned covering;              // カバレッジフラグ
    int collecting_here;            // 現在のファイルを収集中か
    HV *statements;                 // 文カバレッジ
    HV *branches;                   // 分岐カバレッジ
    HV *conditions;                 // 条件カバレッジ
    HV *times;                      // 時間プロファイル
    HV *modules;                    // モジュール情報
    HV *files;                      // ファイル情報
    Perl_ppaddr_t ppaddr[MAXO];     // 元のppaddr
} my_cxt_t;

キー関数

// OPを一意に識別するキー生成
static char *get_key(OP *o) {
    uniq.addr = o;
    uniq.op = *o;
    uniq.op.op_ppaddr = 0;
    if (o->op_type == OP_NEXTSTATE) {
        uniq.fileinfohash = fnv1a_hash(CopFILE, CopLINE);
    }
    return (char *)&uniq;
}

// 文カバレッジ記録
static void cover_statement(pTHX_ OP *op) {
    char *ch = get_key(op);
    SV **count = hv_fetch(MY_CXT.statements, ch, KEY_SZ, 1);
    IV c = SvTRUE(*count) ? SvIV(*count) + 1 : 1;
    sv_setiv(*count, c);
}

// 条件カバレッジ追加
static void add_conditional(pTHX_ OP *op, int cond) {
    SV **count = av_fetch(get_conditional_array(aTHX_ op), cond, 1);
    int c = SvTRUE(*count) ? SvIV(*count) + 1 : 1;
    sv_setiv(*count, c);
}

論理演算の追跡メカニズム

短絡評価を追跡するため、次のOPのop_ppaddrを一時的にget_condition()に置き換える:

static void logop(pTHX) {
    // 次のOPを保存
    next = right->op_next;
    
    // Pending_conditionalsハッシュに記録
    cref = hv_fetch(Pending_conditionals, get_key(next), KEY_SZ, 1);
    av_push(conds, newSViv(PTR2IV(PL_op)));
    
    // 次のOP実行時にget_condition()が呼ばれるよう設定
    next->op_ppaddr = get_condition;
}

static OP *get_condition(pTHX) {
    // 条件の各組み合わせを記録
    // 元のppaddr復元して実行
}

時間プロファイリング

#ifdef HAS_GETTIMEOFDAY
static void cover_time(pTHX) {
    if (collecting(Time)) {
        count = hv_fetch(MY_CXT.times, MY_CXT.profiling_key, KEY_SZ, 1);
        c = SvNV(*count) + elapsed();
        sv_setnv(*count, c);
    }
}
#endif

4. カバレッジ計測の仕組み

全体フロー

  1. 初期化 (import時)

    use Devel::Cover -db => 'cover_db', -coverage => 'all';
    
    • XS BOOT実行: replace_ops() または PL_runops = runops_cover
    • カバレッジデータ構造初期化
    • ファイルフィルタ設定 (@Inc, @Ignore, @Select)
  2. CHECK段階

    CHECK { check }
    
    • walksymtable(\%main::, "find_cv"): 全サブルーチン探索
    • B::main_cv->PADLIST: レキシカル変数からCV発見
    • ファイルフィルタリング
  3. 実行時 (Runtime)

    • OPごとにカバレッジデータ収集
    • check_if_collecting(): ファイルごとに収集ON/OFF
    • 文/分岐/条件/サブルーチン実行カウント
  4. END段階

    END { report() }
    
    • get_cover(@Cvs): 各CVのOPツリー走査
    • B::Deparse活用: ソースコード再構築
      • カスタムdeparse(): 条件分岐検出
      • logop(), logassignop(): 論理演算解析
    • カバレッジデータとコード構造のマッピング
    • データベース書き込み
  5. レポート生成 (cover コマンド)

    cover -report html
    
    • データベース読み込み
    • マージ・集計
    • HTMLやテキストレポート生成

B::Deparseのカスタマイズ

Devel::CoverはB::Deparseの内部関数をフックしてカバレッジ情報を収集:

# オリジナル関数保存
BEGIN {
    $Original{deparse}     = \&B::Deparse::deparse;
    $Original{logop}       = \&B::Deparse::logop;
    $Original{logassignop} = \&B::Deparse::logassignop;
}

# カスタム実装
sub logop {
    my ($op, $left, $right) = @_;
    
    # 左辺と右辺をdeparse
    $left  = $self->deparse($left, 1);
    $right = $self->deparse($right, 1);
    
    # カバレッジ情報記録
    add_condition_cover($op, $highop, $left, $right);
    
    # 分岐カバレッジ記録
    add_branch_cover($op, $lowop, "if ($left)", $File, $Line);
}

この手法により:

  • OPツリーから元のソースコード形式を復元
  • 条件式のテキスト表現を取得 (例: $a && $b)
  • 分岐の種類を判定 (if, elsif, unless, etc.)

5. テストの構成

テストディレクトリ構造

t/ (6ファイル)

  • 内部ユニットテスト
  • /home/hkoba/db/monthly/202512/tmp/Devel--Cover/t/00-report-prereqs.t
  • /home/hkoba/db/monthly/202512/tmp/Devel--Cover/t/internal/
  • /home/hkoba/db/monthly/202512/tmp/Devel--Cover/t/regexp/

tests/ (93ファイル)

  • E2Eテスト: 実際のコードスニペット
  • 各テストファイルに __COVER__ ディレクティブ:
    # __COVER__ criteria statement branch condition
    # __COVER__ test_parameters -merge 0
    # __COVER__ cover_parameters -report text
    

Devel::Cover::Test.pm

  • テストフレームワーク
  • 期待値(gold)ファイルとの比較
  • Makefile.PL がtests/をt/e2e/に変換

テスト実行

# Makefile.PLがテスト生成
for my $t (readdir "tests") {
    # tests/foo -> t/e2e/afoo.t
    print $test <<EOT;
    use Devel::Cover::Test;
    my $test = Devel::Cover::Test->new("$t");
    $test->run_test;
    EOT
}

6. ビルドシステム (Makefile.PL)

主要機能

  1. 依存関係チェック

    check "Storable", $msg;
    check "JSON::MaybeXS", $msg;
    check "Sereal", $msg;
    check "Digest::MD5", $msg;
    check "Template", $msg, "2.00";
    check "Pod::Coverage", $msg, "0.06";
    
  2. Inc.pm生成

    • ビルド時の@INCをキャプチャ
    • 実行時にPerl版が違う場合の警告機能
  3. テスト生成

    • tests/* から t/e2e/*.t を動的生成
  4. カスタムターゲット

    make t          # パラレルテスト実行
    make self_cover # Devel::Cover自身のカバレッジ
    make gold       # 期待値更新
    make diff       # 差分確認
    
  5. XSコンパイル

    • Cover.xs をコンパイル
    • utils/typemap 使用

プリプロセッサフラグ

#define Statement  0x00000001
#define Branch     0x00000002
#define Condition  0x00000004
#define Subroutine 0x00000008
#define Pod        0x00000020
#define Time       0x00000040
#define All        0xffffffff

7. 設定ファイルと依存関係

必須依存関係

  • Digest::MD5: ファイル変更検出
  • Storable/JSON::MaybeXS/Sereal: データ永続化

オプション依存関係

  • Template Toolkit 2.00: HTML/cpancoverレポート
  • PPI::HTML または Perl::Tidy: シンタックスハイライト
  • Pod::Coverage 0.06: PODカバレッジ
  • Pod::Coverage::CountParents: PODカバレッジ改善
  • Browser::Open: -launch フラグ
  • HTML::Entities: HTMLレポート

環境変数

# ユーザー変数
DEVEL_COVER_OPTIONS="-db,mydb,-coverage,statement,branch"
HARNESS_PERL_SWITCHES=-MDevel::Cover
PERL5OPT=-MDevel::Cover

# 開発者変数
DEVEL_COVER_DEBUG=1
DEVEL_COVER_SELF=1        # Devel::Cover自身をカバー
DEVEL_COVER_NO_TESTS=1    # テスト無効化
DEVEL_COVER_DB_FORMAT=Sereal

設定ファイル

  • .perlcriticrc (60KB!): 厳格なコード品質チェック
  • .perltidyrc: コードフォーマット
  • .perlimports.toml: import最適化
  • dist.ini: Dist::Zilla設定

8. カバレッジ計測の重要な仕組みの詳細

OPキーの生成

各OPを一意に識別するために、以下の情報を組み合わせたハッシュキーを生成:

struct unique {
    OP *addr;                // OPアドレス
    OP op;                   // OP構造体コピー
    size_t fileinfohash;     // ファイル名:行番号のハッシュ
};

FNV-1aハッシュ使用でファイル位置情報を圧縮。

条件カバレッジの配列構造

各条件に対して6要素配列:

// 0 - XORの第1オペランドがtrueだったフラグ
// 1 - 短絡せず第2オペランドfalse
// 2 - 短絡せず第2オペランドtrue
// 3 - 短絡した、またはXORで第2オペランドfalse
// 4 - XORで第2オペランドtrue
// 5 - void context フラグ

これにより、and_2, and_3, or_2, or_3, xor_4 の全パターンを記録可能。

ファイルフィルタリング

3段階のフィルタ:

  1. @Inc_re: 標準ライブラリパスを除外
  2. @Ignore_re: 特定パターンを除外 (デフォルト: /Devel/Cover[./])
  3. @Select_re: 特定パターンのみ選択
sub use_file {
    my $file = shift;
    return 0 if grep $file =~ $_, @Inc_re;
    return 0 if @Select_re && !grep $file =~ $_, @Select_re;
    return 0 if @Ignore_re && grep $file =~ $_, @Ignore_re;
    return 1;
}

スレッド対応 (部分的)

#ifdef USE_ITHREADS
MUTEX_LOCK(&DC_mutex);
// クリティカルセクション
MUTEX_UNLOCK(&DC_mutex);
#endif

ただし、READMEに「スレッド未対応」と明記。基本的なミューテックスは実装済み。


結論: Devel::Coverの卓越した設計

Devel::Coverは以下の点で優れた設計を持っています:

  1. 2層アーキテクチャ

    • XS層: 高速なカバレッジデータ収集
    • Perl層: 柔軟なデータ処理とレポート生成
  2. 2つの計測モード

    • runops置換: 包括的だが遅い
    • ppaddr置換: 高速でデフォルト
  3. B::Deparseの巧妙な活用

    • OPツリーからソースコード復元
    • 条件分岐の詳細な解析
  4. 複数の永続化フォーマット

    • Sereal/JSON/Storableの自動選択
    • ポータビリティとパフォーマンスの両立
  5. 包括的なカバレッジ基準

    • Statement, Branch, Condition, Subroutine, Pod, Time
    • 各基準ごとに特殊化されたクラス
  6. 堅牢なテストフレームワーク

    • 93のE2Eテスト
    • gold ファイルによる期待値管理
    • 自己カバレッジ機能

このアーキテクチャにより、Devel::CoverはPerlのコードカバレッジツールのデファクトスタンダードとなっています。

agentId: aa5d2ac (for resuming to continue this agent's work if needed)

エージェントから非常に詳細なレポートが返ってきました。Devel::Cover の構造がよく把握できました。ユーザーに要約して伝えます。


uuid: assistant: 3b9039d8-fd48-4d83-a526-96803d1c5c34, assistant: 52e8432a-a7f3-428c-8825-05542ad81907, assistant: c32555ce-ab8c-458f-8124-2ed018aee297, assistant: be490636-7b71-4316-9254-863083e53550