lib/choir/build/tests.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const build_api = @import("build_api");
  3 const deps = @import("deps.zig");
  4 const module = @import("module.zig");
  5 const cfg_evidence_build_source = @embedFile("tests.zig");
  6 const cfg_evidence_producer_source = @embedFile("../src/properties/evidence.zig");
  7 
  8 pub const Graph = struct {
  9     step: *std.Build.Step,
 10 };
 11 
 12 pub fn addUnit(
 13     b: *std.Build,
 14     configuration: deps.Configuration,
 15     choir: *std.Build.Module,
 16     abi: *std.Build.Module,
 17 ) Graph {
 18     const unit_tests = b.addTest(.{
 19         .root_module = choir,
 20         .filters = configuration.test_filters,
 21     });
 22     const abi_tests = b.addTest(.{
 23         .root_module = abi,
 24         .filters = configuration.test_filters,
 25     });
 26     const run_unit_tests = b.addRunArtifact(unit_tests);
 27     const run_abi_tests = b.addRunArtifact(abi_tests);
 28     const step = b.step("test", "Run Choir unit tests");
 29     step.dependOn(&run_unit_tests.step);
 30     step.dependOn(&run_abi_tests.step);
 31     return .{ .step = step };
 32 }
 33 
 34 pub fn addRewriteObservations(
 35     b: *std.Build,
 36     options: build_api.ModuleOptions,
 37     choir_tests: *std.Build.Module,
 38 ) void {
 39     const generator = b.addExecutable(.{
 40         .name = "choir-update-rewrite-observations",
 41         .root_module = b.createModule(options.createOptions(
 42             b,
 43             "build/observations.zig",
 44             &.{.{ .name = "choir_tests", .module = choir_tests }},
 45         )),
 46     });
 47     const run = b.addRunArtifact(generator);
 48     run.setCwd(b.path("src/passes/observations/fixture"));
 49     run.has_side_effects = true;
 50     const step = b.step(
 51         "update-rewrite-observations",
 52         "Regenerate the four retained rewrite observations; review their diff",
 53     );
 54     step.dependOn(&run.step);
 55 }
 56 
 57 /// Builds the SPIR-V validation gate for the stage emitters under `options`,
 58 /// which the gate's timings need to be optimized. The gate runs the spirv-val
 59 /// that `-Dspirv-val` names and fails without one.
 60 pub fn addStageValidation(
 61     b: *std.Build,
 62     options: build_api.ModuleOptions,
 63     inputs: module.Inputs,
 64 ) void {
 65     const spirv_val = b.option(
 66         []const u8,
 67         "spirv-val",
 68         "The spirv-val binary the stage validation gate runs",
 69     );
 70     const step = b.step(
 71         "spirv-val",
 72         "Validate every emitted vertex and fragment module with spirv-val",
 73     );
 74     const path = spirv_val orelse {
 75         const missing = b.addFail(
 76             "spirv-val needs -Dspirv-val=<pinned nixpkgs#spirv-tools>/bin/spirv-val",
 77         );
 78         step.dependOn(&missing.step);
 79         return;
 80     };
 81     const tool = b.addExecutable(.{
 82         .name = "choir-spirv-val",
 83         .root_module = b.createModule(options.createOptions(
 84             b,
 85             "build/stages.zig",
 86             &.{
 87                 .{ .name = "choir_tests", .module = module.createTestModule(b, options, inputs) },
 88                 .{ .name = "pretty", .module = inputs.pretty },
 89                 .{ .name = "sys", .module = inputs.sys },
 90             },
 91         )),
 92     });
 93     const run = b.addRunArtifact(tool);
 94     run.addArg(path);
 95     _ = run.addOutputDirectoryArg("spirv");
 96     run.has_side_effects = true;
 97     step.dependOn(&run.step);
 98 }
 99 
100 /// Emits every stage module's SPIR-V into a directory that dependents reach as the named lazy
101 /// path `stage-spirv`. Only a dependent that asks for the path builds the emitter, so a package
102 /// that links `choir_abi` alone never builds the compiler.
103 pub fn addStageSpirv(
104     b: *std.Build,
105     options: build_api.ModuleOptions,
106     inputs: module.Inputs,
107 ) void {
108     const tool = b.addExecutable(.{
109         .name = "choir-stage-spirv",
110         .root_module = b.createModule(options.createOptions(
111             b,
112             "build/spirv.zig",
113             &.{
114                 .{ .name = "choir_tests", .module = module.createTestModule(b, options, inputs) },
115                 .{ .name = "pretty", .module = inputs.pretty },
116             },
117         )),
118     });
119     const run = b.addRunArtifact(tool);
120     b.addNamedLazyPath("stage-spirv", run.addOutputDirectoryArg("stage-spirv"));
121 }
122 
123 /// Emits every stage module's MSL into a directory that dependents reach as the named lazy path
124 /// `stage-msl`, with the same laziness as `stage-spirv`.
125 pub fn addStageMsl(
126     b: *std.Build,
127     options: build_api.ModuleOptions,
128     inputs: module.Inputs,
129 ) void {
130     const tool = b.addExecutable(.{
131         .name = "choir-stage-msl",
132         .root_module = b.createModule(options.createOptions(
133             b,
134             "build/msl.zig",
135             &.{
136                 .{ .name = "choir_tests", .module = module.createTestModule(b, options, inputs) },
137                 .{ .name = "pretty", .module = inputs.pretty },
138             },
139         )),
140     });
141     const run = b.addRunArtifact(tool);
142     b.addNamedLazyPath("stage-msl", run.addOutputDirectoryArg("stage-msl"));
143 }
144 
145 /// Emits every stage module as an x86_64 object of host functions into a directory that
146 /// dependents reach as the named lazy path `stage-cpu`, with the same laziness as `stage-spirv`.
147 pub fn addStageCpu(
148     b: *std.Build,
149     options: build_api.ModuleOptions,
150     inputs: module.Inputs,
151 ) void {
152     const tool = b.addExecutable(.{
153         .name = "choir-stage-cpu",
154         .root_module = b.createModule(options.createOptions(
155             b,
156             "build/cpu.zig",
157             &.{
158                 .{ .name = "choir_tests", .module = module.createTestModule(b, options, inputs) },
159                 .{ .name = "pretty", .module = inputs.pretty },
160             },
161         )),
162     });
163     const run = b.addRunArtifact(tool);
164     b.addNamedLazyPath("stage-cpu", run.addOutputDirectoryArg("stage-cpu"));
165 }
166 
167 pub fn addProperties(
168     b: *std.Build,
169     options: build_api.ModuleOptions,
170     configuration: deps.Configuration,
171     dependencies: deps.Requested,
172     choir: *std.Build.Module,
173 ) void {
174     const property_module = b.createModule(options.createOptions(
175         b,
176         "src/properties/test.zig",
177         &.{
178             .{ .name = "alloc_arena", .module = dependencies.inputs.alloc_arena },
179             .{ .name = "choir", .module = choir },
180             .{ .name = "hypothesis", .module = dependencies.hypothesis },
181             .{ .name = "pretty", .module = dependencies.inputs.pretty },
182         },
183     ));
184     const property_tests = b.addTest(.{
185         .root_module = property_module,
186         .filters = configuration.test_filters,
187     });
188     const run_property_tests = b.addRunArtifact(property_tests);
189     const property_step = b.step("pbt", "Run Choir property tests");
190     property_step.dependOn(&run_property_tests.step);
191 }
192 
193 pub fn addCfgEvidence(
194     b: *std.Build,
195     options: build_api.ModuleOptions,
196     dependencies: deps.Requested,
197     choir: *std.Build.Module,
198 ) void {
199     const repository = b.root.joinString(b.allocator, "../..") catch @panic("OOM");
200     const revision = b.addSystemCommand(&.{
201         "git",
202         "-C",
203         repository,
204         "rev-parse",
205         "--verify",
206         "HEAD^{commit}",
207     });
208     revision.setName("capture the Choir CFG evidence revision");
209     revision.has_side_effects = true;
210     const revision_file = revision.captureStdOut(.{ .trim_whitespace = .none });
211     const owned_status = b.addSystemCommand(&.{
212         "git",
213         "-C",
214         repository,
215         "status",
216         "--porcelain=v1",
217         "--untracked-files=all",
218         "--",
219         "lib/choir/src/core",
220         "lib/choir/src/properties/cfg.zig",
221         "lib/choir/src/properties/evidence.zig",
222         "lib/choir/build/tests.zig",
223         "lib/choir/build/register.zig",
224         "build/packages/registry/lib.zig",
225         ".github/workflows/ci-verification.yml",
226     });
227     owned_status.setName("capture the Choir CFG evidence owned-source status");
228     owned_status.has_side_effects = true;
229     const owned_status_file = owned_status.captureStdOut(.{ .trim_whitespace = .none });
230     const evidence_options = b.addOptions();
231     evidence_options.addOption(
232         []const u8,
233         "build_source_sha256",
234         sourceDigest(b, cfg_evidence_build_source),
235     );
236     evidence_options.addOption(
237         []const u8,
238         "producer_source_sha256",
239         sourceDigest(b, cfg_evidence_producer_source),
240     );
241     const evidence_module = b.createModule(options.createOptions(
242         b,
243         "src/properties/evidence.zig",
244         &.{
245             .{ .name = "alloc_arena", .module = dependencies.inputs.alloc_arena },
246             .{ .name = "choir", .module = choir },
247             .{ .name = "evidence_options", .module = evidence_options.createModule() },
248             .{ .name = "hypothesis", .module = dependencies.hypothesis },
249             .{ .name = "pretty", .module = dependencies.inputs.pretty },
250             .{ .name = "sys", .module = dependencies.inputs.sys },
251         },
252     ));
253     const evidence_tests = b.addTest(.{
254         .root_module = evidence_module,
255         .filters = &.{
256             "CFG evidence coverage",
257             "CFG evidence freshness",
258             "CFG evidence receipt",
259         },
260     });
261     const run_tests = b.addRunArtifact(evidence_tests);
262     const tests_step = b.step("cfg-evidence-tests", "Run focused Choir CFG evidence tests");
263     tests_step.dependOn(&run_tests.step);
264 
265     const evidence_exe = b.addExecutable(.{
266         .name = "choir-cfg-evidence",
267         .root_module = evidence_module,
268     });
269     const run_evidence = b.addRunArtifact(evidence_exe);
270     run_evidence.step.dependOn(&run_tests.step);
271     run_evidence.addFileArg(revision_file);
272     run_evidence.addFileArg(owned_status_file);
273     const receipt = run_evidence.addOutputFileArg("choir-cfg-evidence.jsonl");
274     run_evidence.has_side_effects = true;
275     const install = b.addInstallFileWithDir(
276         receipt,
277         .prefix,
278         "verification/choir/choir-cfg-evidence.jsonl",
279     );
280     const evidence_step = b.step(
281         "cfg-evidence",
282         "Run the bounded Choir CFG property and emit evidence",
283     );
284     evidence_step.dependOn(&install.step);
285     b.addNamedLazyPath("cfg-evidence", receipt);
286 }
287 
288 fn sourceDigest(b: *std.Build, source: []const u8) []const u8 {
289     var digest: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined;
290     std.crypto.hash.sha2.Sha256.hash(source, &digest, .{});
291     const hex = std.fmt.bytesToHex(digest, .lower);
292     return b.allocator.dupe(u8, &hex) catch @panic("OOM");
293 }