Files
zig/lib/std/testing/Smith.zig
T
2026-05-27 10:03:51 +01:00

945 lines
32 KiB
Zig

//! Used in conjuncation with `std.testing.fuzz` to generate values
const builtin = @import("builtin");
const std = @import("../std.zig");
const assert = std.debug.assert;
const fuzz_abi = std.Build.abi.fuzz;
const Smith = @This();
/// Null if the fuzzer is being used, in which case this struct will not be mutated.
///
/// Intended to be initialized directly.
in: ?[]const u8,
pub const Weight = fuzz_abi.Weight;
fn intUid(hash: u32) fuzz_abi.Uid {
@disableInstrumentation();
return @bitCast(hash << 1);
}
fn bytesUid(hash: u32) fuzz_abi.Uid {
@disableInstrumentation();
return @bitCast(hash | 1);
}
fn Backing(T: type) type {
return @Int(.unsigned, @bitSizeOf(T));
}
fn toExcessK(T: type, x: T) Backing(T) {
return @bitCast(x -% std.math.minInt(T));
}
fn fromExcessK(T: type, x: Backing(T)) T {
return @as(T, @bitCast(x)) +% std.math.minInt(T);
}
const EnumField = struct {
name: [:0]const u8,
value: comptime_int,
};
fn enumFieldLessThan(_: void, a: EnumField, b: EnumField) bool {
return a.value < b.value;
}
/// Returns an array of weights containing each possible value of `T`.
//
// `inline` to propogate the `comptime`ness of the result
pub inline fn baselineWeights(T: type) []const Weight {
return comptime switch (@typeInfo(T)) {
.bool, .int, .float => i: {
// Reject types that don't have a fixed bitsize (esp. usize)
// since they are not gauraunteed to fit in a u64 across targets.
if (std.mem.indexOfScalar(type, &.{
isize, usize,
c_char, c_longdouble,
c_short, c_ushort,
c_int, c_uint,
c_long, c_ulong,
c_longlong, c_ulonglong,
}, T) != null) {
@compileError("type does not have a fixed bitsize: " ++ @typeName(T));
}
break :i &.{.rangeAtMost(Backing(T), 0, (1 << @bitSizeOf(T)) - 1, 1)};
},
.@"struct" => |s| if (s.backing_integer) |B|
baselineWeights(B)
else
@compileError("non-packed structs cannot be weighted"),
.@"union" => |u| if (u.layout == .@"packed")
baselineWeights(Backing(T))
else
@compileError("non-packed unions cannot be weighted"),
.@"enum" => |e| if (e.mode == .nonexhaustive)
baselineWeights(e.tag_type)
else if (e.field_names.len == 0)
// Cannot be included in below branch due to `log2_int_ceil`
@compileError("exhaustive zero-field enums cannot be weighted")
else e: {
@setEvalBranchQuota(@intCast(4 * e.field_names.len *
std.math.log2_int_ceil(usize, e.field_names.len)));
var sorted_fields = blk: {
var fields: [e.field_names.len]EnumField = undefined;
for (e.field_names, e.field_values, &fields) |f_name, f_value, *field| {
field.* = .{
.name = f_name,
.value = f_value,
};
}
break :blk fields;
};
std.mem.sortUnstable(EnumField, &sorted_fields, {}, enumFieldLessThan);
var weights: []const Weight = &.{};
var seq_first: u64 = sorted_fields[0].value;
for (sorted_fields[0 .. sorted_fields.len - 1], sorted_fields[1..]) |prev, field| {
if (field.value != prev.value + 1) {
weights = weights ++ .{Weight.rangeAtMost(u64, seq_first, prev.value, 1)};
seq_first = field.value;
}
}
weights = weights ++ .{Weight.rangeAtMost(
u64,
seq_first,
sorted_fields[sorted_fields.len - 1].value,
1,
)};
break :e weights;
},
else => @compileError("unexpected type: " ++ @typeName(T)),
};
}
test baselineWeights {
try std.testing.expectEqualSlices(
Weight,
&.{.rangeAtMost(bool, false, true, 1)},
baselineWeights(bool),
);
try std.testing.expectEqualSlices(
Weight,
&.{.rangeAtMost(u4, 0, 15, 1)},
baselineWeights(u4),
);
try std.testing.expectEqualSlices(
Weight,
&.{.rangeAtMost(u4, 0, 15, 1)},
baselineWeights(i4),
);
try std.testing.expectEqualSlices(
Weight,
&.{.rangeAtMost(u16, 0, 0xffff, 1)},
baselineWeights(f16),
);
try std.testing.expectEqualSlices(
Weight,
&.{.rangeAtMost(u4, 0, 15, 1)},
baselineWeights(packed struct(u4) { _: u4 }),
);
try std.testing.expectEqualSlices(
Weight,
&.{.rangeAtMost(u4, 0, 15, 1)},
baselineWeights(packed union { _: u4 }),
);
try std.testing.expectEqualSlices(
Weight,
&.{.rangeAtMost(u4, 0, 15, 1)},
baselineWeights(enum(u4) { _ }),
);
try std.testing.expectEqualSlices(Weight, &.{
.rangeAtMost(u4, 0, 1, 1),
.value(u4, 3, 1),
.value(u4, 5, 1),
.rangeAtMost(u4, 8, 10, 1),
}, baselineWeights(enum(u4) {
a = 1,
b = 5,
c = 8,
d = 3,
e = 0,
f = 9,
g = 10,
}));
}
fn valueFromInt(T: anytype, int: Backing(T)) T {
@disableInstrumentation();
return switch (@typeInfo(T)) {
.@"enum" => @enumFromInt(int),
else => @bitCast(int),
};
}
fn checkWeights(weights: []const Weight, max_incl: u64) void {
@disableInstrumentation();
const w0 = weights[0]; // Sum of weights is zero
assert(w0.weight != 0);
assert(w0.max <= max_incl);
var incl_sum: u64 = (w0.max - w0.min) * w0.weight + (w0.weight - 1); // Sum of weights greater than 2^64
for (weights[1..]) |w| {
assert(w.weight != 0);
assert(w.max <= max_incl);
// This addition will not overflow except with an illegal combination of weights since
// the exclusive sum must be at least one so a span of all values is impossible.
incl_sum += (w.max - w.min + 1) * w.weight; // Sum of weights greater than 2^64
}
}
// `inline` to propogate callee's unique return address
inline fn firstHash() u32 {
return @truncate(std.hash.int(@returnAddress()));
}
// `noinline` to capture a unique return address
pub noinline fn value(s: *Smith, T: type) T {
@disableInstrumentation();
return s.valueWithHash(T, firstHash());
}
// `noinline` to capture a unique return address
pub noinline fn valueWeighted(s: *Smith, T: type, weights: []const Weight) T {
@disableInstrumentation();
return s.valueWeightedWithHash(T, weights, firstHash());
}
// `noinline` to capture a unique return address
pub noinline fn valueRangeAtMost(s: *Smith, T: type, at_least: T, at_most: T) T {
@disableInstrumentation();
return s.valueRangeAtMostWithHash(T, at_least, at_most, firstHash());
}
// `noinline` to capture a unique return address
pub noinline fn valueRangeLessThan(s: *Smith, T: type, at_least: T, less_than: T) T {
@disableInstrumentation();
return s.valueRangeLessThanWithHash(T, at_least, less_than, firstHash());
}
/// It is asserted `len` is nonzero.
/// It is asserted `len` fits within 64 bits.
//
// `noinline` to capture a unique return address
pub noinline fn index(s: *Smith, len: usize) usize {
@disableInstrumentation();
return s.indexWithHash(len, firstHash());
}
/// It is asserted that the weight of `false` is non-zero.
/// It is asserted that the weight of `true` is non-zero.
//
// `noinline` to capture a unique return address
pub noinline fn boolWeighted(s: *Smith, false_weight: u64, true_weight: u64) bool {
@disableInstrumentation();
return s.boolWeightedWithHash(false_weight, true_weight, firstHash());
}
/// This is similar to `value(bool)` however it is gauraunteed to eventually
/// return `true` and provides the fuzzer with an extra hint about the data.
//
// `noinline` to capture a unique return address
pub noinline fn eos(s: *Smith) bool {
@disableInstrumentation();
return s.eosWithHash(firstHash());
}
/// This is similar to `value(bool)` however it is gauraunteed to eventually
/// return `true` and provides the fuzzer with an extra hint about the data.
///
/// It is asserted that the weight of `true` is non-zero.
//
// `noinline` to capture a unique return address
pub noinline fn eosWeighted(s: *Smith, weights: []const Weight) bool {
@disableInstrumentation();
return s.eosWeightedWithHash(weights, firstHash());
}
/// This is similar to `value(bool)` however it is gauraunteed to eventually
/// return `true` and provides the fuzzer with an extra hint about the data.
///
/// It is asserted that the weight of `false` is non-zero.
/// It is asserted that the weight of `true` is non-zero.
//
// `noinline` to capture a unique return address
pub noinline fn eosWeightedSimple(s: *Smith, false_weight: u64, true_weight: u64) bool {
@disableInstrumentation();
return s.eosWeightedSimpleWithHash(false_weight, true_weight, firstHash());
}
// `noinline` to capture a unique return address
pub noinline fn bytes(s: *Smith, out: []u8) void {
@disableInstrumentation();
return s.bytesWithHash(out, firstHash());
}
// `noinline` to capture a unique return address
pub noinline fn bytesWeighted(s: *Smith, out: []u8, weights: []const Weight) void {
@disableInstrumentation();
return s.bytesWeightedWithHash(out, weights, firstHash());
}
/// Returns the length of the filled slice
///
/// It is asserted that `buf.len` fits within a u32
// `noinline` to capture a unique return address
pub noinline fn slice(s: *Smith, buf: []u8) u32 {
@disableInstrumentation();
return s.sliceWithHash(buf, firstHash());
}
/// Returns the length of the filled slice
///
/// It is asserted that `buf.len` fits within a u32
//
// `noinline` to capture a unique return address
pub noinline fn sliceWeightedBytes(s: *Smith, buf: []u8, byte_weights: []const Weight) u32 {
@disableInstrumentation();
return s.sliceWeightedBytesWithHash(buf, byte_weights, firstHash());
}
/// Returns the length of the filled slice
///
/// It is asserted that `buf.len` fits within a u32
//
// `noinline` to capture a unique return address
pub noinline fn sliceWeighted(
s: *Smith,
buf: []u8,
len_weights: []const Weight,
byte_weights: []const Weight,
) u32 {
@disableInstrumentation();
return s.sliceWeightedWithHash(buf, len_weights, byte_weights, firstHash());
}
fn weightsContain(int: u64, weights: []const Weight) bool {
@disableInstrumentation();
var contains: bool = false;
for (weights) |w| {
contains |= w.min <= int and int <= w.max;
}
return contains;
}
/// Asserts `T` can be a member of a packed type
//
// `inline` to propogate the `comptime`ness of the result
inline fn allBitPatternsValid(T: type) bool {
return comptime switch (@typeInfo(T)) {
.void, .bool, .int, .float => true,
inline .@"struct", .@"union" => |c| c.layout == .@"packed" and for (c.field_types) |f_type| {
if (!allBitPatternsValid(f_type)) break false;
} else true,
.@"enum" => |e| e.mode == .nonexhaustive,
else => unreachable,
};
}
test allBitPatternsValid {
try std.testing.expect(allBitPatternsValid(packed struct {
a: void,
b: u8,
c: f16,
d: packed union {
a: u16,
b: i16,
c: f16,
},
e: enum(u4) { _ },
}));
try std.testing.expect(!allBitPatternsValid(packed union {
a: i4,
b: enum(u4) { a },
}));
}
fn UnionTagWithoutUninitializable(T: type) type {
const u = @typeInfo(T).@"union";
const Tag = u.tag_type orelse @compileError("union must have tag");
const e = @typeInfo(Tag).@"enum";
var field_names: [e.field_names.len][]const u8 = undefined;
var field_values: [e.field_names.len]e.tag_type = undefined;
var n_fields = 0;
for (u.field_names, u.field_types) |f_name, f_type| {
switch (f_type) {
noreturn => continue,
else => {},
}
field_names[n_fields] = f_name;
field_values[n_fields] = @intFromEnum(@field(Tag, f_name));
n_fields += 1;
}
return @Enum(e.tag_type, .exhaustive, field_names[0..n_fields], field_values[0..n_fields]);
}
pub fn valueWithHash(s: *Smith, T: type, hash: u32) T {
@disableInstrumentation();
return switch (@typeInfo(T)) {
.void => {},
.bool, .int, .float => full: {
var int: Backing(T) = 0;
comptime var biti = 0;
var rhash = hash; // 'running' hash
inline while (biti < @bitSizeOf(T)) {
const n = @min(@bitSizeOf(T) - biti, 64);
const P = @Int(.unsigned, n);
int |= @as(
@TypeOf(int),
s.valueWeightedWithHash(P, baselineWeights(P), rhash),
) << biti;
biti += n;
rhash = std.hash.int(rhash);
}
break :full @bitCast(int);
},
.@"enum" => |e| if (e.mode == .exhaustive) v: {
if (@bitSizeOf(e.tag_type) <= 64) {
break :v s.valueWeightedWithHash(T, baselineWeights(T), hash);
}
break :v std.enums.fromInt(T, s.valueWithHash(e.tag_type, hash)) orelse
@enumFromInt(e.field_values[0]);
} else @enumFromInt(s.valueWithHash(e.tag_type, hash)),
.optional => |o| if (s.valueWithHash(bool, hash))
null
else
s.valueWithHash(o.child, std.hash.int(hash)),
inline .array, .vector => |a| arr: {
var arr: [a.len]a.child = undefined; // `T` cannot be used due to the vector case
if (a.child != u8) {
for (&arr) |*v| {
v.* = s.valueWithHash(a.child, hash);
}
} else {
s.bytesWithHash(&arr, hash);
}
break :arr arr;
},
.@"struct" => |st| if (!allBitPatternsValid(T)) v: {
var v: T = undefined;
var rhash = hash;
inline for (st.field_names, st.field_types) |f_name, f_type| {
// rhash is incremented in the call so our rhash state is not reused (e.g. with
// two nested structs. note that xor cannot work for this case as the bit would
// be flipped back here)
@field(v, f_name) = s.valueWithHash(f_type, rhash +% 1);
rhash = std.hash.int(rhash);
}
break :v v;
} else @bitCast(s.valueWithHash(st.backing_integer.?, hash)),
.@"union" => if (!allBitPatternsValid(T))
switch (s.valueWithHash(
UnionTagWithoutUninitializable(T),
// hash is incremented in the call so our hash state is not reused for below
std.hash.int(hash +% 1),
)) {
inline else => |t| @unionInit(
T,
@tagName(t),
s.valueWithHash(@FieldType(T, @tagName(t)), hash),
),
}
else
@bitCast(s.valueWithHash(Backing(T), hash)),
else => @compileError("unexpected type '" ++ @typeName(T) ++ "'"),
};
}
pub fn valueWeightedWithHash(s: *Smith, T: type, weights: []const Weight, hash: u32) T {
@disableInstrumentation();
checkWeights(weights, (1 << @bitSizeOf(T)) - 1);
return valueFromInt(T, @intCast(s.valueWeightedWithHashInner(weights, hash)));
}
fn valueWeightedWithHashInner(s: *Smith, weights: []const Weight, hash: u32) u64 {
@disableInstrumentation();
return if (s.in) |*in| int: {
if (in.len < 8) {
@branchHint(.unlikely);
in.* = &.{};
break :int weights[0].min;
}
const int = std.mem.readInt(u64, in.*[0..8], .little);
in.* = in.*[8..];
break :int if (weightsContain(int, weights)) int else weights[0].min;
} else if (builtin.fuzz) int: {
@branchHint(.likely);
break :int fuzz_abi.fuzzer_int(intUid(hash), .fromSlice(weights));
} else unreachable;
}
pub fn valueRangeAtMostWithHash(s: *Smith, T: type, at_least: T, at_most: T, hash: u32) T {
@disableInstrumentation();
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed) {
return fromExcessK(T, s.valueRangeAtMostWithHash(
Backing(T),
toExcessK(T, at_least),
toExcessK(T, at_most),
hash,
));
}
return s.valueWeightedWithHash(T, &.{.rangeAtMost(T, at_least, at_most, 1)}, hash);
}
pub fn valueRangeLessThanWithHash(s: *Smith, T: type, at_least: T, less_than: T, hash: u32) T {
@disableInstrumentation();
if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed) {
return fromExcessK(T, s.valueRangeLessThanWithHash(
Backing(T),
toExcessK(T, at_least),
toExcessK(T, less_than),
hash,
));
}
return s.valueWeightedWithHash(T, &.{.rangeLessThan(T, at_least, less_than, 1)}, hash);
}
/// It is asserted `len` is nonzero.
/// It is asserted `len` fits within 64 bits.
pub fn indexWithHash(s: *Smith, len: usize, hash: u32) usize {
@disableInstrumentation();
assert(len != 0);
return @intCast(s.valueWeightedWithHash(u64, &.{.rangeLessThan(u64, 0, @intCast(len), 1)}, hash));
}
/// It is asserted that the weight of `false` is non-zero.
/// It is asserted that the weight of `true` is non-zero.
pub fn boolWeightedWithHash(s: *Smith, false_weight: u64, true_weight: u64, hash: u32) bool {
@disableInstrumentation();
return s.valueWeightedWithHash(bool, &.{
.value(bool, false, false_weight),
.value(bool, true, true_weight),
}, hash);
}
/// This is similar to `value(bool)` however it is gauraunteed to eventually
/// return `true` and provides the fuzzer with an extra hint about the data.
pub fn eosWithHash(s: *Smith, hash: u32) bool {
@disableInstrumentation();
return s.eosWeightedWithHash(baselineWeights(bool), hash);
}
/// This is similar to `value(bool)` however it is gauraunteed to eventually
/// return `true` and provides the fuzzer with an extra hint about the data.
///
/// It is asserted that the weight of `true` is non-zero.
pub fn eosWeightedWithHash(s: *Smith, weights: []const Weight, hash: u32) bool {
@disableInstrumentation();
checkWeights(weights, 1);
for (weights) |w| (if (w.max == 1) break) else unreachable; // `true` must have non-zero weight
if (s.in) |*in| {
if (in.len == 0) {
@branchHint(.unlikely);
return true;
}
const eos_val = in.*[0] != 0;
in.* = in.*[1..];
return eos_val or b: {
var only_true: bool = true;
for (weights) |w| {
only_true &= @as(u1, @intCast(w.min)) == 1;
}
break :b only_true;
};
} else if (builtin.fuzz) {
@branchHint(.likely);
return fuzz_abi.fuzzer_eos(intUid(hash), .fromSlice(weights));
} else unreachable;
}
/// This is similar to `value(bool)` however it is gauraunteed to eventually
/// return `true` and provides the fuzzer with an extra hint about the data.
///
/// It is asserted that the weight of `false` is non-zero.
/// It is asserted that the weight of `true` is non-zero.
pub fn eosWeightedSimpleWithHash(s: *Smith, false_weight: u64, true_weight: u64, hash: u32) bool {
@disableInstrumentation();
return s.eosWeightedWithHash(&.{
.value(bool, false, false_weight),
.value(bool, true, true_weight),
}, hash);
}
pub fn bytesWithHash(s: *Smith, out: []u8, hash: u32) void {
@disableInstrumentation();
return s.bytesWeightedWithHash(out, baselineWeights(u8), hash);
}
pub fn bytesWeightedWithHash(s: *Smith, out: []u8, weights: []const Weight, hash: u32) void {
@disableInstrumentation();
checkWeights(weights, 255);
if (s.in) |*in| {
var present_weights: [256]bool = @splat(false);
for (weights) |w| {
@memset(present_weights[@intCast(w.min)..@intCast(w.max + 1)], true);
}
const default: u8 = @intCast(weights[0].min);
const copy_len = @min(out.len, in.len);
for (in.*[0..copy_len], out[0..copy_len]) |i, *o| {
o.* = if (present_weights[i]) i else default;
}
in.* = in.*[copy_len..];
@memset(out[copy_len..], default);
} else if (builtin.fuzz) {
@branchHint(.likely);
fuzz_abi.fuzzer_bytes(bytesUid(hash), .fromSlice(out), .fromSlice(weights));
} else unreachable;
}
/// Returns the length of the filled slice
///
/// It is asserted that `buf.len` fits within a u32
pub fn sliceWithHash(s: *Smith, buf: []u8, hash: u32) u32 {
@disableInstrumentation();
return s.sliceWeightedBytesWithHash(buf, baselineWeights(u8), hash);
}
/// Returns the length of the filled slice
///
/// It is asserted that `buf.len` fits within a u32
pub fn sliceWeightedBytesWithHash(
s: *Smith,
buf: []u8,
byte_weights: []const Weight,
hash: u32,
) u32 {
@disableInstrumentation();
return s.sliceWeightedWithHash(
buf,
&.{.rangeAtMost(u32, 0, @intCast(buf.len), 1)},
byte_weights,
hash,
);
}
/// Returns the length of the filled slice
///
/// It is asserted that `buf.len` fits within a u32
pub fn sliceWeightedWithHash(
s: *Smith,
buf: []u8,
len_weights: []const Weight,
byte_weights: []const Weight,
hash: u32,
) u32 {
@disableInstrumentation();
checkWeights(byte_weights, 255);
checkWeights(len_weights, @as(u32, @intCast(buf.len)));
if (s.in) |*in| {
const in_len = len: {
if (in.len < 4) {
@branchHint(.unlikely);
in.* = &.{};
break :len 0;
}
const len = std.mem.readInt(u32, in.*[0..4], .little);
in.* = in.*[4..];
break :len @min(len, in.len);
};
const out_len: u32 = if (weightsContain(in_len, len_weights))
in_len
else
@intCast(len_weights[0].min);
var present_weights: [256]bool = @splat(false);
for (byte_weights) |w| {
@memset(present_weights[@intCast(w.min)..@intCast(w.max + 1)], true);
}
const default: u8 = @intCast(byte_weights[0].min);
const copy_len = @min(out_len, in_len);
for (in.*[0..copy_len], buf[0..copy_len]) |i, *o| {
o.* = if (present_weights[i]) i else default;
}
in.* = in.*[in_len..];
@memset(buf[copy_len..], default);
return out_len;
} else if (builtin.fuzz) {
@branchHint(.likely);
return fuzz_abi.fuzzer_slice(
bytesUid(hash),
.fromSlice(buf),
.fromSlice(len_weights),
.fromSlice(byte_weights),
);
} else unreachable;
}
fn constructInput(comptime values: []const union(enum) {
eos: bool,
int: u64,
bytes: []const u8,
slice: []const u8,
}) []const u8 {
const result = comptime result: {
var result: [
len: {
var len = 0;
for (values) |v| len += switch (v) {
.eos => 1,
.int => 8,
.bytes => |b| b.len,
.slice => |s| 4 + s.len,
};
break :len len;
}
]u8 = undefined;
var w: std.Io.Writer = .fixed(&result);
for (values) |v| switch (v) {
.eos => |e| w.writeByte(@intFromBool(e)) catch unreachable,
.int => |i| w.writeInt(u64, i, .little) catch unreachable,
.bytes => |b| w.writeAll(b) catch unreachable,
.slice => |s| {
w.writeInt(u32, @intCast(s.len), .little) catch unreachable;
w.writeAll(s) catch unreachable;
},
};
break :result result;
};
return &result;
}
test value {
if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const S = struct {
v: void = {},
b: bool = true,
ih: u16 = 123,
iq: u64 = 55555,
io: u128 = (1 << 80) | (1 << 23),
fd: f64 = std.math.pi,
ft: f80 = std.math.e,
eh: enum(u16) { a, _ } = @enumFromInt(999),
eo: enum(u128) { a, b, _ } = .b,
aw: [3]u32 = .{ 1 << 30, 1 << 20, 1 << 10 },
vw: @Vector(3, u32) = .{ 1 << 10, 1 << 20, 1 << 30 },
ab: [3]u8 = .{ 55, 33, 88 },
vb: @Vector(3, u8) = .{ 22, 44, 99 },
s: struct { q: u64 } = .{ .q = 1 },
sz: struct {} = .{},
sp: packed struct(u8) { a: u5, b: u3 } = .{ .a = 31, .b = 3 },
si: packed struct(u8) { a: u5, b: enum(u3) { a, b } } = .{ .a = 15, .b = .b },
u: union(enum(u2)) {
a: u64,
b: u64,
c: noreturn,
} = .{ .b = 777777 },
up: packed union {
a: u16,
b: f16,
} = .{ .b = std.math.phi },
invalid: struct {
ib: u8 = 0,
eb: enum(u8) { a, b } = .a,
eo: enum(u128) { a, b } = .a,
u: union(enum(u1)) { a: noreturn, b: void } = .{ .b = {} },
} = .{},
};
const s: S = .{};
const ft_bits: u80 = @bitCast(s.ft);
const eo_bits = @intFromEnum(s.eo);
var smith: Smith = .{
.in = constructInput(&.{
// v
.{ .int = @intFromBool(s.b) }, // b
.{ .int = s.ih }, // ih
.{ .int = s.iq }, // iq
.{ .int = @truncate(s.io) }, .{ .int = @intCast(s.io >> 64) }, // io
.{ .int = @bitCast(s.fd) }, // fd
.{ .int = @truncate(ft_bits) }, .{ .int = @intCast(ft_bits >> 64) }, // ft
.{ .int = @intFromEnum(s.eh) }, // eh
.{ .int = @truncate(eo_bits) }, .{ .int = @intCast(eo_bits >> 64) }, // eo
.{ .int = s.aw[0] }, .{ .int = s.aw[1] }, .{ .int = s.aw[2] }, // aw
.{ .int = s.vw[0] }, .{ .int = s.vw[1] }, .{ .int = s.vw[2] }, // vw
.{ .bytes = &s.ab }, // ab
.{ .bytes = &@as([3]u8, s.vb) }, // vb
.{ .int = s.s.q }, // s.q
//sz
.{ .int = @as(u8, @bitCast(s.sp)) }, // sp
.{ .int = s.si.a }, .{ .int = @intFromEnum(s.si.b) }, // si
.{ .int = @intFromEnum(s.u) }, .{ .int = s.u.b }, // u
.{ .int = @as(u16, @bitCast(s.up)) }, // up
// invalid values
.{ .int = 555 }, // invalid.ib
.{ .int = 123 }, // invalid.eb
.{ .int = 0 }, .{ .int = 1 }, // invalid.eo
.{ .int = 0 }, // invalid.u
}),
};
try std.testing.expectEqual(s, smith.value(S));
}
test valueWeighted {
var smith: Smith = .{
.in = constructInput(&.{
.{ .int = 200 },
.{ .int = 200 },
.{ .int = 300 },
.{ .int = 400 },
}),
};
try std.testing.expectEqual(200, smith.valueWeighted(u8, &.{.rangeAtMost(u8, 50, 200, 1)}));
try std.testing.expectEqual(50, smith.valueWeighted(u8, &.{.rangeLessThan(u8, 50, 200, 1)}));
const E = enum(u64) { a = 100, b = 200, c = 300 };
try std.testing.expectEqual(E.c, smith.valueWeighted(E, baselineWeights(E)));
try std.testing.expectEqual(E.a, smith.valueWeighted(E, baselineWeights(E)));
try std.testing.expectEqual(12345, smith.valueWeighted(u64, &.{.value(u64, 12345, 1)}));
}
test valueRangeAtMost {
var smith: Smith = .{
.in = constructInput(&.{
.{ .int = 100 },
.{ .int = 100 },
.{ .int = 200 },
.{ .int = 100 },
.{ .int = 200 },
.{ .int = 0 },
}),
};
try std.testing.expectEqual(100, smith.valueRangeAtMost(u8, 0, 250));
try std.testing.expectEqual(100, smith.valueRangeAtMost(u8, 100, 100));
try std.testing.expectEqual(0, smith.valueRangeAtMost(u8, 0, 100));
try std.testing.expectEqual(100 - 128, smith.valueRangeAtMost(i8, -100, 100));
try std.testing.expectEqual(200 - 128, smith.valueRangeAtMost(i8, -100, 100));
try std.testing.expectEqual(-100, smith.valueRangeAtMost(i8, -100, 100));
}
test valueRangeLessThan {
var smith: Smith = .{
.in = constructInput(&.{
.{ .int = 100 },
.{ .int = 100 },
.{ .int = 100 },
.{ .int = 100 + 128 },
}),
};
try std.testing.expectEqual(100, smith.valueRangeLessThan(u8, 0, 250));
try std.testing.expectEqual(0, smith.valueRangeLessThan(u8, 0, 100));
try std.testing.expectEqual(100 - 128, smith.valueRangeLessThan(i8, -100, 100));
try std.testing.expectEqual(-100, smith.valueRangeLessThan(i8, -100, 100));
}
test eos {
var smith: Smith = .{
.in = constructInput(&.{
.{ .eos = false },
.{ .eos = true },
}),
};
try std.testing.expect(!smith.eos());
try std.testing.expect(smith.eos());
try std.testing.expect(smith.eos());
}
test eosWeighted {
var smith: Smith = .{ .in = constructInput(&.{.{ .eos = false }}) };
try std.testing.expect(smith.eosWeighted(&.{.value(bool, true, std.math.maxInt(u64))}));
}
test bytes {
var smith: Smith = .{ .in = constructInput(&.{
.{ .bytes = "testing!" },
.{ .bytes = "ab" },
}) };
var buf: [8]u8 = undefined;
smith.bytes(&buf);
try std.testing.expectEqualSlices(u8, "testing!", &buf);
smith.bytes(buf[0..0]);
smith.bytes(buf[0..3]);
try std.testing.expectEqualSlices(u8, "ab\x00", buf[0..3]);
}
test bytesWeighted {
var smith: Smith = .{ .in = constructInput(&.{
.{ .bytes = "testing!" },
.{ .bytes = "ab" },
}) };
const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)};
var buf: [8]u8 = undefined;
smith.bytesWeighted(&buf, weights);
try std.testing.expectEqualSlices(u8, "testinga", &buf);
smith.bytesWeighted(buf[0..0], weights);
smith.bytesWeighted(buf[0..3], weights);
try std.testing.expectEqualSlices(u8, "aba", buf[0..3]);
}
test slice {
var smith: Smith = .{
.in = constructInput(&.{
.{ .slice = "testing!" },
.{ .slice = "" },
.{ .slice = "ab" },
.{ .bytes = std.mem.asBytes(&std.mem.nativeToLittle(u32, 4)) }, // length past end
}),
};
var buf: [8]u8 = undefined;
try std.testing.expectEqualSlices(u8, "testing!", buf[0..smith.slice(&buf)]);
try std.testing.expectEqualSlices(u8, "", buf[0..smith.slice(&buf)]);
try std.testing.expectEqualSlices(u8, "ab", buf[0..smith.slice(&buf)]);
try std.testing.expectEqualSlices(u8, "", buf[0..smith.slice(&buf)]);
}
test sliceWeightedBytes {
const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)};
var smith: Smith = .{ .in = constructInput(&.{
.{ .slice = "testing!" },
}) };
var buf: [8]u8 = undefined;
try std.testing.expectEqualSlices(
u8,
"testinga",
buf[0..smith.sliceWeightedBytes(&buf, weights)],
);
try std.testing.expectEqualSlices(u8, "", buf[0..smith.sliceWeightedBytes(&buf, weights)]);
}
test sliceWeighted {
const len_weights: []const Weight = &.{.rangeAtMost(u8, 3, 6, 1)};
const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)};
var smith: Smith = .{ .in = constructInput(&.{
.{ .slice = "testing!" },
.{ .slice = "ing!" },
.{ .slice = "ab" },
}) };
var buf: [8]u8 = undefined;
try std.testing.expectEqualSlices(
u8,
"tes",
buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
);
try std.testing.expectEqualSlices(
u8,
"inga",
buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
);
try std.testing.expectEqualSlices(
u8,
"aba",
buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
);
try std.testing.expectEqualSlices(
u8,
"aaa",
buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
);
}