Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Q
Qrakhen.Assertions
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Neumaier
Qrakhen.Assertions
Commits
53344f76
Commit
53344f76
authored
Sep 10, 2025
by
David Neumaier
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
generic assert
parent
ec831ede
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
290 additions
and
0 deletions
+290
-0
Assert.Generic.cs
Assert.Generic.cs
+290
-0
No files found.
Assert.Generic.cs
0 → 100644
View file @
53344f76
using
System.Runtime.CompilerServices
;
using
System.Runtime.Serialization
;
namespace
Qrakhen.Assertions
{
/// <summary>
/// Usage:
/// Assert.That(actual).Equal(expected);
///
/// "Is" may be chained it for readability:
/// Assert.That(1).Is.Equal(1);
///
/// Multiple actual values allowed:
/// Assert.That(a, b, c).Not.Null();
///
/// Multiple expected values allowed:
/// Assert.That(3, 4, 5).Equal(3, 4, 5);
///
/// If one expected value but multiple actual values are given, #
/// they are all matched against that single expected value:
/// Assert.That(5, 5, 5).Equal(5);
/// </summary>
public
static
partial
class
Assert
{
public
static
Assertion
<
T
>
That
<
T
>(
params
T
?[]
parameters
)
=>
new
Assertion
<
T
>().
SetActual
(
parameters
);
internal
delegate
bool
Function
(
object
?
actual
,
object
?
expected
=
null
);
public
class
Assertion
<
T
>
{
private
readonly
Assertion
<
T
>?
_previous
;
private
bool
_sealed
;
private
string
?
_name
;
private
T
?[]
_expected
=
[];
private
T
?[]
_actual
=
[];
private
Function
?
_callback
;
private
Assertion
<
T
>?
_next
;
/// <summary>
/// Full name of this assertion chain, e.g. IsNotNull
/// </summary>
public
string
FullName
{
get
{
Assertion
<
T
>
assertion
=
Last
();
string
name
=
assertion
.
_name
??
""
;
while
(
assertion
.
_previous
!=
null
)
{
assertion
=
assertion
.
_previous
;
name
=
assertion
.
_name
+
name
;
}
return
name
;
}
}
internal
Assertion
(
Assertion
<
T
>?
previous
=
null
)
{
previous
?.
AssertNotSealed
();
_previous
=
previous
;
}
/// <summary>
/// Executes this assertion chain by finding the actual value collection, the expected value collection - if provided - and the initiator function.
/// </summary>
internal
void
Execute
()
{
T
?[]
actual
=
GetActual
();
T
?[]
expected
=
GetExpected
();
Function
?
initiator
=
GetInitiator
();
if
(
actual
.
Length
==
0
)
throw
new
InvalidOperationException
(
$"Could not find actual parameters for assertion, did you forget to add them using Assert.That(actual)?"
);
if
(
initiator
==
null
)
throw
new
InvalidOperationException
(
$"Could not find initiator callback, did you forget a finalizing Assertion like Equals(expected) or Not.Null()?"
);
bool
singleExpected
=
false
;
if
(
actual
.
Length
!=
expected
.
Length
)
{
if
(
expected
.
Length
==
1
)
singleExpected
=
true
;
else
if
(
expected
.
Length
>
1
)
throw
new
InvalidOperationException
(
$"Actual parameter count (
{
actual
.
Length
}
) not matching expected parameter count (
{
expected
.
Length
}
).\n"
+
"Either provide a singular expected value that all actual values get compared against, or a matching amount for element-wise checks."
);
}
for
(
int
i
=
0
;
i
<
actual
.
Length
;
i
++)
{
T
?
currentActual
=
actual
[
i
];
T
?
currentExpected
=
singleExpected
?
expected
[
0
]
:
i
<
expected
.
Length
?
expected
[
i
]
:
default
;
AssertAndThrow
(
initiator
,
currentActual
,
currentExpected
,
FullName
);
}
}
/// <returns>First Assertion in the chain.</returns>
internal
Assertion
<
T
>
First
()
{
Assertion
<
T
>
assertion
=
this
;
while
(
assertion
.
_previous
!=
null
)
assertion
=
assertion
.
_previous
;
return
assertion
;
}
/// <returns>Last Assertion in the chain.</returns>
internal
Assertion
<
T
>
Last
()
{
Assertion
<
T
>
assertion
=
this
;
while
(
assertion
.
_next
!=
null
)
assertion
=
assertion
.
_next
;
return
assertion
;
}
private
void
AssertNotSealed
()
{
if
(
_sealed
)
throw
new
InvalidOperationException
(
$"Can not chain another assertion after sealed assertion
{
_name
}
!"
);
}
private
Function
?
GetInitiator
()
{
Assertion
<
T
>
assertion
=
First
();
Function
?
callback
=
assertion
.
_callback
;
while
(
callback
==
null
&&
assertion
.
_next
!=
null
)
{
assertion
=
assertion
.
_next
;
callback
=
assertion
.
_callback
;
}
return
callback
;
}
private
T
?[]
GetExpected
()
{
Assertion
<
T
>
assertion
=
Last
();
T
?[]
expected
=
assertion
.
_expected
;
while
(
expected
.
Length
==
0
&&
assertion
.
_previous
!=
null
)
{
assertion
=
assertion
.
_previous
;
expected
=
assertion
.
_expected
;
}
return
expected
;
}
private
T
?[]
GetActual
()
{
Assertion
<
T
>
assertion
=
First
();
T
?[]
actual
=
assertion
.
_actual
;
while
(
actual
.
Length
==
0
&&
assertion
.
_next
!=
null
)
{
assertion
=
assertion
.
_next
;
actual
=
assertion
.
_actual
;
}
return
actual
;
}
internal
Assertion
<
T
>
SetActual
(
params
T
?[]
parameters
)
{
_actual
=
(
T
?[])
parameters
.
Clone
();
return
this
;
}
#
region
Private
Flow
-
Pattern
Methods
private
Assertion
<
T
>
Fire
()
{
AssertNotSealed
();
Execute
();
return
Seal
();
}
private
Assertion
<
T
>
Seal
()
{
_sealed
=
true
;
return
this
;
}
private
Assertion
<
T
>
Init
(
string
?
customName
=
null
,
[
CallerMemberName
]
string
?
name
=
null
)
{
AssertNotSealed
();
return
SetName
(
customName
??
name
);
}
private
Assertion
<
T
>
SetName
(
string
?
name
)
{
_name
=
name
;
return
this
;
}
private
Assertion
<
T
>
SetExpected
(
params
T
?[]
parameters
)
{
_expected
=
(
T
?[])
parameters
.
Clone
();
return
this
;
}
private
Assertion
<
T
>
SetCallback
(
Function
callback
)
{
_callback
=
callback
;
return
this
;
}
private
Assertion
<
T
>
Next
()
{
_next
??=
new
Assertion
<
T
>(
this
);
return
_next
;
}
#
endregion
#
region
Public
Flow
-
Pattern
Methods
/// <summary>
/// Asserts whether values are larger than 0
/// </summary>
public
Assertion
<
T
>
Positive
()
=>
Init
().
SetCallback
((
a
,
e
)
=>
(
int
)
a
!
>
0
).
Fire
();
/// <summary>
/// Asserts whether values are equal to expected
/// </summary>
public
Assertion
<
T
>
Equal
(
params
T
?[]
expected
)
=>
Init
().
SetExpected
(
expected
).
SetCallback
(
Equals
).
Fire
();
/// <summary>
/// Asserts whether values are null references
/// </summary>
public
Assertion
<
T
>
Null
()
=>
Init
().
SetCallback
((
a
,
e
)
=>
ReferenceEquals
(
a
,
null
)).
Fire
();
/// <summary>
/// Asserts whether values are default (0, null,)
/// </summary>
public
Assertion
<
T
>
Default
()
=>
Init
().
SetExpected
([
default
]).
SetCallback
(
Equals
).
Fire
();
/// <summary>
/// Asserts whether the next assertion fails
/// </summary>
[
IgnoreDataMember
]
public
Assertion
<
T
>
Not
=>
Init
().
SetCallback
((
a
,
e
)
=>
!
_next
.
_callback
(
a
,
e
)).
Next
();
/// <summary>
/// Readability sugar for grammar-correct coding (e.g. Assert.That(...).Is.Not.Positive();
/// </summary>
[
IgnoreDataMember
]
public
Assertion
<
T
>
Is
=>
Init
().
Next
();
#
endregion
}
private
static
void
AssertAndThrow
(
Function
function
,
object
?
actual
,
object
?
expected
,
string
name
=
""
,
string
message
=
""
)
{
if
(!
function
.
Invoke
(
actual
,
expected
))
{
throw
new
AssertionException
(
name
,
message
,
actual
,
expected
);
}
}
public
class
AssertionException
:
Exception
{
public
readonly
string
Name
;
public
readonly
object
?[]
Parameters
;
public
AssertionException
(
string
name
,
string
message
,
params
object
?[]
parameters
)
:
base
(
$"Assertion
{
name
}
failed:
{
message
}
\nParameters:\n
{
string
.
Join
(
'\n'
,
parameters
.
Select
(
p
=>
$" - <
{
p
?.
GetType
().
Name
??
"null"
}
>
{
p
?.
ToString
()
??
"null"
}
"
))}
"
)
{
Name
=
name
;
Parameters
=
parameters
;
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment