fscheck / FsCheck

Random Testing for .NET
https://fscheck.github.io/FsCheck/
BSD 3-Clause "New" or "Revised" License
1.17k stars 156 forks source link

`StringNoNulls` is passing a null? #654

Closed baynezy closed 8 months ago

baynezy commented 8 months ago

Here is an example reproduction case:

public class FsCheckIssue
{
    [Property]
    public Property WhenTestingGetHashCode_ThenReturnHashCode(StringNoNulls name)
    {
        var property = () =>
        {
            // arrange
            var valueObject = new TestValueObject { Name = name.Get };

            // act
            var result = valueObject.GetHashCode();

            // assert
            return result == valueObject.Name.GetHashCode();
        };

        return property.ToProperty();
    }
}

internal class TestValueObject : ValueObject
{
    public string Name { get; init; } = string.Empty;
    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return Name;
    }
}

internal abstract class ValueObject : IEquatable<ValueObject>
{
    protected abstract IEnumerable<object?> GetEqualityComponents();

    /// <summary>
    /// Determines is the object specified is equal to the current object
    /// </summary>
    /// <param name="other">The object to compare to the current object</param>
    /// <returns>true, if the object specified is equal to the object provided. false, otherwise.</returns>
    public bool Equals(ValueObject? other)
    {
        return Equals((object?) other);
    }

    /// <inheritdoc cref="object.Equals(object?)"/>
    /// <param name="obj">The object to compare to the current object</param>
    public override bool Equals(object? obj)
    {
        if(IsNullOrOfDifferentType(obj)) return false;

        var valueObject = (ValueObject) obj!;
        return GetEqualityComponents()
            .SequenceEqual(valueObject.GetEqualityComponents());
    }

    private bool IsNullOrOfDifferentType(object? obj)
    {
        return obj is null || obj.GetType() != GetType();
    }

    /// <inheritdoc cref="object.GetHashCode"/>
    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Select(x => x?.GetHashCode() ?? 0)
            .Aggregate((x, y) => x ^ y);
    }
}

When I run this I still seem to get:

FsCheck.Xunit.PropertyFailedException

Falsifiable, after 12 tests (0 shrinks) (StdGen (8057856,297288459)):
Original:
StringNoNulls null
<null>

  Exception doesn't have a stacktrace

System.NullReferenceException
Object reference not set to an instance of an object.

I am very confused as the StringNoNulls type is not supposed to return a null value.

What am I doing wrong?

bartelink commented 8 months ago

Not familiar enough with the C# consumption model, but f you're using V3, strings are never null ? (The docs are per V3 -rc1 already anyway, and most people use that)

baynezy commented 8 months ago

@bartelink - I am using version 2.16.6 of FsCheck.Xunit

bartelink commented 8 months ago

Yes - as mentioned, its not impossible to make it work, but I don't know what's going wrong But I'm telling you that one way to resolve it is to sidestep it by going to V3, which would never pass you null for a String input. Obviously moving to V3 may entail navigating some breaking changes though

kurtschelfthout commented 8 months ago

StringNoNulls just removes \000 characters: https://github.com/fscheck/FsCheck/blob/f1bd17add9fc8ac82e58b59ef3b09e522973e83e/src/FsCheck/Internals.DefaultArbs.fs#L620

Try NonEmptyString instead, or the generic NonNull<string>.

baynezy commented 8 months ago

@kurtschelfthout - thanks so much.

NonNull<string> was just the ticket.

bartelink commented 8 months ago

StringNoNulls just removes \000 characters:

Then it should be called StringNoNuls 😆