lib/choir/src/properties/scalars.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const hypothesis = @import("hypothesis");
  3 const choir = @import("choir");
  4 
  5 const ScalarKind = choir.dialects.arith.ScalarKind;
  6 const scalar_kinds = std.enums.values(ScalarKind);
  7 
  8 fn settings() hypothesis.Settings {
  9     var value = hypothesis.Settings.quick()
 10         .withSeed(0x5ca1_a2c1_2026_0710)
 11         .withDatabase("zig-out/hypothesis-failures/choir-arith-scalar-classifier");
 12     value.max_examples = 100;
 13     value.target_examples = 100;
 14     return value;
 15 }
 16 
 17 fn drawUsize(conjecture: *hypothesis.ConjectureData, min: usize, max: usize, shrink_towards: usize) !usize {
 18     return @intCast(try conjecture.drawInteger(@intCast(min), @intCast(max), @intCast(shrink_towards)));
 19 }
 20 
 21 fn referenceTypeName(name: []const u8) ?ScalarKind {
 22     inline for (scalar_kinds) |kind| {
 23         if (std.mem.eql(u8, name, choir.dialects.arith.scalarTypeName(kind))) return kind;
 24     }
 25     return null;
 26 }
 27 
 28 fn referenceSuffix(suffix: []const u8) ?ScalarKind {
 29     inline for (scalar_kinds) |kind| {
 30         if (std.mem.eql(u8, suffix, choir.dialects.arith.scalarTypeSuffix(kind))) return kind;
 31     }
 32     return null;
 33 }
 34 
 35 fn mutateCanonical(
 36     conjecture: *hypothesis.ConjectureData,
 37     source: []const u8,
 38     buffer: []u8,
 39 ) ![]const u8 {
 40     @memcpy(buffer[0..source.len], source);
 41     const mode = try drawUsize(conjecture, 0, 3, 0);
 42     return switch (mode) {
 43         0 => blk: {
 44             const index = try drawUsize(conjecture, 0, source.len - 1, 0);
 45             const replacement: u8 = @intCast(try drawUsize(conjecture, 0, 255, 0));
 46             buffer[index] = if (replacement == buffer[index]) replacement +% 1 else replacement;
 47             break :blk buffer[0..source.len];
 48         },
 49         1 => buffer[0..try drawUsize(conjecture, 0, source.len - 1, 0)],
 50         2 => blk: {
 51             buffer[source.len] = @intCast(try drawUsize(conjecture, 0, 255, 0));
 52             break :blk buffer[0 .. source.len + 1];
 53         },
 54         3 => blk: {
 55             std.mem.copyBackwards(u8, buffer[1 .. source.len + 1], buffer[0..source.len]);
 56             buffer[0] = @intCast(try drawUsize(conjecture, 0, 255, 0));
 57             break :blk buffer[0 .. source.len + 1];
 58         },
 59         else => unreachable,
 60     };
 61 }
 62 
 63 pub const ScalarClassifierProperty = struct {
 64     pub fn property(conjecture: *hypothesis.ConjectureData, allocator: std.mem.Allocator) !void {
 65         var ctx = try choir.Context.init(allocator, choir.Context.Limits.testing);
 66         defer ctx.deinit(allocator);
 67         try choir.dialects.registerAllDialects(&ctx);
 68 
 69         inline for (scalar_kinds) |kind| {
 70             const name = choir.dialects.arith.scalarTypeName(kind);
 71             const suffix = choir.dialects.arith.scalarTypeSuffix(kind);
 72             try std.testing.expectEqual(kind, choir.dialects.arith.scalarKindFromTypeName(name).?);
 73             try std.testing.expectEqual(kind, choir.dialects.arith.scalarKindFromSuffix(suffix).?);
 74             const ty = try ctx.getDialectTypeFromName(name);
 75             try std.testing.expectEqual(kind, choir.dialects.arith.scalarKindFromType(ty).?);
 76         }
 77 
 78         var arbitrary_buffer: [64]u8 = undefined;
 79         const arbitrary_len = try drawUsize(conjecture, 0, arbitrary_buffer.len, 0);
 80         for (arbitrary_buffer[0..arbitrary_len]) |*byte| {
 81             byte.* = @intCast(try drawUsize(conjecture, 0, 255, 0));
 82         }
 83         const arbitrary = arbitrary_buffer[0..arbitrary_len];
 84         try conjecture.target(arbitrary_len, "arbitrary name length");
 85         try std.testing.expectEqual(referenceTypeName(arbitrary), choir.dialects.arith.scalarKindFromTypeName(arbitrary));
 86         try std.testing.expectEqual(referenceSuffix(arbitrary), choir.dialects.arith.scalarKindFromSuffix(arbitrary));
 87 
 88         const field_index = try drawUsize(conjecture, 0, scalar_kinds.len - 1, 0);
 89         const kind = scalar_kinds[field_index];
 90         var mutated_name_buffer: [32]u8 = undefined;
 91         var mutated_suffix_buffer: [32]u8 = undefined;
 92         const mutated_name = try mutateCanonical(
 93             conjecture,
 94             choir.dialects.arith.scalarTypeName(kind),
 95             &mutated_name_buffer,
 96         );
 97         const mutated_suffix = try mutateCanonical(
 98             conjecture,
 99             choir.dialects.arith.scalarTypeSuffix(kind),
100             &mutated_suffix_buffer,
101         );
102         try std.testing.expectEqual(referenceTypeName(mutated_name), choir.dialects.arith.scalarKindFromTypeName(mutated_name));
103         try std.testing.expectEqual(referenceSuffix(mutated_suffix), choir.dialects.arith.scalarKindFromSuffix(mutated_suffix));
104     }
105 };
106 
107 test "property: arith scalar classifiers accept only canonical names" {
108     try hypothesis.checkNamed(ScalarClassifierProperty, "choir-arith-scalar-classifier", settings());
109 }