2019-03-02 16:46:04 -05:00
const std = @import ( " std.zig " ) ;
2021-10-04 23:47:27 -07:00
const builtin = @import ( " builtin " ) ;
2024-07-22 16:03:11 -07:00
const assert = std . debug . assert ;
2020-11-01 18:35:19 +01:00
const math = std . math ;
2019-02-08 18:18:47 -05:00
2024-07-22 16:03:11 -07:00
/// Provides deterministic randomness in unit tests.
/// Initialized on startup. Read-only after that.
pub var random_seed : u32 = 0 ;
2025-02-03 20:00:01 -08:00
pub const FailingAllocator = @import ( " testing/FailingAllocator.zig " ) ;
pub const failing_allocator = failing_allocator_instance . allocator ( ) ;
2025-02-05 14:24:41 -08:00
var failing_allocator_instance = FailingAllocator . init ( base_allocator_instance . allocator ( ) , . {
. fail_index = 0 ,
} ) ;
var base_allocator_instance = std . heap . FixedBufferAllocator . init ( " " ) ;
2020-01-29 21:22:01 -06:00
2020-01-29 12:21:29 -06:00
/// This should only be used in temporary test programs.
2021-10-29 02:08:41 +01:00
pub const allocator = allocator_instance . allocator ( ) ;
2025-02-05 14:24:41 -08:00
pub var allocator_instance : std . heap . GeneralPurposeAllocator ( . {
2025-02-06 14:46:16 -08:00
. stack_trace_frames = if ( std . debug . sys_can_stack_trace ) 10 else 0 ,
2025-02-05 16:39:14 -08:00
. resize_stack_traces = true ,
// A unique value so that when a default-constructed
// GeneralPurposeAllocator is incorrectly passed to testing allocator, or
// vice versa, panic occurs.
. canary = @truncate ( 0x2731e675c3a701ba ) ,
2025-02-05 14:24:41 -08:00
} ) = b : {
if ( ! builtin . is_test ) @compileError ( " testing allocator used when not testing " ) ;
2024-09-02 22:32:21 +01:00
break : b . init ;
2022-05-26 14:19:05 +04:30
} ;
2020-01-29 12:21:29 -06:00
2020-07-08 21:01:13 -07:00
/// TODO https://github.com/ziglang/zig/issues/5738
pub var log_level = std . log . Level . warn ;
2023-10-08 21:00:20 +02:00
// Disable printing in tests for simple backends.
2024-04-16 22:44:55 -07:00
pub const backend_can_print = ! ( builtin . zig_backend == . stage2_spirv64 or builtin . zig_backend == . stage2_riscv64 ) ;
2023-10-08 21:00:20 +02:00
2023-07-01 14:29:11 +02:00
fn print ( comptime fmt : [ ] const u8 , args : anytype ) void {
2023-10-08 17:02:59 +02:00
if ( @inComptime ( ) ) {
@compileError ( std . fmt . comptimePrint ( fmt , args ) ) ;
2023-10-08 21:00:20 +02:00
} else if ( backend_can_print ) {
2023-10-08 17:02:59 +02:00
std . debug . print ( fmt , args ) ;
}
2023-07-01 14:29:11 +02:00
}
2019-02-08 18:18:47 -05:00
/// This function is intended to be used only in tests. It prints diagnostics to stderr
2022-03-17 17:23:22 -07:00
/// and then returns a test failure error when actual_error_union is not expected_error.
2021-05-04 19:36:59 +03:00
pub fn expectError ( expected_error : anyerror , actual_error_union : anytype ) ! void {
2019-02-08 18:18:47 -05:00
if ( actual_error_union ) | actual_payload | {
2023-07-01 14:29:11 +02:00
print ( " expected error.{s}, found {any} \n " , . { @errorName ( expected_error ) , actual_payload } ) ;
2024-11-08 22:03:34 -05:00
return error . TestExpectedError ;
2019-02-08 18:18:47 -05:00
} else | actual_error | {
if ( expected_error != actual_error ) {
2023-07-01 14:29:11 +02:00
print ( " expected error.{s}, found error.{s} \n " , . {
2019-12-08 22:53:51 -05:00
@errorName ( expected_error ) ,
@errorName ( actual_error ) ,
} ) ;
2024-11-08 22:03:34 -05:00
return error . TestUnexpectedError ;
2019-02-08 18:18:47 -05:00
}
}
}
/// This function is intended to be used only in tests. When the two values are not
/// equal, prints diagnostics to stderr to show exactly how they are not equal,
2022-03-17 17:23:22 -07:00
/// then returns a test failure error.
2023-10-07 21:53:10 +02:00
/// `actual` and `expected` are coerced to a common type using peer type resolution.
pub inline fn expectEqual ( expected : anytype , actual : anytype ) ! void {
const T = @TypeOf ( expected , actual ) ;
return expectEqualInner ( T , expected , actual ) ;
}
fn expectEqualInner ( comptime T : type , expected : T , actual : T ) ! void {
2019-12-09 21:56:19 +01:00
switch ( @typeInfo ( @TypeOf ( actual ) ) ) {
2024-08-28 02:35:53 +01:00
. noreturn ,
. @" opaque " ,
. frame ,
. @" anyframe " ,
2019-12-09 21:56:19 +01:00
= > @compileError ( " value of type " ++ @typeName ( @TypeOf ( actual ) ) ++ " encountered " ) ,
2019-02-08 18:18:47 -05:00
2024-08-28 02:35:53 +01:00
. undefined ,
. null ,
. void ,
2019-02-08 18:18:47 -05:00
= > return ,
2024-08-28 02:35:53 +01:00
. type = > {
2020-10-29 11:10:21 +01:00
if ( actual != expected ) {
2023-07-01 14:29:11 +02:00
print ( " expected type {s}, found type {s} \n " , . { @typeName ( expected ) , @typeName ( actual ) } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2020-10-29 11:10:21 +01:00
}
} ,
2024-08-28 02:35:53 +01:00
. bool ,
. int ,
. float ,
. comptime_float ,
. comptime_int ,
. enum_literal ,
. @" enum " ,
. @" fn " ,
. error_set ,
2019-02-08 18:18:47 -05:00
= > {
if ( actual != expected ) {
2023-07-01 14:29:11 +02:00
print ( " expected {}, found {} \n " , . { expected , actual } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
} ,
2024-08-28 02:35:53 +01:00
. pointer = > | pointer | {
2019-02-08 18:18:47 -05:00
switch ( pointer . size ) {
2025-01-15 17:53:05 +00:00
. one , . many , . c = > {
2019-02-08 18:18:47 -05:00
if ( actual != expected ) {
2023-07-01 14:29:11 +02:00
print ( " expected {*}, found {*} \n " , . { expected , actual } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
} ,
2025-01-15 17:53:05 +00:00
. slice = > {
2019-02-08 18:18:47 -05:00
if ( actual . ptr != expected . ptr ) {
2023-07-01 14:29:11 +02:00
print ( " expected slice ptr {*}, found {*} \n " , . { expected . ptr , actual . ptr } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
if ( actual . len != expected . len ) {
2023-07-01 14:29:11 +02:00
print ( " expected slice len {}, found {} \n " , . { expected . len , actual . len } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
} ,
}
} ,
2024-08-28 02:35:53 +01:00
. array = > | array | try expectEqualSlices ( array . child , & expected , & actual ) ,
2019-02-08 18:18:47 -05:00
2024-08-28 02:35:53 +01:00
. vector = > | info | {
2020-01-07 19:14:37 +05:00
var i : usize = 0 ;
2022-01-12 23:53:26 -07:00
while ( i < info . len ) : ( i += 1 ) {
2020-01-07 19:14:37 +05:00
if ( ! std . meta . eql ( expected [ i ] , actual [ i ] ) ) {
2024-10-09 02:44:41 +08:00
print ( " index {d} incorrect. expected {any}, found {any} \n " , . {
2022-01-12 23:53:26 -07:00
i , expected [ i ] , actual [ i ] ,
} ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2020-01-07 19:14:37 +05:00
}
}
} ,
2024-08-28 02:35:53 +01:00
. @" struct " = > | structType | {
2019-05-21 00:29:46 +10:00
inline for ( structType . fields ) | field | {
2021-05-04 19:36:59 +03:00
try expectEqual ( @field ( expected , field . name ) , @field ( actual , field . name ) ) ;
2019-05-21 00:29:46 +10:00
}
2019-02-08 18:18:47 -05:00
} ,
2024-08-28 02:35:53 +01:00
. @" union " = > | union_info | {
2019-02-08 18:18:47 -05:00
if ( union_info . tag_type == null ) {
2025-02-10 23:05:38 -05:00
@compileError ( " Unable to compare untagged union values for type " ++ @typeName ( @TypeOf ( actual ) ) ) ;
2019-02-08 18:18:47 -05:00
}
2019-11-27 22:35:32 +01:00
2021-01-11 11:19:24 -07:00
const Tag = std . meta . Tag ( @TypeOf ( expected ) ) ;
2019-11-27 22:35:32 +01:00
2021-01-11 11:19:24 -07:00
const expectedTag = @as ( Tag , expected ) ;
const actualTag = @as ( Tag , actual ) ;
2019-11-27 22:35:32 +01:00
2021-05-04 19:36:59 +03:00
try expectEqual ( expectedTag , actualTag ) ;
2019-11-27 22:35:32 +01:00
2024-06-18 19:01:05 +02:00
// we only reach this switch if the tags are equal
switch ( expected ) {
inline else = > | val , tag | try expectEqual ( val , @field ( actual , @tagName ( tag ) ) ) ,
2019-11-27 22:35:32 +01:00
}
2019-02-08 18:18:47 -05:00
} ,
2024-08-28 02:35:53 +01:00
. optional = > {
2019-02-08 18:18:47 -05:00
if ( expected ) | expected_payload | {
if ( actual ) | actual_payload | {
2021-05-04 19:36:59 +03:00
try expectEqual ( expected_payload , actual_payload ) ;
2019-02-08 18:18:47 -05:00
} else {
2023-07-01 14:29:11 +02:00
print ( " expected {any}, found null \n " , . { expected_payload } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
} else {
if ( actual ) | actual_payload | {
2023-07-01 14:29:11 +02:00
print ( " expected null, found {any} \n " , . { actual_payload } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
}
} ,
2024-08-28 02:35:53 +01:00
. error_union = > {
2019-02-08 18:18:47 -05:00
if ( expected ) | expected_payload | {
if ( actual ) | actual_payload | {
2021-05-04 19:36:59 +03:00
try expectEqual ( expected_payload , actual_payload ) ;
2019-02-08 18:18:47 -05:00
} else | actual_err | {
2023-07-01 14:29:11 +02:00
print ( " expected {any}, found {} \n " , . { expected_payload , actual_err } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
} else | expected_err | {
if ( actual ) | actual_payload | {
2023-07-01 14:29:11 +02:00
print ( " expected {}, found {any} \n " , . { expected_err , actual_payload } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
} else | actual_err | {
2021-05-04 19:36:59 +03:00
try expectEqual ( expected_err , actual_err ) ;
2019-02-08 18:18:47 -05:00
}
}
} ,
}
}
2019-12-08 22:53:51 -05:00
test " expectEqual.union(enum) " {
2019-11-27 22:35:32 +01:00
const T = union ( enum ) {
a : i32 ,
b : f32 ,
} ;
2019-12-08 22:53:51 -05:00
const a10 = T { . a = 10 } ;
2019-11-27 22:35:32 +01:00
2021-05-04 19:36:59 +03:00
try expectEqual ( a10 , a10 ) ;
2019-11-27 22:35:32 +01:00
}
2024-06-18 19:01:05 +02:00
test " expectEqual union with comptime-only field " {
const U = union ( enum ) {
a : void ,
b : void ,
c : comptime_int ,
} ;
try expectEqual ( U { . a = { } } , . a ) ;
}
2024-10-09 02:44:41 +08:00
test " expectEqual nested array " {
const a = [ 2 ] [ 2 ] f32 {
[ _ ] f32 { 1.0 , 0.0 } ,
[ _ ] f32 { 0.0 , 1.0 } ,
} ;
const b = [ 2 ] [ 2 ] f32 {
[ _ ] f32 { 1.0 , 0.0 } ,
[ _ ] f32 { 0.0 , 1.0 } ,
} ;
try expectEqual ( a , b ) ;
}
test " expectEqual vector " {
const a : @Vector ( 4 , u32 ) = @splat ( 4 ) ;
const b : @Vector ( 4 , u32 ) = @splat ( 4 ) ;
try expectEqual ( a , b ) ;
}
test " expectEqual null " {
const a = . { null } ;
const b = @Vector ( 1 , ? * u8 ) { null } ;
try expectEqual ( a , b ) ;
}
2021-01-11 20:30:43 -05:00
/// This function is intended to be used only in tests. When the formatted result of the template
/// and its arguments does not equal the expected text, it prints diagnostics to stderr to show how
2024-02-26 19:08:27 -08:00
/// they are not equal, then returns an error. It depends on `expectEqualStrings()` for printing
/// diagnostics.
2021-01-11 20:30:43 -05:00
pub fn expectFmt ( expected : [ ] const u8 , comptime template : [ ] const u8 , args : anytype ) ! void {
2024-02-26 19:08:27 -08:00
const actual = try std . fmt . allocPrint ( allocator , template , args ) ;
defer allocator . free ( actual ) ;
return expectEqualStrings ( expected , actual ) ;
2021-01-11 20:30:43 -05:00
}
2021-03-13 13:08:46 +01:00
/// This function is intended to be used only in tests. When the actual value is
/// not approximately equal to the expected value, prints diagnostics to stderr
2022-03-17 17:23:22 -07:00
/// to show exactly how they are not equal, then returns a test failure error.
2022-10-03 19:52:39 +02:00
/// See `math.approxEqAbs` for more information on the tolerance parameter.
/// The types must be floating-point.
2023-10-07 21:53:10 +02:00
/// `actual` and `expected` are coerced to a common type using peer type resolution.
pub inline fn expectApproxEqAbs ( expected : anytype , actual : anytype , tolerance : anytype ) ! void {
const T = @TypeOf ( expected , actual , tolerance ) ;
return expectApproxEqAbsInner ( T , expected , actual , tolerance ) ;
}
2021-03-13 13:08:46 +01:00
2023-10-07 21:53:10 +02:00
fn expectApproxEqAbsInner ( comptime T : type , expected : T , actual : T , tolerance : T ) ! void {
2021-03-13 13:08:46 +01:00
switch ( @typeInfo ( T ) ) {
2024-08-28 02:35:53 +01:00
. float = > if ( ! math . approxEqAbs ( T , expected , actual , tolerance ) ) {
2023-07-01 14:29:11 +02:00
print ( " actual {}, not within absolute tolerance {} of expected {} \n " , . { actual , tolerance , expected } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedApproxEqAbs ;
} ,
2021-03-13 13:08:46 +01:00
2024-08-28 02:35:53 +01:00
. comptime_float = > @compileError ( " Cannot approximately compare two comptime_float values " ) ,
2020-06-23 18:05:32 +02:00
else = > @compileError ( " Unable to compare non floating point values " ) ,
}
}
2024-03-13 15:56:09 -07:00
test expectApproxEqAbs {
2020-11-01 18:35:19 +01:00
inline for ( [ _ ] type { f16 , f32 , f64 , f128 } ) | T | {
const pos_x : T = 12.0 ;
const pos_y : T = 12.06 ;
const neg_x : T = - 12.0 ;
const neg_y : T = - 12.06 ;
2020-06-23 18:05:32 +02:00
2021-05-04 19:36:59 +03:00
try expectApproxEqAbs ( pos_x , pos_y , 0.1 ) ;
try expectApproxEqAbs ( neg_x , neg_y , 0.1 ) ;
2020-11-01 18:35:19 +01:00
}
2020-06-23 18:05:32 +02:00
}
2021-03-13 13:08:46 +01:00
/// This function is intended to be used only in tests. When the actual value is
/// not approximately equal to the expected value, prints diagnostics to stderr
2022-03-17 17:23:22 -07:00
/// to show exactly how they are not equal, then returns a test failure error.
2022-10-03 19:52:39 +02:00
/// See `math.approxEqRel` for more information on the tolerance parameter.
/// The types must be floating-point.
2023-10-07 21:53:10 +02:00
/// `actual` and `expected` are coerced to a common type using peer type resolution.
pub inline fn expectApproxEqRel ( expected : anytype , actual : anytype , tolerance : anytype ) ! void {
const T = @TypeOf ( expected , actual , tolerance ) ;
return expectApproxEqRelInner ( T , expected , actual , tolerance ) ;
}
2021-03-13 13:08:46 +01:00
2023-10-07 21:53:10 +02:00
fn expectApproxEqRelInner ( comptime T : type , expected : T , actual : T , tolerance : T ) ! void {
2021-03-13 13:08:46 +01:00
switch ( @typeInfo ( T ) ) {
2024-08-28 02:35:53 +01:00
. float = > if ( ! math . approxEqRel ( T , expected , actual , tolerance ) ) {
2023-07-01 14:29:11 +02:00
print ( " actual {}, not within relative tolerance {} of expected {} \n " , . { actual , tolerance , expected } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedApproxEqRel ;
} ,
2021-03-13 13:08:46 +01:00
2024-08-28 02:35:53 +01:00
. comptime_float = > @compileError ( " Cannot approximately compare two comptime_float values " ) ,
2020-06-23 18:08:15 +02:00
else = > @compileError ( " Unable to compare non floating point values " ) ,
}
}
2024-03-13 15:56:09 -07:00
test expectApproxEqRel {
2020-11-01 18:35:19 +01:00
inline for ( [ _ ] type { f16 , f32 , f64 , f128 } ) | T | {
2023-06-04 23:09:17 +06:00
const eps_value = comptime math . floatEps ( T ) ;
2022-04-26 10:13:55 -07:00
const sqrt_eps_value = comptime @sqrt ( eps_value ) ;
2021-03-13 13:08:46 +01:00
2020-11-01 18:35:19 +01:00
const pos_x : T = 12.0 ;
2021-03-13 13:08:46 +01:00
const pos_y : T = pos_x + 2 * eps_value ;
2020-11-01 18:35:19 +01:00
const neg_x : T = - 12.0 ;
2021-03-13 13:08:46 +01:00
const neg_y : T = neg_x - 2 * eps_value ;
2020-06-23 18:08:15 +02:00
2021-05-04 19:36:59 +03:00
try expectApproxEqRel ( pos_x , pos_y , sqrt_eps_value ) ;
try expectApproxEqRel ( neg_x , neg_y , sqrt_eps_value ) ;
2020-11-01 18:35:19 +01:00
}
2020-06-23 18:08:15 +02:00
}
2019-02-08 18:18:47 -05:00
/// This function is intended to be used only in tests. When the two slices are not
2022-12-07 22:50:32 -08:00
/// equal, prints diagnostics to stderr to show exactly how they are not equal (with
/// the differences highlighted in red), then returns a test failure error.
2023-05-21 14:27:28 +01:00
/// The colorized output is optional and controlled by the return of `std.io.tty.detectConfig()`.
2020-12-08 21:54:46 -07:00
/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
2021-05-04 19:36:59 +03:00
pub fn expectEqualSlices ( comptime T : type , expected : [ ] const T , actual : [ ] const T ) ! void {
2022-12-07 22:50:32 -08:00
if ( expected . ptr == actual . ptr and expected . len == actual . len ) {
return ;
2022-11-30 18:06:02 -08:00
}
2022-12-07 22:50:32 -08:00
const diff_index : usize = diff_index : {
const shortest = @min ( expected . len , actual . len ) ;
var index : usize = 0 ;
while ( index < shortest ) : ( index += 1 ) {
if ( ! std . meta . eql ( actual [ index ] , expected [ index ] ) ) break : diff_index index ;
}
break : diff_index if ( expected . len == actual . len ) return else shortest ;
} ;
2023-10-08 21:00:20 +02:00
if ( ! backend_can_print ) {
return error . TestExpectedEqual ;
}
2023-07-01 14:29:11 +02:00
print ( " slices differ. first difference occurs at index {d} (0x{X}) \n " , . { diff_index , diff_index } ) ;
2022-12-07 22:50:32 -08:00
// TODO: Should this be configurable by the caller?
const max_lines : usize = 16 ;
const max_window_size : usize = if ( T == u8 ) max_lines * 16 else max_lines ;
// Print a maximum of max_window_size items of each input, starting just before the
// first difference to give a bit of context.
var window_start : usize = 0 ;
if ( @max ( actual . len , expected . len ) > max_window_size ) {
const alignment = if ( T == u8 ) 16 else 2 ;
2023-06-09 16:02:18 -07:00
window_start = std . mem . alignBackward ( usize , diff_index - @min ( diff_index , alignment ) , alignment ) ;
2019-02-08 18:18:47 -05:00
}
2022-12-07 22:50:32 -08:00
const expected_window = expected [ window_start .. @min ( expected . len , window_start + max_window_size ) ] ;
const expected_truncated = window_start + expected_window . len < expected . len ;
const actual_window = actual [ window_start .. @min ( actual . len , window_start + max_window_size ) ] ;
const actual_truncated = window_start + actual_window . len < actual . len ;
2023-08-03 09:42:04 +02:00
const stderr = std . io . getStdErr ( ) ;
const ttyconf = std . io . tty . detectConfig ( stderr ) ;
2022-12-07 22:50:32 -08:00
var differ = if ( T == u8 ) BytesDiffer {
. expected = expected_window ,
. actual = actual_window ,
. ttyconf = ttyconf ,
} else SliceDiffer ( T ) {
. start_index = window_start ,
. expected = expected_window ,
. actual = actual_window ,
. ttyconf = ttyconf ,
} ;
// Print indexes as hex for slices of u8 since it's more likely to be binary data where
// that is usually useful.
const index_fmt = if ( T == u8 ) " 0x{X} " else " {} " ;
2023-07-01 14:29:11 +02:00
print ( " \n ============ expected this output: ============= len: {} (0x{X}) \n \n " , . { expected . len , expected . len } ) ;
2022-12-07 22:50:32 -08:00
if ( window_start > 0 ) {
if ( T == u8 ) {
2023-07-01 14:29:11 +02:00
print ( " ... truncated, start index: " ++ index_fmt ++ " ... \n " , . { window_start } ) ;
2022-12-07 22:50:32 -08:00
} else {
2023-07-01 14:29:11 +02:00
print ( " ... truncated ... \n " , . { } ) ;
2022-12-07 22:50:32 -08:00
}
}
differ . write ( stderr . writer ( ) ) catch { } ;
if ( expected_truncated ) {
const end_offset = window_start + expected_window . len ;
const num_missing_items = expected . len - ( window_start + expected_window . len ) ;
if ( T == u8 ) {
2023-07-01 14:29:11 +02:00
print ( " ... truncated, indexes [ " ++ index_fmt ++ " ..] not shown, remaining bytes: " ++ index_fmt ++ " ... \n " , . { end_offset , num_missing_items } ) ;
2022-12-07 22:50:32 -08:00
} else {
2023-07-01 14:29:11 +02:00
print ( " ... truncated, remaining items: " ++ index_fmt ++ " ... \n " , . { num_missing_items } ) ;
2019-02-08 18:18:47 -05:00
}
}
2022-12-07 22:50:32 -08:00
// now reverse expected/actual and print again
differ . expected = actual_window ;
differ . actual = expected_window ;
2023-07-01 14:29:11 +02:00
print ( " \n ============= instead found this: ============== len: {} (0x{X}) \n \n " , . { actual . len , actual . len } ) ;
2022-12-07 22:50:32 -08:00
if ( window_start > 0 ) {
if ( T == u8 ) {
2023-07-01 14:29:11 +02:00
print ( " ... truncated, start index: " ++ index_fmt ++ " ... \n " , . { window_start } ) ;
2022-12-07 22:50:32 -08:00
} else {
2023-07-01 14:29:11 +02:00
print ( " ... truncated ... \n " , . { } ) ;
2022-12-07 22:50:32 -08:00
}
}
differ . write ( stderr . writer ( ) ) catch { } ;
if ( actual_truncated ) {
const end_offset = window_start + actual_window . len ;
const num_missing_items = actual . len - ( window_start + actual_window . len ) ;
if ( T == u8 ) {
2023-07-01 14:29:11 +02:00
print ( " ... truncated, indexes [ " ++ index_fmt ++ " ..] not shown, remaining bytes: " ++ index_fmt ++ " ... \n " , . { end_offset , num_missing_items } ) ;
2022-12-07 22:50:32 -08:00
} else {
2023-07-01 14:29:11 +02:00
print ( " ... truncated, remaining items: " ++ index_fmt ++ " ... \n " , . { num_missing_items } ) ;
2022-12-07 22:50:32 -08:00
}
}
2023-07-01 14:29:11 +02:00
print ( " \n ================================================ \n \n " , . { } ) ;
2022-12-07 22:50:32 -08:00
return error . TestExpectedEqual ;
}
fn SliceDiffer ( comptime T : type ) type {
return struct {
start_index : usize ,
expected : [ ] const T ,
actual : [ ] const T ,
2023-05-21 14:27:28 +01:00
ttyconf : std . io . tty . Config ,
2022-12-07 22:50:32 -08:00
const Self = @This ( ) ;
pub fn write ( self : Self , writer : anytype ) ! void {
2023-02-18 09:02:57 -07:00
for ( self . expected , 0 .. ) | value , i | {
2023-11-10 05:27:17 +00:00
const full_index = self . start_index + i ;
2022-12-07 22:50:32 -08:00
const diff = if ( i < self . actual . len ) ! std . meta . eql ( self . actual [ i ] , value ) else true ;
2023-05-20 22:30:02 +01:00
if ( diff ) try self . ttyconf . setColor ( writer , . red ) ;
2024-08-28 02:35:53 +01:00
if ( @typeInfo ( T ) == . pointer ) {
std.testing.expectEqualSlices: On failure, print address for pointer types
When comparing slice elements, `std.meta.eql` is used which only compares pointer address and length to determine equality for pointer types. This previously led to confusing results where `expectEqualSlices` would appear to fail on seemingly equal slices (judging by the output of `expectEqualSlices`. For example:
try testing.expectEqualSlices(
[]const i64,
&[_][]const i64{ &[_]i64{ 1, 2, 3 }, &[_]i64{ 5, 5, 5 } },
&[_][]const i64{ &[_]i64{ 1, 2, 3 }, &[_]i64{ 5, 5, 5 } },
);
Previously, this would result in:
============ expected this output: ============= len: 2 (0x2)
[0]: { 1, 2, 3 }
[1]: { 5, 5, 5 }
============= instead found this: ============== len: 2 (0x2)
[0]: { 1, 2, 3 }
[1]: { 5, 5, 5 }
================================================
After this commit, it will result in:
============ expected this output: ============= len: 2 (0x2)
[0]i64@7ff7e2773758: { 1, 2, 3 }
[1]i64@7ff7e2773770: { 5, 5, 5 }
============= instead found this: ============== len: 2 (0x2)
[0]i64@7ff7e2773788: { 1, 2, 3 }
[1]i64@7ff7e27737a0: { 5, 5, 5 }
================================================
2023-08-04 18:00:52 -07:00
try writer . print ( " [{}]{*}: {any} \n " , . { full_index , value , value } ) ;
} else {
try writer . print ( " [{}]: {any} \n " , . { full_index , value } ) ;
}
2023-05-20 22:30:02 +01:00
if ( diff ) try self . ttyconf . setColor ( writer , . reset ) ;
2022-12-07 22:50:32 -08:00
}
}
} ;
}
const BytesDiffer = struct {
expected : [ ] const u8 ,
actual : [ ] const u8 ,
2023-05-21 14:27:28 +01:00
ttyconf : std . io . tty . Config ,
2022-12-07 22:50:32 -08:00
pub fn write ( self : BytesDiffer , writer : anytype ) ! void {
2023-08-03 09:42:04 +02:00
var expected_iterator = std . mem . window ( u8 , self . expected , 16 , 16 ) ;
var row : usize = 0 ;
2022-12-07 22:50:32 -08:00
while ( expected_iterator . next ( ) ) | chunk | {
// to avoid having to calculate diffs twice per chunk
var diffs : std . bit_set . IntegerBitSet ( 16 ) = . { . mask = 0 } ;
2023-08-03 09:42:04 +02:00
for ( chunk , 0 .. ) | byte , col | {
const absolute_byte_index = col + row * 16 ;
2022-12-07 22:50:32 -08:00
const diff = if ( absolute_byte_index < self . actual . len ) self . actual [ absolute_byte_index ] != byte else true ;
2023-08-03 09:42:04 +02:00
if ( diff ) diffs . set ( col ) ;
try self . writeDiff ( writer , " {X:0>2} " , . { byte } , diff ) ;
if ( col == 7 ) try writer . writeByte ( ' ' ) ;
2022-12-07 22:50:32 -08:00
}
try writer . writeByte ( ' ' ) ;
if ( chunk . len < 16 ) {
var missing_columns = ( 16 - chunk . len ) * 3 ;
if ( chunk . len < 8 ) missing_columns += 1 ;
try writer . writeByteNTimes ( ' ' , missing_columns ) ;
}
2023-08-03 09:42:04 +02:00
for ( chunk , 0 .. ) | byte , col | {
const diff = diffs . isSet ( col ) ;
if ( std . ascii . isPrint ( byte ) ) {
try self . writeDiff ( writer , " {c} " , . { byte } , diff ) ;
} else {
// TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed
if ( self . ttyconf == . windows_api ) {
try self . writeDiff ( writer , " . " , . { } , diff ) ;
continue ;
}
// Let's print some common control codes as graphical Unicode symbols.
// We don't want to do this for all control codes because most control codes apart from
// the ones that Zig has escape sequences for are likely not very useful to print as symbols.
switch ( byte ) {
'\n' = > try self . writeDiff ( writer , " ␊ " , . { } , diff ) ,
'\r' = > try self . writeDiff ( writer , " ␍ " , . { } , diff ) ,
'\t' = > try self . writeDiff ( writer , " ␉ " , . { } , diff ) ,
else = > try self . writeDiff ( writer , " . " , . { } , diff ) ,
}
}
2022-12-07 22:50:32 -08:00
}
try writer . writeByte ( '\n' ) ;
2023-08-03 09:42:04 +02:00
row += 1 ;
2022-12-07 22:50:32 -08:00
}
}
2023-08-03 09:42:04 +02:00
fn writeDiff ( self : BytesDiffer , writer : anytype , comptime fmt : [ ] const u8 , args : anytype , diff : bool ) ! void {
2023-05-20 22:30:02 +01:00
if ( diff ) try self . ttyconf . setColor ( writer , . red ) ;
2023-08-03 09:42:04 +02:00
try writer . print ( fmt , args ) ;
2023-05-20 22:30:02 +01:00
if ( diff ) try self . ttyconf . setColor ( writer , . reset ) ;
2022-12-07 22:50:32 -08:00
}
} ;
test {
try expectEqualSlices ( u8 , " foo \x00 " , " foo \x00 " ) ;
try expectEqualSlices ( u16 , & [ _ ] u16 { 100 , 200 , 300 , 400 } , & [ _ ] u16 { 100 , 200 , 300 , 400 } ) ;
const E = enum { foo , bar } ;
const S = struct {
v : E ,
} ;
try expectEqualSlices (
S ,
& [ _ ] S { . { . v = . foo } , . { . v = . bar } , . { . v = . foo } , . { . v = . bar } } ,
& [ _ ] S { . { . v = . foo } , . { . v = . bar } , . { . v = . foo } , . { . v = . bar } } ,
) ;
2019-02-08 18:18:47 -05:00
}
2022-03-08 10:43:13 -08:00
/// This function is intended to be used only in tests. Checks that two slices or two arrays are equal,
/// including that their sentinel (if any) are the same. Will error if given another type.
pub fn expectEqualSentinel ( comptime T : type , comptime sentinel : T , expected : [ : sentinel ] const T , actual : [ : sentinel ] const T ) ! void {
try expectEqualSlices ( T , expected , actual ) ;
const expected_value_sentinel = blk : {
switch ( @typeInfo ( @TypeOf ( expected ) ) ) {
2024-08-28 02:35:53 +01:00
. pointer = > {
2022-03-08 10:43:13 -08:00
break : blk expected [ expected . len ] ;
} ,
2024-08-28 02:35:53 +01:00
. array = > | array_info | {
2022-03-08 10:43:13 -08:00
const indexable_outside_of_bounds = @as ( [ ] const array_info . child , & expected ) ;
break : blk indexable_outside_of_bounds [ indexable_outside_of_bounds . len ] ;
} ,
else = > { } ,
}
} ;
const actual_value_sentinel = blk : {
switch ( @typeInfo ( @TypeOf ( actual ) ) ) {
2024-08-28 02:35:53 +01:00
. pointer = > {
2022-03-08 10:43:13 -08:00
break : blk actual [ actual . len ] ;
} ,
2024-08-28 02:35:53 +01:00
. array = > | array_info | {
2022-03-08 10:43:13 -08:00
const indexable_outside_of_bounds = @as ( [ ] const array_info . child , & actual ) ;
break : blk indexable_outside_of_bounds [ indexable_outside_of_bounds . len ] ;
} ,
else = > { } ,
}
} ;
if ( ! std . meta . eql ( sentinel , expected_value_sentinel ) ) {
2023-07-01 14:29:11 +02:00
print ( " expectEqualSentinel: 'expected' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {} \n " , . { sentinel , expected_value_sentinel } ) ;
2022-03-08 10:43:13 -08:00
return error . TestExpectedEqual ;
}
if ( ! std . meta . eql ( sentinel , actual_value_sentinel ) ) {
2023-07-01 14:29:11 +02:00
print ( " expectEqualSentinel: 'actual' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {} \n " , . { sentinel , actual_value_sentinel } ) ;
2022-03-08 10:43:13 -08:00
return error . TestExpectedEqual ;
}
}
2022-03-17 17:23:22 -07:00
/// This function is intended to be used only in tests.
/// When `ok` is false, returns a test failure error.
2021-05-04 19:36:59 +03:00
pub fn expect ( ok : bool ) ! void {
if ( ! ok ) return error . TestUnexpectedResult ;
2019-02-08 18:18:47 -05:00
}
2019-12-22 06:37:25 +05:00
2020-04-27 18:26:59 -04:00
pub const TmpDir = struct {
dir : std . fs . Dir ,
parent_dir : std . fs . Dir ,
sub_path : [ sub_path_len ] u8 ,
const random_bytes_count = 12 ;
std/base64: cleanups & support url-safe and other non-padded variants
This makes a few changes to the base64 codecs.
* The padding character is optional. The common "URL-safe" variant, in
particular, is generally not used with padding. This is also the case for
password hashes, so having this will avoid code duplication with bcrypt,
scrypt and other functions.
* The URL-safe variant is added. Instead of having individual constants
for each parameter of each variant, we are now grouping these in a
struct. So, `standard_pad_char` just becomes `standard.pad_char`.
* Types are not `snake_case`'d any more. So, `standard_encoder` becomes
`standard.Encoder`, as it is a type.
* Creating a decoder with ignored characters required the alphabet and
padding. Now, `standard.decoderWithIgnore(<ignored chars>)` returns a
decoder with the standard parameters and the set of ignored chars.
* Whatever applies to `standard.*` obviously also works with `url_safe.*`
* the `calcSize()` interface was inconsistent, taking a length in the
encoder, and a slice in the encoder. Rename the variant that takes a
slice to `calcSizeForSlice()`.
* In the decoder with ignored characters, add `calcSizeUpperBound()`,
which is more useful than the one that takes a slice in order to size
a fixed buffer before we have the data.
* Return `error.InvalidCharacter` when the input actually contains
characters that are neither padding nor part of the alphabet. If we
hit a padding issue (which includes extra bits at the end),
consistently return `error.InvalidPadding`.
* Don't keep the `char_in_alphabet` array permanently in a decoder;
it is only required for sanity checks during initialization.
* Tests are unchanged, but now cover both the standard (padded) and
the url-safe (non-padded) variants.
* Add an error set, rename `OutputTooSmallError` to `NoSpaceLeft`
to match the `hex2bin` equivalent.
2021-03-19 19:26:30 +01:00
const sub_path_len = std . fs . base64_encoder . calcSize ( random_bytes_count ) ;
2020-04-27 18:26:59 -04:00
pub fn cleanup ( self : * TmpDir ) void {
self . dir . close ( ) ;
self . parent_dir . deleteTree ( & self . sub_path ) catch { } ;
self . parent_dir . close ( ) ;
self .* = undefined ;
}
} ;
2024-07-09 22:36:38 +02:00
pub fn tmpDir ( opts : std . fs . Dir . OpenOptions ) TmpDir {
2020-04-27 18:26:59 -04:00
var random_bytes : [ TmpDir . random_bytes_count ] u8 = undefined ;
2020-12-17 20:03:41 -07:00
std . crypto . random . bytes ( & random_bytes ) ;
2020-04-27 18:26:59 -04:00
var sub_path : [ TmpDir . sub_path_len ] u8 = undefined ;
2020-11-16 09:38:32 -05:00
_ = std . fs . base64_encoder . encode ( & sub_path , & random_bytes ) ;
2020-04-27 18:26:59 -04:00
2023-11-10 05:27:17 +00:00
const cwd = std . fs . cwd ( ) ;
2024-05-29 10:20:15 -07:00
var cache_dir = cwd . makeOpenPath ( " .zig-cache " , . { } ) catch
@panic ( " unable to make tmp dir for testing: unable to make and open .zig-cache dir " ) ;
2020-04-27 18:26:59 -04:00
defer cache_dir . close ( ) ;
2023-11-10 05:27:17 +00:00
const parent_dir = cache_dir . makeOpenPath ( " tmp " , . { } ) catch
2024-05-29 10:20:15 -07:00
@panic ( " unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir " ) ;
2023-11-10 05:27:17 +00:00
const dir = parent_dir . makeOpenPath ( & sub_path , opts ) catch
2020-04-27 18:26:59 -04:00
@panic ( " unable to make tmp dir for testing: unable to make and open the tmp dir " ) ;
return . {
. dir = dir ,
. parent_dir = parent_dir ,
. sub_path = sub_path ,
} ;
}
2021-05-04 19:36:59 +03:00
pub fn expectEqualStrings ( expected : [ ] const u8 , actual : [ ] const u8 ) ! void {
2020-04-29 14:40:52 +03:00
if ( std . mem . indexOfDiff ( u8 , actual , expected ) ) | diff_index | {
2020-08-07 22:35:15 -07:00
print ( " \n ====== expected this output: ========= \n " , . { } ) ;
2020-04-29 14:40:52 +03:00
printWithVisibleNewlines ( expected ) ;
2020-08-07 22:35:15 -07:00
print ( " \n ======== instead found this: ========= \n " , . { } ) ;
2020-04-29 14:40:52 +03:00
printWithVisibleNewlines ( actual ) ;
2020-08-07 22:35:15 -07:00
print ( " \n ====================================== \n " , . { } ) ;
2020-04-29 14:40:52 +03:00
var diff_line_number : usize = 1 ;
for ( expected [ 0 .. diff_index ] ) | value | {
if ( value == '\n' ) diff_line_number += 1 ;
}
2020-11-26 09:48:12 +01:00
print ( " First difference occurs on line {d}: \n " , . { diff_line_number } ) ;
2020-04-29 14:40:52 +03:00
2020-08-07 22:35:15 -07:00
print ( " expected: \n " , . { } ) ;
2020-04-29 14:40:52 +03:00
printIndicatorLine ( expected , diff_index ) ;
2020-08-07 22:35:15 -07:00
print ( " found: \n " , . { } ) ;
2020-04-29 14:40:52 +03:00
printIndicatorLine ( actual , diff_index ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2020-04-29 14:40:52 +03:00
}
}
2022-03-18 00:12:22 -07:00
pub fn expectStringStartsWith ( actual : [ ] const u8 , expected_starts_with : [ ] const u8 ) ! void {
if ( std . mem . startsWith ( u8 , actual , expected_starts_with ) )
return ;
const shortened_actual = if ( actual . len >= expected_starts_with . len )
actual [ 0 .. expected_starts_with . len ]
else
actual ;
print ( " \n ====== expected to start with: ========= \n " , . { } ) ;
printWithVisibleNewlines ( expected_starts_with ) ;
2022-09-28 23:02:49 -04:00
print ( " \n ====== instead started with: =========== \n " , . { } ) ;
2022-03-18 00:12:22 -07:00
printWithVisibleNewlines ( shortened_actual ) ;
print ( " \n ========= full output: ============== \n " , . { } ) ;
printWithVisibleNewlines ( actual ) ;
print ( " \n ====================================== \n " , . { } ) ;
return error . TestExpectedStartsWith ;
}
2021-05-04 19:36:59 +03:00
pub fn expectStringEndsWith ( actual : [ ] const u8 , expected_ends_with : [ ] const u8 ) ! void {
2020-12-08 21:54:46 -07:00
if ( std . mem . endsWith ( u8 , actual , expected_ends_with ) )
return ;
const shortened_actual = if ( actual . len >= expected_ends_with . len )
2021-10-29 00:00:50 +02:00
actual [ ( actual . len - expected_ends_with . len ) .. ]
2020-12-08 21:54:46 -07:00
else
actual ;
print ( " \n ====== expected to end with: ========= \n " , . { } ) ;
printWithVisibleNewlines ( expected_ends_with ) ;
print ( " \n ====== instead ended with: =========== \n " , . { } ) ;
printWithVisibleNewlines ( shortened_actual ) ;
print ( " \n ========= full output: ============== \n " , . { } ) ;
printWithVisibleNewlines ( actual ) ;
print ( " \n ====================================== \n " , . { } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEndsWith ;
2020-12-08 21:54:46 -07:00
}
2023-01-30 06:00:14 +08:00
/// This function is intended to be used only in tests. When the two values are not
/// deeply equal, prints diagnostics to stderr to show exactly how they are not equal,
/// then returns a test failure error.
2023-10-07 21:53:10 +02:00
/// `actual` and `expected` are coerced to a common type using peer type resolution.
2023-01-30 06:00:14 +08:00
///
/// Deeply equal is defined as follows:
2023-10-07 21:53:10 +02:00
/// Primitive types are deeply equal if they are equal using `==` operator.
2023-01-30 06:00:14 +08:00
/// Struct values are deeply equal if their corresponding fields are deeply equal.
/// Container types(like Array/Slice/Vector) deeply equal when their corresponding elements are deeply equal.
/// Pointer values are deeply equal if values they point to are deeply equal.
///
2023-09-23 13:25:57 -07:00
/// Note: Self-referential structs are supported (e.g. things like std.SinglyLinkedList)
/// but may cause infinite recursion or stack overflow when a container has a pointer to itself.
2023-10-07 21:53:10 +02:00
pub inline fn expectEqualDeep ( expected : anytype , actual : anytype ) error { TestExpectedEqual } ! void {
const T = @TypeOf ( expected , actual ) ;
return expectEqualDeepInner ( T , expected , actual ) ;
}
fn expectEqualDeepInner ( comptime T : type , expected : T , actual : T ) error { TestExpectedEqual } ! void {
2023-01-30 06:00:14 +08:00
switch ( @typeInfo ( @TypeOf ( actual ) ) ) {
2024-08-28 02:35:53 +01:00
. noreturn ,
. @" opaque " ,
. frame ,
. @" anyframe " ,
2023-01-30 06:00:14 +08:00
= > @compileError ( " value of type " ++ @typeName ( @TypeOf ( actual ) ) ++ " encountered " ) ,
2024-08-28 02:35:53 +01:00
. undefined ,
. null ,
. void ,
2023-01-30 06:00:14 +08:00
= > return ,
2024-08-28 02:35:53 +01:00
. type = > {
2023-01-30 06:00:14 +08:00
if ( actual != expected ) {
2023-07-01 14:29:11 +02:00
print ( " expected type {s}, found type {s} \n " , . { @typeName ( expected ) , @typeName ( actual ) } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
} ,
2024-08-28 02:35:53 +01:00
. bool ,
. int ,
. float ,
. comptime_float ,
. comptime_int ,
. enum_literal ,
. @" enum " ,
. @" fn " ,
. error_set ,
2023-01-30 06:00:14 +08:00
= > {
if ( actual != expected ) {
2023-07-01 14:29:11 +02:00
print ( " expected {}, found {} \n " , . { expected , actual } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
} ,
2024-08-28 02:35:53 +01:00
. pointer = > | pointer | {
2023-01-30 06:00:14 +08:00
switch ( pointer . size ) {
// We have no idea what is behind those pointers, so the best we can do is `==` check.
2025-01-15 17:53:05 +00:00
. c , . many = > {
2023-01-30 06:00:14 +08:00
if ( actual != expected ) {
2023-07-01 14:29:11 +02:00
print ( " expected {*}, found {*} \n " , . { expected , actual } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
} ,
2025-01-15 17:53:05 +00:00
. one = > {
2023-01-30 06:00:14 +08:00
// Length of those pointers are runtime value, so the best we can do is `==` check.
switch ( @typeInfo ( pointer . child ) ) {
2024-08-28 02:35:53 +01:00
. @" fn " , . @" opaque " = > {
2023-01-30 06:00:14 +08:00
if ( actual != expected ) {
2023-07-01 14:29:11 +02:00
print ( " expected {*}, found {*} \n " , . { expected , actual } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
} ,
else = > try expectEqualDeep ( expected .* , actual .* ) ,
}
} ,
2025-01-15 17:53:05 +00:00
. slice = > {
2023-01-30 06:00:14 +08:00
if ( expected . len != actual . len ) {
2023-07-01 14:29:11 +02:00
print ( " Slice len not the same, expected {d}, found {d} \n " , . { expected . len , actual . len } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
var i : usize = 0 ;
while ( i < expected . len ) : ( i += 1 ) {
expectEqualDeep ( expected [ i ] , actual [ i ] ) catch | e | {
2023-07-01 14:29:11 +02:00
print ( " index {d} incorrect. expected {any}, found {any} \n " , . {
2023-01-30 06:00:14 +08:00
i , expected [ i ] , actual [ i ] ,
} ) ;
return e ;
} ;
}
} ,
}
} ,
2024-08-28 02:35:53 +01:00
. array = > | _ | {
2023-01-30 06:00:14 +08:00
if ( expected . len != actual . len ) {
2023-07-01 14:29:11 +02:00
print ( " Array len not the same, expected {d}, found {d} \n " , . { expected . len , actual . len } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
var i : usize = 0 ;
while ( i < expected . len ) : ( i += 1 ) {
expectEqualDeep ( expected [ i ] , actual [ i ] ) catch | e | {
2023-07-01 14:29:11 +02:00
print ( " index {d} incorrect. expected {any}, found {any} \n " , . {
2023-01-30 06:00:14 +08:00
i , expected [ i ] , actual [ i ] ,
} ) ;
return e ;
} ;
}
} ,
2024-08-28 02:35:53 +01:00
. vector = > | info | {
if ( info . len != @typeInfo ( @TypeOf ( actual ) ) . vector . len ) {
print ( " Vector len not the same, expected {d}, found {d} \n " , . { info . len , @typeInfo ( @TypeOf ( actual ) ) . vector . len } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
var i : usize = 0 ;
while ( i < info . len ) : ( i += 1 ) {
expectEqualDeep ( expected [ i ] , actual [ i ] ) catch | e | {
2023-07-01 14:29:11 +02:00
print ( " index {d} incorrect. expected {any}, found {any} \n " , . {
2023-01-30 06:00:14 +08:00
i , expected [ i ] , actual [ i ] ,
} ) ;
return e ;
} ;
}
} ,
2024-08-28 02:35:53 +01:00
. @" struct " = > | structType | {
2023-01-30 06:00:14 +08:00
inline for ( structType . fields ) | field | {
expectEqualDeep ( @field ( expected , field . name ) , @field ( actual , field . name ) ) catch | e | {
2023-07-01 14:29:11 +02:00
print ( " Field {s} incorrect. expected {any}, found {any} \n " , . { field . name , @field ( expected , field . name ) , @field ( actual , field . name ) } ) ;
2023-01-30 06:00:14 +08:00
return e ;
} ;
}
} ,
2024-08-28 02:35:53 +01:00
. @" union " = > | union_info | {
2023-01-30 06:00:14 +08:00
if ( union_info . tag_type == null ) {
2025-02-10 23:05:38 -05:00
@compileError ( " Unable to compare untagged union values for type " ++ @typeName ( @TypeOf ( actual ) ) ) ;
2023-01-30 06:00:14 +08:00
}
const Tag = std . meta . Tag ( @TypeOf ( expected ) ) ;
const expectedTag = @as ( Tag , expected ) ;
const actualTag = @as ( Tag , actual ) ;
try expectEqual ( expectedTag , actualTag ) ;
2024-06-18 19:01:05 +02:00
// we only reach this switch if the tags are equal
2023-01-30 06:00:14 +08:00
switch ( expected ) {
inline else = > | val , tag | {
try expectEqualDeep ( val , @field ( actual , @tagName ( tag ) ) ) ;
} ,
}
} ,
2024-08-28 02:35:53 +01:00
. optional = > {
2023-01-30 06:00:14 +08:00
if ( expected ) | expected_payload | {
if ( actual ) | actual_payload | {
try expectEqualDeep ( expected_payload , actual_payload ) ;
} else {
2023-07-01 14:29:11 +02:00
print ( " expected {any}, found null \n " , . { expected_payload } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
} else {
if ( actual ) | actual_payload | {
2023-07-01 14:29:11 +02:00
print ( " expected null, found {any} \n " , . { actual_payload } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
}
} ,
2024-08-28 02:35:53 +01:00
. error_union = > {
2023-01-30 06:00:14 +08:00
if ( expected ) | expected_payload | {
if ( actual ) | actual_payload | {
try expectEqualDeep ( expected_payload , actual_payload ) ;
} else | actual_err | {
2023-07-01 14:29:11 +02:00
print ( " expected {any}, found {any} \n " , . { expected_payload , actual_err } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
}
} else | expected_err | {
if ( actual ) | actual_payload | {
2023-07-01 14:29:11 +02:00
print ( " expected {any}, found {any} \n " , . { expected_err , actual_payload } ) ;
2023-01-30 06:00:14 +08:00
return error . TestExpectedEqual ;
} else | actual_err | {
try expectEqualDeep ( expected_err , actual_err ) ;
}
}
} ,
}
}
test " expectEqualDeep primitive type " {
try expectEqualDeep ( 1 , 1 ) ;
try expectEqualDeep ( true , true ) ;
try expectEqualDeep ( 1.5 , 1.5 ) ;
try expectEqualDeep ( u8 , u8 ) ;
try expectEqualDeep ( error . Bad , error . Bad ) ;
// optional
{
const foo : ? u32 = 1 ;
const bar : ? u32 = 1 ;
try expectEqualDeep ( foo , bar ) ;
try expectEqualDeep ( ? u32 , ? u32 ) ;
}
// function type
{
const fnType = struct {
fn foo ( ) void {
unreachable ;
}
} . foo ;
try expectEqualDeep ( fnType , fnType ) ;
}
}
test " expectEqualDeep pointer " {
const a = 1 ;
const b = 1 ;
try expectEqualDeep ( & a , & b ) ;
}
test " expectEqualDeep composite type " {
try expectEqualDeep ( " abc " , " abc " ) ;
const s1 : [ ] const u8 = " abc " ;
const s2 = " abcd " ;
const s3 : [ ] const u8 = s2 [ 0 .. 3 ] ;
try expectEqualDeep ( s1 , s3 ) ;
const TestStruct = struct { s : [ ] const u8 } ;
try expectEqualDeep ( TestStruct { . s = " abc " } , TestStruct { . s = " abc " } ) ;
try expectEqualDeep ( [ _ ] [ ] const u8 { " a " , " b " , " c " } , [ _ ] [ ] const u8 { " a " , " b " , " c " } ) ;
// vector
2023-07-06 19:48:42 +02:00
try expectEqualDeep ( @as ( @Vector ( 4 , u32 ) , @splat ( 4 ) ) , @as ( @Vector ( 4 , u32 ) , @splat ( 4 ) ) ) ;
2023-01-30 06:00:14 +08:00
// nested array
{
const a = [ 2 ] [ 2 ] f32 {
[ _ ] f32 { 1.0 , 0.0 } ,
[ _ ] f32 { 0.0 , 1.0 } ,
} ;
const b = [ 2 ] [ 2 ] f32 {
[ _ ] f32 { 1.0 , 0.0 } ,
[ _ ] f32 { 0.0 , 1.0 } ,
} ;
try expectEqualDeep ( a , b ) ;
try expectEqualDeep ( & a , & b ) ;
}
2025-02-15 12:41:58 +10:00
// inferred union
const TestStruct2 = struct {
const A = union ( enum ) { b : B , c : C } ;
const B = struct { } ;
const C = struct { a : * const A } ;
} ;
const union1 = TestStruct2 . A { . b = . { } } ;
try expectEqualDeep (
TestStruct2 . A { . c = . { . a = & union1 } } ,
TestStruct2 . A { . c = . { . a = & union1 } } ,
) ;
2023-01-30 06:00:14 +08:00
}
2020-04-29 14:40:52 +03:00
fn printIndicatorLine ( source : [ ] const u8 , indicator_index : usize ) void {
const line_begin_index = if ( std . mem . lastIndexOfScalar ( u8 , source [ 0 .. indicator_index ] , '\n' ) ) | line_begin |
line_begin + 1
else
0 ;
const line_end_index = if ( std . mem . indexOfScalar ( u8 , source [ indicator_index .. ] , '\n' ) ) | line_end |
( indicator_index + line_end )
else
source . len ;
printLine ( source [ line_begin_index .. line_end_index ] ) ;
2023-08-03 09:42:04 +02:00
for ( line_begin_index .. indicator_index ) | _ |
print ( " " , . { } ) ;
2022-07-25 14:51:22 +02:00
if ( indicator_index >= source . len )
print ( " ^ (end of string) \n " , . { } )
else
print ( " ^ (' \\ x{x:0>2}') \n " , . { source [ indicator_index ] } ) ;
2020-04-29 14:40:52 +03:00
}
fn printWithVisibleNewlines ( source : [ ] const u8 ) void {
var i : usize = 0 ;
2021-04-09 10:36:05 +02:00
while ( std . mem . indexOfScalar ( u8 , source [ i .. ] , '\n' ) ) | nl | : ( i += nl + 1 ) {
2023-05-02 22:08:54 +10:00
printLine ( source [ i .. ] [ 0 .. nl ] ) ;
2020-04-29 14:40:52 +03:00
}
2020-11-26 09:48:12 +01:00
print ( " {s}␃ \n " , . { source [ i .. ] } ) ; // End of Text symbol (ETX)
2020-04-29 14:40:52 +03:00
}
fn printLine ( line : [ ] const u8 ) void {
2020-05-22 00:27:51 -04:00
if ( line . len != 0 ) switch ( line [ line . len - 1 ] ) {
2023-08-03 09:42:04 +02:00
' ' , '\t' = > return print ( " {s}⏎ \n " , . { line } ) , // Return symbol
2020-05-22 00:27:51 -04:00
else = > { } ,
} ;
2020-11-26 09:48:12 +01:00
print ( " {s} \n " , . { line } ) ;
2020-04-29 14:40:52 +03:00
}
2021-01-22 15:45:28 +01:00
test {
2021-05-04 19:36:59 +03:00
try expectEqualStrings ( " foo " , " foo " ) ;
2020-04-29 14:40:52 +03:00
}
2020-09-28 07:29:53 -06:00
2022-01-13 00:35:50 -08:00
/// Exhaustively check that allocation failures within `test_fn` are handled without
/// introducing memory leaks. If used with the `testing.allocator` as the `backing_allocator`,
/// it will also be able to detect double frees, etc (when runtime safety is enabled).
///
/// The provided `test_fn` must have a `std.mem.Allocator` as its first argument,
/// and must have a return type of `!void`. Any extra arguments of `test_fn` can
/// be provided via the `extra_args` tuple.
///
/// Any relevant state shared between runs of `test_fn` *must* be reset within `test_fn`.
///
/// The strategy employed is to:
/// - Run the test function once to get the total number of allocations.
/// - Then, iterate and run the function X more times, incrementing
/// the failing index each iteration (where X is the total number of
/// allocations determined previously)
///
2022-06-23 17:20:24 -07:00
/// Expects that `test_fn` has a deterministic number of memory allocations:
/// - If an allocation was made to fail during a run of `test_fn`, but `test_fn`
/// didn't return `error.OutOfMemory`, then `error.SwallowedOutOfMemoryError`
/// is returned from `checkAllAllocationFailures`. You may want to ignore this
/// depending on whether or not the code you're testing includes some strategies
/// for recovering from `error.OutOfMemory`.
/// - If a run of `test_fn` with an expected allocation failure executes without
/// an allocation failure being induced, then `error.NondeterministicMemoryUsage`
/// is returned. This error means that there are allocation points that won't be
/// tested by the strategy this function employs (that is, there are sometimes more
/// points of allocation than the initial run of `test_fn` detects).
///
2022-01-13 00:35:50 -08:00
/// ---
///
2022-06-23 17:20:24 -07:00
/// Here's an example using a simple test case that will cause a leak when the
2022-01-13 00:35:50 -08:00
/// allocation of `bar` fails (but will pass normally):
///
/// ```zig
/// test {
/// const length: usize = 10;
/// const allocator = std.testing.allocator;
/// var foo = try allocator.alloc(u8, length);
/// var bar = try allocator.alloc(u8, length);
///
/// allocator.free(foo);
/// allocator.free(bar);
/// }
/// ```
///
/// The test case can be converted to something that this function can use by
/// doing:
///
/// ```zig
/// fn testImpl(allocator: std.mem.Allocator, length: usize) !void {
/// var foo = try allocator.alloc(u8, length);
/// var bar = try allocator.alloc(u8, length);
///
/// allocator.free(foo);
/// allocator.free(bar);
/// }
///
/// test {
/// const length: usize = 10;
/// const allocator = std.testing.allocator;
/// try std.testing.checkAllAllocationFailures(allocator, testImpl, .{length});
/// }
/// ```
///
/// Running this test will show that `foo` is leaked when the allocation of
/// `bar` fails. The simplest fix, in this case, would be to use defer like so:
///
/// ```zig
/// fn testImpl(allocator: std.mem.Allocator, length: usize) !void {
/// var foo = try allocator.alloc(u8, length);
/// defer allocator.free(foo);
/// var bar = try allocator.alloc(u8, length);
/// defer allocator.free(bar);
/// }
/// ```
pub fn checkAllAllocationFailures ( backing_allocator : std . mem . Allocator , comptime test_fn : anytype , extra_args : anytype ) ! void {
2024-08-28 02:35:53 +01:00
switch ( @typeInfo ( @typeInfo ( @TypeOf ( test_fn ) ) . @" fn " . return_type .? ) ) {
. error_union = > | info | {
2022-01-13 00:35:50 -08:00
if ( info . payload != void ) {
@compileError ( " Return type must be !void " ) ;
}
} ,
else = > @compileError ( " Return type must be !void " ) ,
}
2024-08-28 02:35:53 +01:00
if ( @typeInfo ( @TypeOf ( extra_args ) ) != . @" struct " ) {
2022-01-13 00:35:50 -08:00
@compileError ( " Expected tuple or struct argument, found " ++ @typeName ( @TypeOf ( extra_args ) ) ) ;
}
const ArgsTuple = std . meta . ArgsTuple ( @TypeOf ( test_fn ) ) ;
2024-08-28 02:35:53 +01:00
const fn_args_fields = @typeInfo ( ArgsTuple ) . @" struct " . fields ;
2022-12-13 22:30:06 +01:00
if ( fn_args_fields . len == 0 or fn_args_fields [ 0 ] . type != std . mem . Allocator ) {
2022-01-13 00:35:50 -08:00
@compileError ( " The provided function must have an " ++ @typeName ( std . mem . Allocator ) ++ " as its first argument " ) ;
}
const expected_args_tuple_len = fn_args_fields . len - 1 ;
if ( extra_args . len != expected_args_tuple_len ) {
2022-12-04 15:03:32 +02:00
@compileError ( " The provided function expects " ++ std . fmt . comptimePrint ( " {d} " , . { expected_args_tuple_len } ) ++ " extra arguments, but the provided tuple contains " ++ std . fmt . comptimePrint ( " {d} " , . { extra_args . len } ) ) ;
2022-01-13 00:35:50 -08:00
}
// Setup the tuple that will actually be used with @call (we'll need to insert
// the failing allocator in field @"0" before each @call)
var args : ArgsTuple = undefined ;
2024-08-28 02:35:53 +01:00
inline for ( @typeInfo ( @TypeOf ( extra_args ) ) . @" struct " . fields , 0 .. ) | field , i | {
2022-01-13 00:35:50 -08:00
const arg_i_str = comptime str : {
var str_buf : [ 100 ] u8 = undefined ;
const args_i = i + 1 ;
const str_len = std . fmt . formatIntBuf ( & str_buf , args_i , 10 , . lower , . { } ) ;
break : str str_buf [ 0 .. str_len ] ;
} ;
@field ( args , arg_i_str ) = @field ( extra_args , field . name ) ;
}
// Try it once with unlimited memory, make sure it works
const needed_alloc_count = x : {
2023-08-28 20:25:05 -05:00
var failing_allocator_inst = std . testing . FailingAllocator . init ( backing_allocator , . { } ) ;
2022-01-13 00:35:50 -08:00
args . @" 0 " = failing_allocator_inst . allocator ( ) ;
2022-12-12 15:32:37 +02:00
try @call ( . auto , test_fn , args ) ;
2023-08-28 20:25:05 -05:00
break : x failing_allocator_inst . alloc_index ;
2022-01-13 00:35:50 -08:00
} ;
var fail_index : usize = 0 ;
while ( fail_index < needed_alloc_count ) : ( fail_index += 1 ) {
2023-08-28 20:25:05 -05:00
var failing_allocator_inst = std . testing . FailingAllocator . init ( backing_allocator , . { . fail_index = fail_index } ) ;
2022-01-13 00:35:50 -08:00
args . @" 0 " = failing_allocator_inst . allocator ( ) ;
2022-12-12 15:32:37 +02:00
if ( @call ( . auto , test_fn , args ) ) | _ | {
2022-06-23 17:20:24 -07:00
if ( failing_allocator_inst . has_induced_failure ) {
return error . SwallowedOutOfMemoryError ;
} else {
return error . NondeterministicMemoryUsage ;
}
2022-01-13 00:35:50 -08:00
} else | err | switch ( err ) {
error . OutOfMemory = > {
if ( failing_allocator_inst . allocated_bytes != failing_allocator_inst . freed_bytes ) {
print (
2022-11-12 20:03:24 +01:00
" \n fail_index: {d}/{d} \n allocated bytes: {d} \n freed bytes: {d} \n allocations: {d} \n deallocations: {d} \n allocation that was made to fail: {} " ,
2022-01-13 00:35:50 -08:00
. {
fail_index ,
needed_alloc_count ,
failing_allocator_inst . allocated_bytes ,
failing_allocator_inst . freed_bytes ,
failing_allocator_inst . allocations ,
failing_allocator_inst . deallocations ,
2022-06-23 17:02:29 -07:00
failing_allocator_inst . getStackTrace ( ) ,
2022-01-13 00:35:50 -08:00
} ,
) ;
return error . MemoryLeakDetected ;
}
} ,
else = > return err ,
}
}
}
2022-09-29 09:43:22 +02:00
/// Given a type, references all the declarations inside, so that the semantic analyzer sees them.
2020-09-28 07:29:53 -06:00
pub fn refAllDecls ( comptime T : type ) void {
2021-10-04 23:47:27 -07:00
if ( ! builtin . is_test ) return ;
2022-01-31 22:25:49 -07:00
inline for ( comptime std . meta . declarations ( T ) ) | decl | {
2023-07-25 11:15:59 -04:00
_ = & @field ( T , decl . name ) ;
2020-09-28 07:29:53 -06:00
}
}
2022-07-13 18:46:10 +04:30
2022-09-29 09:43:22 +02:00
/// Given a type, recursively references all the declarations inside, so that the semantic analyzer sees them.
/// For deep types, you may use `@setEvalBranchQuota`.
2022-07-13 18:46:10 +04:30
pub fn refAllDeclsRecursive ( comptime T : type ) void {
2022-09-29 09:42:58 +02:00
if ( ! builtin . is_test ) return ;
2022-07-13 18:46:10 +04:30
inline for ( comptime std . meta . declarations ( T ) ) | decl | {
2023-07-25 11:15:59 -04:00
if ( @TypeOf ( @field ( T , decl . name ) ) == type ) {
switch ( @typeInfo ( @field ( T , decl . name ) ) ) {
2024-08-28 02:35:53 +01:00
. @" struct " , . @" enum " , . @" union " , . @" opaque " = > refAllDeclsRecursive ( @field ( T , decl . name ) ) ,
2023-07-25 11:15:59 -04:00
else = > { } ,
2022-07-13 18:46:10 +04:30
}
}
2023-07-25 11:15:59 -04:00
_ = & @field ( T , decl . name ) ;
2022-07-13 18:46:10 +04:30
}
}
2024-07-22 16:45:19 -07:00
pub const FuzzInputOptions = struct {
corpus : [ ] const [ ] const u8 = & . { } ,
} ;
2024-09-09 19:36:52 -07:00
/// Inline to avoid coverage instrumentation.
pub inline fn fuzz (
2025-02-11 11:54:12 -08:00
context : anytype ,
comptime testOne : fn ( context : @TypeOf ( context ) , input : [ ] const u8 ) anyerror ! void ,
2024-09-09 19:36:52 -07:00
options : FuzzInputOptions ,
) anyerror ! void {
2025-02-11 11:54:12 -08:00
return @import ( " root " ) . fuzz ( context , testOne , options ) ;
2024-07-22 16:45:19 -07:00
}