implement std.testing.fuzzInput

For now this returns a dummy fuzz input.
This commit is contained in:
Andrew Kelley 2024-07-22 18:06:58 -07:00
parent 3256df2ff8
commit 6f3767862d
4 changed files with 125 additions and 53 deletions

View file

@ -1,18 +1,26 @@
//! Default test runner for unit tests.
const builtin = @import("builtin");
const std = @import("std");
const io = std.io;
const builtin = @import("builtin");
const testing = std.testing;
pub const std_options = .{
.logFn = log,
};
var log_err_count: usize = 0;
var cmdline_buffer: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
var fba_buffer: [8192]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&fba_buffer);
const crippled = switch (builtin.zig_backend) {
.stage2_riscv64 => true,
else => false,
};
pub fn main() void {
if (builtin.zig_backend == .stage2_riscv64) {
@disableInstrumentation();
if (crippled) {
return mainSimple() catch @panic("test failure\n");
}
@ -25,13 +33,15 @@ pub fn main() void {
if (std.mem.eql(u8, arg, "--listen=-")) {
listen = true;
} else if (std.mem.startsWith(u8, arg, "--seed=")) {
std.testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch
testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch
@panic("unable to parse --seed command line argument");
} else {
@panic("unrecognized command line argument");
}
}
fba.reset();
if (listen) {
return mainServer() catch @panic("internal test runner failure");
} else {
@ -40,6 +50,7 @@ pub fn main() void {
}
fn mainServer() !void {
@disableInstrumentation();
var server = try std.zig.Server.init(.{
.gpa = fba.allocator(),
.in = std.io.getStdIn(),
@ -55,24 +66,24 @@ fn mainServer() !void {
return std.process.exit(0);
},
.query_test_metadata => {
std.testing.allocator_instance = .{};
defer if (std.testing.allocator_instance.deinit() == .leak) {
testing.allocator_instance = .{};
defer if (testing.allocator_instance.deinit() == .leak) {
@panic("internal test runner memory leak");
};
var string_bytes: std.ArrayListUnmanaged(u8) = .{};
defer string_bytes.deinit(std.testing.allocator);
try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.
defer string_bytes.deinit(testing.allocator);
try string_bytes.append(testing.allocator, 0); // Reserve 0 for null.
const test_fns = builtin.test_functions;
const names = try std.testing.allocator.alloc(u32, test_fns.len);
defer std.testing.allocator.free(names);
const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
defer std.testing.allocator.free(expected_panic_msgs);
const names = try testing.allocator.alloc(u32, test_fns.len);
defer testing.allocator.free(names);
const expected_panic_msgs = try testing.allocator.alloc(u32, test_fns.len);
defer testing.allocator.free(expected_panic_msgs);
for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| {
name.* = @as(u32, @intCast(string_bytes.items.len));
try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
try string_bytes.ensureUnusedCapacity(testing.allocator, test_fn.name.len + 1);
string_bytes.appendSliceAssumeCapacity(test_fn.name);
string_bytes.appendAssumeCapacity(0);
expected_panic_msg.* = 0;
@ -86,13 +97,13 @@ fn mainServer() !void {
},
.run_test => {
std.testing.allocator_instance = .{};
testing.allocator_instance = .{};
log_err_count = 0;
const index = try server.receiveBody_u32();
const test_fn = builtin.test_functions[index];
var fail = false;
var skip = false;
var leak = false;
is_fuzz_test = false;
test_fn.func() catch |err| switch (err) {
error.SkipZigTest => skip = true,
else => {
@ -102,13 +113,14 @@ fn mainServer() !void {
}
},
};
leak = std.testing.allocator_instance.deinit() == .leak;
const leak = testing.allocator_instance.deinit() == .leak;
try server.serveTestResults(.{
.index = index,
.flags = .{
.fail = fail,
.skip = skip,
.leak = leak,
.fuzz = is_fuzz_test,
.log_err_count = std.math.lossyCast(
@TypeOf(@as(std.zig.Server.Message.TestResults.Flags, undefined).log_err_count),
log_err_count,
@ -118,7 +130,7 @@ fn mainServer() !void {
},
else => {
std.debug.print("unsupported message: {x}", .{@intFromEnum(hdr.tag)});
std.debug.print("unsupported message: {x}\n", .{@intFromEnum(hdr.tag)});
std.process.exit(1);
},
}
@ -126,6 +138,7 @@ fn mainServer() !void {
}
fn mainTerminal() void {
@disableInstrumentation();
const test_fn_list = builtin.test_functions;
var ok_count: usize = 0;
var skip_count: usize = 0;
@ -143,18 +156,19 @@ fn mainTerminal() void {
var leaks: usize = 0;
for (test_fn_list, 0..) |test_fn, i| {
std.testing.allocator_instance = .{};
testing.allocator_instance = .{};
defer {
if (std.testing.allocator_instance.deinit() == .leak) {
if (testing.allocator_instance.deinit() == .leak) {
leaks += 1;
}
}
std.testing.log_level = .warn;
testing.log_level = .warn;
const test_node = root_node.start(test_fn.name, 0);
if (!have_tty) {
std.debug.print("{d}/{d} {s}...", .{ i + 1, test_fn_list.len, test_fn.name });
}
// Track in a global variable so that `fuzzInput` can see it.
if (test_fn.func()) |_| {
ok_count += 1;
test_node.end();
@ -208,10 +222,11 @@ pub fn log(
comptime format: []const u8,
args: anytype,
) void {
@disableInstrumentation();
if (@intFromEnum(message_level) <= @intFromEnum(std.log.Level.err)) {
log_err_count +|= 1;
}
if (@intFromEnum(message_level) <= @intFromEnum(std.testing.log_level)) {
if (@intFromEnum(message_level) <= @intFromEnum(testing.log_level)) {
std.debug.print(
"[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n",
args,
@ -222,6 +237,7 @@ pub fn log(
/// Simpler main(), exercising fewer language features, so that
/// work-in-progress backends can handle it.
pub fn mainSimple() anyerror!void {
@disableInstrumentation();
// is the backend capable of printing to stderr?
const enable_print = switch (builtin.zig_backend) {
else => false,
@ -266,3 +282,34 @@ pub fn mainSimple() anyerror!void {
}
if (failed != 0) std.process.exit(1);
}
const FuzzerSlice = extern struct {
ptr: [*]const u8,
len: usize,
inline fn toSlice(s: FuzzerSlice) []const u8 {
return s.ptr[0..s.len];
}
};
var is_fuzz_test: bool = undefined;
extern fn fuzzer_next() FuzzerSlice;
pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 {
@disableInstrumentation();
if (crippled) {
return "";
} else if (builtin.fuzz) {
return fuzzer_next().toSlice();
} else {
is_fuzz_test = true;
if (options.corpus.len == 0) {
return "";
} else {
var prng = std.Random.DefaultPrng.init(testing.random_seed);
const random = prng.random();
return options.corpus[random.uintLessThan(usize, options.corpus.len)];
}
}
}

View file

@ -1,13 +1,14 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
export threadlocal var __sancov_lowest_stack: usize = 0;
export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, stop: [*]u8) void {
std.debug.print("__sanitizer_cov_8bit_counters_init start={*}, stop={*}\n", .{ start, stop });
std.log.debug("__sanitizer_cov_8bit_counters_init start={*}, stop={*}", .{ start, stop });
}
export fn __sanitizer_cov_pcs_init(pcs_beg: [*]const usize, pcs_end: [*]const usize) void {
std.debug.print("__sanitizer_cov_pcs_init pcs_beg={*}, pcs_end={*}\n", .{ pcs_beg, pcs_end });
std.log.debug("__sanitizer_cov_pcs_init pcs_beg={*}, pcs_end={*}", .{ pcs_beg, pcs_end });
}
export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {
@ -47,16 +48,61 @@ export fn __sanitizer_cov_trace_switch(val: u64, cases_ptr: [*]u64) void {
const len = cases_ptr[0];
const val_size_in_bits = cases_ptr[1];
const cases = cases_ptr[2..][0..len];
std.debug.print("0x{x}: switch on value {d} ({d} bits) with {d} cases\n", .{
std.log.debug("0x{x}: switch on value {d} ({d} bits) with {d} cases", .{
pc, val, val_size_in_bits, cases.len,
});
}
export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
const pc = @returnAddress();
std.debug.print("0x{x}: indirect call to 0x{x}\n", .{ pc, callee });
std.log.debug("0x{x}: indirect call to 0x{x}", .{ pc, callee });
}
fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
std.debug.print("0x{x}: comparison of {d} and {d}\n", .{ pc, arg1, arg2 });
std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 });
}
const Fuzzer = struct {
gpa: Allocator,
rng: std.Random.DefaultPrng,
input: std.ArrayListUnmanaged(u8),
const Slice = extern struct {
ptr: [*]const u8,
len: usize,
fn toSlice(s: Slice) []const u8 {
return s.ptr[0..s.len];
}
fn fromSlice(s: []const u8) Slice {
return .{
.ptr = s.ptr,
.len = s.len,
};
}
};
fn next(f: *Fuzzer) ![]const u8 {
const gpa = f.gpa;
const rng = fuzzer.rng.random();
const len = rng.uintLessThan(usize, 64);
try f.input.resize(gpa, len);
rng.bytes(f.input.items);
return f.input.items;
}
};
var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
var fuzzer: Fuzzer = .{
.gpa = general_purpose_allocator.allocator(),
.rng = std.Random.DefaultPrng.init(0),
.input = .{},
};
export fn fuzzer_next() Fuzzer.Slice {
return Fuzzer.Slice.fromSlice(fuzzer.next() catch |err| switch (err) {
error.OutOfMemory => @panic("out of memory"),
});
}

View file

@ -1137,32 +1137,10 @@ pub fn refAllDeclsRecursive(comptime T: type) void {
}
}
const FuzzerSlice = extern struct {
ptr: [*]const u8,
len: usize,
fn toSlice(s: FuzzerSlice) []const u8 {
return s.ptr[0..s.len];
}
};
extern fn fuzzer_next() FuzzerSlice;
pub const FuzzInputOptions = struct {
corpus: []const []const u8 = &.{},
};
pub fn fuzzInput(options: FuzzInputOptions) []const u8 {
@disableInstrumentation();
if (builtin.fuzz) {
return fuzzer_next().toSlice();
} else {
if (options.corpus.len == 0) {
return "";
} else {
var prng = std.Random.DefaultPrng.init(std.testing.random_seed);
const random = prng.random();
return options.corpus[random.uintLessThan(usize, options.corpus.len)];
}
}
pub inline fn fuzzInput(options: FuzzInputOptions) []const u8 {
return @import("root").fuzzInput(options);
}

View file

@ -53,7 +53,7 @@ pub const Message = struct {
/// - null-terminated string_bytes index
/// * expected_panic_msg: [tests_len]u32,
/// - null-terminated string_bytes index
/// - 0 means does not expect pani
/// - 0 means does not expect panic
/// * string_bytes: [string_bytes_len]u8,
pub const TestMetadata = extern struct {
string_bytes_len: u32,
@ -68,7 +68,8 @@ pub const Message = struct {
fail: bool,
skip: bool,
leak: bool,
log_err_count: u29 = 0,
fuzz: bool,
log_err_count: u28 = 0,
};
};