Skip to content

Commit

Permalink
Fix nullref, add tests, bump package version to 1.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
kasthack committed Dec 18, 2021
1 parent 0000000 commit 0000000
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PublishDocumentationFile>true</PublishDocumentationFile>
<PublishDocumentationFiles>true</PublishDocumentationFiles>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>1.0.2</PackageVersion>
<PackageVersion>1.0.3</PackageVersion>
<PackageDescription>.NotEmpty&lt;T&gt;() test extension</PackageDescription>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>kasthack nunit xunit mstest test empty null notempty emptinness nullability</PackageTags>
Expand Down
36 changes: 27 additions & 9 deletions src/kasthack.NotEmpty.Core/NotEmptyExtensionsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@

public abstract class NotEmptyExtensionsBase
{
public void NotEmpty<T>(T? value) => this.NotEmptyInternal(value);
public void NotEmpty<T>(T? value)
{
// workaround for boxed structs
if (value is not null && typeof(T) == typeof(object) && value.GetType() != typeof(object))
{
this.NotEmptyBoxed(value, null!);
}

this.NotEmptyInternal(value);
}

protected abstract void Assert(bool value, string message);

private void NotEmptyInternal<T>(T? value, string? path = null)
{
string message = $"value{path} is empty";
string message = GetEmptyMessage(path);
this.Assert(!EqualityComparer<T>.Default.Equals(default!, value!), message);
switch (value)
{
Expand All @@ -24,33 +33,41 @@ private void NotEmptyInternal<T>(T? value, string? path = null)
var index = 0;
foreach (var item in e)
{
this.NotEmptyBox(item, $"{path}[{index++}]");
this.NotEmptyBoxed(item, $"{path}[{index++}]");
}

this.Assert(index != 0, message);
break;
default:
foreach (var pathValue in CachedPropertyExtractor<T>.GetProperties(value))
{
this.NotEmptyBox(pathValue.Value, $"{path}.{pathValue.Path}");
this.NotEmptyBoxed(pathValue.Value, $"{path}.{pathValue.Path}");
}

break;
}
}

private void NotEmptyBox(object? value, string path)
private static string GetEmptyMessage(string? path)
{
this.Assert(value is not null, $"value{path} is empty");
CachedEmptyDelegate.GetDelegate(value!.GetType())(value, path);
return $"value{path} is empty";
}

private void NotEmptyBoxed(object? value, string? path)
{
this.Assert(value is not null, GetEmptyMessage(path));
CachedEmptyDelegate.GetDelegate(this, value!.GetType())(value, path);
}

private static class CachedEmptyDelegate
{
private static readonly MethodInfo NotEmptyMethod = typeof(NotEmptyExtensionsBase).GetMethod(nameof(NotEmptyExtensionsBase.NotEmptyInternal), BindingFlags.NonPublic | BindingFlags.Static)!.GetGenericMethodDefinition();
private static readonly MethodInfo NotEmptyMethod = typeof(NotEmptyExtensionsBase)
.GetMethod(nameof(NotEmptyExtensionsBase.NotEmptyInternal), BindingFlags.NonPublic | BindingFlags.Instance)!
.GetGenericMethodDefinition();

private static readonly Dictionary<Type, Action<object?, string>> Delegates = new();

public static Action<object?, string> GetDelegate(Type type)
public static Action<object?, string> GetDelegate(NotEmptyExtensionsBase @this, Type type)
{
if (!Delegates.TryGetValue(type, out var result))
{
Expand All @@ -63,6 +80,7 @@ private static class CachedEmptyDelegate
result = (Action<object?, string>)Expression
.Lambda(
Expression.Call(
Expression.Constant(@this),
NotEmptyMethod.MakeGenericMethod(type),
Expression.Convert(
valueParam,
Expand Down
34 changes: 34 additions & 0 deletions src/kasthack.NotEmpty.Tests/GenericTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace kasthack.NotEmpty.Tests
{
public class XunitNotEmptyTest : NotEmptyTestBase
{
public XunitNotEmptyTest()
: base(x => kasthack.NotEmpty.Xunit.NotEmptyExtensions.NotEmpty(x))
{
}
}

public class NunitNotEmptyTest : NotEmptyTestBase
{
public NunitNotEmptyTest()
: base(x => kasthack.NotEmpty.Nunit.NotEmptyExtensions.NotEmpty(x))
{
}
}

public class MsTestNotEmptyTest : NotEmptyTestBase
{
public MsTestNotEmptyTest()
: base(x => kasthack.NotEmpty.MsTest.NotEmptyExtensions.NotEmpty(x))
{
}
}

public class RawNotEmptyTest : NotEmptyTestBase
{
public RawNotEmptyTest()
: base(x => kasthack.NotEmpty.Raw.NotEmptyExtensions.NotEmpty(x))
{
}
}
}
53 changes: 53 additions & 0 deletions src/kasthack.NotEmpty.Tests/NotEmptyTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace kasthack.NotEmpty.Tests
{
using System;
using System.Collections.Generic;

using global::Xunit;

public abstract class NotEmptyTestBase
{
private readonly Action<object?> action;

public NotEmptyTestBase(Action<object?> action) => this.action = action;

[Fact]
public void NullThrows() => Assert.ThrowsAny<Exception>(() => this.action(null));

[Fact]
public void ObjectWorks() => this.action(new object());

[Fact]
public void ObjectWithPropsWorks() => this.action(new { Value = 1 });

[Fact]
public void ObjectWithDefaultThrows() => Assert.ThrowsAny<Exception>(() => this.action(new { Value = 0 }));

[Fact]
public void NestedObjectWithDefaultThrows() => Assert.ThrowsAny<Exception>(() => this.action(new { Property = new { Value = 0, } }));

[Fact]
public void PrimitiveWorks() => this.action(1);

[Fact]
public void ZeroThrows() => Assert.ThrowsAny<Exception>(() => this.action(0));

[Fact]
public void EmptyArrayThrows() => Assert.ThrowsAny<Exception>(() => this.action(new object[] { }));

[Fact]
public void EmptyListThrows() => Assert.ThrowsAny<Exception>(() => this.action(new List<object>()));

[Fact]
public void ListWorks() => this.action(new List<object> { new object() });

[Fact]
public void ArrayWorks() => this.action(new object[] { new object() });

[Fact]
public void ArrayWithDefaultThrows() => Assert.ThrowsAny<Exception>(() => this.action(new object[] { 1, 0 }));

[Fact]
public void ArrayWithNullThrows() => Assert.ThrowsAny<Exception>(() => this.action(new object?[] { null, new object() }));
}
}
30 changes: 30 additions & 0 deletions src/kasthack.NotEmpty.Tests/kasthack.NotEmpty.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\kasthack.NotEmpty.MsTest\kasthack.NotEmpty.MsTest.csproj" />
<ProjectReference Include="..\kasthack.NotEmpty.Nunit\kasthack.NotEmpty.Nunit.csproj" />
<ProjectReference Include="..\kasthack.NotEmpty.Raw\kasthack.NotEmpty.Raw.csproj" />
<ProjectReference Include="..\kasthack.NotEmpty.Xunit\kasthack.NotEmpty.Xunit.csproj" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions src/kasthack.NotEmpty.sln
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{FA63E98E-9B05-
..\.github\workflows\push.yml = ..\.github\workflows\push.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kasthack.NotEmpty.Tests", "kasthack.NotEmpty.Tests\kasthack.NotEmpty.Tests.csproj", "{49386A26-B16E-45B3-93D7-5846A721C2EC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -59,6 +61,10 @@ Global
{78A06400-338F-4494-9BC9-A414080B7824}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78A06400-338F-4494-9BC9-A414080B7824}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78A06400-338F-4494-9BC9-A414080B7824}.Release|Any CPU.Build.0 = Release|Any CPU
{49386A26-B16E-45B3-93D7-5846A721C2EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49386A26-B16E-45B3-93D7-5846A721C2EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49386A26-B16E-45B3-93D7-5846A721C2EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49386A26-B16E-45B3-93D7-5846A721C2EC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down

0 comments on commit 0000000

Please sign in to comment.