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

View File

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