Skip to content

Commit fdc29d8

Browse files
authored
Merge pull request #155 from feO2x/155-increase-code-coverage
Increase Code Coverage
2 parents 46bbe62 + 5ca4d58 commit fdc29d8

11 files changed

Lines changed: 525 additions & 37 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Plans typically have acceptance criteria with check boxes. Check each box when y
1010

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

13+
## Test Rules
14+
15+
Read ./tests/AGENTS.md for details on how to write tests.
16+
1317
## Here is Your Space
1418

1519
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.

Light.GuardClauses.slnx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
<File Path="README.md" />
1616
</Folder>
1717
<Folder Name="/src/">
18+
<File Path="src/Directory.Build.props" />
1819
<Project Path="src/Light.GuardClauses/Light.GuardClauses.csproj" />
1920
</Folder>
2021
<Folder Name="/tests/">
22+
<File Path="tests/AGENTS.md" />
23+
<File Path="tests/Directory.Build.props" />
2124
<Project Path="tests/Light.GuardClauses.InternalRoslynAnalyzers.Tests/Light.GuardClauses.InternalRoslynAnalyzers.Tests.csproj" />
2225
<Project Path="tests/Light.GuardClauses.SourceCodeTransformation.Tests/Light.GuardClauses.SourceCodeTransformation.Tests.csproj" />
2326
<Project Path="tests/Light.GuardClauses.Tests/Light.GuardClauses.Tests.csproj" />

tests/AGENTS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# AGENTS.md for Testing
2+
3+
## How to structure tests
4+
5+
- Do not use mocking frameworks like Moq or NSubstitute for test doubles, use hand-written test doubles instead.
6+
- Use FluentAssertions instead of xunit's `Assert` class. The library is pinned to 7.x.x to avoid licensing issues.
7+
- Keep Test Coverage at least at 93%. Microsoft.Testing.Extensions.CodeCoverage is available to get test coverage metrics.
8+
- Prefer Sociable Tests as proposed by Martin Fowler. Only use Solitary Tests as a last resort.
9+
10+
# How to run tests
11+
12+
- `dotnet test` for usual test runs.
13+
- `dotnet test -- --coverage --coverage-output-format cobertura` for test coverage metrics.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using FluentAssertions;
3+
using Xunit;
4+
5+
namespace Light.GuardClauses.Tests.ComparableAssertions;
6+
7+
public static class NumericCustomFactorySuccessTests
8+
{
9+
[Fact]
10+
public static void MustBePositiveReturnsValidValuesWithoutInvokingFactories()
11+
{
12+
1.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(1);
13+
2L.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(2L);
14+
3m.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(3m);
15+
4f.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(4f);
16+
5d.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(5d);
17+
TimeSpan.FromTicks(6).MustBePositive(_ => throw new InvalidOperationException()).Should()
18+
.Be(TimeSpan.FromTicks(6));
19+
((short) 7).MustBePositive(_ => throw new InvalidOperationException()).Should().Be(7);
20+
}
21+
22+
[Fact]
23+
public static void MustBeNegativeReturnsValidValuesWithoutInvokingFactories()
24+
{
25+
(-1).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-1);
26+
(-2L).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-2L);
27+
(-3m).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-3m);
28+
(-4f).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-4f);
29+
(-5d).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-5d);
30+
TimeSpan.FromTicks(-6).MustBeNegative(_ => throw new InvalidOperationException()).Should()
31+
.Be(TimeSpan.FromTicks(-6));
32+
((short) -7).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-7);
33+
}
34+
35+
[Fact]
36+
public static void MustNotBePositiveReturnsValidValuesWithoutInvokingFactories()
37+
{
38+
0.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0);
39+
0L.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0L);
40+
0m.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0m);
41+
0f.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0f);
42+
0d.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0d);
43+
TimeSpan.Zero.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(TimeSpan.Zero);
44+
((short) 0).MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0);
45+
}
46+
47+
[Fact]
48+
public static void MustNotBeNegativeReturnsValidValuesWithoutInvokingFactories()
49+
{
50+
0.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0);
51+
0L.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0L);
52+
0m.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0m);
53+
0f.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0f);
54+
0d.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0d);
55+
TimeSpan.Zero.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(TimeSpan.Zero);
56+
((short) 0).MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0);
57+
}
58+
59+
[Fact]
60+
public static void MustNotBeZeroReturnsValidValuesWithoutInvokingFactories()
61+
{
62+
1.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(1);
63+
2L.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(2L);
64+
3m.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(3m);
65+
4f.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(4f);
66+
5d.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(5d);
67+
TimeSpan.FromTicks(6).MustNotBeZero(_ => throw new InvalidOperationException()).Should()
68+
.Be(TimeSpan.FromTicks(6));
69+
((short) 7).MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(7);
70+
}
71+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using FluentAssertions;
4+
using Light.GuardClauses.FrameworkExtensions;
5+
using Xunit;
6+
7+
namespace Light.GuardClauses.Tests.FrameworkExtensions;
8+
9+
public static class EnumerableCountTests
10+
{
11+
[Fact]
12+
public static void CountWithExceptionDetailsUsesStringLengthWithoutEnumerating()
13+
{
14+
IEnumerable value = "text";
15+
16+
value.Count("value", "message").Should().Be(4);
17+
}
18+
19+
[Fact]
20+
public static void GenericCountWithExceptionDetailsUsesEveryAvailableStrategy()
21+
{
22+
IEnumerable<char> text = "text";
23+
var collection = new HashSet<int> { 1, 2, 3 };
24+
var readOnlyCollection = new ReadOnlyCollectionOnly<int>([1, 2, 3, 4]);
25+
26+
text.GetCount("text", "message").Should().Be(4);
27+
collection.GetCount("collection", "message").Should().Be(3);
28+
readOnlyCollection.GetCount("readOnlyCollection", "message").Should().Be(4);
29+
Yield(1, 2, 3, 4, 5).GetCount("items", "message").Should().Be(5);
30+
}
31+
32+
[Fact]
33+
public static void IsOneOfEnumeratesSourcesThatAreNotCollections()
34+
{
35+
2.IsOneOf(Yield(1, 2, 3)).Should().BeTrue();
36+
4.IsOneOf(Yield(1, 2, 3)).Should().BeFalse();
37+
}
38+
39+
private static IEnumerable<T> Yield<T>(params T[] items)
40+
{
41+
foreach (var item in items)
42+
{
43+
yield return item;
44+
}
45+
}
46+
47+
private sealed class ReadOnlyCollectionOnly<T>(IReadOnlyCollection<T> items) : IReadOnlyCollection<T>
48+
{
49+
public int Count => items.Count;
50+
51+
public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
52+
53+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
54+
}
55+
}

tests/Light.GuardClauses.Tests/FrameworkExtensions/MultiplyAddHashTests.cs

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static void ThreeParameters(string value1, int value2, char value3)
4040
[Theory]
4141
[InlineData("Foo", 42, 'a', 87.73665)]
4242
[InlineData("Bar", -177422, 'Y', -15.25)]
43-
[InlineData(null, 0, default(char), 0.0)]
43+
[InlineData(null, 0, '\0', 0.0)]
4444
public static void FourParameters(string value1, int value2, char value3, double value4)
4545
{
4646
var hashCode1 = MultiplyAddHash.CreateHashCode(value1, value2, value3, value4);
@@ -53,4 +53,164 @@ public static void FourParameters(string value1, int value2, char value3, double
5353

5454
hashCode1.Should().Be(hashCode2);
5555
}
56-
}
56+
57+
[Fact]
58+
public static void EverySupportedArityMatchesTheBuilder()
59+
{
60+
var values = new object[]
61+
{ 1, "two", 3m, '4', 5L, 6f, 7d, (short) 8, (byte) 9, 10u, 11ul, null, 13, 14, 15, 16 };
62+
63+
MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4])
64+
.Should().Be(CreateExpectedHash(values, 5));
65+
MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4], values[5])
66+
.Should().Be(CreateExpectedHash(values, 6));
67+
MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4], values[5], values[6])
68+
.Should().Be(CreateExpectedHash(values, 7));
69+
MultiplyAddHash.CreateHashCode(
70+
values[0],
71+
values[1],
72+
values[2],
73+
values[3],
74+
values[4],
75+
values[5],
76+
values[6],
77+
values[7]
78+
)
79+
.Should().Be(CreateExpectedHash(values, 8));
80+
MultiplyAddHash.CreateHashCode(
81+
values[0],
82+
values[1],
83+
values[2],
84+
values[3],
85+
values[4],
86+
values[5],
87+
values[6],
88+
values[7],
89+
values[8]
90+
)
91+
.Should().Be(CreateExpectedHash(values, 9));
92+
MultiplyAddHash.CreateHashCode(
93+
values[0],
94+
values[1],
95+
values[2],
96+
values[3],
97+
values[4],
98+
values[5],
99+
values[6],
100+
values[7],
101+
values[8],
102+
values[9]
103+
)
104+
.Should().Be(CreateExpectedHash(values, 10));
105+
MultiplyAddHash.CreateHashCode(
106+
values[0],
107+
values[1],
108+
values[2],
109+
values[3],
110+
values[4],
111+
values[5],
112+
values[6],
113+
values[7],
114+
values[8],
115+
values[9],
116+
values[10]
117+
)
118+
.Should().Be(CreateExpectedHash(values, 11));
119+
MultiplyAddHash.CreateHashCode(
120+
values[0],
121+
values[1],
122+
values[2],
123+
values[3],
124+
values[4],
125+
values[5],
126+
values[6],
127+
values[7],
128+
values[8],
129+
values[9],
130+
values[10],
131+
values[11]
132+
)
133+
.Should().Be(CreateExpectedHash(values, 12));
134+
MultiplyAddHash.CreateHashCode(
135+
values[0],
136+
values[1],
137+
values[2],
138+
values[3],
139+
values[4],
140+
values[5],
141+
values[6],
142+
values[7],
143+
values[8],
144+
values[9],
145+
values[10],
146+
values[11],
147+
values[12]
148+
)
149+
.Should().Be(CreateExpectedHash(values, 13));
150+
MultiplyAddHash.CreateHashCode(
151+
values[0],
152+
values[1],
153+
values[2],
154+
values[3],
155+
values[4],
156+
values[5],
157+
values[6],
158+
values[7],
159+
values[8],
160+
values[9],
161+
values[10],
162+
values[11],
163+
values[12],
164+
values[13]
165+
)
166+
.Should().Be(CreateExpectedHash(values, 14));
167+
MultiplyAddHash.CreateHashCode(
168+
values[0],
169+
values[1],
170+
values[2],
171+
values[3],
172+
values[4],
173+
values[5],
174+
values[6],
175+
values[7],
176+
values[8],
177+
values[9],
178+
values[10],
179+
values[11],
180+
values[12],
181+
values[13],
182+
values[14]
183+
)
184+
.Should().Be(CreateExpectedHash(values, 15));
185+
MultiplyAddHash.CreateHashCode(
186+
values[0],
187+
values[1],
188+
values[2],
189+
values[3],
190+
values[4],
191+
values[5],
192+
values[6],
193+
values[7],
194+
values[8],
195+
values[9],
196+
values[10],
197+
values[11],
198+
values[12],
199+
values[13],
200+
values[14],
201+
values[15]
202+
)
203+
.Should().Be(CreateExpectedHash(values, 16));
204+
}
205+
206+
private static int CreateExpectedHash(object[] values, int count)
207+
{
208+
var builder = MultiplyAddHashBuilder.Create();
209+
for (var i = 0; i < count; i++)
210+
{
211+
builder = builder.CombineIntoHash(values[i]);
212+
}
213+
214+
return builder.BuildHash();
215+
}
216+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Text;
3+
using FluentAssertions;
4+
using Light.GuardClauses.FrameworkExtensions;
5+
using Xunit;
6+
7+
namespace Light.GuardClauses.Tests.FrameworkExtensions;
8+
9+
public static class TextExtensionsTests
10+
{
11+
[Fact]
12+
public static void NestedExceptionMessagesAreAppendedInOrder()
13+
{
14+
var exception = new InvalidOperationException("outer", new ArgumentException("inner"));
15+
16+
var result = new StringBuilder().AppendExceptionMessages(exception);
17+
18+
result.ToString().Should().Be($"outer{Environment.NewLine}{Environment.NewLine}inner{Environment.NewLine}");
19+
exception.GetAllExceptionMessages().Should().Be(result.ToString());
20+
}
21+
22+
[Fact]
23+
public static void EmptyTextDoesNotEqualNonEmptyTextWhenIgnoringWhiteSpace() =>
24+
string.Empty.EqualsOrdinalIgnoreWhiteSpace("content").Should().BeFalse();
25+
}

0 commit comments

Comments
 (0)