Skip to content

refactor: reduce allocations for context#816

Merged
vbreuss merged 3 commits intomainfrom
topic/speedup-string-comparison
Oct 18, 2025
Merged

refactor: reduce allocations for context#816
vbreuss merged 3 commits intomainfrom
topic/speedup-string-comparison

Conversation

@vbreuss
Copy link
Copy Markdown
Member

@vbreuss vbreuss commented Oct 18, 2025

Refactor to speed up string comparison by avoiding allocations for contexts and default comparer and leveraging fast, built-in string APIs.

Key changes:

  • Remove list from ResultContexts and replace with custom implementation
  • Make comparer parameter nullable across string match types and interface
  • Add fast-paths using StringComparison-based APIs when no custom comparer is provided

@vbreuss vbreuss self-assigned this Oct 18, 2025
@vbreuss vbreuss added the refactor A change or improvement without functional impact label Oct 18, 2025
Copilot AI review requested due to automatic review settings October 18, 2025 15:27
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Refactor to speed up string comparison by avoiding default comparer allocations and leveraging fast, built-in string APIs. Also enables MSTest v4 test parallelization.

  • Make comparer parameter nullable across string match types and interface
  • Add fast-paths using StringComparison-based APIs when no custom comparer is provided
  • Enable assembly-level test parallelization for MSTest4

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Tests/Frameworks/aweXpect.Frameworks.MsTest4.Tests/MsTest4FrameworkTests.cs Adds assembly-level Parallelize attribute to run tests in parallel.
Source/aweXpect.Core/Options/StringEqualityOptions.cs Stops defaulting comparer and passes through nullable comparer; relies on match types to handle null.
Source/aweXpect.Core/Options/StringEqualityOptions.WildcardMatchType.cs Allows nullable comparer in signature.
Source/aweXpect.Core/Options/StringEqualityOptions.SuffixMatchType.cs Adds null-comparer handling with StartsWith/EndsWith fallback using StringComparison.
Source/aweXpect.Core/Options/StringEqualityOptions.RegexMatchType.cs Allows nullable comparer in signature.
Source/aweXpect.Core/Options/StringEqualityOptions.PrefixMatchType.cs Adds null-comparer handling with StartsWith fallback using StringComparison.
Source/aweXpect.Core/Options/StringEqualityOptions.ExactMatchType.cs Adds null-comparer handling with string.Equals fallback using StringComparison.
Source/aweXpect.Core/Options/StringEqualityOptions.ContainingMatchType.cs Adds null-comparer handling with string.Contains fallback; imports System for StringComparison.
Source/aweXpect.Core/Core/IStringMatchType.cs Changes interface contract to accept a nullable comparer.

Comment thread Source/aweXpect.Core/Core/IStringMatchType.cs
Comment thread Source/aweXpect.Core/Core/IStringMatchType.cs
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Oct 18, 2025

Test Results

    4 files   -     37      4 suites   - 37   24s ⏱️ - 3m 29s
1 197 tests  - 16 787  1 196 ✅  - 16 786  1 💤  - 1  0 ❌ ±0 
3 483 runs   - 47 109  3 482 ✅  - 47 108  1 💤  - 1  0 ❌ ±0 

Results for commit 19c41fd. ± Comparison against base commit bafccdb.

This pull request removes 16838 and adds 51 tests. Note that renamed tests count towards both.
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenAwaited_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenAwaited_WithoutReturnValue_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenNotAwaited_ShouldBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenNotAwaited_WithoutReturnValue_ShouldBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenNotAwaited_WithoutReturnValue_WithVerifyInMethod_ShouldStillBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedStatically_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedStatically_WithoutReturnValue_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedWithStaticUsing_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedWithStaticUsing_WithoutReturnValue_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerified_ShouldNotBeFlagged
…
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message0e1de4a0-2065-4a55-b8e4-b7070595a16e")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message65314c4a-a142-4e3c-aea3-6b50528e2278")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message6bb08037-901c-4b4f-a094-b281fd2e026c")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "message3135195d-fb30-427a-a020-8e9169c3a6db")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "message48e32c72-4e0e-4ae5-bd71-88c9899d491b")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "messagedacc5425-2833-4d79-818e-8f36e8e3f4fc")
aweXpect.Core.Tests.FailTests ‑ Test_ShouldThrowException(reason: "reason186ad58d-9136-4c26-8765-b1d130554c70")
aweXpect.Core.Tests.FailTests ‑ Test_ShouldThrowException(reason: "reason33619ec5-9ace-415d-904f-4fda1d5d9183")
aweXpect.Core.Tests.FailTests ‑ Test_ShouldThrowException(reason: "reason87bcd92a-f99c-4115-a524-e5d221bcb975")
aweXpect.Core.Tests.FailTests ‑ Unless_ShouldThrowException(condition: False, reason: "reason3642aab1-e546-4921-8df2-d5ed122a1d59")
…

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Oct 18, 2025

🚀 Benchmark Results

Details

BenchmarkDotNet v0.14.0, Ubuntu 24.04.3 LTS (Noble Numbat)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.415
[Host] : .NET 8.0.21 (8.0.2125.47513), X64 RyuJIT AVX2

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Method Mean Error StdDev Gen0 Gen1 Allocated
Bool_aweXpect 265.0 ns 2.08 ns 1.95 ns 0.0424 - 712 B
Bool_FluentAssertions 235.1 ns 1.25 ns 1.11 ns 0.0567 - 952 B
Bool_TUnit 200.3 ns 0.98 ns 0.87 ns 0.0396 - 664 B
Equivalency_aweXpect 294,951.5 ns 868.80 ns 770.17 ns 20.0195 0.9766 335556 B
Equivalency_FluentAssertions 2,179,796.7 ns 14,173.01 ns 11,835.10 ns 273.4375 46.8750 4584416 B
Equivalency_TUnit 294,305.2 ns 1,268.31 ns 1,186.38 ns 13.1836 0.4883 224760 B
Int_GreaterThan_aweXpect 239.1 ns 0.72 ns 0.56 ns 0.0486 - 816 B
Int_GreaterThan_FluentAssertions 241.7 ns 2.06 ns 1.82 ns 0.0730 - 1224 B
Int_GreaterThan_TUnit 218.7 ns 1.36 ns 1.27 ns 0.0486 - 816 B
ItemsCount_AtLeast_aweXpect 499.8 ns 2.77 ns 2.32 ns 0.0868 - 1464 B
ItemsCount_AtLeast_FluentAssertions 469.7 ns 2.48 ns 2.20 ns 0.1197 - 2008 B
ItemsCount_AtLeast_TUnit 14,421.5 ns 81.90 ns 76.61 ns 1.5106 - 25432 B
String_aweXpect 478.2 ns 4.46 ns 4.17 ns 0.0734 - 1232 B
String_FluentAssertions 537.4 ns 9.05 ns 8.03 ns 0.1287 - 2168 B
String_TUnit 254.5 ns 2.97 ns 2.78 ns 0.0496 - 832 B
StringArray_aweXpect 2,008.6 ns 5.98 ns 5.59 ns 0.1717 - 2888 B
StringArray_FluentAssertions 1,232.8 ns 8.32 ns 7.38 ns 0.2480 - 4152 B
StringArray_TUnit 702.4 ns 4.33 ns 4.05 ns 0.0820 - 1376 B
StringArrayInAnyOrder_aweXpect 2,589.4 ns 9.08 ns 8.49 ns 0.1831 - 3080 B
StringArrayInAnyOrder_FluentAssertions 126,330.4 ns 566.47 ns 442.27 ns 3.6621 - 61262 B
StringArrayInAnyOrder_TUnit 789.6 ns 3.72 ns 3.48 ns 0.0858 - 1440 B

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Oct 18, 2025

👽 Mutation Results

Mutation testing badge

aweXpect

Details
File Score Killed Survived Timeout No Coverage Ignored Compile Errors Total Detected Total Undetected Total Mutants

The final mutation score is NaN%

Coverage Thresholds: high:80 low:60 break:0

aweXpect.Core

Details
File Score Killed Survived Timeout No Coverage Ignored Compile Errors Total Detected Total Undetected Total Mutants
Options/StringEqualityOptions.ContainingMatchType.cs 29.17% 14 3 0 31 13 8 14 34 69
Options/StringEqualityOptions.cs 100.00% 62 0 2 0 13 22 64 0 99
Options/StringEqualityOptions.ExactMatchType.cs 97.44% 73 2 3 0 17 8 76 2 103
Options/StringEqualityOptions.PrefixMatchType.cs 91.94% 57 4 0 1 16 8 57 5 86
Options/StringEqualityOptions.RegexMatchType.cs 89.47% 17 1 0 1 5 8 17 2 32
Options/StringEqualityOptions.SuffixMatchType.cs 63.77% 44 4 0 21 17 8 44 25 94
Options/StringEqualityOptions.WildcardMatchType.cs 100.00% 24 0 0 0 5 8 24 0 37

The final mutation score is 81.32%

Coverage Thresholds: high:80 low:60 break:0

Copilot AI review requested due to automatic review settings October 18, 2025 18:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

Source/aweXpect.Core/Core/ResultContexts.cs:66

  • The new Add implementation inserts each new node after the head (index 1), changing semantics from append (List.Add) to a modified insertion order. This will reorder contexts compared to previous behavior and may impact message composition. To preserve original append semantics, append to the end of the list: traverse to the tail and set tail._next = context (or maintain a tail pointer for O(1) appends).
	public ResultContexts Add(ResultContext context)
	{
		if (_isOpen)
		{
			if (_first is null)
			{
				_first = context;
			}
			else
			{
				context._next = _first._next;
				_first._next = context;
			}
		}

		return this;
	}

Comment thread Source/aweXpect.Core/Core/Polyfills/StringExtensions.cs
Comment thread Pipeline/Build.cs
@sonarqubecloud
Copy link
Copy Markdown

@vbreuss vbreuss changed the title refactor: speedup string comparison refactor: reduce allocations for context Oct 18, 2025
@vbreuss vbreuss enabled auto-merge (squash) October 18, 2025 18:55
@vbreuss vbreuss merged commit 5efae61 into main Oct 18, 2025
14 checks passed
@vbreuss vbreuss deleted the topic/speedup-string-comparison branch October 18, 2025 18:56
@github-actions
Copy link
Copy Markdown
Contributor

This is addressed in release v2.27.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor A change or improvement without functional impact state: released The issue is released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants