0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Zig] メインプロジェクトからサブプロジェクトのテストを一括実行する

Last updated at Posted at 2024-08-30

EDIT (2024/11/03):
バージョン0.14以降、std.Build.initialized_depsフィールドは廃止された。
かわりにstd.Build.graph.dependency_cacheを走査する。
このフィールドの型はstd.HashMapUnmanagedであり、値の型は*std.Build.Dependencyで0.13の時と同じ

あらまし

たとえば以下のようなフォルダ構成。

root
├── 📁sub-proj_1
│   ├── 📁src
│   └── 📃build.zig
├── 📁sub-proj_2
│   ├── 📁src
│   └── 📃build.zig
├── 📁sub-proj_3
│   ├── 📁src
│   └── 📃build.zig
├── 📃build.zig

rootがメインプロジェクトで直下にbuild.zigを持つ。
そしてサブプロジェクトが配下に置かれている。
そんな構成。

各サブプロジェクトは、ユニットテスト用のビルド設定が定義されていて、zig build testで実行できるものとする。

このような環境でメインプロジェクトから全てのサブプロジェクトのテストを一括に実行するにはどうするのがスマートか。

その一例を思いついたので記録として残す。

方法(あかんやつ)

一番簡単な方法は、各サブプロジェクトのテスト定義をメインプロジェクトのbuild.zigにまるっとコピーすることである。
しかしこの方法は、サブプロジェクトの変更を常にトラックしてメインプロジェクトに反映させる必要があるため煩雑である。何より美しくない。

方法

zig initでプロジェクトを作成した場合に、以下のようなテスト構成でテンプレートから作成される

  1. std.Build.addTestを呼んでテスト用のコンパイル構成を作成する。
  2. std.Build.addRunArtifactを呼んでテストランナーを作成する。

となっている。

そしてこの作成されるテストランナーは、プロジェクト内に閉じたものであるため、外側のプロジェクトからは呼べない1

また、テストのコンパイル結果はプロジェクト内の.zig-cacheにいるため、特定は容易ではない2

実施手順

1. サブプロジェクトの構成

事前準備として、サブプロジェクトでのテスト構成を作成する際、以下のような記述にする。

const exe_unit_tests = b.addTest(.{
    .name = "test-sub-proj_1{s}-{s}",,
    .root_source_file = b.path("src/main.zig"),
    .target = target,
    .optimize = optimize,
});

// テスト構成は省略

// ランナーを作成する
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);

// 成果物としてインストールする
                test_step.dependOn(&b.addInstallArtifact(exe_unit_tests, .{.dest_sub_path = "../test/sub-proj_1"}).step);

ポイントは2点。

  • テストに名前をつけること
    これは、後でメインプロジェクトからテスト構成を収集するが、その際デフォの設定の構成を省くためである。
    たいていの公開されたパッケージは、名前を付け替えるまではしないため、これを行うことでサブプロジェクトのみを効果的に収集できるようになる(と思ってる)。
  • 成果物として、zig-out下にインストールすること
    メインプロジェクトからの収集の目印として使用する。

2. サブプロジェクトをメインプロジェクトに追加

サブプロジェクトをメインプロジェクトのbuild.zig.zonに追加する。

.{
    .name = "main_proj",
    .version = "0.0.0",
    .dependencies = .{
        .sub_proj_1 = .{ .path = ".sub-proj_1" },
        .sub_proj_2 = .{ .path = ".sub-proj_2" },
        .sub_proj_3 = .{ .path = ".sub-proj_3" },
    }
    .paths = .{}
}

この際.pathで指定するのが便利。

3. サブプロジェクトのテストを収集

メインプロジェクトのzig.buildに以下の関数を作成する

fn addTestAll(b: *std.Build) void {
    const run_step = b.step("test", "Run all unit tests");
    // 0.13系までの書き方
    // var deps_iter = b.initialized_deps.valueIterator();
    // 以下は0.14系の書き方
    var deps_iter = b.graph.dependency_cache.valueIterator();

    while(deps_iter.next()) |dep| {
        var tls_iter = dep.*.builder.top_level_steps.iterator();
        while (tls_iter.next()) |entry| {
            const tls = entry.value_ptr.*;
            if (! std.mem.eql(u8, tls.step.name, "test")) continue;

            for (tls.step.dependencies.items) |dep_step| {
                if (dep_step.id != .install_artifact) continue;

                const inst: *std.Build.Step.InstallArtifact = dep_step.cast(std.Build.Step.InstallArtifact) orelse continue;

                if (inst.artifact.kind == .@"test") {
                    const path = b.pathResolve(&.{"test/", inst.artifact.name});
                    std.debug.print("Test found: {s}\n", .{path});
                    // install test artifact
                    const install_step = b.addInstallArtifact(
                        inst.artifact, 
                        .{
                            .dest_sub_path = path, 
                            .dest_dir = .{.override = .prefix}
                        }
                    );
                    // invoke test
                    const invoke_step = b.addSystemCommand(&.{b.pathResolve(&.{b.install_prefix, path})});
                    invoke_step.step.dependOn(&install_step.step);
                    run_step.dependOn(&invoke_step.step);
                }
            }
        }
    }
}

この関数では以下のことを行なっている。

  1. zig build test用のランナーを作成する
  2. build.zig.zonに追加された全ての依存パッケージを列挙
  3. そのなかで、ステップ名がtestとなっているものを省く
  4. 依存パッケージの中から、さらにstd.Build.Step.InstallArtifact以外のステップを省く
  5. 成果物のkindフィールドが@test以外を省く3
  6. メインプロジェクトに成果物としてインストールする
    その際、zig-out/test下にインストールすることで混在することを避けている
    . std.Build.addSystemCommandでインストールしたテストを実行するよう構成する
  7. メインプロジェクトのランナーに関連づける

4. addTestAllを呼ぶ

build()関数の最後で呼んでおけば良いかと。

pub fn build(b: std.Build) void {
    // 省略
    addTestAll();
}

5. 実行

zig build testの実行で全てのサブプロジェクトのテストが実行される。
この記述の良いところは、新たなサブプロジェクトを追加する際に、同じような記述を行っておくと自動的にトラックしてくれることである。

おまけ

サブプロジェクトでテストを成果物として登録した効果として、VS Code等でデバック構成として登録しておくことができる。

困った時デバッガ頼りのときにスムーズに開始できて嬉しい。

  1. 正確には外部コマンドを叩く構成を追加し、カレントディレクトリをサブプロジェクトに移してzig build testを実行させればできなくもない。

  2. zig build test --verboseでパスを知ることは一応できる。

  3. std.Build.addTestkindフィールドが@testで作成される。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?