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
44 changes: 29 additions & 15 deletions .github/workflows/build-test-publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup .NET Core
uses: actions/setup-dotnet@v1
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: 5.0.103
dotnet-version: 8.0.*

- name: Install dependencies
run: dotnet restore
Expand All @@ -30,16 +30,30 @@ jobs:
run: dotnet build --configuration Release --no-restore

- name: Test
run: dotnet test --no-restore --verbosity normal
run: dotnet test --no-restore --verbosity normal --framework net8

- name: Publish on version change
id: publish_nuget
uses: rohith/publish-nuget@v2
with:
PROJECT_FILE_PATH: ./src/Comparation/Comparation.csproj
VERSION_REGEX: ^\s*<Version>(.*)<\/Version>\s*$
TAG_COMMIT: true
TAG_FORMAT: v*
NUGET_KEY: ${{secrets.NUGETKEY}}
NUGET_SOURCE: https://api.nuget.org
INCLUDE_SYMBOLS: true
shell: bash
env:
NUGETKEY: ${{ secrets.NUGETKEY }}
run: |
function publish_project() {
ProjectName="$1"
Version=$(grep --only-matching --perl-regex '<Version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+)?</Version>' "$ProjectName/$ProjectName.csproj" | grep --only-matching --perl-regex '[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+)?')
echo "Version=$Version"
if test -z "$Version"; then
echo "Could not get version from $ProjectName.csproj"
exit 1
fi
git fetch --tags
if test -n "$(git tag --list "$ProjectName-$Version" | tr -d '[:space:]')"; then
echo "Version $Version already exists for $ProjectName"
else
rm -rf nuget
dotnet pack "$ProjectName" --configuration Release --no-build --include-symbols --nologo --output nuget &&\
dotnet nuget push nuget/*.nupkg --source 'https://api.nuget.org/v3/index.json' --api-key "$NUGETKEY" --skip-duplicate &&\
git tag "$ProjectName-$Version" && git push origin "$ProjectName-$Version" || { echo "Error occurred"; exit 1; }
fi
}

publish_project Comparation
10 changes: 5 additions & 5 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup .NET Core
uses: actions/setup-dotnet@v1
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: 5.0.103
dotnet-version: 8.0.*

- name: Install dependencies
run: dotnet restore
Expand All @@ -30,4 +30,4 @@ jobs:
run: dotnet build --configuration Release --no-restore

- name: Test
run: dotnet test --no-restore --verbosity normal
run: dotnet test --no-restore --verbosity normal --framework net8
4 changes: 2 additions & 2 deletions src/Comparation.Tests/Comparation.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net48;net5.0</TargetFrameworks>
<TargetFrameworks>net48;net8</TargetFrameworks>
<OutputPath>..\..\bin\Comparation.Tests</OutputPath>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<LangVersion>9</LangVersion>
Expand All @@ -13,7 +13,7 @@
<Nullable>annotations</Nullable>
</PropertyGroup>

<PropertyGroup Condition="$(TargetFramework) == 'net5.0'">
<PropertyGroup Condition="$(TargetFramework) == 'net8'">
<Nullable>enable</Nullable>
</PropertyGroup>

Expand Down
80 changes: 80 additions & 0 deletions src/Comparation.Tests/Core/EqualitySuite.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Comparation.Tests.Equality.Aspects;

namespace Comparation.Tests.Core
{
public sealed class EqualitySuite<T> : IEnumerable<Named<Test>>
{
private readonly string name;
private readonly IEqualityComparer<T> sut;
private readonly Named<T?>[][] equalGroups;

public EqualitySuite(string name, IEqualityComparer<T> sut, Named<T?>[][] equalGroups)
{
this.name = name;
this.sut = sut;
this.equalGroups = equalGroups;
}

private IEnumerable<Named<Test>> Yield()
{
var allItems = equalGroups.SelectMany(group => group).ToList();
var uniqueNames = allItems.Select(item => item.Name).Distinct().ToList();
if (uniqueNames.Count != allItems.Count)
{
throw new Exception("Duplicate items");
}

return Cases();

IEnumerable<Named<Test>> Cases()
{
for (var i = 0; i < equalGroups.Length; i++)
{
for (var j = 0; j < equalGroups.Length; j++)
{
for (var k = 0; k < equalGroups[i].Length; k++)
{
for (var m = 0; m < equalGroups[j].Length; m++)
{
var a = equalGroups[i][k];
var b = equalGroups[j][m];
var expectation = i == j;
var sign = expectation
? "=="
: "!=";
yield return (
$"{name}: {a.Name} {sign} {b.Name}",
new EqualityShouldWorkAsExpected<T>(
sut,
a.Value,
b.Value,
expectation: expectation
)
);

if (expectation)
{
yield return (
$"{name}: HashCode({a.Name}) {sign} HashCode({b.Name})",
new EqualityShouldGiveSameHashCodeForEqualObjects<T>(
sut,
a.Value,
b.Value
)
);
}
}
}
}
}
}
}

public IEnumerator<Named<Test>> GetEnumerator() => Yield().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ namespace Comparation.Tests.Equality.Aspects
public sealed class EqualityShouldGiveSameHashCodeForEqualObjects<T> : Test
{
private readonly IEqualityComparer<T> equality;
private readonly T a;
private readonly T b;
private readonly T? a;
private readonly T? b;

public EqualityShouldGiveSameHashCodeForEqualObjects(IEqualityComparer<T> equality, T a, T b)
public EqualityShouldGiveSameHashCodeForEqualObjects(IEqualityComparer<T> equality, T? a, T? b)
{
this.equality = equality;
this.a = a;
Expand Down
41 changes: 39 additions & 2 deletions src/Comparation.Tests/Equality/Cases.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Comparation.Tests.Core;
Expand All @@ -11,6 +12,41 @@ public sealed class Cases : IEnumerable<TestCaseData>
{
private const string Category = "Equality";

private static EqualitySuite<IReadOnlyCollection<string?>> StringSetSuite()
{
// ReSharper disable InconsistentNaming
Named<IReadOnlyCollection<string?>?> nullSet = (nameof(nullSet), null);
Named<IReadOnlyCollection<string?>?> emptySet = (nameof(emptySet), Array.Empty<string>());
Named<IReadOnlyCollection<string?>?> singleNull = (nameof(singleNull), new string?[] { null });
Named<IReadOnlyCollection<string?>?> singleA = (nameof(singleA), new[] { "a" });
Named<IReadOnlyCollection<string?>?> singleBigA = (nameof(singleBigA), new[] { "A" });
Named<IReadOnlyCollection<string?>?> twoAs = (nameof(twoAs), new[] { "a", "A" });
Named<IReadOnlyCollection<string?>?> ab = (nameof(ab), new[] { "a", "b" });
Named<IReadOnlyCollection<string?>?> ba = (nameof(ba), new[] { "b", "a" });
Named<IReadOnlyCollection<string?>?> abc = (nameof(abc), new[] { "a", "b", "c" });
Named<IReadOnlyCollection<string?>?> ab2c = (nameof(ab2c), new[] { "a", "b", "c", "b" });
Named<IReadOnlyCollection<string?>?> abnull = (nameof(abnull), new[] { "a", "b", null });
Named<IReadOnlyCollection<string?>?> nullba = (nameof(nullba), new[] { null, "b", "a" });
// ReSharper restore InconsistentNaming

return new EqualitySuite<IReadOnlyCollection<string?>>(
nameof(SetEquality<string>),
new SetEquality<string>(
Comparation.Equality.Of<string>().By(@string => @string.ToLowerInvariant())
),
new[]
{
new[] { nullSet },
new[] { emptySet },
new[] { singleNull },
new[] { singleA, singleBigA, twoAs },
new[] { ab, ba },
new[] { abc, ab2c },
new[] { abnull, nullba }
}
);
}

public IEnumerator<TestCaseData> GetEnumerator() => Sequence.Concat(
new WorkAsExpected(),
new Examples(),
Expand All @@ -19,7 +55,8 @@ public IEnumerator<TestCaseData> GetEnumerator() => Sequence.Concat(
new GiveSameHashCodeForEqualObjects(),
new TreatTreatsNullNotEqualToObject(),
new Transitive(),
new Equal()
new Equal(),
StringSetSuite()
)
.Select(@case => new NamePrefixing<Test>(Category, @case))
.Select(@case => new TestCaseData(@case.Value).SetName(@case.Name).SetCategory(Category))
Expand Down
9 changes: 9 additions & 0 deletions src/Comparation.Tests/TestsEntryPoint.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using Comparation.Tests.Core;
using NUnit.Framework;

[assembly: LevelOfParallelism(8)]

namespace Comparation.Tests
{
[Parallelizable(ParallelScope.All)]
public sealed class TestsEntryPoint
{
[Test]
Expand All @@ -12,5 +15,11 @@ public void Run(Test test)
{
test.Run();
}

[Test]
public void FakeTest()
{
Assert.Pass();
}
}
}
16 changes: 10 additions & 6 deletions src/Comparation/Box.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@ public Equality(IEqualityComparer<T> equality)
this.equality = equality;
}

public bool Equals(Box<T?> x, Box<T?> y) => (x.value, y.value) switch
{
({ } a, { } b) => equality.Equals(a, b),
(null, null) => true,
_ => false
};
#if NETSTANDARD2_0 || NETSTANDARD2_1
public bool Equals(Box<T?> x, Box<T?> y) => equality.Equals(x.value!, y.value!);
#else
public bool Equals(Box<T?> x, Box<T?> y) => equality.Equals(x.value, y.value);
#endif

public int GetHashCode(Box<T?> obj) => obj.value is { } value
? equality.GetHashCode(value)
: 0;
}
}

internal static class Box
{
public static Box<T> Wrap<T>(T item) => new(item);
}
}
32 changes: 26 additions & 6 deletions src/Comparation/CollectionEquality.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
#if NET6_0_OR_GREATER
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#endif

namespace Comparation
{
Expand Down Expand Up @@ -77,10 +81,14 @@ public int GetHashCode(IReadOnlyCollection<T?> collection)
foreach (var item in x)
{
var box = new Box<T?>(item);
#if NET6_0_OR_GREATER
CollectionsMarshal.GetValueRefOrAddDefault(counts, box, out _)++;
#else
var newCount = counts.TryGetValue(box, out var count)
? count + 1
: 1;
counts[box] = newCount;
#endif
}

return counts;
Expand All @@ -91,21 +99,33 @@ private static bool Equals(IEnumerable<T?> y, Dictionary<Box<T?>, int> counts)
foreach (var item in y)
{
var box = new Box<T?>(item);
#if NET6_0_OR_GREATER
ref var count = ref CollectionsMarshal.GetValueRefOrNullRef(counts, box);
if (Unsafe.IsNullRef(ref count))
{
return false;
}

count--;
if (count < 0)
{
return false;
}
#else
if (counts.TryGetValue(box, out var count))
{
if (count == 1)
if (count <= 0)
{
counts.Remove(box);
}
else
{
counts[box] = count - 1;
return false;
}

counts[box] = count - 1;
}
else
{
return false;
}
#endif
}

return true;
Expand Down
3 changes: 1 addition & 2 deletions src/Comparation/Comparation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
<PropertyGroup>
<OutputPath>..\..\bin\Comparation</OutputPath>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TargetFrameworks>netstandard2.0;netstandard2.1;net6</TargetFrameworks>
<LangVersion>9</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<TargetFrameworks>netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
Expand Down
Loading