Skip to content

Commit

Permalink
Unit test ISpanFormattable exceeding initial buffer length
Browse files Browse the repository at this point in the history
* Update class xmldoc
* Add missing pragma NET6_0_OR_GREATER
* Add unit test for ISpanFormattable exceeding buffer on first attempt
  • Loading branch information
axunonb committed Jul 7, 2024
1 parent a6940e3 commit fb41f9f
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 11 deletions.
20 changes: 14 additions & 6 deletions src/SmartFormat.Tests/Extensions/DefaultFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,32 @@ public void Call_With_IFormattable_Argument()
public void ISpanFormattable_Exceeding_Stackalloc_Buffer()
{
var smart = GetFormatter();
var data = new ISpanFormattableTest(DefaultFormatter.StackAllocCharBufferSize + 1);
var data = new ISpanFormattableTest();
var result = smart.Format("{0}", data);
Assert.That(result, Is.EqualTo(data.ToString()));

Assert.Multiple(() =>
{
// On first attempt, the data will not fit into the buffer
Assert.That(data.TrialNo, Is.GreaterThan(1));
Assert.That(result, Is.EqualTo(data.ToString()));
});
}

private class ISpanFormattableTest : ISpanFormattable
internal class ISpanFormattableTest : ISpanFormattable
{
char[] _buffer;

public ISpanFormattableTest(int size)
internal int TrialNo;

public ISpanFormattableTest()
{
_buffer = new char[size];
_buffer = new char[1024];
_buffer.AsSpan().Fill('b');
}

public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
{
if (destination.Length < _buffer.Length)
if (++TrialNo == 1 || destination.Length < _buffer.Length)
{
charsWritten = 0;
return false;
Expand Down
41 changes: 40 additions & 1 deletion src/SmartFormat.Tests/ZString/ZCharArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,45 @@ public void Buffer_Write_ISpanFormattable()

Assert.That(buffer.ToString(), Is.EqualTo("12.3400"));
}

[Test]
public void ISpanFormattable_Exceeding_BufferLength()
{
var buffer = new ZCharArray(16);
var initialBufferCapacity = buffer.Capacity;
var data = new SpanFormattable();
buffer.Write(data, Span<char>.Empty);

Assert.Multiple(() =>
{
// On first attempt, the data will not fit into the buffer
Assert.That(data.TrialNo, Is.GreaterThan(1));
Assert.That(buffer.Capacity, Is.GreaterThan(initialBufferCapacity));
});
}

internal record SpanFormattable : ISpanFormattable
{
internal int TrialNo;

public string ToString(string? format, IFormatProvider? formatProvider)
{
return nameof(SpanFormattable);
}

public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
{
if (++TrialNo == 1)
{
charsWritten = 0;
return false;
}

destination.Fill('#');
charsWritten = destination.Length;
return true;
}
}
#endif

[Test]
Expand All @@ -96,7 +135,7 @@ public void Buffer_Reset_Size_To_Zero()
Assert.That(buffer.Length, Is.EqualTo(0));
});
}

[Test]
public void Buffer_Thread_Safety()
{
Expand Down
21 changes: 18 additions & 3 deletions src/SmartFormat/Extensions/DefaultFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,31 @@

namespace SmartFormat.Extensions;

#if NET6_0_OR_GREATER
/// <summary>
/// Does the default formatting.
/// This formatter in always required, unless you implement your own.
/// <pre/>
/// It supports <see cref="ISpanFormattable"/>, <see cref="IFormattable"/>, and <see cref="ICustomFormatter"/>.
/// </summary>
#else
/// <summary>
/// Do the default formatting, same logic as "String.Format".
/// Does the default formatting.
/// This formatter in always required, unless you implement your own.
/// <pre/>
/// It supports <see cref="IFormattable"/> and <see cref="ICustomFormatter"/>.
/// </summary>
#endif
public class DefaultFormatter : IFormatter
{

#if NET6_0_OR_GREATER
/// <summary>
/// The maximum size of the stack-allocated buffer
/// for formatting <see cref="ISpanFormattable"/> objects.
/// for formatting <see cref="System.ISpanFormattable"/> objects.
/// </summary>
internal const int StackAllocCharBufferSize = 512;
#endif

/// <summary>
/// Obsolete. <see cref="IFormatter"/>s only have one unique name.
Expand Down Expand Up @@ -68,7 +83,7 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
if (current is ISpanFormattable spanFormattable)
{
// ISpanFormattable has the same speed as IFormattable,
// but brings less GC pressure (about 25% less for processing 1234567.890123f).
// but brings less GC pressure (e.g. 25% less for processing 1234567.890123f).

var fmtTextSpan = format != null ? format.AsSpan() : Span<char>.Empty;

Expand Down
2 changes: 1 addition & 1 deletion src/SmartFormat/ZString/ZCharArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public void Write(ISpanFormattable data, ReadOnlySpan<char> format, IFormatProvi
{
ThrowIfDisposed();

// Increases the buffer size until its big enough
// Increases the buffer size until it's big enough
while (true)
{
if (data.TryFormat(_bufferArray.AsSpan(_currentLength), out var written, format, provider))
Expand Down

0 comments on commit fb41f9f

Please sign in to comment.