Skip to content

Conversation

@TimothyMakkison
Copy link
Contributor

@TimothyMakkison TimothyMakkison commented Jan 16, 2026

By default StringBuilder starts with a capacity of 16. This is a problem in ValueAssertion as it initialises Context with a StringBuilder before appending $"Assert.That({expression ?? "?"})", at a minimum this is 14 characters which is fine, but in the common case where the expression is not null, it will exceed the default of 16 and immediately have to add another StringBuilder.
This pretty much guaranteed that any instance of ValueAssertion would allocate 2 StringBuilder and 2 char[] on initialisation.

I solved this by initialising the StringBuilder with 13 + expression.Length.

The question is: how much additional capacity should be given? Too small and you frequently pay the cost to create a new StringBuilder and char[], too large and you over allocate a char[]. Making this decision based too much on the benchmarks would be optimising for the benchmarks and not real life. WDYT

Before

image

(expression?.Length ?? 1) + 13 no unused space, the follwing assertion will have to create a new StringBuilder

image

(expression?.Length ?? 1) + 24 aka unused length of 11

image

### (expression?.Length ?? 1) + 32 aka unused length of 19

image

Note that the benchmarks give a general idea

@thomhurst
Copy link
Owner

Summary

Pre-sizes StringBuilder in ValueAssertion to avoid reallocation on the hot path (test execution).

Critical Issues

Off-by-one error in capacity calculation (TUnit.Assertions/Sources/ValueAssertion.cs:19)

The current calculation uses + 13, but when expression is null, the appended string is:

"Assert.That(?)"  // 14 characters, not 13

Breakdown:

  • "Assert.That(" = 12 chars
  • "?" = 1 char
  • ")" = 1 char
  • Total = 14 chars

Fix: Change line 19 to:

var expressionBuilder = new StringBuilder((expression?.Length ?? 1) + 13);

This ensures when expression is null, we allocate 1 + 13 = 14 characters (correct size).

When expression is provided (common case), we allocate expression.Length + 13 which is also correct:

  • "Assert.That(" = 12 chars
  • expression = expression.Length chars
  • ")" = 1 char
  • Total = expression.Length + 13 ✅

Suggestions

None - the performance optimization approach is sound and aligns with TUnit's "Performance First" rule for hot paths.

Verdict

⚠️ REQUEST CHANGES - Off-by-one error in capacity calculation

@TimothyMakkison
Copy link
Contributor Author

TimothyMakkison commented Jan 16, 2026

MB I've just updated the benchmarks screenshots because I was screenshotting the wrong thing

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants