Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
627 changes: 528 additions & 99 deletions src/TestFramework/TestFramework/Assertions/Assert.That.cs

Large diffs are not rendered by default.

57 changes: 30 additions & 27 deletions src/TestFramework/TestFramework/Resources/FrameworkMessages.resx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema

Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple

There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -387,4 +387,7 @@ Actual: {2}</value>
<data name="IsInRangeMaxValueMustBeGreaterThanOrEqualMinValue" xml:space="preserve">
<value>The maximum value must be greater than or equal to the minimum value.</value>
</data>
<data name="AssertThatFailedToEvaluate" xml:space="preserve">
<value>&lt;Failed to evaluate&gt;</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Skutečnost: {2}</target>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">Elementy &lt;{0}&gt; se v kolekci nenachází.</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">Řetězec „{0}“ nezačíná řetězcem „{1}“. {2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Tatsächlich: {2}</target>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">Element(e) &lt;{0}&gt; ist/sind in der Auflistung nicht vorhanden.</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">Die Zeichenfolge „{0}“ beginnt nicht mit der Zeichenfolge „{1}“. {2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ Real: {2}</target>
<target state="translated">Los elementos &lt;{0}&gt; no están presentes en la colección.</target>
<note />
</trans-unit>
<trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">La cadena "{0}" no empieza con la cadena "{1}". {2}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Réel : {2}</target>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">L’élément ou les éléments &lt;{0}&gt; n’est ou ne sont pas présent(s) dans la collection.</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">La chaîne '{0}' ne commence pas par la chaîne '{1}'. {2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ Effettivo: {2}</target>
<target state="translated">Gli elementi &lt;{0}&gt; non sono presenti nella raccolta.</target>
<note />
</trans-unit>
<trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">La stringa '{0}' non inizia con la stringa '{1}'. {2}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Actual: {2}</source>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">要素 &lt;{0}&gt; がコレクション内に存在しません。</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">文字列 '{0}' は文字列 '{1}' で始まりません。{2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Actual: {2}</source>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">컬렉션에 &lt;{0}&gt; 요소가 없습니다.</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">'{0}' 문자열이 '{1}' 문자열로 시작되지 않습니다. {2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Rzeczywiste: {2}</target>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">Elementy &lt;{0}&gt; nie występują w kolekcji.</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">Ciąg „{0}” nie rozpoczyna się od ciągu „{1}”. {2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Real: {2}</target>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">O(s) elemento(s) &lt;{0}&gt; não está/estão presente(s) na coleção.</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">A cadeia de caracteres “{0}” não começa com a cadeia de caracteres “{1}”. {2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ Actual: {2}</source>
</source>
<target state="translated">
Не удается найти указанный член ({0}). Возможно, требуется снова создать закрытый метод доступа
либо член является закрытым и определенным на основе базового класса. Если справедливо последнее, то
либо член является закрытым и определенным на основе базового класса. Если справедливо последнее, то
необходимо передать тип, определяющий член, в конструктор для PrivateObject.
</target>
<note />
Expand All @@ -307,8 +307,8 @@ Actual: {2}</source>
that defines the member into PrivateObject's constructor.
</source>
<target state="translated">
Не удается найти конструктор с указанной сигнатурой. Возможно, требуется снова создать закрытый
метод доступа либо член является закрытым и определенным на основе базового класса. Если
Не удается найти конструктор с указанной сигнатурой. Возможно, требуется снова создать закрытый
метод доступа либо член является закрытым и определенным на основе базового класса. Если
справедливо последнее, то необходимо передать тип, определяющий член, в конструктор для PrivateObject.
</target>
<note />
Expand All @@ -328,6 +328,11 @@ Actual: {2}</source>
<target state="translated">Элементы &lt;{0}&gt; отсутствуют в коллекции.</target>
<note />
</trans-unit>
<trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">Строка "{0}" не начинается со строки "{1}". {2}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ Gerçekte olan: {2}</target>
<source>Element(s) &lt;{0}&gt; is/are not present in the collection.</source>
<target state="translated">Öğe(ler) &lt;{0}&gt; koleksiyonda mevcut değildir.</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
</trans-unit> <trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit> <trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">'{0}' dizesi, '{1}' dizesi ile başlamıyor. {2}</target>
<note />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ Actual: {2}</source>
<target state="translated">集合中不存在元素 &lt;{0}&gt;。</target>
<note />
</trans-unit>
<trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">字符串 '{0}' 不以字符串 '{1}' 开头。{2}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ Actual: {2}</source>
<target state="translated">集合中不存在元素 &lt;{0}&gt;。</target>
<note />
</trans-unit>
<trans-unit id="AssertThatFailedToEvaluate">
<source>&lt;Failed to evaluate&gt;</source>
<target state="new">&lt;Failed to evaluate&gt;</target>
<note />
</trans-unit>
<trans-unit id="StartsWithFail">
<source>String '{0}' does not start with string '{1}'. {2}</source>
<target state="translated">字串 '{0}' 不是以字串 '{1}' 開頭。{2}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,10 +479,10 @@ public void That_NewExpression_ExtractsVariablesCorrectly()
.WithMessage($"""
Assert.That(() => new DateTime(year, month, day) == DateTime.MinValue) failed.
Details:
DateTime.MinValue = {DateTime.MinValue.ToString(CultureInfo.CurrentCulture)}
DateTime.MinValue = {DateTime.MinValue.ToString(CultureInfo.InvariantCulture)}
day = 25
month = 12
new DateTime(year, month, day) = {new DateTime(year, month, day).ToString(CultureInfo.CurrentCulture)}
new DateTime(year, month, day) = {new DateTime(year, month, day).ToString(CultureInfo.InvariantCulture)}
year = 2023
""");
}
Expand Down Expand Up @@ -1042,4 +1042,64 @@ public void That_WithNullConstantAndVariable_OnlyIncludesVariable()
nullVariable = null
""");
}

public void That_DoesNotEvaluateTwice_WhenAssertionFails()
{
var box = new Box();

// If we evaluate twice, box.GetValueWithSideEffect() is called once on comparison, and once when message for assertion is built.
// We compare to 0 to force failure.
Action act = () => Assert.That(() => box.GetValueWithSideEffect() == 0);

// GetValueWithSideEffect() should report 1, which is the value when we evaluate only once.
act.Should().Throw<AssertFailedException>()
.WithMessage("""
Assert.That(() => box.GetValueWithSideEffect() == 0) failed.
Details:
box.GetValueWithSideEffect() = 1
""");

// We call again, this should be second call now.
box.GetValueWithSideEffect().Should().Be(2);
}

public void That_DoesNotEvaluateTwice_WhenAssertionFails_NoSideEffect()
{
int i = 1;
Action act = () => Assert.That(() => i + i == 0);

act.Should().Throw<AssertFailedException>()
.WithMessage("""
Assert.That(() => i + i == 0) failed.
Details:
i = 1
""");
}

public void That_DoesEvaluateTwice_WhenMethodIsLeaf()
{
var box = new Box();

// Compare to 0 to force failure.
Action act = () => Assert.That(() => box.GetValueWithSideEffect() + box.GetValueWithSideEffect() == 0);

act.Should().Throw<AssertFailedException>()
.WithMessage("""
Assert.That(() => box.GetValueWithSideEffect() + box.GetValueWithSideEffect() == 0) failed.
Details:
box.GetValueWithSideEffect() = 1
box.GetValueWithSideEffect() (2) = 2
""");
}

private class Box
{
private int _c;

public int GetValueWithSideEffect()
{
_c++;
return _c;
}
}
}
Loading