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 " ) ;
2021-07-27 08:59:34 +09:00
2020-11-01 18:35:19 +01:00
const math = std . math ;
2020-08-07 22:35:15 -07:00
const print = std . debug . print ;
2019-02-08 18:18:47 -05:00
2020-01-29 21:22:01 -06:00
pub const FailingAllocator = @import ( " testing/failing_allocator.zig " ) . FailingAllocator ;
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 ( ) ;
2022-05-26 14:19:05 +04:30
pub var allocator_instance = b : {
if ( ! builtin . is_test )
@compileError ( " Cannot use testing allocator outside of test block " ) ;
break : b std . heap . GeneralPurposeAllocator ( . { } ) { } ;
} ;
2020-01-29 12:21:29 -06:00
2021-10-29 02:08:41 +01:00
pub const failing_allocator = failing_allocator_instance . allocator ( ) ;
pub var failing_allocator_instance = FailingAllocator . init ( base_allocator_instance . allocator ( ) , 0 ) ;
2020-01-29 13:18:04 -06:00
2020-08-07 22:35:15 -07:00
pub var base_allocator_instance = std . heap . FixedBufferAllocator . init ( " " ) ;
2020-01-29 13:18:04 -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 ;
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 | {
2021-05-15 01:41:12 +03:00
std . debug . print ( " expected error.{s}, found {any} \n " , . { @errorName ( expected_error ) , actual_payload } ) ;
2021-05-04 19:36:59 +03:00
return error . TestUnexpectedError ;
2019-02-08 18:18:47 -05:00
} else | actual_error | {
if ( expected_error != actual_error ) {
2021-05-15 01:41:12 +03:00
std . debug . print ( " expected error.{s}, found error.{s} \n " , . {
2019-12-08 22:53:51 -05:00
@errorName ( expected_error ) ,
@errorName ( actual_error ) ,
} ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedError ;
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.
2020-09-30 01:31:08 -04:00
/// `actual` is casted to the type of `expected`.
2021-05-04 19:36:59 +03:00
pub fn expectEqual ( expected : anytype , actual : @TypeOf ( expected ) ) ! void {
2019-12-09 21:56:19 +01:00
switch ( @typeInfo ( @TypeOf ( actual ) ) ) {
2019-07-21 19:56:37 -04:00
. NoReturn ,
. BoundFn ,
. Opaque ,
. Frame ,
2019-07-26 19:52:35 -04:00
. 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
2019-07-21 19:56:37 -04:00
. Undefined ,
. Null ,
. Void ,
2019-02-08 18:18:47 -05:00
= > return ,
2020-10-29 11:10:21 +01:00
. Type = > {
if ( actual != expected ) {
2021-05-15 01:41:12 +03:00
std . debug . 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
}
} ,
2019-07-21 19:56:37 -04:00
. Bool ,
. Int ,
. Float ,
. ComptimeFloat ,
. ComptimeInt ,
. EnumLiteral ,
. Enum ,
. Fn ,
. ErrorSet ,
2019-02-08 18:18:47 -05:00
= > {
if ( actual != expected ) {
2021-05-15 01:41:12 +03:00
std . debug . 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
}
} ,
2019-07-21 19:56:37 -04:00
. Pointer = > | pointer | {
2019-02-08 18:18:47 -05:00
switch ( pointer . size ) {
2020-02-25 21:29:56 +01:00
. One , . Many , . C = > {
2019-02-08 18:18:47 -05:00
if ( actual != expected ) {
2021-05-15 01:41:12 +03:00
std . debug . 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
}
} ,
2020-02-26 01:18:23 -05:00
. Slice = > {
2019-02-08 18:18:47 -05:00
if ( actual . ptr != expected . ptr ) {
2021-05-15 01:41:12 +03:00
std . debug . 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 ) {
2021-05-15 01:41:12 +03:00
std . debug . 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
}
} ,
}
} ,
2021-05-04 19:36:59 +03:00
. Array = > | array | try expectEqualSlices ( array . child , & expected , & actual ) ,
2019-02-08 18:18:47 -05:00
2022-01-12 23:53:26 -07: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 ] ) ) {
2022-01-12 23:53:26 -07:00
std . debug . print ( " index {} incorrect. expected {}, found {} \n " , . {
i , expected [ i ] , actual [ i ] ,
} ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2020-01-07 19:14:37 +05:00
}
}
} ,
2019-07-21 19:56:37 -04: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
} ,
2019-07-21 19:56:37 -04:00
. Union = > | union_info | {
2019-02-08 18:18:47 -05:00
if ( union_info . tag_type == null ) {
@compileError ( " Unable to compare untagged union values " ) ;
}
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
// we only reach this loop if the tags are equal
2019-12-09 21:56:19 +01:00
inline for ( std . meta . fields ( @TypeOf ( actual ) ) ) | fld | {
2019-11-27 22:35:32 +01:00
if ( std . mem . eql ( u8 , fld . name , @tagName ( actualTag ) ) ) {
2021-05-04 19:36:59 +03:00
try expectEqual ( @field ( expected , fld . name ) , @field ( actual , fld . name ) ) ;
2019-11-27 22:35:32 +01:00
return ;
}
}
// we iterate over *all* union fields
2019-12-08 22:53:51 -05:00
// => we should never get here as the loop above is
2019-11-27 22:35:32 +01:00
// including all possible values.
unreachable ;
2019-02-08 18:18:47 -05:00
} ,
2019-07-21 19:56:37 -04: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 {
2021-05-15 01:41:12 +03:00
std . debug . 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 | {
2021-05-15 01:41:12 +03:00
std . debug . 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
}
}
} ,
2019-07-21 19:56:37 -04:00
. ErrorUnion = > {
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 | {
2021-05-15 01:41:12 +03:00
std . debug . 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 | {
2021-05-15 01:41:12 +03:00
std . debug . 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
}
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
/// they are not equal, then returns an error.
pub fn expectFmt ( expected : [ ] const u8 , comptime template : [ ] const u8 , args : anytype ) ! void {
const result = try std . fmt . allocPrint ( allocator , template , args ) ;
defer allocator . free ( result ) ;
if ( std . mem . eql ( u8 , result , expected ) ) return ;
print ( " \n ====== expected this output: ========= \n " , . { } ) ;
print ( " {s} " , . { expected } ) ;
print ( " \n ======== instead found this: ========= \n " , . { } ) ;
print ( " {s} " , . { result } ) ;
print ( " \n ====================================== \n " , . { } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedFmt ;
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.
2021-05-04 19:36:59 +03:00
pub fn expectApproxEqAbs ( expected : anytype , actual : @TypeOf ( expected ) , tolerance : @TypeOf ( expected ) ) ! void {
2021-03-13 13:08:46 +01:00
const T = @TypeOf ( expected ) ;
switch ( @typeInfo ( T ) ) {
2021-05-04 19:36:59 +03:00
. Float = > if ( ! math . approxEqAbs ( T , expected , actual , tolerance ) ) {
2021-05-15 01:41:12 +03:00
std . debug . 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
. ComptimeFloat = > @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 " ) ,
}
}
2021-03-13 13:08:46 +01: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.
2021-05-04 19:36:59 +03:00
pub fn expectApproxEqRel ( expected : anytype , actual : @TypeOf ( expected ) , tolerance : @TypeOf ( expected ) ) ! void {
2021-03-13 13:08:46 +01:00
const T = @TypeOf ( expected ) ;
switch ( @typeInfo ( T ) ) {
2021-05-04 19:36:59 +03:00
. Float = > if ( ! math . approxEqRel ( T , expected , actual , tolerance ) ) {
2021-05-15 01:41:12 +03:00
std . debug . 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
. ComptimeFloat = > @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 " ) ,
}
}
2021-03-13 13:08:46 +01:00
test " expectApproxEqRel " {
2020-11-01 18:35:19 +01:00
inline for ( [ _ ] type { f16 , f32 , f64 , f128 } ) | T | {
2021-03-13 13:08:46 +01:00
const eps_value = comptime math . epsilon ( 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
/// 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.
2020-12-08 21:54:46 -07:00
/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
2022-11-30 11:48:06 -08:00
/// If your inputs are slices of bytes, consider calling `expectEqualBytes` instead.
2021-05-04 19:36:59 +03:00
pub fn expectEqualSlices ( comptime T : type , expected : [ ] const T , actual : [ ] const T ) ! void {
2019-02-08 18:18:47 -05:00
// TODO better printing of the difference
// If the arrays are small enough we could print the whole thing
// If the child type is u8 and no weird bytes, we could print it as strings
// Even for the length difference, it would be useful to see the values of the slices probably.
if ( expected . len != actual . len ) {
2021-05-15 01:41:12 +03:00
std . debug . print ( " slice lengths differ. expected {d}, found {d} \n " , . { expected . len , actual . len } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
2019-02-08 18:18:47 -05:00
}
var i : usize = 0 ;
while ( i < expected . len ) : ( i += 1 ) {
2019-12-22 06:37:25 +05:00
if ( ! std . meta . eql ( expected [ i ] , actual [ i ] ) ) {
2021-05-15 01:41:12 +03:00
std . debug . print ( " index {} incorrect. expected {any}, found {any} \n " , . { i , expected [ i ] , actual [ i ] } ) ;
2021-05-04 19:36:59 +03:00
return error . TestExpectedEqual ;
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 ) ) ) {
. Pointer = > {
break : blk expected [ expected . len ] ;
} ,
. Array = > | array_info | {
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 ) ) ) {
. Pointer = > {
break : blk actual [ actual . len ] ;
} ,
. Array = > | array_info | {
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 ) ) {
std . debug . print ( " expectEqualSentinel: 'expected' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {} \n " , . { sentinel , expected_value_sentinel } ) ;
return error . TestExpectedEqual ;
}
if ( ! std . meta . eql ( sentinel , actual_value_sentinel ) ) {
std . debug . print ( " expectEqualSentinel: 'actual' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {} \n " , . { sentinel , actual_value_sentinel } ) ;
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 ;
}
} ;
2022-07-15 13:05:16 +03:00
pub const TmpIterableDir = struct {
iterable_dir : std . fs . IterableDir ,
parent_dir : std . fs . Dir ,
sub_path : [ sub_path_len ] u8 ,
const random_bytes_count = 12 ;
const sub_path_len = std . fs . base64_encoder . calcSize ( random_bytes_count ) ;
pub fn cleanup ( self : * TmpIterableDir ) void {
self . iterable_dir . close ( ) ;
self . parent_dir . deleteTree ( & self . sub_path ) catch { } ;
self . parent_dir . close ( ) ;
self .* = undefined ;
}
} ;
2020-04-27 18:26:59 -04:00
pub fn tmpDir ( opts : std . fs . Dir . OpenDirOptions ) TmpDir {
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
2022-11-24 08:10:06 -07:00
var cwd = std . fs . cwd ( ) ;
2020-05-18 17:10:02 +02:00
var cache_dir = cwd . makeOpenPath ( " zig-cache " , . { } ) catch
2020-04-27 18:26:59 -04:00
@panic ( " unable to make tmp dir for testing: unable to make and open zig-cache dir " ) ;
defer cache_dir . close ( ) ;
var parent_dir = cache_dir . makeOpenPath ( " tmp " , . { } ) catch
@panic ( " unable to make tmp dir for testing: unable to make and open zig-cache/tmp dir " ) ;
var dir = parent_dir . makeOpenPath ( & sub_path , opts ) catch
@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 ,
} ;
}
2022-07-15 13:05:16 +03:00
pub fn tmpIterableDir ( opts : std . fs . Dir . OpenDirOptions ) TmpIterableDir {
var random_bytes : [ TmpIterableDir . random_bytes_count ] u8 = undefined ;
std . crypto . random . bytes ( & random_bytes ) ;
var sub_path : [ TmpIterableDir . sub_path_len ] u8 = undefined ;
_ = std . fs . base64_encoder . encode ( & sub_path , & random_bytes ) ;
2022-11-24 08:10:06 -07:00
var cwd = std . fs . cwd ( ) ;
2022-07-15 13:05:16 +03: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 " ) ;
defer cache_dir . close ( ) ;
var parent_dir = cache_dir . makeOpenPath ( " tmp " , . { } ) catch
@panic ( " unable to make tmp dir for testing: unable to make and open zig-cache/tmp dir " ) ;
var dir = parent_dir . makeOpenPathIterable ( & sub_path , opts ) catch
@panic ( " unable to make tmp dir for testing: unable to make and open the tmp dir " ) ;
return . {
. iterable_dir = dir ,
. parent_dir = parent_dir ,
. sub_path = sub_path ,
} ;
}
2019-12-22 06:37:25 +05: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 } ,
} ;
2021-05-04 19:36:59 +03:00
try expectEqual ( a , b ) ;
2019-12-22 06:37:25 +05:00
}
2020-01-07 19:14:37 +05:00
test " expectEqual vector " {
var a = @splat ( 4 , @as ( u32 , 4 ) ) ;
var b = @splat ( 4 , @as ( u32 , 4 ) ) ;
2021-05-04 19:36:59 +03:00
try expectEqual ( a , b ) ;
2020-01-07 19:14:37 +05:00
}
2020-04-29 14:40:52 +03:00
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
}
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 ] ) ;
{
var i : usize = line_begin_index ;
while ( i < indicator_index ) : ( i += 1 )
2020-08-07 22:35:15 -07:00
print ( " " , . { } ) ;
2020-04-29 14:40:52 +03:00
}
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 ) {
2020-04-29 14:40:52 +03:00
printLine ( source [ i .. i + nl ] ) ;
}
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 ] ) {
2021-04-09 10:39:13 +02:00
' ' , '\t' = > return print ( " {s}⏎ \n " , . { line } ) , // Carriage 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-11-30 11:48:06 -08:00
/// This function is intended to be used only in tests. When the two slices are not
/// equal, prints hexdumps of the inputs with the differences highlighted in red to stderr,
/// then returns a test failure error. The colorized output is optional and controlled
/// by the return of `std.debug.detectTTYConfig()`.
pub fn expectEqualBytes ( expected : [ ] const u8 , actual : [ ] const u8 ) ! void {
std . testing . expectEqualSlices ( u8 , expected , actual ) catch | err | {
var differ = BytesDiffer {
. expected = expected ,
. actual = actual ,
. ttyconf = std . debug . detectTTYConfig ( ) ,
} ;
const stderr = std . io . getStdErr ( ) ;
std . debug . print ( " \n ============ expected this output: ============= \n \n " , . { } ) ;
differ . write ( stderr . writer ( ) ) catch { } ;
// now reverse expected/actual and print again
differ . expected = actual ;
differ . actual = expected ;
std . debug . print ( " \n ============= instead found this: ============== \n \n " , . { } ) ;
differ . write ( stderr . writer ( ) ) catch { } ;
std . debug . print ( " \n ================================================ \n \n " , . { } ) ;
return err ;
} ;
}
const BytesDiffer = struct {
expected : [ ] const u8 ,
actual : [ ] const u8 ,
ttyconf : std . debug . TTY . Config ,
pub fn write ( self : BytesDiffer , writer : anytype ) ! void {
var expected_iterator = ChunkIterator { . bytes = self . expected } ;
while ( expected_iterator . next ( ) ) | chunk | {
// to avoid having to calculate diffs twice per chunk
var diffs : std . bit_set . IntegerBitSet ( 16 ) = . { . mask = 0 } ;
for ( chunk ) | byte , i | {
var absolute_byte_index = ( expected_iterator . index - chunk . len ) + i ;
const diff = if ( absolute_byte_index < self . actual . len ) self . actual [ absolute_byte_index ] != byte else true ;
if ( diff ) diffs . set ( i ) ;
try self . writeByteDiff ( writer , " {X:0>2} " , byte , diff ) ;
if ( i == 7 ) try writer . writeByte ( ' ' ) ;
}
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 ) ;
}
for ( chunk ) | byte , i | {
const byte_to_print = if ( std . ascii . isPrint ( byte ) ) byte else '.' ;
try self . writeByteDiff ( writer , " {c} " , byte_to_print , diffs . isSet ( i ) ) ;
}
try writer . writeByte ( '\n' ) ;
}
}
fn writeByteDiff ( self : BytesDiffer , writer : anytype , comptime fmt : [ ] const u8 , byte : u8 , diff : bool ) ! void {
if ( diff ) self . ttyconf . setColor ( writer , . Red ) ;
try writer . print ( fmt , . { byte } ) ;
if ( diff ) self . ttyconf . setColor ( writer , . Reset ) ;
}
const ChunkIterator = struct {
bytes : [ ] const u8 ,
index : usize = 0 ,
pub fn next ( self : * ChunkIterator ) ? [ ] const u8 {
if ( self . index == self . bytes . len ) return null ;
const start_index = self . index ;
const end_index = @min ( self . bytes . len , start_index + 16 ) ;
self . index = end_index ;
return self . bytes [ start_index .. end_index ] ;
}
} ;
} ;
test {
try expectEqualBytes ( " foo \x00 " , " foo \x00 " ) ;
}
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 {
switch ( @typeInfo ( @typeInfo ( @TypeOf ( test_fn ) ) . Fn . return_type .? ) ) {
. ErrorUnion = > | info | {
if ( info . payload != void ) {
@compileError ( " Return type must be !void " ) ;
}
} ,
else = > @compileError ( " Return type must be !void " ) ,
}
if ( @typeInfo ( @TypeOf ( extra_args ) ) != . Struct ) {
@compileError ( " Expected tuple or struct argument, found " ++ @typeName ( @TypeOf ( extra_args ) ) ) ;
}
const ArgsTuple = std . meta . ArgsTuple ( @TypeOf ( test_fn ) ) ;
const fn_args_fields = @typeInfo ( ArgsTuple ) . Struct . fields ;
if ( fn_args_fields . len == 0 or fn_args_fields [ 0 ] . field_type != std . mem . Allocator ) {
@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 ) {
@compileError ( " The provided function expects " ++ ( comptime std . fmt . comptimePrint ( " {d} " , . { expected_args_tuple_len } ) ) ++ " extra arguments, but the provided tuple contains " ++ ( comptime std . fmt . comptimePrint ( " {d} " , . { extra_args . len } ) ) ) ;
}
// 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 ;
inline for ( @typeInfo ( @TypeOf ( extra_args ) ) . Struct . fields ) | field , i | {
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 : {
var failing_allocator_inst = std . testing . FailingAllocator . init ( backing_allocator , std . math . maxInt ( usize ) ) ;
args . @" 0 " = failing_allocator_inst . allocator ( ) ;
try @call ( . { } , test_fn , args ) ;
break : x failing_allocator_inst . index ;
} ;
var fail_index : usize = 0 ;
while ( fail_index < needed_alloc_count ) : ( fail_index += 1 ) {
var failing_allocator_inst = std . testing . FailingAllocator . init ( backing_allocator , fail_index ) ;
args . @" 0 " = failing_allocator_inst . allocator ( ) ;
if ( @call ( . { } , 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 | {
2022-02-18 19:21:21 +00:00
if ( decl . is_pub ) _ = @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 | {
if ( decl . is_pub ) {
if ( @TypeOf ( @field ( T , decl . name ) ) == type ) {
switch ( @typeInfo ( @field ( T , decl . name ) ) ) {
. Struct , . Enum , . Union , . Opaque = > refAllDeclsRecursive ( @field ( T , decl . name ) ) ,
else = > { } ,
}
}
_ = @field ( T , decl . name ) ;
}
}
}