From 5c9ddd63c817441b5ee613de82cc9174b048f7d4 Mon Sep 17 00:00:00 2001 From: forgnar Date: Sat, 14 Nov 2026 12:52:18 +0100 Subject: [PATCH] test: cover every public member in the allocation properties V9 audit (M7): every Combine and CombineAsync arity, FromNullable(factory) for references, reading and comparing the case types and ValidationError now have allocation lines; the class remarks list every exclusion. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012weRUz3QcTAaQw32ZmQkoK --- .../AllocationProperties.cs | 84 +++++++++++++++++-- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/tests/DotValid.PropertyTests/AllocationProperties.cs b/tests/DotValid.PropertyTests/AllocationProperties.cs index cdd76dd..d0a39aa 100644 --- a/tests/DotValid.PropertyTests/AllocationProperties.cs +++ b/tests/DotValid.PropertyTests/AllocationProperties.cs @@ -10,12 +10,13 @@ namespace DotValid.PropertyTests; /// /// /// Skipped in Debug builds (see ); CI runs them in Release. -/// Not covered on purpose: creating an invalid case, mapping its errors or accumulating errors (each builds an array of -/// errors), Equals(object) (boxes -/// its argument), and validated is null / is not null on an initialized validation (the compiler reads the -/// union's Value, which boxes the case). Reading an existing invalid case allocates nothing. Sequences -/// (Sequence, Traverse, Values, Errors, Partition) allocate like LINQ (enumerators -/// and the lists they return; see CollectionBenchmarks). +/// Every public member has a line here or is listed now (V9 audit, M7). Not covered on purpose: ToString (it +/// builds a string); creating an invalid case, mapping its errors or accumulating errors, including +/// Ensure/Check when the predicate fails and FromNullable for (each builds +/// an array of errors); Equals(object) (boxes its argument); validated is null / is not null on an +/// initialized validation (the compiler reads the union's Value, which boxes the case); and sequences +/// (Sequence, Traverse, Values, Errors, Partition), which allocate like LINQ (see +/// CollectionBenchmarks). Reading and comparing an existing invalid case allocates nothing. /// public sealed class AllocationProperties { @@ -92,6 +93,19 @@ public void Combining_valid_validations_allocates_nothing(int value, int other) Allocations.AssertNone("Apply", () => _ = function.Apply(second)); Allocations.AssertNone("Combine(2)", () => _ = Validated.Combine(validated, second, static (a, b) => a + b)); Allocations.AssertNone("Combine(3)", () => _ = Validated.Combine(validated, second, validated, static (a, b, c) => a + b + c)); + Allocations.AssertNone("Combine(4)", () => _ = Validated.Combine(validated, second, validated, second, static (a, b, c, d) => a + b + c + d)); + Allocations.AssertNone( + "Combine(5)", + () => _ = Validated.Combine(validated, second, validated, second, validated, static (a, b, c, d, e) => a + b + c + d + e)); + Allocations.AssertNone( + "Combine(6)", + () => _ = Validated.Combine( + validated, second, validated, second, validated, second, static (a, b, c, d, e, f) => a + b + c + d + e + f)); + Allocations.AssertNone( + "Combine(7)", + () => _ = Validated.Combine( + validated, second, validated, second, validated, second, validated, + static (a, b, c, d, e, f, g) => a + b + c + d + e + f + g)); Allocations.AssertNone( "Combine(8)", () => _ = Validated.Combine( @@ -109,6 +123,7 @@ public void Building_blocks_on_the_valid_path_allocate_nothing(int value) Allocations.AssertNone("FromNullable (present)", () => _ = Validated.FromNullable(present, -1)); Allocations.AssertNone("FromNullable(factory) (present)", () => _ = Validated.FromNullable(present, static () => -1)); Allocations.AssertNone("FromNullable (reference)", () => _ = Validated.FromNullable(Text, -1)); + Allocations.AssertNone("FromNullable(factory) (reference)", () => _ = Validated.FromNullable(Text, static () => -1)); Allocations.AssertNone("ValidationError", () => _ = new ValidationError("Name", "is empty")); } @@ -126,6 +141,28 @@ public void Async_members_with_completed_tasks_allocate_nothing(Validated _ = Validated.CombineAsync(CompletedValidOne, CompletedValidOne, static (a, b) => a + b).IsCompleted); + Allocations.AssertNone( + "CombineAsync(3)", + () => _ = Validated.CombineAsync(CompletedValidOne, CompletedValidOne, CompletedValidOne, static (a, b, c) => a + b + c).IsCompleted); + Allocations.AssertNone( + "CombineAsync(4)", + () => _ = Validated.CombineAsync( + CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, static (a, b, c, d) => a + b + c + d).IsCompleted); + Allocations.AssertNone( + "CombineAsync(5)", + () => _ = Validated.CombineAsync( + CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, + static (a, b, c, d, e) => a + b + c + d + e).IsCompleted); + Allocations.AssertNone( + "CombineAsync(6)", + () => _ = Validated.CombineAsync( + CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, + static (a, b, c, d, e, f) => a + b + c + d + e + f).IsCompleted); + Allocations.AssertNone( + "CombineAsync(7)", + () => _ = Validated.CombineAsync( + CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, CompletedValidOne, + CompletedValidOne, static (a, b, c, d, e, f, g) => a + b + c + d + e + f + g).IsCompleted); Allocations.AssertNone( "CombineAsync(8)", () => _ = Validated.CombineAsync( @@ -142,6 +179,41 @@ public void Comparing_allocates_nothing(Validated validated, Validated Allocations.AssertNone("!=", () => _ = validated != other); Allocations.AssertNone("GetHashCode", () => _ = validated.GetHashCode()); Allocations.AssertNone("Valid ==", () => _ = new Valid(1) == new Valid(2)); + Allocations.AssertNone("Valid !=", () => _ = new Valid(1) != new Valid(2)); + Allocations.AssertNone("Valid Equals", () => _ = new Valid(1).Equals(new Valid(2))); + Allocations.AssertNone("Valid GetHashCode", () => _ = new Valid(1).GetHashCode()); + Allocations.AssertNone("Valid Value and Deconstruct", () => + { + var success = new Valid(1); + success.Deconstruct(out var value); + _ = success.Value + value; + }); + } + + [Property(Skip = Allocations.SkipInDebug)] + public void Reading_and_comparing_existing_invalid_cases_allocates_nothing(int first, int second) + { + var failure = new Invalid(first, second); + var other = new Invalid(second); + var error = new ValidationError("Name", "is empty"); + var otherError = new ValidationError("Age", "is negative"); + + Allocations.AssertNone("Invalid ==", () => _ = failure == other); + Allocations.AssertNone("Invalid !=", () => _ = failure != other); + Allocations.AssertNone("Invalid Equals", () => _ = failure.Equals(other)); + Allocations.AssertNone("Invalid GetHashCode", () => _ = failure.GetHashCode()); + Allocations.AssertNone("Invalid Errors and Deconstruct", () => + { + failure.Deconstruct(out var errors); + _ = failure.Errors.Length + errors[0]; + }); + Allocations.AssertNone("ValidationError ==", () => _ = error == otherError); + Allocations.AssertNone("ValidationError GetHashCode", () => _ = error.GetHashCode()); + Allocations.AssertNone("ValidationError Deconstruct", () => + { + var (property, message) = error; + _ = property.Length + message.Length; + }); } private const string? Text = "text";