Skip to content
Merged
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
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Plans typically have acceptance criteria with check boxes. Check each box when y

Read ./ai-plans/AGENTS.md for details on how to write plans.

## Test Rules

Read ./tests/AGENTS.md for details on how to write tests.

## Here is Your Space

If you encounter something worth noting while you are working on this code base, write it down here in this section. Once you are finished, I will discuss it with you, and we can decide where to put your notes.
3 changes: 3 additions & 0 deletions Light.GuardClauses.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
<File Path="README.md" />
</Folder>
<Folder Name="/src/">
<File Path="src/Directory.Build.props" />
<Project Path="src/Light.GuardClauses/Light.GuardClauses.csproj" />
</Folder>
<Folder Name="/tests/">
<File Path="tests/AGENTS.md" />
<File Path="tests/Directory.Build.props" />
<Project Path="tests/Light.GuardClauses.InternalRoslynAnalyzers.Tests/Light.GuardClauses.InternalRoslynAnalyzers.Tests.csproj" />
<Project Path="tests/Light.GuardClauses.SourceCodeTransformation.Tests/Light.GuardClauses.SourceCodeTransformation.Tests.csproj" />
<Project Path="tests/Light.GuardClauses.Tests/Light.GuardClauses.Tests.csproj" />
Expand Down
13 changes: 13 additions & 0 deletions tests/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# AGENTS.md for Testing

## How to structure tests

- Do not use mocking frameworks like Moq or NSubstitute for test doubles, use hand-written test doubles instead.
- Use FluentAssertions instead of xunit's `Assert` class. The library is pinned to 7.x.x to avoid licensing issues.
- Keep Test Coverage at least at 93%. Microsoft.Testing.Extensions.CodeCoverage is available to get test coverage metrics.
- Prefer Sociable Tests as proposed by Martin Fowler. Only use Solitary Tests as a last resort.

# How to run tests

- `dotnet test` for usual test runs.
- `dotnet test -- --coverage --coverage-output-format cobertura` for test coverage metrics.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using FluentAssertions;
using Xunit;

namespace Light.GuardClauses.Tests.ComparableAssertions;

public static class NumericCustomFactorySuccessTests
{
[Fact]
public static void MustBePositiveReturnsValidValuesWithoutInvokingFactories()
{
1.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(1);
2L.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(2L);
3m.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(3m);
4f.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(4f);
5d.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(5d);
TimeSpan.FromTicks(6).MustBePositive(_ => throw new InvalidOperationException()).Should()
.Be(TimeSpan.FromTicks(6));
((short) 7).MustBePositive(_ => throw new InvalidOperationException()).Should().Be(7);
}

[Fact]
public static void MustBeNegativeReturnsValidValuesWithoutInvokingFactories()
{
(-1).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-1);
(-2L).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-2L);
(-3m).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-3m);
(-4f).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-4f);
(-5d).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-5d);
TimeSpan.FromTicks(-6).MustBeNegative(_ => throw new InvalidOperationException()).Should()
.Be(TimeSpan.FromTicks(-6));
((short) -7).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-7);
}

[Fact]
public static void MustNotBePositiveReturnsValidValuesWithoutInvokingFactories()
{
0.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0);
0L.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0L);
0m.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0m);
0f.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0f);
0d.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0d);
TimeSpan.Zero.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(TimeSpan.Zero);
((short) 0).MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0);
}

[Fact]
public static void MustNotBeNegativeReturnsValidValuesWithoutInvokingFactories()
{
0.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0);
0L.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0L);
0m.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0m);
0f.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0f);
0d.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0d);
TimeSpan.Zero.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(TimeSpan.Zero);
((short) 0).MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0);
}

[Fact]
public static void MustNotBeZeroReturnsValidValuesWithoutInvokingFactories()
{
1.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(1);
2L.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(2L);
3m.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(3m);
4f.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(4f);
5d.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(5d);
TimeSpan.FromTicks(6).MustNotBeZero(_ => throw new InvalidOperationException()).Should()
.Be(TimeSpan.FromTicks(6));
((short) 7).MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(7);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Collections;
using System.Collections.Generic;
using FluentAssertions;
using Light.GuardClauses.FrameworkExtensions;
using Xunit;

namespace Light.GuardClauses.Tests.FrameworkExtensions;

public static class EnumerableCountTests
{
[Fact]
public static void CountWithExceptionDetailsUsesStringLengthWithoutEnumerating()
{
IEnumerable value = "text";

value.Count("value", "message").Should().Be(4);
}

[Fact]
public static void GenericCountWithExceptionDetailsUsesEveryAvailableStrategy()
{
IEnumerable<char> text = "text";
var collection = new HashSet<int> { 1, 2, 3 };
var readOnlyCollection = new ReadOnlyCollectionOnly<int>([1, 2, 3, 4]);

text.GetCount("text", "message").Should().Be(4);
collection.GetCount("collection", "message").Should().Be(3);
readOnlyCollection.GetCount("readOnlyCollection", "message").Should().Be(4);
Yield(1, 2, 3, 4, 5).GetCount("items", "message").Should().Be(5);
}

[Fact]
public static void IsOneOfEnumeratesSourcesThatAreNotCollections()
{
2.IsOneOf(Yield(1, 2, 3)).Should().BeTrue();
4.IsOneOf(Yield(1, 2, 3)).Should().BeFalse();
}

private static IEnumerable<T> Yield<T>(params T[] items)
{
foreach (var item in items)
{
yield return item;
}
}

private sealed class ReadOnlyCollectionOnly<T>(IReadOnlyCollection<T> items) : IReadOnlyCollection<T>
{
public int Count => items.Count;

public IEnumerator<T> GetEnumerator() => items.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static void ThreeParameters(string value1, int value2, char value3)
[Theory]
[InlineData("Foo", 42, 'a', 87.73665)]
[InlineData("Bar", -177422, 'Y', -15.25)]
[InlineData(null, 0, default(char), 0.0)]
[InlineData(null, 0, '\0', 0.0)]
public static void FourParameters(string value1, int value2, char value3, double value4)
{
var hashCode1 = MultiplyAddHash.CreateHashCode(value1, value2, value3, value4);
Expand All @@ -53,4 +53,164 @@ public static void FourParameters(string value1, int value2, char value3, double

hashCode1.Should().Be(hashCode2);
}
}

[Fact]
public static void EverySupportedArityMatchesTheBuilder()
{
var values = new object[]
{ 1, "two", 3m, '4', 5L, 6f, 7d, (short) 8, (byte) 9, 10u, 11ul, null, 13, 14, 15, 16 };

MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4])
.Should().Be(CreateExpectedHash(values, 5));
MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4], values[5])
.Should().Be(CreateExpectedHash(values, 6));
MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4], values[5], values[6])
.Should().Be(CreateExpectedHash(values, 7));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7]
)
.Should().Be(CreateExpectedHash(values, 8));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8]
)
.Should().Be(CreateExpectedHash(values, 9));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9]
)
.Should().Be(CreateExpectedHash(values, 10));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9],
values[10]
)
.Should().Be(CreateExpectedHash(values, 11));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9],
values[10],
values[11]
)
.Should().Be(CreateExpectedHash(values, 12));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9],
values[10],
values[11],
values[12]
)
.Should().Be(CreateExpectedHash(values, 13));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9],
values[10],
values[11],
values[12],
values[13]
)
.Should().Be(CreateExpectedHash(values, 14));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9],
values[10],
values[11],
values[12],
values[13],
values[14]
)
.Should().Be(CreateExpectedHash(values, 15));
MultiplyAddHash.CreateHashCode(
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9],
values[10],
values[11],
values[12],
values[13],
values[14],
values[15]
)
.Should().Be(CreateExpectedHash(values, 16));
}

private static int CreateExpectedHash(object[] values, int count)
{
var builder = MultiplyAddHashBuilder.Create();
for (var i = 0; i < count; i++)
{
builder = builder.CombineIntoHash(values[i]);
}

return builder.BuildHash();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Text;
using FluentAssertions;
using Light.GuardClauses.FrameworkExtensions;
using Xunit;

namespace Light.GuardClauses.Tests.FrameworkExtensions;

public static class TextExtensionsTests
{
[Fact]
public static void NestedExceptionMessagesAreAppendedInOrder()
{
var exception = new InvalidOperationException("outer", new ArgumentException("inner"));

var result = new StringBuilder().AppendExceptionMessages(exception);

result.ToString().Should().Be($"outer{Environment.NewLine}{Environment.NewLine}inner{Environment.NewLine}");
exception.GetAllExceptionMessages().Should().Be(result.ToString());
}

[Fact]
public static void EmptyTextDoesNotEqualNonEmptyTextWhenIgnoringWhiteSpace() =>
string.Empty.EqualsOrdinalIgnoreWhiteSpace("content").Should().BeFalse();
}
Loading
Loading